Deploying a Pylons App to Production, Step-by-Step (Part 1 of 2)

I think there’s a very good reason why the “Deployment” chapter in the official Pylons book is listed in section entitled “Expert Pylons.” Deploying a Pylons app into a production environment can be a real hair-puller for the folks coming from the PHP or ASP/ASP.Net worlds.

Like most everything else about Pylons, you have a lot of choices when it comes to production deployment. The goal of this article is to take you through one particular process, step-by-step. This isn’t your only option, and (depending on your needs) may not even be the best choice for you. However, it’s worked well for me. The components that I will be using are:

  • CentOS 5.2
  • Apache 2 / Nginx 0.7.65
  • FastCGI / flup
  • Pylons 0.9.7
  • Subversion

Preparing the Production Environment

Installing Nginx

Nginx (“engine x”) is a lightweight HTTP server. Until this project, I’d never touched it. However, I decided to try it after I managed to bork my Apache install trying to recompile it with FastCGI support. After spending two hours fixing that, I was loathe to continue poking at the httpd beast with a stick. Additionally, I knew that reddit serves about 200M pageviews/mo. using Nginx in front of a Pylons app, so it made sense to at least explore it as another path.

I was very pleasantly surprised with the outcome. Even though I compiled from source and had zero prior experience with it, getting Nginx working was actually the easiest part of this project. It simply needs to be downloaded, compiled, and installed:

cd /var/src
wget http://nginx.org/download/nginx-0.7.65.tar.gz
tar zxvf nginx-0.7.65.tar.gz
cd nginx-0.7.65
./configure
make && make install

Finally, let’s add a new non-privileged account and adjust the file ownership:

adduser nginx
chown -R nginx:nginx /usr/local/nginx

Tada! You now have a working installation of nginx installed in /usr/local/nginx. If the config command yells at you, you probably need to install some dependencies. On one of the servers I use, it complained about not being able to find PCRE, however yum install pcre-devel quickly solved that problem.

You’ll most likely want Nginx to start and stop when the server boots/shutsdown, so go ahead and add the appropriate init.d script:

cd /etc/init.d/
wget http://blog.rightbrainnetworks.com/custom/nginx_init.txt
mv nginx_initd.txt nginx
chmod +x nginx
chkconfig --add nginx
chkconfig --level 345 nginx on

If you’ve modified the default install location (by running ./configure --prefix=/my/new/path when you configured nginx), be sure to modify the init.d script appropriately. We’ll want to configure Nginx before we start it, but first we’ll deal with Apache…

Dealing with Apache

If you’re able to remove Apache from your existing server, or you’re building a box specifically for your Pylons app, you’re in an enviable position. Feel free to skip this section. However, for the rest of us wanting to run Nginx and Apache on the same server to maintain existing applications/websites, there are a couple of hoops that need to be jumped through first. You have three basic options when choosing to go this route:

  1. Add another public IP address to your server. Configure Apache not to listen on that new address and use it exclusively with Nginx.
  2. Proxy the Apache requests using Nginx.
  3. Proxy the Nginx requests using Apache.

Option one is certainly the most simple and is the preferable choice of the three. However, adding an additional IP may not an option for a lot of people. I personally run almost all my stuff on Amazon EC2 these days. The only way for me to get an additional IP address is by launching an additional instance and I’d rather not pay for that unless necessary.

Configuring Nginx to pass through (proxy) requests for my legacy sites to Apache is certainly doable. But being that a goal of mine is to leave the existing environment as untouched as possible, it probably makes the most sense to configure Nginx to run behind Apache rather than vice-versa. Fortunately, this isn’t difficult to configure. At the bottom of the /etc/httpd/conf/httpd.conf file add the following:

httpd.conf

        ServerName www.your-pylons-website.com
        ServerAlias your-pylons-website.com
        ProxyPass / https://www.rightbrainnetworks.com:8080/
        ProxyPassReverse / https://www.rightbrainnetworks.com:8080/
        ProxyPreserveHost On
        ErrorLog /path/to/your/error_log
        CustomLog "|/usr/sbin/rotatelogs /path/to/your/access_logs/www.%Y%m%d.log 84500" combined

Depending on the complexity of your httpd.conf, it might be better to create this config in a seperate file and put it in the Apache config includes directory (/etc/httpd/conf.d). Lines #4 and #5 contain the TCP port number that you will be configuring Nginx to listen on. You may pick almost any port that you wish, but since you’ll be running Nginx under a non-privileged (non-root) user account for security reasons, the port has to be >1024. Feel free to change the “ErrorLog”, “CustomLog”, “ServerName”, and “ServerAlias” lines to suit your needs. Once the config is modified, give Apache a heads-up with a /etc/init.d/httpd reload command.

Configuring Nginx

Now that we know which port Apache is expecting Nginx to be listening at, we can finish the Nginx configuration. The file that we’re interested in is the main config file, /usr/local/nginx/conf/nginx.conf:

nginx.conf
user nginx;
worker_processes  1;

error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       8080;
        server_name  www.your-pylons-project.com your-pylons-project.com;

        location / {
             include /usr/local/nginx/conf/fastcgi.conf;
             fastcgi_index index;
             fastcgi_pass  127.0.0.1:9000;
         }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

}

As previously mentioned, this is the first time that I’ve used Nginx so its config file is largely the default. The lines that I’ve modified/added are #1, #35-36, and #39-41. The port number specified in line #35 should match the port you chose in the Apache ProxyPass configuration. Or, if you’re not using Apache in front of Nginx (or have the two daemons bound to separate IP addresses), you’ll probably want to listen on the standard port 80. Line #41 is the host/port used by FastCGI to communicate with your Pylons app. Again, you may chose almost any arbitrary port, so long as it matches the value in your project’s production.ini (but we’ll get to that topic in Part 2).

Conclusion

At this point, you should have your production environment ready to go and awaiting your Pylons application. In Part 2 of this tutorial I will discuss how to package up the application for deployment directly from your Subversion repository and then fire it up in a live environment.