Configuring uWSGI and NGINX for use with Flask-SocketIO

Posted on Fri 11 December 2020 in posts

Now that Flask-Meld is working, it's time to start getting things ready for production. I've been wanting to start developing Meld components for Conveyor. Flask-Meld utilizes WebSockets, which Conveyor does not currently support. To make all of the magic happen NGINX and uWSGI configurations will need to be updated to support WebSockets.

For this configuration we will be using Python 3.8. If you don't have python 3.8 or newer installed on your web server, let's take care of that first. You can add the deadsnakes ppa and install many versions of python from there.

Install Python 3.8

add-apt-repository ppa:deadsnakes/ppa
apt-get update
apt-get install -y python3.8 python3.8-venv python3.8-dev

Install uwsgi

wget -N -P /usr/local/ https://projects.unbit.it/downloads/uwsgi-2.0.19.1.tar.gz
cd /usr/local; tar -zxvf uwsgi-2.0.19.1.tar.gz

Build uWSGI plugins for python3.8 and gevent

cd /usr/local/uwsgi-2.0.19.1
PYTHON=python3.8 ./uwsgi --build-plugin "plugins/python python38"
PYTHON=python3.8 ./uwsgi --build-plugin "plugins/gevent gevent"

Allow uWSGI to be runnable

mkdir -p /usr/local/bin
mkdir -p /etc/uwsgi/sites # to store the site configuration
ln -sf /usr/local/uwsgi-2.0.19.1/uwsgi /usr/local/bin/uwsgi
ln -sf /usr/local/uwsgi-2.0.19.1 /usr/local/uwsgi

Create a service file for uWSGI emperor

Of course since web servers are generally over provisioned we are going to use uWSGI emperor mode. I love this feature of uWSGI.

If you need to deploy a big number of apps on a single server, or a group of servers, the Emperor mode is just the ticket. It is a special uWSGI instance that will monitor specific events and will spawn/stop/reload instances (known as vassals, when managed by an Emperor) on demand.

Create the emperor service file at /etc/systemd/system/emperor.uwsgi.service with the following contents:

[Unit]
Description=uWSGI Emperor
After=syslog.target

[Service]
ExecStart=/usr/local/bin/uwsgi --emperor /etc/uwsgi/sites --pidfile=/tmp/uwsgi.pid
# Requires systemd version 211 or newer
RuntimeDirectory=uwsgi
Restart=always
KillSignal=SIGQUIT
Type=notify
StandardError=syslog
NotifyAccess=all

[Install]
WantedBy=multi-user.target

Start and enable the uwsgi emperor service

systemctl daemon-reload
systemctl start emperor.uwsgi.service
systemctl enable emperor.uwsgi.service

configure the ini file

I will include a full configuration file as this step can be difficult to debug if you run into issues. The key config variable to use websocks are

  • http-socket = /tmp/name-of-socket.sock
  • http-websockets = true
  • gevent = 1000
  • plugin = gevent
[uwsgi]
http-socket = /tmp/meld.conveyor.dev.sock
chmod-socket = 666
http-websockets = true
gevent = 1000
chdir = /www/http/meld.conveyor.dev
home = venv
plugins-dir = /usr/local/uwsgi
plugin = python38
plugin = gevent
processes = 1
threads = 1
uid = conveyor
pid = www-data
wsgi-file = app.py
vacuum = true
callable = app
die-on-term = true

Configure NGINX

Create a location to proxy /socket.io requests to the socket

server {

server_name meld.conveyor.dev;
root /home/conveyor/meld.conveyor.dev/_meld;
location / {
    try_files $uri @proxy_to_app;
}
location /socket.io {
    include proxy_params;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_pass http://unix:/tmp/meld.conveyor.dev.sock;
}
location @proxy_to_app {
    include uwsgi_params;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_pass http://unix:/tmp/meld.conveyor.dev.sock;
}

include general.conf;

listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/meld.conveyor.dev/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/meld.conveyor.dev/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

server {
if ($host = meld.conveyor.dev) {
return 301 https://$host$request_uri;
} # managed by Certbot

listen 80;

server_name meld.conveyor.dev;
return 404; # managed by Certbot

}

With all of that configured you can serve your application with WebSockets enabled. It's a bit of a process to set up. Luckily, Flask-Meld applications with WebSocket support can now be deployed with Conveyor.dev.