Brandon Konkle
Brandon Konkle

Freelance full-stack Node & React developer, supporter of intersectional social justice, enthusiastic nerd, loving husband, and father. Hire me for your next project!

I'm a Node & React developer with more than 8 years of experience creating high performance web applications and architectures. If you're looking for help with your next project, hire me today!

My Newsletter


Subscribe to my newsletter for a weekly look at the latest news, tools, and techniques from the React community.

Tags


Share


Twitter


Django on uWSGI and Nginx

Brandon KonkleBrandon Konkle

I recently moved Pegasus News from Perlbal, Lighttpd, and Apache to Nginx and uWSGI. We balance the traffic between 3 physical servers, and the systems were struggling under the load even after weeks of Apache conf tweaking. We began having issues with excessively slow page loads, request timeouts, and intermittent errors with OGR transformations.

I decided to move us to a lighter application server so that we could get the most out of our system resources, and after a lot of research and testing I chose uWSGI. While I was at it, I decided to replace Perlbal and Lighttpd with Nginx because of its great configuration syntax and excellent performance. I also upgraded us to Ubuntu 10.04 and Postgres 8.4.

The result was a resounding success! Memory usage and CPU load on each of the web nodes dropped dramatically. Swap usage dropped to almost nothing. The overall responsiveness of the site improved noticeably, and the timeout errors and OGR failures disappeared entirely.

If you'd like to give this stack a try, read on for an overview of the setup on Ubuntu 10.04. I'm using the standard Ubuntu source package for Nginx, but modifying it slightly and them installing it with dpkg-buildpackage.

This post is just about the setup relevant to Nginx and uWSGI. If you need a more complete server setup guide, try my Provisioning a new server post.

uWSGI

Before we build Nginx, uWSGI needs to be compiled so that its module can be included in the Nginx build.

:::sh
$ sudo apt-get install build-essential python-dev libxml2-dev
$ wget http://projects.unbit.it/downloads/uwsgi-0.9.5.4.tar.gz
$ tar -xzf uwsgi-0.9.5.4.tar.gz
$ cd uwsgi-0.9.5.4
$ make -f Makefile.Py26

Copy the executable to the local sbin directory.

:::sh
$ sudo cp uwsgi /usr/local/sbin

Also, copy the default uwsgi settings to the /etc/nginx directory.

:::sh
$ sudo mkdir /etc/nginx
$ sudo cp nginx/uwsgi_params /etc/nginx
$ cd ..

Nginx

We need to slightly modify the nginx package from Ubuntu to add uWSGI (and, optionally, SSL).

:::sh
$ sudo apt-get install libssl-dev
$ sudo apt-get build-dep nginx
$ apt-get source nginx
$ cd nginx-0.7*
$ emacs debian/rules

Add the following lines to the end of the configure options. Make sure to include backslashes so that all the options are interpreted as being on one line. If you don't need SSL, ignore that line.

:::text
    --with-http_ssl_module 
    --add-module=$(CURDIR)/../uwsgi-0.9.5.4/nginx

Build, install, and hold the packages.

:::sh
$ dpkg-buildpackage
$ cd ..
$ sudo dpkg -i nginx*.deb
$ echo "nginx hold" | sudo dpkg --set-selections
$ echo "nginx-dbg hold" | sudo dpkg --set-selections

Starting with nginx-0.8.41, you can add something like --http-uwsgi-temp-path=/var/lib/nginx/uwsgi to the debian/rules so that the temp files are kept in the right place. Until then (probably a later version of Ubuntu), you'll need to create a /usr/local/nginx/uwsgi_temp folder to use for the temp files.

:::sh
$ sudo mkdir -p /usr/local/nginx/uwsgi_temp

Supervisor

To manage the uWSGI processes, I use Supervisor. In Ubuntu 10.04, you can simply install it with apt-get.

:::sh
$ sudo apt-get install supervisor

Configuration

Supervisor

To configure uWSGI, I use command-line options in my Supervisor config. The example below is similar to what I use in production for Pegasus, but you'll want to take a look at the uWSGI docs and tweak for your situation.

:::text
[program:myapp]
command=/usr/local/sbin/uwsgi
  --home /home/myuser/.virtualenvs/myapp/
  --module myapp.deploy.wsgi
  --socket 10.1.2.3:10000
  --pythonpath /sites/myapp.com/code/myapp
  --processes 5
  --master
  --harakiri 120
  --max-requests 5000
directory=/sites/myapp.com/code/myapp
environment=DJANGO_SETTINGS_MODULE='myapp.settings'
user=www-data
autostart=true
autorestart=true
stdout_logfile=/sites/myapp.com/logs/uwsgi.log
redirect_stderr=true
stopsignal=QUIT

The module I specify in the --module option simply contains:

:::python
import django.core.handlers.wsgi

application = django.core.handlers.wsgi.WSGIHandler()

The IP address in the --socket option is the server's IP address on the interface reserved for local network traffic. On Rackspace, eth1 is your local interface. Use the ifconfig command to find your IP address on the local interface. If you're just using uWSGI on localhost, you can use something like --socket /sites/myapp.com/var/run/myapp.sock instead to avoid the overhead of the full TCP stack.

My value for --harakiri is rather high, and my value for --max-requests is rather low. You may not even need either of these options, but I'm using them to solve some problems specific to Pegasus.

Nginx

For the site-specific Nginx config, I'm using something like this:

#!nginx
upstream myapp {
    server 10.1.2.3:10000;
    server 10.1.2.4:10000;
    server 10.1.2.5:10000;
}

server {
    listen 80;
    listen 443;
    server_name myapp.com www.myapp.com;

    access_log /sites/myapp.com/logs/nginx-access.log;
    error_log /sites/myapp.com/logs/nginx-error.log;

    root /sites/myapp.com/public;

    ssl_certificate /sites/myapp.com/ssl/myapp.crt;
    ssl_certificate_key /sites/myapp.com/ssl/myapp.key;

    location / {
        # This checks for a file called simply "downtime" in the public
        # directory, and puts up the downtime page if it exists.
        if (-f /sites/myapp.com/public/downtime) {
            return 503;
        }

        uwsgi_pass myapp;
        include uwsgi_params;
    }

    location /media {
        # This makes static media available at the /media/ url.  The
        # media will continue to be available during site downtime,
        # allowing you to use styles and images in your maintenance page.
        alias /sites/myapp.com/public/media;
    }

    error_page 502 503 504 @maintenance;
    location @maintenance {
        # In this example, there's a directory in the site media files
        # called "downtime" that contains a "maintenance.html" file and
        # any styles and images needed for the maintenance page.
        root /sites/myapp.com/public/media/downtime;
        rewrite ^(.*)$ /maintenance.html break;    
    }
}

If you're just using uWSGI on localhost, then skip the upstream section and use something like uwsgi_pass unix:///sites/myapp.com/var/run/myapp.sock; in the root location definition instead.

Firing it up

Restart Supervisor with sudo /etc/init.d/supervisor restart, and it should reload the config and bring the uWSGI processes up. Restart Nginx with sudo /etc/init.d/nginx restart, and you should now be able to reach your site. If not, take a look at the logs and start troubleshooting.

I'm a Node & React developer with more than 8 years of experience creating high performance web applications and architectures. If you're looking for help with your next project, hire me today!

Comments