Einleitung
Die Server-Architektur bei myposter umfasst mehrere Server-Gruppen, die wir in Nodes zusammengefasst haben. So ist jede Node-Gruppe für eine einzige Aufgabe zuständig und kann dadurch einfach repliziert und skaliert werden.
Insgesamt betreiben wir in 2 Rechenzentren 22 Bare-Metal-Server. Von diesen 22 Bare-Metal-Servern dienen 7 zur Virtualisierung von weiteren Servern, womit wir (Stand heute) eine Gesamtzahl von 42 Servern betreiben.
Balancer-Nodes
Die Balancer-Nodes regeln den ein- und ausgehenden Traffic für alle Nodes. Prinzipiell kommt dort jeder HTTP Request an und wird anhand definierter Regeln an eine bestimmte Gruppe von Nodes (Backend) weitergeleitet. So werden z.B. alle Anfragen an store.myposter.de an die Store-Nodes weitergeleitet, alle Anfragen an static.myposter.de an die Static-Nodes und wenn ein Kunde die Seite www.myposter.de/magazin aufruft, erkennt auch dies der Load-Balancer und leitet den Request an die entsprechende Blog-Node weiter. Zusätzlich kümmern sich die Balancer-Nodes um die SSL-Termination, damit dies nicht von den anderen Nodes gemacht werden muss.
Zusätzlich haben wir hier einige Sicherheitsmechanismen eingebaut, welche einen Request auf ein statisches Backend weiterleiten, sollte ein bösartiger Angriff erfolgen.
Trifft keine bestimmte Regel zu, wird der Request an eine der App-Nodes weitergeleitet.
App-Nodes
Um die Last des eingehenden Traffics besser zu verteilen und die nötige Redundanz sicherzustellen, haben wir mehrere App-Nodes in Betrieb. Der Traffic an die App-Nodes wird ausschließlich von den Balancer-Nodes durchgereicht und, da dort bereits die SSL-Termination stattfindet, entlastet das die App-Nodes schon im Vorfeld.
Die gesamte Applikation ist darauf ausgelegt unabhängig von der Session auf jeder App-Node ausgeführt zu werden. Dafür ist z.B. der Session-Storage zentral in einem Key-Value-Store gespeichert.
Store-Nodes
Die Store-Nodes sind in einem Failover-Verbund mit DRDB, Heartbeat und STONITH. Das bedeutet, dass immer einer der Store-Nodes aktiv ist, während der andere als Slave-Server bereitsteht, wenn der primäre Server, aus welchem Grund auch immer, nicht mehr erreichbar ist.
Static-Nodes
Auf den Static-Nodes werden ausschließlich kleinere Dateien gehostet, welche jedoch besonders schnell ausgeliefert werden sollen. Da die Store-Nodes langsame Spindel-Festplatten haben und die Static-Nodes vor allem viele kleine Dateien ausliefern, wurde hier auf die wesentlich schnelleren SSD-Festplatten gesetzt.
Blog-Nodes
Wir betreiben zwei Blogs: das Fotomagazin und die Team-Website. Beide Blogs werden mit einer bekannten Open-Source-Software betrieben. Da diese Software und vor allem die Plugins öfters Sicherheitslücken ausweisen, welche einen Einbruch in unsere Systeme erleichtern würden, wurde hier besonders auf die Sicherheit geachtet. So wurde z.B. sichergestellt, dass eine Blog-Node ausschließlich mit den Balancer-Nodes kommunizieren kann. Auf das restliche Setup haben die Blog-Nodes keinen Zugriff. Dementsprechend sind die Blog-Nodes eigenständige LAMP-Server, welche mit keinem anderen Teil der Infrastruktur kommunizieren.
Worker-Nodes
Die Worker-Nodes übernehmen Aufgaben, die unabhängig von den App-Nodes ausgeführt werden können. Über supervisord werden verschiedenste PHP-Prozesse gestartet und verwaltet, welche langlaufende Aufgaben übernehmen, wie z.B. die Bildberechnung im Konfigurator, deren Funktionsweise weiter unten beschrieben ist. Aber auch CRON-Jobs werden von diesen Nodes ausgeführt. Meist werden die langlaufenden Aufgaben asynchron ausgeführt, d.h. dass keine andere Node aktiv auf eine Response wartet.
Datenbank-Nodes
Die Datenbank-Nodes sind, ebenso wie die Store-Nodes, in einem Failover-Verbund. Die Replikation vom Master-Server auf den Slave-Server erfolgt via Binlog-Replikation.
Broker-Nodes
Auf den Broker-Nodes wird RabbitMQ ausgeführt. Auch diese Nodes haben wir in einem Failover-Verbund. Die Broker-Nodes dienen zur Kommunikation z.B. zwischen App-Nodes und Worker-Nodes.
Redis-Nodes
Die Redis-Nodes dienen dem Caching und dem serverübergreifenden Session-Handling. Um die Requests über alle App-Nodes per Round-Robin-Balancing zu verteilen, haben wir den Session-Storage hierhin verlagert. Zusätzlich sind fast alle Bereiche einer HTML-Seite, außer z.B. der Header mit kundenspezifischen Daten, gecached, so dass wir die bestmöglichen Antwortzeiten erreichen.
Monitoring- und Logging-Infrastruktur
Alle Nodes müssen überwacht werden um sicherzustellen, dass alle benötigten Dienste zu jeder Zeit ausgeführt werden. Zusätzlich müssen Anomalien schnell und einfach erkannt werden. Dazu haben wir diverse Tools im Einsatz, die uns dabei unterstützen:
Funktionsweise der Bildberechnung
Aufgrund der großen Menge an Bildern, die Kunden bei myposter hochladen, ist die Bildberechnung ein sehr komplexes Thema. Lädt ein Kunde z.B. ein Bild hoch, müssen viele Vorschaubilder mit verschiedensten Effekten generiert werden. Würde diese Berechnung auf den App-Nodes geschehen, würde das die User-Experience negativ beeinflussen, da die gesamte Website bei hohem Besucheraufkommen langsam werden würde. Um dieses Problem zu umgehen, haben wir uns schon seit längerem dazu entschlossen, die Bildberechnung auf eigene Nodes zu verlagern. Wenn nun ein Kunde ein Bild hoch lädt, passiert folgendes:
Durch diese Entkoppelung haben wir eine enorme Entlastung der App-Nodes erreicht, vor allem unter Berücksichtigung, dass Digitalkameras immer hochauflösendere Bilder erstellen. Nun können wir ausschließlich die Nodes für die Bildberechnung skalieren, ohne dabei unnötig App-Nodes bereitstellen zu müssen.
Ein wichtiger Aspekt der Bildberechnung ist auch, dass sie synchron ausgeführt wird. Eine App-Node wartet dabei eine gewisse Zeit auf eine Antwort von einer Worker-Node. Erhält die App-Node in der vorgegebenen Zeit keine Antwort, z.B. in einem Fehlerfall, führt die App-Node die Bildberechnung selbstständig durch. Dadurch wird sichergestellt, dass die Bildberechnung in jedem Fall durchgeführt wird.
Lang laufende Aufgaben
Lang laufende Aufgaben bzw. „Long-Running-Tasks“ lassen sich mit einem HTTP-Request nur schwer durchführen, da hier vor allem Timeouts zu vorzeitigen Abbrüchen der Aufgaben führen. Webserver sind auf möglichst kurze Request/Response Zyklen konfiguriert – alles was länger als diese Zeit dauert, wird vom Webserver abgebrochen.
An einigen Stellen benötigen wir jedoch die Möglichkeit genau diese Limitierung zu umgehen. Ein Verschieben der Aufgaben über z.B. exec() oder proc_open() in den Hintergrund würde allerdings die Last der App-Nodes negativ beeinflussen und, wenn zu viele solcher Aufgaben gleichzeitig gestartet werden, die Antwortzeiten des Servers verschlechtern.
Diese lang laufenden Aufgaben werden, wie auch schon die Bildberechnung, auf den Worker-Nodes durchgeführt. Die App-Node, die die Aufgabe entgegennimmt, generiert eine Message und schickt diese an die aktive Broker-Node. Die Broker-Node verteilt diese Aufgabe an eine Worker-Node, die diese Aufgabe verarbeitet.
Je nach Art der Aufgaben werden im Vorfeld mehrere Prozesse auf den Worker-Nodes gestartet, welche die Aufgabe verarbeiten können. Bei manchen Aufgaben generieren wir z.B. rund 80.000 Nachrichten auf einmal. Je nach Konfiguration der Broker-Node können mehrere Prozesse auf Nachrichten einer Queue reagieren, wodurch Nachrichten parallel verarbeitet werden können.
Deployment der Serverkonfiguration
Die vorhergehenden Beschreibungen lassen erahnen, dass so ein komplexes Setup eine jederzeit nachvollziehbare und wiederholbare Konfiguration erfordert. Wichtig ist vor allem, dass die Konfiguration nach einem Update noch reibungslos funktioniert. Ein weiterer wichtiger Aspekt ist, dass die Konfiguration auch so automatisiert wie möglich für die Staging- und Development-Umgebungen verwendet werden kann.
Um dies zu ermöglichen, haben wir uns für Puppet zur Provisionierung der Server entschieden. Mit Puppet können wir unabhängig von der Environment unterschiedliche Konfigurationen einspielen, Gruppen von Servern definieren und die Konfiguration der Server in Git hinterlegen um Änderungen und deren Gründe einfach nachvollziehen zu können.