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
- 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 ngix 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:
- Add another public IP address to your server. Configure Apache not to bind to that new address and use it exclusively with Nginx.
- Proxy the Apache requests using Nginx.
- Proxy the Nginx requests using Apache.
Option one is certainly the most simple of the bunch and is the preferable choice of the three. However, adding an additional IP may not an option for a lot of people. Configuring Nginx to pass through (proxy) requests for your 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 your /etc/httpd/conf/httpd.conf file add the following:
1 2 3 4 5 6 7 8 9 | <VirtualHost *:80> ServerName www.your-pylons-website.com ServerAlias your-pylons-website.com ProxyPass / http://localhost:8080/ ProxyPassReverse / http://localhost: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 </VirtualHost> |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | 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;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
# root html;
# index index.html index.htm;
include /usr/local/nginx/conf/fastcgi.conf;
fastcgi_index index;
fastcgi_pass 127.0.0.1:9000;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
# HTTPS server
#
#server {
# listen 443;
# server_name localhost;
# ssl on;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_timeout 5m;
# ssl_protocols SSLv2 SSLv3 TLSv1;
# ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
} |
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 #42-48. 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 #47 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 up with the value in your project’s production.ini (but we’ll get to that topic in Part 2 of this tutorial).
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.