Konstruktor

Deploying asynchronous Django

March 01, 2020

Asyncronous Django

Django has slowly but steadily been adding support for async Python. To get access to this functionality Django needs to be deployed with an ASGI server.

At this stage async support only applies to the outer ASGI application. Internally everything remains synchronous. Asynchronous middleware, views, etc. are not yet supported. You can, however, use ASGI middleware around Django’s application, allowing you to combine Django with other ASGI frameworks.

Support for async views, middleware and ORM are not yet there but a Django 3.1 LTS release will be coming out around august and hopefully some of these will be supported by then.

Asyncronous benefits

Great benefits are to be had once full async support arrives in Django.

  • Parallel ORM queries
  • Views could query external API’s without blocking threads
  • Running slow-response / long-poll endpoints efficiently

Until then there is no need to switch your applications over unless you want to start experimenting with asynchronous code.

ASGI

We are all used to deploying Django with WSGI by gunicorn on uWSGI. ASGI is a successor to WSGI and translates as Asynchronous Server Gateway Interface - meant to support asyncronous Python.

A very nice talk “An introduction to ASGI” was given by Philip Jones at PyLondinium 2019

Firs 5 minutes on a server

After you have provisioned your fresh server for deployment you should follow a guide to lock down your server before we begin the actual deployment process.

Update the repositories and upgrade your system.

apt update
apt upgrade
apt install autoconf build-essential python3-dev

Install Fail2ban

Fail2ban scans log files and bans IPs that show the malicious signs.

apt-get install fail2ban

Deployment user

useradd -m deploy -s /bin/bash
usermod -aG sudo deploy

As we are not using passwords allow to use sudo withour a password.

visudo

Add NOPASSWD:ALL at the end of the sudo group configuration.

%sudo ALL=(ALL:ALL) NOPASSWD:ALL

SSH keys

To enhance your server’s security, it’s strongly recommend setting up SSH key authentication instead of using passwords.

Add your SSH key to the .ssh/authorized_keys in the created deploy users home folder.

mkdir /home/deploy/.ssh
nano /home/deploy/.ssh/authorized_keys
chmod 400 /home/deploy/.ssh/authorized_keys
chown deploy:deploy /home/deploy -R

Lock down SSH

nano /etc/ssh/sshd_config

Disable root login & dont allow password authentication.

PermitRootLogin no
PasswordAuthentication no

Restart the ssh service for the changes to take effect.

service ssh restart

Firewall setup

ufw app list
ufw allow OpenSSH
ufw enable
ufw status
Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)

Test the deploy user

Do not shut down the root SSH window before you have fully tested that the setup we just did works. You can be locked out from your server without any access.

To test just access the server with the created user via ssh and run a random command with sudo access.

ssh deploy@<yourip>
sudo ls /

If you’d like to read more extensively on securing your server a “Guide to User Data Security” by Brian Pontarelli is an extensive writeup.

Python

NB! Everything from here on should be done with the deploy user.

Lets deploy our application with the latest stable Python version 3.8. Django 3 supports Python 3.6+.

sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt-get update
sudo apt-get install git
sudo apt-get install python3.8 python3.8-dev python3.8-distutils python3-pip
python3.8 -m pip install --upgrade pip setuptools wheel
python3.8 -m pip install virtualenv

Application

Make a folder to deploy your application into.

sudo mkdir /srv/<application name>/ /srv/<application name>/log/
sudo chown deploy:deploy /srv/<application name>/
cd /srv/<application name>/
python3.8 -m virtualenv env
source env/bin/activate
git clone [email protected]:<username>/<repository>.git app
cd app/
pip install -r requirements.txt --upgrade

NGINX

Install NGINX, make sure it’s running and remove the default site configuration.

sudo apt install nginx
sudo service nginx status
sudo rm /etc/nginx/sites-enabled/default
sudo service nginx reload
sudo ufw allow 'Nginx Full'
sudo ufw reload

LetEncryt

sudo apt update
sudo apt install software-properties-common
sudo add-apt-repository universe
sudo add-apt-repository ppa:certbot/certbot
sudo apt update
sudo apt install certbot python-certbot-nginx

sudo certbot certonly --nginx

Follow the instructions on screen to configure your certificate & domain. Also remember to add the domain you are provisioning the certificate for be pointing at your new server.

Generate DH parameters.

sudo openssl dhparam -out /etc/nginx/dhparam.pem 4096

This is going to take time. 2048 could be used but SSL Labs requires a 4096 bit key to get a 100% score for Key Exchange.

Test SSL certificate automatic renewal.

sudo certbot renew --dry-run

Renewal setup?

Proxy configuration

Create our domain specific proxy configuration for NGINX.

sudo nano /etc/nginx/sites-available/upcount
server {
  listen 443;
  server_name api.upcount.app;

  ssl_certificate /etc/letsencrypt/live/api.upcount.app/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/api.upcount.app/privkey.pem;

  ssl on;
  ssl_session_cache shared:SSL:10m;
  ssl_session_timeout 10m;
  ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
  ssl_ciphers 'kEECDH+ECDSA+AES128 kEECDH+ECDSA+AES256 kEECDH+AES128 kEECDH+AES256 kEDH+AES128 kEDH+AES256 DES-CBC3-SHA +SHA !aNULL !eNULL !LOW !kECDH !DSS !MD5 !RC4 !EXP !PSK !SRP !CAMELLIA !SEED';
  ssl_prefer_server_ciphers on;
  ssl_dhparam /etc/nginx/dhparam.pem;

  location / {
    proxy_pass http://localhost:8000;
    proxy_redirect off;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Ssl on;
  }

  location /static/ {
    root /srv/upcount/app;
  }
}

Link to enabled sites.

sudo ln -s /etc/nginx/sites-available/<application> /etc/nginx/sites-enabled/<application>

Gunicorn + Uvicorn worker

Uvicorn currently in production should be run with Gunicorn based on the Uvicorn deployment guide.

Uvicorn includes a Gunicorn worker class allowing you to run ASGI applications, with all of Uvicorn’s performance benefits, while also giving you Gunicorn’s fully-featured process management.

This allows you to increase or decrease the number of worker processes on the fly, restart worker processes gracefully, or perform server upgrades without downtime.

So make sure your requirements.txt contains both.

Running your app

For a typical Django project, invoking Gunicorn with a Uvicorn worker for production

gunicorn myproject.asgi:application  -w 4 -k uvicorn.workers.UvicornWorke

This would launch the application with 4 workers. If you want to know more about “How many workers?” please read the Gunicorn ducumentation for more details.

Keeping the app running

To keep your application running you can use systemd - a service manager already installed on Ubuntu 18.04.

There are many options but the main reason to use it is because it’s already there and installed. Very well tested and widely used.

Lets create a systemd service configiration file.

sudo nano /etc/systemd/system/upcount.service
[Unit]
Description=Upcount API
After=network.target

[Service]
User=deploy
Group=deploy
WorkingDirectory=/srv/upcount/app
ExecStart=/srv/upcount/env/bin/gunicorn myproject.asgi:application  -w 4 -k uvicorn.workers.UvicornWorker
Restart=always

[Install]
WantedBy=multi-user.target

After adding this configuration, you can start the service

sudo systemctl daemon-reload
sudo systemctl start upcount
sudo systemctl status upcount

PostgreSQL

Install postgres

sudo apt install postgresql

Create a user ‘deploy’ with the interactive createuser command.

sudo -u postgres createuser --interactive

And create a database for the application

sudo -u postgres createdb upcount

Set a password for the created role

sudo -u postgres psql
ALTER USER user_name WITH PASSWORD 'new_password';

Visit your site & continue development!

These steps complete the Django setup. Visit your site and enjoy - start coding your application features!


A blog by Madis Väin
Thoughts on product & software engineering.