Django on uWSGI and Nginx
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.