This is Jawher Moussa's blog

in which he writes about technical stuff

Running multiple python apps with nginx and uwsgi in emperor mode

This is a recipe on how to easily run multiple Python web applications using uwsgi server (in emperor mode) and behind nginx. Most existing docs and blogs would show how to manually start uwsgi to run a single app. In this post, I’ll show how to configure uwsgi as a system service (with upstart) capable of serving multiple python WSGI compliant web applications by simply placing them in a standard location and adding an standard xml file.

It took me many many hours and sleepless nights to get this setup to work. I was on the verge of giving up on uwsgi/nginx and falling back to apache/mod python. I’m not proud of it, but when it worked, I had (and still don’t have) no idea why it did. So I would advice the readers not to change too much at a time, and rather work incrementally, with small changes at a time and immediate testing afterwards.

I’m using this setup on a couple EC2 machines running Ubuntu 11.10, nginx 1.0.5-1, Python 2.7.2, uwsgi 0.9.8.1-1 and using the bottle web framework, but the same should work for any other WSGI compliant framework.

1. Installing the required software

2. Configuration

uwsgi

We’ll run uwsgi using upstart so that it is automatically started on startup.

We’ll create a uwsgi.conf file in /etc/init:

sudo emacs /etc/init/uwsgi.conf:

With this content:

# uWSGI - manage uWSGI application server                                                                                                                                                                
#                                                                                                                                                                                                    

description     "uWSGI Emperor"

start on (filesystem and net-device-up IFACE=lo)
stop on runlevel [!2345]

respawn

env LOGTO=/var/log/uwsgi.log
env BINPATH=/usr/bin/uwsgi

exec $BINPATH --emperor /home/ubuntu/apps/vassals/ --logto $LOGTO

Notice the path telling uwsgi emperor mode where to look for applications’ configuration files (We’ll be using the xml syntax here). You’ll need to modify this to match your setup.

Then run this command to refresh the services configuration:

sudo initctl reload-configuration

We also want to make sure that uwsgi is using the system installed python (2.7 in this case) and not another version (2.6), so we need to run this:

sudo update-alternatives --set uwsgi /usr/bin/uwsgi_python27

And start the uwsgi service:

sudo service uwsgi start

nginx

We’ll create a apps.conf file in the /etc/nginx/sites-enabled directory to tell nginx to pass through requests to uwsgi:

sudo emacs /etc/nginx/sites-enabled/apps.conf

server {
  listen 80;
  server_name jawher.me;
  index index.html;

  location /app1/ {
    include uwsgi_params;
    uwsgi_param SCRIPT_NAME /app1;
    uwsgi_modifier1 30;
    uwsgi_pass unix://tmp/app1.sock;
  }

  location /app2/ {
    include uwsgi_params;
    uwsgi_param SCRIPT_NAME /app2;
    uwsgi_modifier1 30;
    uwsgi_pass unix://tmp/app2.sock;
  }
}

You’ll need to customize this file to match your setup (server name, apps locations, socket names, etc.)

Also, there’s a bit of magic here, the uwsgi_param SCRIPT_NAME /app? and uwsgi_modifier1 30 thingies. Without them, requesting jawher.me/app1/index would end up calling app1/index on the bottle web application (i.e. the nginx location gets passed to the wsgi application, which is not what we want most of the time). With this modifier and script param thingies, the nginx location is stripped before invoking the python application.

Start the nginx server if it is not already running:

sudo service nginx start

Also, whenever you change the nginx config files, remember to run the following command:

sudo service nginx reload

3. The test applications

We’ll create to test web applications to test our setup:

An app in this case is a python file that defines a bottle app:

emacs /home/ubuntu/apps/app1/app1.py

import bottle
import os

@bottle.get('/index')
def index():
    return "App 1 live and kicking"

if __name__ == '__main__':
    bottle.debug(True)
    bottle.run(reloader=True, port=8080)
else:
    os.chdir(os.path.dirname(__file__))
    application = bottle.default_app()

This file can be executed with python, in which case the main bloc will launch the application in debug mode, or with uwsgi without a change to the file. Don’t change the variable name application, as that’s what uwsgi will be looking for (unless you tell it otherwise).

We’ll also need an xml file to tell uwsgi emperor mode how to handle our app:

emacs /home/ubuntu/apps/vassals/app1.xml

<uwsgi>
	<plugins>python</plugins>
	<master>true</master>
	<processes>1</processes>
	<vaccum>true</vaccum>
	<chmod-socket>666</chmod-socket>
	<socket>/tmp/%n.sock</socket>
	<uid>www-data</uid>
	<gid>www-data</gid>
	<pythonpath>%d../%n</pythonpath>
	<module>%n</module>
</uwsgi>

This config file is completely generic, and we could reuse the exact same content for any other app. In order to do this, it has to heavily rely on these conventions:

If this is not acceptable for your setup, you’ll need to change the values of the <pythonpath> and <module> tags to match your setup.

The second application is exactly the same, except for ‘app 1’ which gets replaced by ‘app 2’:

emacs /home/ubuntu/apps/app2/app2.py

import bottle
import os

@bottle.get('/index')
def index():
    return "App 2 live and kicking"

if __name__ == '__main__':
    bottle.debug(True)
    bottle.run(reloader=True, port=8080)
else:
    os.chdir(os.path.dirname(__file__))
    application = bottle.default_app()

emacs /home/ubuntu/apps/vassals/app2.xml

<uwsgi>
	<plugins>python</plugins>
	<master>true</master>
	<processes>1</processes>
	<vaccum>true</vaccum>
	<chmod-socket>666</chmod-socket>
	<socket>/tmp/%n.sock</socket>
	<uid>www-data</uid>
	<gid>www-data</gid>
	<pythonpath>%d../%n</pythonpath>
	<module>%n</module>
</uwsgi>

4. Troubleshooting

When it doesn’t work, you’ll usually end up with an unhelpful 502 error. To diagnose the problem: