Preamble
In this post I will define typical production environment on OpenBSD OS for deployment of Ruby on Rails applications.
There are few common things:
1. The Ruby on Rails framework doesn’t support concurrent running in multiple threads within the same process, and so to scale it and fully utilize available hardware we need to execute application in several processes.
2. We need load-balancer to spread incoming requests between application instances.
3. We need separate web-server to serve static content.
Overall configuration
At first all incoming HTTP requests from a clients come to httpd web-server, it servers all static content, and send other requests to HAProxy.
HAProxy receives requests and selects free Thin instance, forwards the request to it, receives a response and passes it back to httpd.
Following diagram should give you basic understanding about common work of components :
OpenBSD Httpd – standard OpenBSD web-server
I suggest OpenBSD standard web-server as you can find it as part of OpenBSD base installation, it checked for security issues and being updated as part of OpenBSD. We will use it to serve static content and don’t bother our Thin servers.
file: httpd.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 | BindAddress SERVER_IP_ADDRESS # Dynamic Shared Object (DSO) Support # caching proxy LoadModule proxy_module /usr/lib/apache/modules/libproxy.so # allow Symbolic Links for root of our static content and all sub-directories Options +FollowSymLinks ServerAdmin ADMIN_EMAIL # path to root of our static content DocumentRoot /var/www/railsdocs/RAILS_PROJECT/public ServerName SERVER_NAME ServerAlias www.SERVER_NAME # directories that contain static content (they excluded from dispatching to HAProxy) ProxyPass /images ! ProxyPass /stylesheets ! ProxyPass /javascripts ! ProxyPass /500.html ! ProxyPass /503.html ! # address where to send and from receive requests (HAProxy listens that address) ProxyPass / http://127.0.0.1:4000/ ProxyPassReverse / http://127.0.0.1:4000/ # Disallows remote servers to be mapped into the space of the local server. ProxyRequests Off # Don't use incoming Host HTTP request header for proxy request. ProxyPreserveHost Off ErrorLog logs/SERVER_NAME-error_log CustomLog logs/SERVER_NAME-access_log common |
see for configuration details: man httpd
For OpenBSD 4.6/4.6 -Stable
It’s the hard part, OpenBSD4.6 has a bug in mod_proxy module so ‘!’ directive doesn’t work.
You have to edit following file: /usr/src/usr.sbin/httpd/src/modules/proxy/mod_proxy.c
Find method: static int proxy_trans(request_rec *r)
in that method after condition: if (len > 0) {
add 2 string:
1 2 | if (ent[i].real[0] == '!' && ent[i].real[1] == '\0') return DECLINED; |
so final part of code:
…
1 2 3 | if (len > 0) { if (ent[i].real[0] == '!' && ent[i].real[1] == '\0') return DECLINED; |
…
Then recompile your system, it’s common procedure for following -stable so you should already know it otherwise see for details for building instructions: http://www.openbsd.org/faq/faq5.html
Thin – high performance ruby web server
We need some Ruby web-server, and it seems that at this time Thin provides best performance.
At least we see such results on Thin homepage: http://code.macournoyer.com/thin/
file: start.sh
1 2 | # start work instances thin start -C thin-production.yml |
file: thin-production.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | --- environment: production port: 4001 address: 127.0.0.1 daemonize: true servers: 4 chdir: /var/www/railsdocs/RAILS_PROJECT pid: tmp/pids/thin.pid log: log/thin.log user: myuser group: mygroup require: [] |
see for configuration details: http://code.macournoyer.com/thin/usage/
HAproxy – TCP/ HTTP load balancer
As Rails doesn’t support concurrent running each incoming request should be assigned to a separate process. HAProxy can be configured to send only one request at a time to every Thin server, it will always pick instance that is not busy with something.
It provides bunch of other useful things like:
– route HTTP requests depending on statically assigned cookies ;
– switch to backup servers in the event a main one fails ;
– accept connections to special ports dedicated to service monitoring ;
– add/modify/delete HTTP headers both ways ;
– block requests matching a particular pattern ;
for full documentation see: http://haproxy.1wt.eu/#docs
note:
If someone thinks that we could use nginx for that purpose check following performance comparison of HAProxy and Nginx:
http://affectioncode.wordpress.com/2008/06/28/another-comparison-of-haproxy-and-nginx/
file: haproxy.cfg
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 | defaults log global mode http # provides more detailed information about HTTP contents, such as the request and some cookies option httplog # do not to log any session which didn't transfer any data option dontlognull # allow the proxy to break their persistence and redistribute connections in case of failure option redispatch # the number of attempts to reconnect after a connection failure to a server retries 3 # the time we accept to wait for a connection to establish on a server contimeout 100000 # the time we accept to wait for data from the client, or for the client to accept data clitimeout 100000 # the time we accept to wait for data from the server, or for the server to accept data srvtimeout 100000 listen project_proxy 127.0.0.1:4000 balance roundrobin # creates an HTTP 'X-Forwarded-For' header which contains the client's IP address. # This is useful to let the final web server know what the client address was option forwardfor # using “maxconn 1″ improves performance with Rails. # As Rails instance can process only 1 request “maxconn 1″ force HAProxy to select next free instance server app1_1 127.0.0.1:4001 check inter 60000 rise 2 fall 5 maxconn 1 server app1_2 127.0.0.1:4002 check inter 60000 rise 2 fall 5 maxconn 1 server app1_3 127.0.0.1:4003 check inter 60000 rise 2 fall 5 maxconn 1 server app1_4 127.0.0.1:4004 check inter 60000 rise 2 fall 5 maxconn 1 # httpd web-server will handle it in case of 503, 504 errors due to it's static content errorloc 503 http://DOMAIN_NAME/503.html errorloc 504 http://DOMAIN_NAME/504.html # statistics page thru http://127.0.0.1:8080 listen stats 127.0.0.1:8080 balance roundrobin mode http stats uri / |
see for configuration details: http://haproxy.1wt.eu/#docs