If my customers read this, they won’t remain such for a long time. :-) But, then, it’s roughly the truth: my production servers tend to be an horrible mess and I sometimes wonder how they manage to actually work without too many issues. Or, at least, that used to be the truth.
Until very recently I used sometimes Apache and sometimes Lighttd, relying on FastCGI to make them speak with the Catalyst web applications. I was at least wise enough to not have FastCGI processes spawned by the web server but to manage them myself instead: I sometimes used daemontools and sometimes Gentoo init system for that.
At one point I decided I wanted something which:
- was more straightforward to manage
- standardized all my production deployments
- consumed less memory
- could maybe provide a bit of speed-up
After a bit of research a decided to try nginx as web server and uWSGI to manage application start and stop.
Configuration was all in all fairly easy, but there were a couple of caveats, so I’ll go through the entire process.
uWSGI
uWSGI is a great and lighting fast piece of software which can be used to spawn processes for any application which supports the PSGI interface. Catalyst supports it out of the box.
You should find a myapp.psgi file in the top directory of your application. If it doesn’t exists (mainly because you created your app before Catalyst began to support PSGI), you can easily create it yourself:
uWSGI is pre-packaged for many distribution, or can be downloaded and compiled. Once you have it installed, you can launch your application as follows:
Please note that uWSGI has a ton of options, so you should take a look at the documentation. The example above launches a master process which then spawns 2 worker processes, which are the instances of your application (–psgi /usr/local/catalyst/MyApp/myapp.psgi). The server is bound to a TCP socket (localhost, port 8789). The remaining options tell uWSGI to run as a deamon, to keep a log file and to write the process PID in a file.
You can use threads instead of processes if you wish, or even both.
The operating system init system is an ideal candidate for uWSGI master processes launch: Gentoo Linux, for instance, has a nice uWSGI configuration system which is straightforward to use (even though I had to patch it a bit in order for it to work properly for my use case).
nginx
Fast and with a minimal memory footprint, with uWSGI support out of the box, nginx is a great web server. It is also surprisingly easy to configure, much more than it’s rivals! That’s what you need for a virtual host which talk to your uWSGI server:
This configuration maps your web application to the root location (/). The uwsgi_params file contains the parameters which nginx passes on to uWSGI, which are tipically the following:
…and it works like a charm! That’s all! … Except, what happens if you don’t want to map your application to / but to, say /app instead? It is entirely possible, but there is a caveat.
There is something in Catalyst which messes the URLs up when you don’t map them to root (this happens also with the reverse proxy configuration, while Mojolicious for instance works perfectly). It’s probably just matter to write a Plack middleware for nginx: there is one here, but it’s not yet on CPAN and I didn’t try it. Instead, I modified nginx configuration as follows:
An extra SCRIPT_NAME parameter is passed, while PATH_INFO is modified. You also need to include a uwsgi_params_stripped file, to avoid passing PATH_INFO with more than one value:
Note: you can also use FastCGI or reverse proxy to make uWSGI server and nginx talk, but direct uwsgi support is the most efficient way to do it.
And what about some lengthy administrative tasks (old file deletion, mail queue processing, …) you application might have to do? The easiest way with Catalyst is to creation an action (with restricted, maybe IP-based, access) which you execute either by hand on with a cronjob. If one of these tasks requires, say, 15 minutes, you need to configure nginx not to timeout while waiting for a response from the application - but you surely don’t want to set the gateway timeout to 15 minutes for all your users.
The solution is easy. Just configure another mapping within localhost, with the appropriate settings: