10. Oktober 2017

Anforderungen an Applikationen in Kubernetes

Anforderungen an Applikationen in Kubernetes (Bild: https://github.com/kubernetes/kubernetes/blob/master/logo/usage_guidelines.md + Daniel Theuke)

Im Blogartikel zu Kubernetes (K8s) sind wir bereits auf einige Vorteile von Kubernetes-Containern eingegangen. Ebenso haben wir im Artikel Continuous Integration für Kubernetes mit Jenkins erfahren, wie man Applikationen in K8s konfigurieren und automatisch deployen lassen kann.

Kann man denn jede beliebige Applikation in Kubernetes laufen lassen? Wie so häufig ist die Antwort darauf ein klares Jein.

Hier soll es vor allem um die Anforderungen an Web-Applikationen gehen. Natürlich kann man all das, was irgendwo läuft, auch in einen Docker-Container packen und dementsprechend in Kubernetes betreiben. Allerdings bietet allein die Tatsache, dass eine Anwendung in Kubernetes läuft, noch keine echten Vorteile.

Anforderungen an die Applikation

Damit es überhaupt Sinn macht, eine Applikation in Kubernetes zu starten, muss die Applikation die folgenden Grundvoraussetzungen erfüllen:

  • Die Applikation muss ohne menschlichen Eingriff starten;
  • Die Applikation muss damit zurechtkommen, dass sie ggf. ohne Vorwarnung neu gestartet wird;
  • Die Applikation sollte mehrfach gleichzeitig (ggf. in verschiedenen Versionen) laufen können;
  • Die Applikation muss überwachbar sein.

Start ohne menschlichen Eingriff

Dass eine Applikation ohne menschlichen Eingriff startet, klingt zunächst nicht so ungewöhnlich, ist im Kubernetes-Umfeld aber besonders wichtig. Da Kubernetes versucht, sich automatisch selbst auszubalancieren, kann es immer wieder passieren, dass ein Container abgeschossen und auf einem anderen Worker neugestartet wird. Im Fall der Fälle müsste mitten in der Nacht ein Admin aus dem Bett gescheucht werden, um einen Systemstart durchzuführen.

Dies betrifft unter anderem auch das Aktualisieren der Datenbank. Wenn die Applikation startet, sollte sie entweder ein Umfeld vorfinden, mit dem sie arbeiten kann, oder sie sollte in der Lage sein, sich dieses Umfeld selbstständig zu erstellen (Näheres dazu in einem späteren Artikel zum Thema Datenbank-Migration).

Neustart ohne Vorwarnung

In den meisten Fällen wird Kubernetes versuchen, eine Anwendung mit Vorwarnung herunterzufahren. Wenn die Anwendung darauf jedoch nicht achtet oder nicht rechtzeitig reagiert, wird sie von Kubernetes zwangsbeendet. Dies kann auch passieren, wenn Kubernetes feststellt, dass eine Anwendung zu viel RAM benötigt.

Das heißt, die Applikation an sich muss zustandslos sein, darf also selbst keine Informationen nur im RAM vorhalten (außer ggf. Cache). Wenn ein mehrstufiger Prozess läuft, muss der Zustand des Prozesses an einer Stelle gespeichert werden, sodass eine andere Instanz ansatzlos fortfahren kann. Alternativ müssen die Aktionen so gestaltet sein, dass sie sich ggf. automatisch rückgängig machen, sollte der Prozess nicht komplett durchlaufen (beispielsweise über Datenbank-Transaktionen).

Skalieren von Applikationen

Die meisten Applikationen laufen ohne Probleme, solange sie nur einmal laufen. Allerdings kann es zu Problemen kommen, wenn eine Anwendung mehrfach läuft.
Beispielweise könnten beide Instanzen der Applikation versuchen, auf dieselbe Datei oder denselben Datensatz schreibend zuzugreifen oder beide sich wiederholende Aufgaben durchführen.

Aber aus welchem Grund möchte unsere Applikation überhaupt etwas in eine Datei schreiben? In den meisten Fällen haben die einzelnen Instanzen getrennte Dateisysteme, in die Dateien geschrieben werden können. Allerdings ist dies äquivalent zu einem Zustand in der Applikation, den wir unter allen Umständen vermeiden müssen. Natürlich könnten wir ein geteiltes persistentes Dateisystem einbinden, auf das die Dateien geschrieben werden, aber ob das sinnvoll ist, ist fraglich:

Für Logs sollte man die Verwendung eines Log-Analyse Tools, wie z.B. Graylog, in Erwägung ziehen. Hiermit können die Logs mehrerer Instanzen problemlos analysiert und so zusätzliche Messwerte für die Überwachung der Applikation gewonnen werden. Eine Log-Datei nach einem Fehler zu durchsuchen ist lästig, eine beliebig große Anzahl von Dateien (eine pro laufender Instanz) danach zu durchsuchen unmöglich.

Für temporäre Dateien empfiehlt es sich, diese, sofern möglich, im RAM zu behalten oder im Dateisystem des virtuellen Hostsystems abzulegen. Wenn diese einen nicht reproduzierbaren Teil des Zustands eines Vorgangs darstellen, sollte diese Datei an einem persistenten Ort gespeichert sein, sodass es nicht zu Überschneidungen, sowohl vom Dateinamen als auch vom Schreiben und Lesen der Datei, kommen kann. Hierzu eignet sich je nach Anwendungsfall auch eine MessageQueue, Datenbank, Object- oder KeyValue-Store.

CronJobs nicht skalieren

Ein weiterer, vielleicht noch wichtigerer Punkt ist die (Nicht-)Verwendung von sich wiederholenden Aufgaben (CronJobs) innerhalb der Applikation. Manche dieser Aufgaben können ohne Probleme mehrfach laufen, insbesondere, wenn es sich hierbei nur um die Aktualisierung eines serverinternen Caches handelt. Andere Aufgaben, wie z.B. das Versenden von E-Mails oder das Importieren von externen Datenquellen, sollen oder dürfen nicht mehrfach laufen.

Für die Lösung derartiger CronJob-Probleme gibt es grundsätzlich drei Lösungen:

  • Die unterschiedlichen Instanzen der Applikation miteinander zu synchronisieren;
  • Den CronJob aus der skalierbaren Applikation auszulagern und das Ergebnis per Schnittstelle zu übermitteln;
  • Den CronJob per Schnittstelle in einer (zufälligen) Instanz zu starten.

Diese drei Varianten sind natürlich nicht ohne Hindernisse zu implementieren, in den meisten Fällen ist die letzte Variante aber die einfachste. Für die zweite und dritte Variante kann außerdem das Feature von Kubernetes zum Starten von CronJob Pods verwendet werden.

Überwachung von Applikationen

Wenn nicht festgestellt werden kann, dass etwas funktioniert, dann tut es das meistens auch nicht. Daher ist es wichtig, in die Applikation Mechanismen einzubauen, die angeben, ob es der Anwendung gerade gut geht oder ob sie nur noch als Zombie an den kostbaren Ressourcen des Servers nagt. In Kubernetes können dazu verschiedene Wege verwendet werden. Bei Web-Anwendungen ist dies meist ein URL-Aufruf, der innerhalb einer bestimmten Zeit mit HTTP 200 – OK antwortet.

Es empfiehlt sich aber auch dringend, den erwarteten Speicherverbrauch der einzelnen Komponenten im Normal- und Last-Betrieb anzugeben, sodass Kubernetes weiß, ob eine zusätzliche Instanz der Applikation noch auf den Worker passt oder nicht. Außerdem kann Kubernetes so Memory-Leaks entdecken und die Applikation zur Not neustarten.

Fazit

Kubernetes eignet sich für viele Dinge, manche Applikationen brauchen jedoch zunächst eine Anpassung, bevor sie sich in Kubernetes beliebig skalieren lassen.

Hier noch einmal eine kleine Übersicht zu Anwendungsarten, für die Kubernetes geeignet ist:

  • Statische Applikationen wie HTML- oder JS-basierte Anwendungen;
  • Anwendungen, die schnell starten;
  • Anwendungen, die „wenig“ RAM verbrauchen;
  • Clusterfähige Applikationen.

Und im Umkehrschluss sind diese Applikationsarten für Kubernetes eher ungeeignet:

  • Anwendungen, die Standleitungen z.B. über TCP erfordern;
  • Anwendungen, die nicht unterbrochen werden dürfen;
  • Anwendungen, die nur einmal laufen sollen/dürfen;
  • Anwendungen, die keine parallelisierbaren CronJobs ausführen;
  • Anwendungen, die langwierige Aufgaben ausführen;
  • Anwendungen, die Persistenz bereitstellen sollen (Datenbanken o.ä.).

(Daniel Theuke)

Keine Kommentare