Sonntag, 7. Februar 2010

Rails-Applikationen mit nginx und Thin

Wer Rails-Applikationen deployen möchte, wird vermutlich als erstes auf die Kombination Apache + Phusion Passenger stossen. So ging es auch mir. Praktisch insofern, da auf fast jedem Webserver ohnehin ein Apache läuft und der Passenger in Windeseile installiert ist. Beim Passenger handelt es sich um ein Apache-Modul (mod_rails), die Konfiguration in einem vHost ist ein Dreizeiler. Also eigentlich alles toll... wenn da nicht die Geschwindigkeit wäre. Wenn der Passenger erst mal läuft, ist die Geschwindigkeit durchaus als gut zu bewerten, aber wehe, wenn die fragliche Rails-Applikation eine Weile nicht mehr benutzt wurde. Dann werden die Passenger-Instanzen meiner Beobachtung nach nämlich vollständig abgeschossen und müssen neu gestartet werden, wenn doch wieder auf die Applikation zugegriffen wird. Auf meinem Server (AMD Athlon64 X2 5.600+, 4 GB RAM, 2 x 400 GB HDD im RAID-1; drei virtuelle Maschinen) dauert das mitunter ca. 20 - 30 Sekunden und macht in dieser Zeit die komplette VM unbenutzbar, andere Webseiten werden also nicht mehr ausgeliefert, der SSH-Daemon reagiert nicht.

Seit ich angefangen habe, mich mit Rails zu beschäftigen, lese ich einige Blogs von Firmen, die sich "hauptberuflich" mit Rails auf die eine oder andere Art und Weise beschäftigen. Dazu gehören unter anderem amerikanische Unternehmen wie Engine Yard [1], Rackspace[2] und GitHub[3]. In Deutschland ist Rails leider noch immer nicht so recht angekommen, die Anzahl wirklich interessanter, innovativer Unternehmen in diesem Bereich hält sich also stark in Grenzen. Die soeben genannten Unternehmen betreiben entweder für sich selbst oder im Kundenauftrag große Rails-Installationen und können sich solche Probleme, wie die im vorherigen Absatz geschilderten, absolut nicht erlauben. Insbesondere Engine Yard und GitHub betreiben viel Social Networking und haben sehr aktive gepflegte Blogs. Und in diesen bin ich auf so einige interessante Informationen gestossen, unter anderem auch mein Problem betreffend.

Wenn man eine Applikation in Rails entwickelt, verwendet man während der Entwicklungsphase der Einfachheit halber einen sehr rudimentären kleinen Webserver namens WEBrick. Er tut das, was er tun soll, startet schnell, eignet sich für den Produktbetrieb aufgrund fehlender Sicherheitsfeatures und ziemlich mauer Performance nur bedingt. Alternativ kann man Mongrel nutzen. Auch dieser ist fest mit Rails verbunden, bietet aber deutlich mehr Performance als WEBrick. Mongrel existiert auch in einer speziellen Cluster-Version. Mit Clustering ist hiermit gar nicht mal das verteilte Rechnen auf verschiedenen Systemen gemeint, sondern nur die Tatsache, dass mehrere Instanzen gestartet werden können. Dies ist ein im Rails-Bereich meinen Informationen nach ein übliches Setup. Es ist einfach aufzusetzen und Mongrel genügt für die meisten Zwecke. Aber Mongrel ist nun mal kein Apache und steht diesem in seinen Konfigurationsmöglichkeiten massiv nach. Einen reinen Mongrel-Cluster zu betreiben scheint also auch kein probates Mittel zu sein.

Nach dem Studium einiger Blog-Artikel entschied ich mich dann für den Toolstack, um den es hier gehen soll: nginx und Thin. nginx[4] als voll-funktionalen Webserver mit wenig Ressourcenhunger und Thin[5] als Application Server. Thin beschreibt sich selbst als die optimale Kombination aus dem Mongrel parser, Event Machine (einer Netzwerk-I/O-Bibliothek) und Rack (Minimal-Interface zwischen Webserver und Ruby-Applikation). Dieser Toolstack begeistert mich noch immer, ob er das auch unter wirklicher Last tun würde, kann ich (noch) nicht sagen, ich werde dann berichten.

Nun zur Installation.

Der erste Schritt sollte die Installation des entsprechenden Gems (und eines zugehörigen) sein. Mittels

sudo gem install thin; sudo gem install eventmachine --source http://code.macournoyer.com

installieren wir Thin. Um zu testen, ob alles in Ordnung ist, nehmen wir einen Verzeichniswechsel in zu unserer Rails-Applikation vor und tippen

thin start

ein. Es sollten ein paar Statusmeldungen ausgegeben werden, danach könnt ihr über http://domain.tld:3000 eure Rails-Applikation aufrufen. Sollte Thin nicht starten, was bei mir der Fall war, müsst ihr Thin noch zu eurer PATH-Variable hinzufügen. Thin liegt auf meinem Server (Ubuntu 9.04 Server) im Verzeichnis

/var/lib/gems/1.8/bin

Stellt sicher, dass dieser Eintrag als allererster in der PATH-Variable steht. Ich habe zu diesem Zweck meiner ~/.bashrc einfach am Ende folgende Zeile hinzugefügt:

export PATH=/var/lib/gems/1.8/bin:$PATH

Da das aber nicht das ist, was wir wollen, installieren wir nun nginx. Ich habe mich dafür entschieden, die aktuelle Stable aus dem Source Code zu kompilieren. Ein Blick auf http://nginx.org/en/download.html zeigt uns alle verfügbaren Versionen. Ich arbeite aktuell mit Version 0.7.65. Nach dem Download mittels

wget http://nginx.org/download/nginx-0.7.65.tar.gz

entpacken wir das Archiv mit

tar xvfz nginx-0.7.65.tar.gz

in das aktuelle Verzeichnis. In dieses Verzeichnis wird nun gewechselt. Ich habe meinen nginx mit den folgenden Optionen kompiliert:

 
./configure \
--prefix=/usr \
--conf-path=/etc/nginx/nginx.conf \
--http-log-path=/var/log/nginx/access_log \
--error-log-path=/var/log/nginx/error_log \
--pid-path=/var/run/nginx.pid \
--http-client-body-temp-path=/var/tmp/nginx/client \
--http-proxy-temp-path=/var/tmp/nginx/proxy \
--http-fastcgi-temp-path=/var/tmp/nginx/fastcgi \
--with-md5-asm --with-md5=/usr/include \
--with-sha1-asm \
--with-sha1=/usr/include \
--with-http_realip_module \
--with-http_ssl_module \
--with-http_stub_status_module

Der abschließende Build und die Installation erledigen wir so:

make && make install

Nun geben wir noch

sudo thin install && sudo /usr/sbin/update-rc.d -f thin defaults

um Thin automatisch starten zu lassen.

Weiter geht es mit der Konfiguration.

Konfigurieren wir als erstes Thin für den Live-Betrieb:

sudo thin config -C /etc/thin/railsapp.yml -c /var/www/railsapp --servers 2 --socket /var/run/thin/railsapp.sock -e production

Dieser Einzeiler legt im Verzeichnis /etc/thin/ eine Konfigurationsdatei im YAML-Format mit dem Namen railsapp.yml an. In dieser Datei wird festgehalten, wie Thin arbeiten soll. In diesem konkreten Falle wird, was sehr empfehlenswert ist, eine Socket- anstelle einer TCP/IP-Verbindung verwendet. Es werden zwei Serverinstanzen gestartet, die parallel laufen. ACHTUNG! Wer hier die Zahl zu hoch wählt, macht es seinem Server ganz schnell ganz schwer. Also vorsichtig erhöhen und nur, wenn es unbedingt nötig ist. Bemerkt man einen Flaschenhals, kann man hier natürlich rumschrauben. Das Hauptverzeichnis der Rails-Anwendung ist in diesem Falle /var/www/railsapp und die Sockets werden in /var/run/thin/ angelegt. Der letzte Schalter legt noch die Rails-Environment fest, hier wird also das Production Environment genutzt.

Die Konfiguration von nginx ist recht simpel. Meine /etc/nginx/nginx.conf sieht wie folgt aus:

  
user www-data;
worker_processes 2;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
# multi_accept on;
}
http {
include /etc/nginx/mime.types;
access_log /var/log/nginx/access.log;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
tcp_nodelay on;
gzip on;
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}

Als Systembenutzer setzt dieses Setup einen Benutzer mit dem Namen www-data voraus. Außerdem werden zwei Worker-Prozesse gestartet. Wer in seiner Rails-Anwendung eine Benutzerverwaltung einsetze, sollte natürlich auf https anstelle von http setzen. Wie man einen https-Container mit nginx einrichtet, werde ich gesondert an dieser Stelle beschreiben.

Im Verzeichnis /etc/nginx/sites-available legen wir nun einen vHost-Container für unsere Rails-Anwendung an. Dieser sollte einen sprechenden Namen als Dateinamen erhalten, also bspw. railsapp.domain.tld. Bei mir sieht der Inhalt wie folgt aus:

  
upstream railsapp {
server unix:/var/run/thin/railsapp.0.sock;
server unix:/var/run/thin/railsapp.1.sock;
}
server {
listen 80;
server_name railsapp.domain.tld;
access_log /var/www/railsapp/log/access.log;
error_log /var/www/railsapp/log/error.log;
root /var/www/railsapp/public/;
index index.html;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
if (-f $request_filename/index.html) {
rewrite (.*) $1/index.html break;
}
if (-f $request_filename.html) {
rewrite (.*) $1.html break;
}
if (!-f $request_filename) {
proxy_pass http://railsapp;
break;
}
}
}

Mittels

ln -s /etc/nginx/sites-available/railsapp.domain.tld /etc/nginx/sites-enabled/railsapp.domain.tld

legen wir einen Symlink in das Verzeichnis sites-enabled an. In diesem befinden sich alle vHost-Container, die nginx bedienen soll.

Nun starten wir noch nginx und Thin neu

sudo /etc/init.d/nginx restart; sudo /etc/init.d/thin restart

Gebt Thin ein paar Sekunden, um wirklich antworten zu können. Danach sollte eure Rails-Anwendung über Port 80 ausgeliefert werden.

Sollte ich beim Erstellen dieser Anleitung einen Fehler gemacht haben, lasst es mich bitte wissen, möchte ja keine falschen Informationen im Netz publizieren.

[1] http://www.engineyard.com/
[2] http://www.rackspace.com/
[3] https://github.com/
[4] http://wiki.nginx.org/Main
[5] http://code.macournoyer.com/thin/

Posted via email from ulfklose's posterous