Posterous theme by Cory Watilo

Filed under: Ruby on Rails

Rails-Anwendung auf https umleiten

Immer wieder stehe ich vor dem gleichen Problem, der Blogbeitrag soll also auch gleichzeitig als Gedächtnisstütze für mich dienen.

Für mein Standardsetup verwende ich als Webserver nginx (Anzahl Worker = Anzahl Cores), als Rails-Applicationserver kommt thin mit zwei Workern zum Einsatz. Eine Standardkonfiguration könnte nun beispielsweise so aussehen:

# /etc/nginx/sites-available/my-site.de

upstream my-site {
  server   unix:/tmp/thin.my-site.0.sock;
  server   unix:/tmp/thin.my-site.1.sock;
}

server {
  listen   80;
  server_name my-site.de www.my-site.de;

  access_log /var/www/my-site.de/www/logs/access.log;
  error_log /var/www/my-site.de/www/logs/error.log;

  root   /var/www/my-site.de/www/htdocs/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://my-site;
      break;
    }

  }
}

In Verbindung mit dieser /etc/thin/my-site.yml funktioniert eine Rails-Anwendung auch einwandfrei:

---
pid: tmp/pids/thin.pid
wait: 30
timeout: 30
log: log/thin.log
max_conns: 1024
require: []

environment: production
max_persistent_conns: 512
servers: 2
daemonize: true
chdir: /var/www/my-site.de/www/htdocs
socket: /tmp/thin.sock

Sämtlicher Traffic läuft bei diesem Setup unverschlüsselt über Port 80, also Standard-http. Wenn man nun aber ein Login, bspw. für ein CMS wie Radiant, realisieren möchte, wäre es natürlich wünschenswert, den Traffic verschlüsselt über Port 443, also https, laufen zu lassen. Notwendig ist das aber aus meiner Sicht nur bei Zugriffen auf das Admin-Backend. Im nachfolgenden Setup werden sämtliche Anfragen auf http://www.my-site.de/admin/{IRGENDWAS} auf https://www.my-site.de/admin/{IRGENDWAS} umgeleitet. Requests auf die Website selbst bleiben von der Umleitung also unberührt.

# /etc/nginx/sites-available/my-site.de

upstream my-site {
  server   unix:/tmp/thin.my-site.0.sock;
  server   unix:/tmp/thin.my-site.1.sock;
}

server {
  listen   80;
  server_name my-site.de www.my-site.de;

  access_log /var/www/my-site.de/www/logs/access.log;
  error_log /var/www/my-site.de/www/logs/error.log;

  root   /var/www/my-site.de/www/htdocs/public/;
  index  index.html;

  rewrite ^/admin/(.*) https://www.my-site.de/admin/$1 permanent; # Rewrite-Regel für die Umleitung aller Anfragen auf /admin

  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://my-site;
      break;
    }

  }
}

server {
  listen 443;
  server_name my-site.de www.my-site.de;

  ssl                 on;
  ssl_certificate     /etc/ssl/certs/startssl.crt;
  ssl_certificate_key /etc/ssl/private/startssl.key;
  ssl_ciphers         ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM;
  ssl_protocols       SSLv3 TLSv1;

  access_log /var/www/my-site.de/www/logs/access.log;
  error_log /var/www/my-site.de/www/logs/error.log;

  root   /var/www/my-site.de/www/htdocs/public/;
  index  index.html;

  location / {
    proxy_set_header  X-FORWARDED_PROTO https;
    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://my-site;
      break;
    }
  }

}

Meine Zertifikate beziehe ich übrigens zumeist von StartSSL. Die Zertifikate kosten nichts und werden trotzdem von allen aktuellen Browsern akzeptiert. Nur den grünen Hintergrund bekommt man mit diesem Zertifikat nicht, was ich persönlich aber als unkritisch einstufe. Googles Chrome wird derzeit aber leider nicht für die Erstellung der Zertifikate unterstützt, mit der aktuellen Version von Apples Safari geht’s aber einwandfrei.

Kurz noch zur Erklärung, wieso nginx und thin:

  • nginx ziehe ich Apache vor, weil er deutlich schlanker und performanter ist. Für PHP-Setups verwende ich zumeist weiterhin den Apache, weil dieser sich deutlich einfacher mit PHP aufsetzen lässt, als dies mit nginx der Fall ist.
  • thin ist extrem einfach aufzusetzen, einfacher als Unicorn oder mongrel_cluster, die im Endeffekt genau das Gleiche tun. Und dank der Fähigkeit von thin, auch als UNIX-Socket zu laufen, ist er auch extrem schnell.

Von Fall zu Fall mag die Eignung der Alternativen eher gegeben sein, ich habe aber ziemlich gute Erfahrungen mit diesem Setup gemacht. Es ist einfach zu administrieren und äußerst performant.

Ein kleiner Tipp noch zum Schluss. Um sowohl thin als auch nginx in den aktuellen Versionen zu fahren, benutze ich folgende Wege der Installation:

nginx installiere ich über das eigens dafür bereitgestellte PPA, welches wie folgt in Ubuntu 10.04 LTS oder neuer eingebunden werden kann:

nginx=stable # use nginx=development for latest development version
sudo su -
add-apt-repository ppa:nginx/$nginx
apt-get update 
apt-get install nginx

thin installiere ich aus den Ruby-Gems heraus und nutze dann den komfortablen Installer, den die Jungs mitliefern:

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

Um die Anwendungsumgebungen nicht händisch erstellen zu müssen, kann man das thin-Kommando benutzen:

sudo thin config -C /etc/thin/my-site.yml -c /var/www/my-site.de/www/htdocs --servers 2 -socket /tmp/thin.my-site.sock -e production

Dieser Befehl erstellt eine Konfigurationsdatei für thin, wie sie weiter oben zu sehen ist.

Heroku: Rails-Anwendungen in der Cloud

Die Cloud, das Buzzword des letzten Jahres, ist in aller Munde. Und hat auch schon erste Federn lassen müssen. Was die Cloud eigentlich kennzeichnet, ist nahezu grenzenlose Skalierbarkeit. Wenn es nach den Befürwortern der Cloud geht, bucht niemand mehr physikalische Maschinen, sondern Speicherplatz, RAM, Rechenleistung in dem Umfang, den er benötigt. Wird zwischenzeitlich mal mehr gebraucht, dreht man kurz an der entsprechenden Schraube und zahlt eben für die Zeit ein wenig mehr. Es gibt unheimlich viele Anbieter, gerade Amazon, eigentlich Interneteinzelhändler, hat in diesem Bereich von sich reden gemacht. Amazon bietet alles denkbare an Cloud-Dienstleistungen an, was man sich nur so vorstellen kann. Genau das ist dem Wikileaks-Projekt zum Verhängnis geworden, weil der Anbieter somit auch am einzigen Hebel sitzt. Legt er den um, ist Feierabend. Amazon bietet zwar eine recht große Produktpalette an, alles abdecken tun sie dann aber auch nicht.

Wer beispielsweise Rails-Anwendungen in der Cloud laufen lassen möchte, ist auf andere Dienstleister angewiesen. In den letzten Jahren hat sich ein Anbieter namens Heroku einen Namen in der Community gemacht. Erst kürzlich wurde Heroku von salesforce.com, einem großen Anbieter von Geschäftsanwendungen, für rund 212 Mio. US-$ aufgekauft.

Heroku hat ein für den Entwickler sehr effizient zu nutzendes und einfaches Deployment-Verfahren entwickelt, welches komplett Git- und Rake-gestützt ist. Der Rails-Entwickler muss sich also nicht mit der Administration und Konfiguration von Webservern herumärgern. Ein Vorteil gegenüber den klassischen Rails-Hostern, von denen es ohnehin relativ wenige gibt ist, dass man nicht auf die vom Hoster installierten Ruby-Gem-Versionen angewiesen ist, sondern seine eigenen Versionen spezifizieren kann, wie man es von seiner Entwicklungsmaschine her kennt.

Wer reine Rails-3-Anwendungen deployen möchte, hat seine Anwendung binnen weniger Minuten online:

sudo gem install heroku
git init
heroku create
git add .
git commit -a -m 'first deployment commit to Heroku'
git push heroku master
heroku rake db:migrate
heroku open

Das war es schon. Die Anwendung sollte online sein und sich im Browser geöffnet haben.

Bei Rails-2-Anwendungen gestaltet sich das Deployment etwas schwieriger, aber auch nicht viel. Bevor man die oben erwähnte Befehlskette anschubsen kann, muss erst einmal eine Datei namens .gems erstellt werden. In dieser müssen dann alle erforderlichen Gems, ggf. inklusive Versionsnummer notiert werden. Beispiel:

rails --version 2.3.5
i18n --version 0.4.2
rack --version 1.0.1

Erst dann darf deployed werden. Neben den oben erwähnten Möglichkeiten gibt es aber noch viele weitere, die allesamt auf den wirklich tollen Support- und Dokumentations-Seiten von Heroku dokumentiert sind. So kann man, sofern man bereits lokal PostgreSQL verwendet, seinen kompletten Datenbankinhalt mittels heroku db:push in die Anwendung bei Heroku pushen. PostgreSQL ist übrigens die einzige Datenbank, die von Heroku angeboten wird. Laut Heroku deswegen, weil sie dort die optimale Kombination zwischen Zuverlässigkeit, Datenintegrität und Geschwindigkeit sehen. Ein Statement, das ich durchaus unterschreiben kann. Das heroku-Gem ist äußerst mächtig und bietet einem Zugriff auf sämtliche installierbare Add-Ons (s.u.), auf die Logs und noch vieles mehr. Eine Studie der Dokumentation ist empfehlenswert.

Ab sofort kann dann direkt in den Anwendungscontainer bei Heroku deployed werden, indem man ein ganz reguläres Git-Commit erstellt. Einfacher geht es kaum noch.

Nachdem die erste Version der Anwendung deployed wurde, kann man sie um einige tolle, teilweise auch kostenlos nutzbare Add-Ons erweitern. Dazu gehören nützliche Erweiterungen wie CNAME-Einträge für die Anwendung (damit man sie auch unter einer eigenen (Sub-)Domain betreiben kann), Jasondb, MongoDB und CouchDB für die NoSQL-Anhänger unter uns, Exception-Tracker, Echtzeitsuche, New Relic, SMS-Gateways, automatisierte Datenbankbackups, etc. Manche kosten gar nichts, manche nur wenig Geld, andere wiederum sind recht teuer (wobei teuer mal wieder relativ ist). Dolle ins Geld geht ein SSL-Zertifikat, da solche Zertifikate weiterhin IP-basiert sind, was bei einem Cloud-basierten Dienst natürlich recht finster werden kann.

Apropos Kosten: jeder kann bei Heroku beliebig viele Anwendungen hosten lassen, die auch erst mal nichts kosten, in der Leistungsfähigkeit aber arg eingeschränkt sind. Benötigt man mehr Ressourcen, muss man in die Tasche greifen, ab ca. 36 US-$ (0,05 $-Cent pro Stunde) monatlich geht es los. Dabei unterteilt Heroku in Dynos und Worker. Dynos beschleunigen das Frontend, Worker die Hintergrundprozesse der Rails-Anwendung. Der Maximalausbau liegt bei 24 Dynos und 24 Workern, wofür dann aber auch 2,35 US-$ pro Stunde, oder umgerechnet rund 1.700 US-$ monatlich anfallen. Die Performancestufe dürfte aber auch „gehobenen“ Ansprüchen genügen.

Damit ist aber noch nicht Schluss, denn eine dedizierte Datenbank ist bei dem Preis noch nicht inklusive. Kostenlos gibt es 5 MB Shared Database, für 20 US-$ monatlich 20 GB Shared Database. Wer gern eine dedizierte Datenbank hätte, muss bspw. für den kleinsten Tarif Ronin 200 US-$ monatlich berappen. Dafür erhält er 16 gleichzeitige Verbindungen, 1,7 GB RAM und 1 computing unit. Für 6.400 US-$ gibt es 400 Verbindungen, 68 GB RAM und 26 computing units. Wer’s braucht…

Herokus Dateisystem ist read-only, Dateiuploads lassen sich also nicht realisieren. Hierzu kann/sollte man, auch laut Heroku-Dokumentation, auf Anbieter wie Amazon S3 ausweichen. Für Rails-Anwendungen gibt es diverse Möglichkeiten, Uploads zu Amazons S3 (Simple Storage Service) aus der Anwendung heraus zu realisieren. Namentlich erwähnt werden Attachment-Fu und Paperclip. Eine persönliche Präferenz kann ich hier abgeben, ich hab mit beiden noch nicht gearbeitet. Heroku empfiehlt im Übrigen generell, große Dateien, die die Applikation zum Download bereitstellen soll, aus Performancegründen zu S3 oder ähnlichen Services auszulagern, weil das Dateisystem von Heroku nicht für derartige Anwendungszwecke konzipiert und optimiert wurde.

Um die Performanceunterschiede messen zu können, habe ich eine Installation von Redmine bei Heroku vorgenommen. Das Deployen dieser Anwendung ist leider nicht total trivial, die einzelnen Schritte habe ich deswegen in einem Gist (welcher auch noch mal ganz unten eingebettet ist) niedergeschrieben.

Zum Ergebnis meiner Benchmarks, gemessen auf einer Projektübersichtsseite mit ab -c 50 -n 200:

  1. 1 Dyno (kostenlos): 7,98 Requests pro Sekunde
  2. 2 Dynos: 13,15 Requests pro Sekunde
  3. 3 Dynos: 18,46 Requests pro Sekunde
  4. 10 Dynos: 53,46 Requests pro Sekunde
  5. 24 Dynos: 84,14 Requests pro Sekunde
  6. 1 Worker: 7,64 Requests pro Sekunde
  7. 2 Worker: 7,74 Requests pro Sekunde

Die Anzahl der Worker wirkt sich also in keiner Weise auf die Anwendungsperformance aus, die Anzahl der Dynos hingegen beträchtlich. Den größten Gewinn (prozentual gesehen) bekommt man hier, wenn man von dem einen kostenlosen Dyno auf einen zweiten, kostenpflichtigen aufrüstet. Die monatlichen Kosten liegen mit diesem bei rund 36 US-$, also umgerechnet in etwa 27 €. Eigentlich nicht zu viel verlangt, man darf nur nicht vergessen, dass in diesem Preis noch kein Storage und nur 5 MB an Datenbankplatz inklusive ist.

Zum Vergleich: mein nicht optimierter Root-Server (Athlon64 X2 6.400+, 4 GB RAM, OpenVZ) liefert rund 10,5 Requests pro Sekunde. Den muss ich natürlich selbst administrieren, warten, etc. Und das Deployment ist auch längst nicht so bequem bzw. müsste erst mal von mir auf diesen Bequemlichkeitslevel gebracht werden.

Heroku bietet für einen akzeptablen Preis einen wirklich tollen und performanten sowie äußerst flexiblen Service an. Für den Rails-Entwickler, der keine Lust hat, sich mit der Einrichtung und Administration eines Rails-fähigen Webservers rumzuschlagen und ggf. auftretende Probleme zu beheben ist Heroku aus meiner Sicht optimal. Zumal man hier nicht gleich einen zweiten Server hinzu kaufen muss, nur weil die Anwendung an ein oder zwei Stunden am Tag mal mehr Ressourcen braucht, als es die eigene Hardware zulässt. Die Anmeldung kostet nichts und das Deployen der eigenen (oder fremden) Anwendung genau so wenig. Einem Versuch steht also nichts im Wege. Viel Spaß dabei.

Radiant Forms Extension und Google Mail/Google Apps

Radiant_-_content_management_simplified
Im Augenblick setze ich die erste Kundenpräsenz mit dem auf Ruby on Rails basierenden CMS Radiant auf. Einen etwas ausführlicheren Artikel zu diesem Thema werde ich bei Zeiten auch noch veröffentlichen. An dieser Stelle möchte ich aber erst mal eine Fehlerquelle enttarnen, die mich etwas Gebastle gekostet hat.

Die eingangs erwähnte Website soll auch ein Kontaktformular enthalten. Zu diesem Zwecke habe ich die Erweiterung Radiant Forms Extension von Dirk Kelly verwendet. Ich habe mich bei der Einrichtung der Extension strikt an die Informationen der README gehalten, trotzdem kamen die über das Formular eingegebenen Daten nicht an.

Um die Extension mit einem Google-Apps- oder Google-Mail-Konto zu verwenden, scheint folgender Inhalt für die Konfigurationsdatei erforderlich zu sein:

Mittels dieser Konfiguration versendet die Extension tadellos die im Formular eingegebenen Mails.

Für mich auch ein Stolperstein waren die Tags. Mittlerweile ist die README angepasst, ich bin aber noch über eine veraltete Version gestolpert. Achtet also darauf, dass ihr auch die korrekten Tags, nämlich r:form:read und nicht r:get, verwendet. Die aktuelle README aber wie gesagt enthält bereits die korrigierten Daten.