Recently I made the final step in converting my website over to nginx. I decided to manage my django application, byteflow, with supervisor. I have had great success with this over at AGI and thought I should advocate the success I’ve had to the public by extending it to my own site.
I first heard about supervisor at pycon last year and thought it could be useful in many ways, especially at the office. In a nutshell, supervisor ‘supervises’ processes, and allows you to manage them with a simple interface, but I’ll go into more detail about it later. Around the same time that I discovered supervisor I had also started experimenting with nginx and fastcgi to run my blog. I ended up going with lighty and fastcgi instead, however, mainly due to familiarity. Things change, and so do my opinions on technology. Nginx sold me on it’s performance and simplistic configuration, plain and simple. So now, here’s the meat and potatoes.
To break it down, here is what we’re looking at.
INTARWEBS -> nginx -> fastcgi -> django/byteflow(managed by supervisor)
Everything below assumes you have installed nginx, django, byteflow and supervisor.
So how about a look at the actual setup.
First, let’s take a look at the code to fire up the django app. I’ve dubbed it runserver.py and put it in my byteflow code tree.
#!/usr/bin/env python
if __name__ == '__main__':
from flup.server.fcgi_fork import WSGIServer
from django.core.handlers.wsgi import WSGIHandler
WSGIServer(WSGIHandler()).run()
This keeps the django devs happy by not relying on manage.py to keep up with the server stuffs.
It’s pretty simple.. Starts up flups’ WSGIServer and uses django’s WSGIHandler to do the dirty work. Not much to it, really.
Now, lets take a look at the supervisor setup. You can get a lot of what you need by running:
# sudo echo_supervisord_conf > /etc/supervisord.conf
That sets up a basic (but verbose!) supervisor config. I’ve trimmed it down to this:
[unix_http_server]
file=/tmp/supervisor.sock ; (the path to the socket file)
[supervisord]
logfile=/var/log/supervisord/supervisord.log ; (main log file;default $CWD/supervisord.log)
logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB)
logfile_backups=10 ; (num of main logfile rotation backups;default 10)
loglevel=info ; (log level;default info; others: debug,warn,trace)
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
nodaemon=false ; (start in foreground if true;default false)
minfds=1024 ; (min. avail startup file descriptors;default 1024)
minprocs=200 ; (min. avail process descriptors;default 200)
user=nobody ; (default is current user, required if root)
childlogdir=/var/log/supervisord/ ; ('AUTO' child log dir, default $TEMP)
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
; Production setup
[fcgi-program:django_fcgi]
socket=tcp://127.0.0.1:8080 ; We reference this later in nginx
command = /home/bsmith/Dev/byteflow/runserver.py ; Calls the above code
environment=PYTHONPATH=/home/bsmith/Dev/byteflow ; Setup needed environment
environment=DJANGO_SETTINGS_MODULE=settings
; Development setup
[fcgi-program:django_dev_fcgi]
socket=tcp://127.0.0.1:8081
command = /home/bsmith/Dev/byteflow_new/runserver.py
environment=PYTHONPATH=/home/bsmith/Dev/byteflow_new
environment=DJANGO_SETTINGS_MODULE=settings
Simple enough, eh? Comments in the configuration should explain what’s going on.
Now you can crank it up by running:
$ sudo supervisord
and check the status…
$ sudo supervisorctl status
django_dev_fcgi:django_dev_fcgi_0 RUNNING pid 15949, uptime 0:00:08
django_fcgi:django_fcgi_0 RUNNING pid 15950, uptime 0:00:08
Could it be more simple? I doubt it. This barely begins to scratch the surface of what supervisor can do. Process groups/pools, XML-RPC interface for remote management, built-in web interface for process management(utilizing XML-RPC interface), tons of process management options (priority, umask, user/group, capture std* pipes, environment variables, auto restart/start, process naming), event listeners/handling and a simple configuration ta boot! All I’m sayin’ is, it’s awesome…I’m just sayin’.
Keeping with our simple but awesome theme, enter nginx…
My main config:
user www-data;
# Could vary by number of processors available.
worker_processes 1;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
sendfile on;
keepalive_timeout 65;
tcp_nodelay on;
gzip on;
include /etc/nginx/sites/*;
}
and my various site-specific configurations that I keep /etc/nginx/sites:
server {
# We listen on port 80
listen 80;
server_name just-another.net;
# access and error logs for our site
access_log /var/log/nginx/my_site_access_log;
error_log /var/log/nginx/my_site_error_log;
# Configure redirect for our fastcgi server
# The fastcgi server later runs on localhost Port 8080
location / {
expires 10d;
# to fastcgi server could use socket..
#fastcgi_pass unix:{project_location}/log/django.sock;
fastcgi_pass 127.0.0.1:8080;
fastcgi_param PATH_INFO $fastcgi_script_name;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_pass_header Authorization;
fastcgi_intercept_errors on;
}
location /images {
# For my own images..
alias /home/bsmith/public_html/images;
expires 10d;
}
# Alias for static content like themes
location /static {
alias /home/bsmith/Dev/byteflow/static;
expires 10d;
}
# Alias for python contrib.admin stuff, needed for admin interface
location /admin-media {
# Point to my most recent install of django.
alias /home/bsmith/Dev/django_trunk/django/contrib/admin/media;
expires 10d;
}
# Point to media
location /media {
alias /home/bsmith/public_html/media;
expires 10d;
}
# Use feedburner for my feeds.
rewrite ^/rpc(.*) http://feeds.feedburner.com/Just-anothernetBlogPosts$1 permanent;
}
Pretty simple, eh? I have the same server setup for my ‘dev’ site, only with a volatile code base that I can hack on.
Now when I make code changes all I need to do is reload the supervisor process like so:
$ sudo supervisorctl restart django_fcgi:django_fcgi_0
for the production site or replacing the process name with django_dev_fcgi:django_dev_fcgi_0 to restart the dev site. I could also restart everything by replacing a process name with all as an argument.
The possibilities here for process management endless and I couldn’t be happier with how simple it has become to run what I have.
Basically, this setup is easy to get running and easy to keep running. Highly recommended for any lazy django site maintainer like myself!
Comments
Hey, how about looking into mod_wsgi for nginx instead of flup? It helped me get rid of a lot of wierd bugs as well as a very nice performance boost.
There’s one drawback which is that you no longer have total control over spawning (nginx will spawn for you — which you control with the “workers” directive) — but since nginx accepts a wide range of signals you can monitor the workers and just send a HUP if you want a quick reload
Nice, I’ll check it out for sure. In the past I had stayed away from it because it didn’t come with the distribution.
I’ve not run into any bugs with flup as of yet, I’d be interested in hearing what you have seen so I know what to look for.
My flup issues are very diverse.. I started out with flup somewhere around django 0.95 where (I guess) both Django and flup were equal offenders. It resulted in broken pipes and flush errors between the load balancer (http server) and the application. I later moved to python-fastcgi but now reside in mod_wsgi. Mod_wsgi is by far the best performer.. It’s easier to sort out tracebacks as well, since some end up in the http server logs and some on the python side..
One thing to remember — when running mod_wsgi on nginx — is that each worker will embed your python app; so using the same web server for serving images is… sub-optimal. My solution is two nginx servers, one compiled for only proxying the other (and serving static content) and one for handling mod_wsgi requests.
I think I’ll check it out. Thus far I’ve not seen issues with flup, but they way you’re building up mod_wsgi makes it sound like it’s worth a look for sure. Thanks for sharing your experiences with it!
Really great post.
Supervisord is on my to-do list. I’ve got some long-running processes that read and write message queues that are fed by third-party customers, and occasionally my processes crash when customers feed garbage to them.
Is there some easy way to make supervisor tell me when it finds the corpse of a dead process?
Thanks!
Looks like the Events system is what you are after.
I’ve not used this personally, but it looks like it would be useful for something like notification of or response to specific events (like a dying process).
Hope that gets you going in the right direction!
Do you use development version of supervisor or their recent release?
They don’t mention [fcgi-program:x] in their documentation.
The most current release on the site is 3.0a6 which has the fastcgi code in it.
Don’t think there is any official docs yet, though you can get an idea of how it works here:
http://lists.supervisord.org/pipermail/supervisor-users/2008-February/000170.html
I may take it upon myself to get some docs together. I just looked through the source and it’s clean, so it’s simple to figure out everything that’s going on!
So, why choose supervisior over daemontools or runit( which is daemontools )? I’m curious, since i was going to go the runit route for any of my servers…
The biggest two selling points for me are the configuration interface and stability, with feature set in a close third.
I don’t think I’ve ever run across something so simple so configure and manage. Even people I work with who had never heard of it were pleasantly surprised by the configuration file and management tools I presented to them.
As far as stability is concerned, it’s not a concern :). Until recently (due to server patching in general) I had a set of servers managing some processes with supervisord for nearly 6 months without a restart.
The supervisord docs say you gotta have a section [program:foo], but you have [fcgi-program:foo].
Actually, I’ve only read the first few pages of docs, and I haven’t read them very closely. But still, what is going on with your fcgi-program deal?
Also, are you using the rpcinterface? If so, what for?
The fcgi stuff in 3.0a6 is undocumented as of yet. Take a look here for reference: http://www.mail-archive.com/supervisor-users@lists.supervisord.org/msg00023.html
At the office, I use the RPC interface to allow certain developers the ability to manage processes.
Also, the supervisorctl program uses the rpc interface for management of the processes.
How do you make sure supervisord starts up automatically when you reboot your server?
In case you didn’t get the answer you were looking for on twitter, you could try this: http://lists.supervisord.org/pipermail/supervisor-users/2007-September/000092.html
I’ve tried this for a couple hours now… I can’t seem to figure out what my issue is:
my supervisord conf looks pretty much like yours. I set supervisord to run with debug level logs. Here is my log:
2009-04-05 12:59:29,813 CRIT Set uid to user 99
2009-04-05 12:59:29,960 INFO RPC interface ‘supervisor’ initialized
2009-04-05 12:59:29,961 INFO RPC interface ‘supervisor’ initialized
2009-04-05 12:59:29,961 INFO supervisord started with pid 17278
I can access the web interface with :9001, but it doesn’t list any programs there. Needless to say, my fcgi server doesn’t start.
Any ideas? I have the latest version 3.06a and am running CentOS 5.2 (Python 2.4)
Thanks a lot! This will be a huge help, I’ve written my own and I really want to switch.
Best, Jacob
Are you sure the fcgi stuff is in 3.06a?
grep -ri fcgi /usr/lib/python2.4/site-packages/supervisor-3.0a6-py2.4.egg/supervisor/
nada…
Yes, this stuff is only in trunk.
important
Should probably note this in the article. I’m still not getting to work 100%, but it’s seeming closer :)
Thanks for this tute!
Comment form for «byteflow/django+supervisord+nginx = WIN»