To be honest, I did like our previous approach of deploying FastAPI with screen -S background_terminal However, there are some cons to this approach:
uvicorn main:app
, it typically runs in a single process.In order to overcome these limitations let's utilize the power of Gunicorn. The main advantages will be:
gunicorn -w N main:app
, where N is the number of worker processes, you can scale your application horizontally by adding more processes to handle incoming requests.gunicorn
can also be used in conjunction with a reverse proxy (such as Nginx or Apache) for load balancingDon't worry I won't tell you to open up another screen and execute the gunicorn initialization command! This time we are going to follow a difficult but near-industry-standard approach of making gunicorn a Daemon process and managing it by systemctl. Before that, I would like to change the role to a non-root user and do some initial server setup for the processes to come. Let's ssh and execute the below initial_server_setup.sh file and do the basic configuration.
#!/bin/bash
set -euo pipefail
########################
### SCRIPT VARIABLES ###
########################
# Name of the user to create and grant sudo privileges
# put your desired username
USERNAME=johnwick
# Whether to copy over the root user's `authorized_keys` file to the new sudo
# user.
COPY_AUTHORIZED_KEYS_FROM_ROOT=true
####################
### SCRIPT LOGIC ###
####################
# Add sudo user and grant privileges
useradd --create-home --shell "/bin/bash" --groups sudo "${USERNAME}"
# Check whether the root account has a real password set
encrypted_root_pw="$(grep root /etc/shadow | cut --delimiter=: --fields=2)"
if [ "${encrypted_root_pw}" != "*" ]; then
# Transfer auto-generated root password to user if present
# and lock the root account to password-based access
echo "${USERNAME}:${encrypted_root_pw}" | chpasswd --encrypted
passwd --lock root
else
# Delete invalid password for user if using keys so that a new password
# can be set without providing a previous value
passwd --delete "${USERNAME}"
fi
# Expire the sudo user's password immediately to force a change
chage --lastday 0 "${USERNAME}"
# Create SSH directory for sudo user
home_directory="$(eval echo ~${USERNAME})"
mkdir --parents "${home_directory}/.ssh"
# Copy `authorized_keys` file from root if requested
if [ "${COPY_AUTHORIZED_KEYS_FROM_ROOT}" = true ]; then
cp /root/.ssh/authorized_keys "${home_directory}/.ssh"
fi
# Adjust SSH configuration ownership and permissions
chmod 0700 "${home_directory}/.ssh"
chmod 0600 "${home_directory}/.ssh/authorized_keys"
chown --recursive "${USERNAME}":"${USERNAME}" "${home_directory}/.ssh"
# Disable root SSH login with password
sed --in-place 's/^PermitRootLogin.*/PermitRootLogin prohibit-password/g' /etc/ssh/sshd_config
if sshd -t -q; then
systemctl restart sshd
fi
# Add exception for SSH and then enable UFW firewall
ufw allow OpenSSH
ufw allow 80
ufw allow 443
ufw --force enable
sudo apt update
- Let's wait for 1 minute, this is the cheapest droplet and is a little slow. It might be doing some booking for now. Now, you can execute bash initial_server_setup.sh
Now, you can try sshing with johnwick@ip_of_vm. The first ssh attempt will ask you to choose a password. Now, we can definitely pull our codebase and set up the virtual environment.
git clone https://github.com/sourabhsinha396/fastapi-blog
cd fastapi-blog/
ls
sudo apt install python3-pip python3-venv nginx
python3 -m venv env
# lets activate the virtualenv and install the requirements
johnwick@bobthebuilder:~/fastapi-blog$ source ./env/bin/activate
(env) johnwick@bobthebuilder:~/fastapi-blog$ cd backend/
(env) johnwick@bobthebuilder:~/fastapi-blog/backend$ pip install -r requirements.txt
(env) johnwick@bobthebuilder:~/fastapi-blog/backend$ pip install gunicorn==21.2.0
Now, let's jump on to the Gunicorn configuration. Create a Gunicorn service file. Create a file named your_app_name_gunicorn.service
in /etc/systemd/system/
#fastapi_gunicorn.service
[Unit]
Description=Gunicorn instance to serve fastapi_blog
After=network.target
[Service]
User=johnwick
Group=johnwick
WorkingDirectory=/home/johnwick/fastapi-blog/backend
ExecStart=/home/johnwick/fastapi-blog/env/bin/gunicorn -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000 main:app
[Install]
WantedBy=multi-user.target
Now, we can enable and start the Gunicorn service and check its status.
johnwick@bobthebuilder:/etc/systemd/system$ sudo systemctl enable fastapi_gunicorn
Created symlink /etc/systemd/system/multi-user.target.wants/fastapi_gunicorn.service → /etc/systemd/system/fastapi_gunicorn.service.
johnwick@bobthebuilder:/etc/systemd/system$ sudo systemctl start fastapi_gunicorn
johnwick@bobthebuilder:/etc/systemd/system$ curl localhost:8000
johnwick@bobthebuilder:/etc/systemd/system$ sudo systemctl status fastapi_gunicorn
● fastapi_gunicorn.service - Gunicorn instance to serve fastapi_blog
Loaded: loaded (/etc/systemd/system/fastapi_gunicorn.service; enabled; vendor preset: enabled)
Active: active (running) since Tue 2023-11-21 16:54:42 UTC; 6s ago
Main PID: 9798 (gunicorn)
Tasks: 5 (limit: 512)
Memory: 178.0M
CPU: 3.661s
Now, we need to tackle nginx stuffs, let's start by creating a nginx file inside of /etc/nginx/sites-available/
johnwick@bobthebuilder:/etc/nginx/sites-available$ sudo nano fastapi_nginx
Let's put our ip, domain name, and port info in the below nginx file content.
server {
listen 80;
server_name 147.182.213.99 algoholic.pro;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Finally, we can link it to sites-enabled, test our configuration, and restart nginx if everything looks good.
sudo nginx -t
#nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
#nginx: configuration file /etc/nginx/nginx.conf test is successful
sudo ln -s /etc/nginx/sites-available/fastapi_nginx /etc/nginx/sites-enabled/
sudo systemctl restart nginx
Time to visit the IP and Domain on a HTTP scheme. Visit http://your_ip_or_domain
Brige the gap between Tutorial hell and Industry. We want to bring in the culture of Clean Code, Test Driven Development.
We know, we might make it hard for you but definitely worth the efforts.
© Copyright 2022-23 Team FastAPITutorial