Multi Trac / Django Hosting with mod_wsgi
As you might now we switched the pocoo trac to mercurial, mod_wsgi and splitted it in the same go. The new structure is can be found at dev.pocoo.org. What you cannot see there is how all that is implemented. And I tell you. It’s dead simple.
Basically we use mod_wsgi for hosting the tracs. There are many reasons for that but the most important one is that you can host multiple trac instances without much configuration. Basically the configuration binds a wsgi application to a URL match rule. One important thing is the maximum-requests setting. To understand this parameter you have to know that mod_wsgi does not only keep a pool of running python interpreters, but also your application with all data in the memory. Now that’s a big difference to mod_php where your application is sourced on request and removed from the memory after the reqest. So basically you cannot create memory holes, which you can do in python. If your application leaks memory (and trac tends to do so) you can tell mod_wsgi to restart one python interpreter after 500 requests for example. That setting of course depends on your trac version, the number of plugins etc. And especially how many memory you have in your application. Here the Apache config:
<VirtualHost *:80>
ServerName dev.example.org
RewriteEngine On
WSGIDaemonProcess tracs threads=10 maximum-requests=500
RewriteCond %{REQUEST_URI} ^/([a-z_]+)
RewriteRule . - [E=TRAC_ID:%1]
WSGIScriptAliasMatch ^/([a-z_]+) /var/trac/trac.wsgi
<Directory /var/trac>
WSGIProcessGroup tracs
WSGIApplicationGroup %{GLOBAL}
Order deny,allow
Allow from all
</Directory>
<LocationMatch /([a-z_]+)/login>
AuthType "Basic"
AuthName "Trac Instances Login"
AuthUserFile /var/trac/users
Require valid-user
</LocationMatch>
</VirtualHost>
Then we need a trac.wsgi file which is basically just a minimal WSGI application that dispatches our key. Say we have our trac instances in /var/trac/instances, every trac in it’s own folder. Then we can use this code:
#!/usr/bin/python
from os import environ, path
from trac.web.main import dispatch_request
def application(environ, start_response):
trac_path = path.join('/var/trac/instances', environ['TRAC_ID'])
if path.exists(trac_path):
environ['trac.env_path'] = trac_path
return dispatch_request(environ, start_response)
start_response('404 NOT FOUND', [('Content-Type', 'text/plain')])
return ['Not Found']
You can of course modify that not found message, maybe render a fancy HTML page or just redirect to the index of that domain or whatever. The important thing is that you set the path to the trac instance before calling the dispatch_request function. In theory you can do that from the apache config too, but in that situation you cannot check if the trac really exists.
And now about the django hosting part. Basically you can do the same for django. Django basically has a environment key called the DJANGO_SETTINGS_MODULE key. This key basically controls what settings module django will import. Unfortunately the whole django core is not process safe, so you cannot run two different django powered applications in the same python interpreter. This however is not that much of an issue with mod_wsgi, because you can tell mod_wsgi to not share the interpreter. (In the trac config above we shared the interpreter to save some memory)
Your config could look like this:
<VirtualHost *:80>
ServerName www.example.org
WSGIDaemonProcess django_app1 threads=10 maximum-requests=5000
WSGIScriptAlias /app1 /var/www/django_app1.wsgi
WSGIDaemonProcess django_app2 threads=10 maximum-requests=5000
WSGIScriptAlias /app2 /var/www/django_app2.wsgi
<Location /app1>
WSGIProcessGroup django_app1
WSGIApplicationGroup %{GLOBAL}
</Location>
<Location /app2>
WSGIProcessGroup django_app2
WSGIApplicationGroup %{GLOBAL}
</Location>
</VirtualHost>
The actual “django_appX.wsgi” file is very, very simple. It just adds the folder and instanciates the django wsgi app:
#!/usr/bin/python
import sys, os
sys.path.insert(0, '/path/to/django_appX')
os.environ['DJANGO_SETTINGS_MODULE'] = 'django_appX.settings'
from django.core.handlers.wsgi import WSGIHandler
application = WSGIHandler()
Hope I could help a little bit, if you have some questions to our server setup just send me a mail or write a comment. Finally, we first encountered some problems with mod_wsgi two months ago, but at the moment everything is working well, a lot better than any other server setup we used. You can even put python applications into the context of another user which basically replaces fastcgi + suexec.
And btw, the support we got from Graham is really, really good :)
Update: removed WSGIPassAuthorization like Graham suggested in the comments below.
To use mod_rewrite to check for the actual existence of a Trac instance, see example right at the bottom of:
http://code.google.com/p/modwsgi/wiki/IntegrationWithTrac
This means you do not have to do it in the WSGI script file.
I don’t have my mod_rewrite book with me at the moment so I can look up how to do it, but instead of returning Forbidden, you could even possibly have it redirect to some index page listing available sites. The rewrite rules can probably also be comprised into one group to avoid having to match the URL more than once by using [S] modifier to RewriteRule. When I can look up the book and work out how to clean it up, I’ll fix the example.
Comment by Graham Dumpleton — Wednesday, September 12th, 2007 @ 3:24 amBTW, you shouldn’t need:
WSGIPassAuthorization On
This is because Apache is handling authentication and not Trac. All Trac needs to see passed through is the REMOTE_USER variable which will still be passed even if WSGIPassAuthorization is not set to On.
Comment by Graham Dumpleton — Wednesday, September 12th, 2007 @ 4:43 amRight. One could check with RewriteCond /filename/ -f. Haven’t thought about that and assemble full paths directly from the config. So many ways :-)
Comment by Armin Ronacher — Wednesday, September 12th, 2007 @ 12:35 pmAre there advantages with mod_wsgi in comparison with fastcgi+suexec?
Comment by BrD — Wednesday, September 12th, 2007 @ 8:04 pmThe biggest advantage is certainly that it gracefully kills the processes after n requests. That ensures that if the application is leaky, it won’t trash the server. And other big advantage is that you have one possible point of failure less because you don’t need the python module that connects to FastCGI. You just have mod_wsgi which looks for the file you defined in the apache config and all the rest is controlled from the apache config.
No daemon process you have to kill yourself (the ruby guys from eins.de for example use fastcgi for their rails hosting and wrote a script that kill processes that life longer than 30 minutes etc.) and no daemon process you have to start yourself (although apache is able to manage your fastcgi processes).
Of course there are disadvantages too. You have the same problem like with any other mod_foobar module i guess. So don’t let two applications in your apache link to different versions of the same library, don’t expect that mod_wsgi works on lighttpd.
But nonetheless my favorite hosting solution for the moment.
Comment by Armin Ronacher — Wednesday, September 12th, 2007 @ 9:16 pmThanks for posting this. It inspired me to pursue the (much less ambitious) project of converting a small Trac-managed repo from Subversion to Mercurial. It only took a few minutes, all history was preserved via hgsvn, and it seems to be working great.
Comment by Paul — Thursday, September 13th, 2007 @ 2:51 pmFrom the beginning your blog was trash. But now it is great. I hope you gonna keep writing that way.
Comment by crashcrash07 — Friday, April 11th, 2008 @ 6:34 am