Services können allerlei integrierte Funktionen für Zuverlässigkeit und Ausfallsicherheit bieten. Um jedoch in der Praxis zuverlässig zu sein, müssen sie auch mit vorhersehbaren Ausfällen zurechtkommen. Bei Amazon entwickeln wir skalierbare und redundante Services, weil Hardware nun einmal früher oder später ausfallen wird. Jede Festplatte hat nur eine bestimmte maximale Lebensdauer und jede Software stürzt irgendwann einmal ab. Der Zustand eines Servers mag einen binären Eindruck vermitteln: entweder er funktioniert oder er funktioniert nicht (und ist dann einfach nicht erreichbar). Leider stimmt das so nicht. Wir haben festgestellt, dass ein ausgefallener Server nicht einfach herunterfährt, sondern unvorhersehbare und manchmal unverhältnismäßige Schäden im System anrichten kann. Mit Zustandsprüfungen werden Probleme dieser Art automatisch erkannt und behoben.

In diesem Artikel wird beschrieben, wie wir mithilfe von Zustandsprüfungen vereinzelte Serverausfälle ermitteln und mit diesen Ausfällen umgehen. Außerdem erläutern wir, was passieren kann, wenn auf den Einsatz von Zustandsprüfungen verzichtet wird, und wie kleine Probleme zu Totalausfällen mutieren können, wenn Systeme angesichts erkannter Ausfälle überreagieren. Außerdem bieten wir Einblicke in die Erfahrung von Amazon in Bezug auf Kompromisslösungen, wenn verschiedene Zustandsprüfungsimplementierungen zur Wahl stehen.

Kleine Ausfälle mit großen Auswirkungen

In meinen Anfängen als Softwareentwickler bei Amazon arbeitete ich in der Abteilung für das Website-Rendering von Amazon.com. Beim Bearbeiten einer Änderung, mit der wir neue Tools hinzufügen und Einblicke in die Leistung der Software erhalten wollten, baute ich leider einen Fehler ein. Er wurde zwar nur selten ausgelöst, aber wenn es passierte, sorgte dieser Fehler dafür, dass der betroffene Webserver für jede Anforderung nur leere Fehlerseiten ausgab. Das Problem konnte nur durch einen Neustart des Webserverprozesses behoben werden. Wir machten den Fehler ausfindig und setzten die Änderung schnell wieder zurück. Dann machten wir ausführliche Tests und führten Prozessverbesserungen ein, um vergleichbare Zustände künftig erfassen zu können. Doch während sich der Fehler in der Produktionsumgebung befand, trat bei ein paar Servern einer größeren Flotte auf einmal ein undefinierbares Problem auf.
 
Die Tatsache, dass die Server nicht selbst erkennen konnten, dass sie sich in einem fehlerhaften Zustand befanden, machte es wirklich kompliziert, den Fehler zu finden. Darüber hinaus konnten die Server ihre Zustandsdaten nicht mehr an die Überwachungssysteme übermitteln. Deshalb wurden sie nicht automatisch außer Betrieb genommen und die üblichen Warnmeldungen blieben aus. Zu allem Überfluss wurden die Serverprozesse stark beschleunigt, sodass die betroffenen Server in kürzester Zeit mehr leere Fehlerseiten ausgaben als nicht betroffene Server normale Webseiten. Unsere damals eingesetzte Load-Balancer-Technologie bevorzugte schnelle gegenüber langsamen Servern. Ein unverhältnismäßiger Anteil des Traffics wurde deshalb an die fehlerhaften Server weitergeleitet, was die Auswirkungen noch verstärkte.

Da bei der Überwachung an mehreren Punkten im System Fehlerraten und Latenzzeiten gemessen werden, wurden zusätzliche Warnmeldungen ausgelöst. Überwachungssysteme und Betriebsprozesse dieser Art können zwar dazu beitragen, das Problem einzudämmen, aber um die Auswirkungen all dieser verschiedenen Fehler zu minimieren, müssen Ausfälle schnell erkannt und die Ursachen korrigiert werden. Dazu bedarf es der richtigen Zustandsprüfungen.

Kompromisse bei der Zustandsprüfung

Zustandsprüfungen bieten die Möglichkeit, einen Service auf einem bestimmten Server zu fragen, ob er in der Lage ist, seine Arbeit erfolgreich zu erledigen. Ein Load Balancer stellt diese Fragen jedem Server in regelmäßigen Abständen, um zu bestimmen, an welche Server Traffic weitergeleitet werden kann. Ein Service, der Nachrichten aus einer Warteschlange abruft, führt eventuell eine Diagnoseabfrage mit sich selbst durch, bevor die Verarbeitung der Warteschlange fortgesetzt wird. Überwachungs-Agents, die auf jedem einzelnen Server oder auf einer externen Überwachungsflotte ausgeführt werden, könnten Server fragen, ob sie fehlerfrei laufen, um im Fall einer negativen Antwort eine Warnmeldung auszugeben oder das Problem automatisch zu beheben.

Wenn ein fehlerhafter Server nicht außer Betrieb genommen wird, kann das die Verfügbarkeit des Service insgesamt in einem unverhältnismäßigen Ausmaß beeinträchtigen – das hat uns das Beispiel mit meinem Websitefehler gezeigt. Bei einer Flotte von zehn Servern reduziert ein fehlerhafter Server die Verfügbarkeit der gesamten Flotte auf 90 % oder weniger. Zudem weisen einige Load-Balancer-Algorithmen, z. B. "least request" (geringste Anzahl von Anforderungen), den schnellsten Servern die meiste Arbeit zu. Wenn ein Server dann ausfällt, dauert es meistens nicht lange, bis Anforderungen unbeantwortet bleiben. Dadurch entsteht in der Serviceflotte ein schwarzes Loch, weil der fehlerhafte Server mehr Anforderungen erhält als andere Server. Manchmal erhöhen wir die Schutzmaßnahmen, um die Entstehung von schwarzen Löchern zu verhindern. Dazu gleichen wir die Rate der fehlerhaften Anforderungen an die durchschnittliche Latenz der erfolgreichen Anforderungen an. In anderen Szenarien, etwa beim Abrufen von Warteschlangen, erfordert dieses Problem jedoch einen komplizierteren Lösungsweg. Wenn ein Warteschlangenabrufer Nachrichten beispielsweise so schnell abruft, wie er sie empfängt, wird ein fehlerhafter Server ebenfalls zu einem schwarzen Loch. In Anbetracht dieser vielfältigen Umgebungen für die Lastverteilung verwenden wir unterschiedliche Ansätze, um den Schutz von Systemen mit teilweise ausgefallenen Servern zu gewährleisten.

Unserer eigenen Erfahrung nach können Server aus den verschiedensten Gründen unabhängig voneinander ausfallen: Laufwerke lassen sich plötzlich nicht mehr beschreiben und verursachen sofortige Anforderungsfehler; Systemzeiten sind auf einmal nicht synchron, sodass Aufrufe an Abhängigkeiten nicht authentifiziert werden; der Abruf aktualisierter kryptografischer Inhalte durch einen Server schlägt fehl, woraufhin Ent- und Verschlüsselungsvorgänge scheitern; kritische Supportprozesse werden von internen Fehlern lahmgelegt; Arbeitsspeicherverluste und Deadlocks zwingen die Datenverarbeitung in die Knie.

Serverausfälle können aber auch miteinander zusammenhängen, sodass ein Serverfehler den Ausfall anderer oder aller Server in einer Flotte zur Folge hat. Zusammenhängende Ursachen umfassen Ausfälle gemeinsamer Abhängigkeiten und Probleme in großen Netzwerkumgebungen. Bei einer optimalen Zustandsprüfung wird jeder Aspekt des Server- und Anwendungszustands geprüft. Bestenfalls wird dabei auch bestätigt, dass nicht kritische Supportvorgänge ausgeführt werden. Problematisch wird es, wenn die Zustandsprüfung aus einem nicht kritischen Grund fehlschlägt und dieser Fehler serverübergreifende Auswirkungen hat. Wenn Automatisierung dazu führt, dass Server außer Betrieb genommen werden, die noch in der Lage wären, nützliche Aufgaben zu erledigen, steht ihr Nutzen nicht mehr im Verhältnis zum angerichteten Schaden.

Die Schwierigkeit von Zustandsprüfungen liegt in dieser Spannung zwischen den Vorteilen gründlicher Prüfungen und der schnellen Behebung von Ausfällen einzelner Server auf der einen Seite und dem Schaden, den falsch positive Fehler in der gesamten Flotte anrichten können, auf der anderen Seite. Eine der Herausforderungen beim Einrichten einer soliden Zustandsprüfung besteht demnach darin, einen Schutz vor falsch positiven Diagnosen einzubauen. Das bedeutet im Allgemeinen, dass die beteiligte Automatisierung aufhören muss, Traffic an individuelle fehlerhafte Server weiterzuleiten, aber Traffic weiterhin zulassen sollte, wenn das Problem die gesamte Flotte zu betreffen scheint.

Möglichkeiten der Zustandsmessung

Serverfehler können viele Ursachen haben und es gibt eine Reihe von Punkten in unseren Systemen, an denen wir den Zustand eines Servers messen. Einige Zustandsprüfungen liefern definitive Belege dafür, dass bei einem bestimmten Server ein unabhängiger Fehler aufgetreten ist. Andere Prüfungen liefern weniger klare Hinweise und melden im Fall von zusammenhängenden Ausfällen falsch positive Ergebnisse. Einige Zustandsprüfungen sind schwierig zu implementieren. Andere werden bei der Einrichtung von Services wie Amazon Elastic Compute Cloud (Amazon EC2) und Elastic Load Balancing implementiert. Jeder Diagnosetyp hat individuelle Vorteile.

Livetests

Mit Livetests wird geprüft, dass eine grundlegende Konnektivität mit einem Service besteht und ein Serverprozess vorhanden ist. Sie werden oft durch einen Load Balancer oder einen externen Überwachungs-Agent ausgeführt. Üblicherweise sind Livetests in einen Service integriert und müssen nicht von einem Anwendungsautor eingerichtet werden. Bei Amazon werden unter anderem folgende Livetests eingesetzt:

• Tests, die bestätigen, dass ein Server den erwarteten Port überwacht und neue TCP-Verbindungen zulässt
• Tests, die grundlegende HTTP-Anforderungen durchführen und prüfen, ob der Server mit dem Statuscode 200 antwortet
• Statusprüfungen für Amazon EC2, die grundlegende Aspekte wie die Erreichbarkeit des Netzwerks testen, die für den Systembetrieb notwendig sind

Lokale Zustandsprüfungen

Lokale Zustandsprüfungen gehen über den Umfang von Livetests hinaus und ermöglichen, die Funktionsfähigkeit der Anwendung zu verifizieren. Dabei werden Ressourcen getestet, die der Server nicht mit anderen Servern gemeinsam nutzt. Deshalb ist es unwahrscheinlich, dass diese Prüfungen auf mehreren Servern der Flotte gleichzeitig fehlschlagen. Lokale Zustandsprüfungen beinhalten folgende Tests:

• Schreib- und Lesezugriff auf Laufwerke. Man könnte meinen, dass ein zustandsloser Service kein beschreibbares Laufwerk erfordert. Die Services, die wir bei Amazon einsetzen, nutzen zugehörige Laufwerke jedoch für Vorgänge wie die Überwachung, Protokollierung und Veröffentlichung asynchroner Messdaten.
• Absturz- und Fehlerereignisse bei kritischen Prozessen. Einige Services verarbeiten Anforderungen über einen On-Server-Proxy (ähnlich wie NGINX) und führen ihre Geschäftslogik in einem anderen Serverprozess aus. Bei einem Livetest wird möglicherweise nur geprüft, ob der Proxyprozess ausgeführt wird. Der Prozess einer lokalen Zustandsprüfung hingegen kann über den Proxy auf die Anwendung erweitert werden, um zu prüfen, dass beide Komponenten ausgeführt werden und Anforderungen fehlerfrei beantworten. Bei unserem Websitebeispiel vom Anfang des Artikels war es interessanterweise so, dass die vorhandene Zustandsprüfung gründlich genug war, um die Ausführung und Ansprechbarkeit des Rendering-Prozesses zu testen, aber nicht gründlich genug, um zu gewährleisten, dass die Antworten fehlerfrei waren.
• Fehlende Supportprozesse. Wenn der Überwachungs-Daemon eines Hosts fehlt, ist der Operator möglicherweise "blind" und hat keinen Einblick in den Zustand der zugehörigen Services. Andere Supportprozesse übertragen per Push Mess- und Abrechnungsdatensätze oder empfangen aktualisierte Anmeldeinformationen. Server mit fehlerhaften Supportprozessen gefährden die Funktionalität auf subtile, schwer festzustellende Weise.

Abhängigkeitsbezogene Zustandsprüfungen

Abhängigkeitsbezogene Zustandsprüfungen untersuchen die Fähigkeit einer Anwendung zur Kommunikation mit benachbarten Systemen. Mithilfe dieser Prüfungen werden im Idealfall lokale Serverprobleme erkannt, z. B. abgelaufene Anmeldeinformationen, die dem Server die Interaktion mit einer abhängigen Komponente verwehren. Dabei können allerdings auch falsch positive Fehler erfasst werden, wenn Probleme mit der Abhängigkeit selbst vorliegen. Aufgrund dieser falsch positiven Diagnosen sollten wir vorsichtig auf fehlgeschlagene abhängigkeitsbezogene Zustandsprüfungen reagieren. Bei abhängigkeitsbezogenen Zustandsprüfungen wird Folgendes getestet:

• Fehlerhafte Konfigurationen oder veraltete Metadaten. Wenn ein Prozess asynchron nach aktualisierten Metadaten oder Konfigurationen sucht, der Aktualisierungsmechanismus auf einem Server jedoch nicht funktioniert, können die Daten auf dem Server von den Daten der anderen Server abweichen, was zu unvorhersehbarem Verhalten führen kann. Wird ein Server eine Weile nicht aktualisiert, weiß er jedoch nicht, ob der Mechanismus fehlerhaft ist oder ob das zentrale Aktualisierungssystem einfach keine Aktualisierungen veröffentlicht hat.
• Kommunikation mit Peer-Servern oder Abhängigkeiten. Außergewöhnliches Netzwerkverhalten kann die Kommunikation einer Server-Untergruppe in einer Flotte mit Abhängigkeiten beeinträchtigen, ohne sich auf die Traffic-Zustellung für diese Server auszuwirken. Auch Softwareprobleme wie Deadlocks oder Fehler in Verbindungspools können die Netzwerkkommunikation stören.
• Sonstige ungewöhnliche Softwarefehler, die einen Prozess-Bounce erfordern. Deadlocks, Arbeitsspeicherverluste oder Statusbeschädigungsfehler können einen Server dazu veranlassen, Fehlermeldungen auszugeben. 

Anomalieerkennung

Bei der Anomalieerkennung werden alle Server einer Flotte unter die Lupe genommen, um ungewöhnliche Verhaltensmuster einzelner Server zu erkennen. Durch die Aggregation von Überwachungsdaten pro Server können wir Fehlerraten, Latenzdaten und andere Attribute kontinuierlich vergleichen, um anomale Server ausfindig zu machen und automatisch aus dem Service zu entfernen. Mithilfe der Anomalieerkennung können unter anderem folgende Abweichungen ermittelt werden, die ein Server nicht selbst erkennen kann:

• Zeitversatz. Insbesondere bei Servern, die einer hohen Belastung ausgesetzt sind, können plötzliche und erhebliche Zeitabweichungen festgestellt werden. Verschiedene Sicherheitsmaßnahmen erfordern, dass die Uhrzeit auf einem Client nicht mehr als fünf Minuten von der tatsächlichen Uhrzeit abweicht. Dies trifft z. B. für die Überprüfung von signierten Anforderungen zu, die an AWS gesendet werden. Ist die Abweichung größer als zulässig, werden die Anforderungen an AWS-Services nicht zugestellt.
• Veralteter Code. Wenn ein Server über einen längeren Zeitraum vom Netzwerk getrennt oder ausgeschaltet war, besteht bei erneuter Aktivierung die Gefahr, dass ausgeführter Code extrem veraltet und mit dem Rest der Flotte inkompatibel ist.
• Unerwartete Fehlermodi. Manchmal sorgen Serverfehler dafür, dass der Server eine Fehlermeldung ausgibt, die er dem Client zuordnet, statt sich selbst (HTTP 400 statt HTTP 500). Wenn Server langsamer werden, statt auszufallen, oder schneller reagieren als ihre Peer-Server, kann das ein Zeichen dafür sein, dass sie falsche Antworten an die Aufrufer zurückgeben. Die Anomalieerkennung ist eine sehr effektive Lösung, um unerwartete Fehlermodi zu erfassen.

Damit sie in der Praxis einwandfrei funktioniert, müssen allerdings ein paar Voraussetzungen erfüllt sein:

• Die Server sollten ähnliche Aufgaben haben. Wenn wir verschiedene Traffic-Typen explizit an verschiedene Servertypen leiten, verhalten sich die Server möglicherweise nicht ähnlich genug, um die Erkennung von Ausreißern zu ermöglichen. Setzen wir jedoch Load Balancer ein, um den Traffic an die Server zu leiten, ist ihr Verhalten mit hoher Wahrscheinlichkeit ähnlich.
• Die Flotten sollten relativ homogen sein. In Flotten mit unterschiedlichen Instance-Typen sind einige Instances möglicherweise langsamer als andere, was zu einer fälschlichen Erkennung passiver Server führen kann. Um dieses Szenario zu verhindern, sortieren wir Metriken nach Instance-Typ.
• Fehler oder Verhaltensabweichungen müssen gemeldet werden. Da wir uns darauf verlassen, dass Server Fehler eigenständig melden – was passiert, wenn ihre Überwachungssysteme ebenfalls fehlerhaft sind? Glücklicherweise können wir auf dem Client eines Service Tools hinzufügen. Load Balancer wie Application Load Balancer veröffentlichen Zugriffsprotokolle, denen wir für jede Anforderung entnehmen können, welcher Backend-Server kontaktiert wurde, wie die Antwortzeit ausgefallen ist und ob die Anforderung erfolgreich war oder nicht. 

Sichere Reaktion auf Fehler bei der Zustandsprüfung

Wenn ein Server seinen Zustand als fehlerhaft bestimmt, kann er auf zwei Arten reagieren. Im Extremfall kann der Server lokal entscheiden, keine Aufgaben mehr anzunehmen und seinen Betrieb selbst einzustellen, indem er sich bei der Zustandsprüfung des Load Balancers als fehlerhaft meldet oder die Abfrage einer Warteschlange abbricht. Eine andere Art zu reagieren wäre, dass der Server eine zentrale Entscheidungsstelle über sein Problem informiert und dem zentralen System das weitere Verfahren überlasst. Das zentrale System kann das Problem sicher beheben, ohne dass die ganze Flotte von der Automatisierung vom Netz genommen wird.

Zustandsprüfungen lassen sich auf unterschiedliche Weise implementieren und nutzen. In diesem Abschnitt finden Sie einige Muster, die wir bei Amazon verwenden.

Fail-Open-Konzept

Einige Load Balancer können als smarte, zentrale Entscheidungsstelle fungieren. Sind die Resultate der Zustandsprüfung eines Servers negativ, sendet der Load Balancer diesem Server keinen Traffic mehr. Sind jedoch die Resultate der Zustandsprüfung aller Server negativ, geht der Load Balancer in den Fail-Open-Zustand über und lässt Traffic zu allen Servern zu. Wir können Load Balancer nutzen, um eine abhängigkeitsbezogene Zustandsprüfung sicher zu implementieren. Gegebenenfalls können wir dabei auch eine Prüfung einrichten, die Abfragen an die zugehörige Datenbank sendet und sicherstellt, dass alle nicht kritischen Supportprozesse ausgeführt werden.

Der AWS Network Load Balancer geht beispielsweise in den Fail-Open-Zustand über, wenn die Zustandsprüfungen aller Server negativ ausfallen. Er stellt auch den Traffic in fehlerhafte Availability Zones ein, wenn alle Server der betreffenden Zone als fehlerhaft gemeldet werden. (Weitere Informationen zur Verwendung von Network Load Balancers für Zustandsprüfungen erhalten Sie in der Dokumentation zu Elastic Load Balancing.) Unser Application Load Balancer sowie auch Amazon Route 53 unterstützen ebenso das Fail-Open-Konzept. (Weitere Informationen zur Konfiguration von Zustandsprüfungen mit Route 53 finden Sie in der Dokumentation zu Route 53.)

Wenn wir auf das Fail-Open-Konzept setzen, testen wir die Fehlermodi der abhängigkeitsbezogenen Zustandsprüfung. Denken Sie zum Beispiel an einen Service, bei dem Server mit einem gemeinsamen Datenspeicher verbunden sind. Wenn die Performance des Datenspeichers einbricht oder er eine geringe Fehlerrate zurückgibt, können die abhängigkeitsbezogenen Zustandsprüfungen der Server gelegentlich negativ ausfallen. Dieser Zustand kann zu einem unterbrochenen Serverbetrieb führen, aber er löst keinen Fail-Open-Betrieb aus. Teilweise Ausfälle von Abhängigkeiten mit diesen Zustandsprüfungen zu durchdenken und zu testen ist wichtig, damit ein Ausfall nicht dazu führen kann, dass eine tiefgehende Zustandsprüfung die Lage noch verschlimmert.

Zwar ist das Fail-Open-Konzept nützlich, aber wir bei Amazon sind Dingen skeptisch gegenüber, die wir nicht vollständig durchdenken oder in allen Lagen testen können. Wir konnten noch keinen allgemeingültigen Beweis liefern, dass ein Fail-Open-Zustand bei allen Arten von Überlastungen, teilweisen Ausfällen oder Gray Failures in einem System oder dessen Abhängigkeiten wie erwartet ausgelöst wird. Aufgrund dieser Einschränkung beschränken Teams bei Amazon die Zustandsprüfungen ihrer schnell agierenden Load Balancer auf lokale Überprüfungen und verlassen sich auf zentralisierte Systeme, um mit Bedacht auf umfangreichere abhängigkeitsbezogene Zustandsprüfungen zu reagieren. Das heißt nicht, dass wir das Fail-Open-Konzept nicht nutzen oder dessen Funktion in bestimmten Fällen nicht belegen konnten. Aber wenn eine Logik in kurzer Zeit eine große Anzahl an Servern ansteuern kann, verwenden wir sie nur mit großer Vorsicht.

Zustandsprüfungen ohne Sicherung

Die schnellste und einfachste Wiederherstellungsmethode scheint zu sein, Server selbst auf ihre Probleme reagieren zu lassen. Allerdings ist diese Methode auch am riskantesten, wenn der Server seinen Zustand falsch bestimmt oder die Gesamtsituation einer Flotte nicht kennt. Treffen alle Server einer Flotte zur gleichen Zeit die gleiche falsche Entscheidung, kann dies zu Fehlerkaskaden in benachbarten Services führen. Dieses Risiko stellt uns vor einen Kompromiss. Befindet sich eine Lücke in der Zustandsprüfung und -überwachung, könnte ein Server die Verfügbarkeit eines Service senken, bis das Problem erkannt wird. In diesem Szenario wird jedoch ein Totalausfall des Service aufgrund von unerwartetem Verhalten bei einer Zustandsprüfung in der gesamten Flotte vermieden.

Bei der Implementierung von Zustandsprüfungen ohne integrierte Sicherung halten wir uns an folgende Best Practices:

• Den Produzenten (Load Balancer, Thread zur Abfrage einer Warteschlange) so konfigurieren, dass Livetests und lokale Zustandsprüfungen durchgeführt werden. Server werden vom Load Balancer nur automatisch außer Betrieb genommen, wenn ein Problem definitiv lokal von einem Server verursacht wird, z. B. durch ein defektes Laufwerk.
• Andere externe Überwachungssysteme zur Durchführung abhängigkeitsbezogener Zustandsprüfungen und Anomalieerkennungen konfigurieren. Diese Systeme könnten versuchen, Instances automatisch zu beenden oder einen Mitarbeiter zu alarmieren.

Wenn wir Systeme so auslegen, dass sie automatisch auf Fehler bei abhängigkeitsbezogenen Zustandsprüfungen reagieren, ist der korrekte Schwellenwert entscheidend, damit das automatisierte System nicht unerwartet drastische Maßnahmen ergreift. Teams bei Amazon, die zustandsbehaftete Server wie Amazon DynamoDB, Amazon S3 und Amazon Relational Database Service (Amazon RDS) betreiben, haben hohe Anforderungen an die Zuverlässigkeit rund um das Thema Serveraustausch. Es bestehen auch eine zurückhaltende Quotenbegrenzung und Feedback-Loops zur Steuerung, damit die Automatisierung beim Überschreiten der Schwellenwerte beendet wird und Mitarbeiter eine Benachrichtigung erhalten. Beim Einsatz derartiger Automatisierung müssen wir aber bemerken, wenn ein Server eine abhängigkeitsbezogene Zustandsprüfung nicht besteht. Bei bestimmten Metriken verlassen wir uns nur darauf, dass die Server ihren individuellen Status selbst an ein zentrales Überwachungssystem melden. Um Fällen Rechnung zu tragen, in denen der Server so fehlerhaft ist, dass er seinen Zustand selbst nicht mehr übermitteln kann, werden die Server auch aktiv für Zustandsprüfungen kontaktiert. 

Zustandsprüfungen priorisieren

Insbesondere bei Überlastung ist es wichtig, dass Server die Zustandsprüfungen gegenüber den regulären Aufgaben mit Priorität behandeln. In dieser Situation kann die ausbleibende oder eine nur langsame Reaktion auf Zustandsprüfungen Brownout-Szenarien noch verschlimmern. 

Besteht ein Server die Zustandsprüfung eines Load Balancers nicht, weist er den Load Balancer zu einer sofortigen Außerbetriebnahme des Servers für einen nicht unerheblichen Zeitraum an. Beim Ausfall eines einzigen Servers stellt das kein Problem dar. Besteht jedoch für Ihren Service großer Datenverkehr, möchten Sie Kapazitätsengpässe unbedingt vermeiden. Das Abschalten von Servern eines Services, der bereits überlastet ist, kann zu einer Abwärtsspirale führen. Werden die verbleibenden Server dazu gezwungen, noch mehr Datenverkehr zu übernehmen, überlasten diese wahrscheinlich auch, bestehen die Zustandsprüfung nicht und die Flotte wird noch weiter dezimiert.

Das Problem dabei ist nicht, dass überlastete Server im Zustand der Überlastung Fehler zurückgeben. Vielmehr besteht es darin, dass die Server auf die Ping-Anforderungen des Load Balancers nicht rechtzeitig reagieren. Die Zustandsprüfungen der Load Balancer sind schließlich wie jeder andere Remote-Seviceaufruf mit Zeitüberschreitungen konfiguriert. Ausgefallene Server reagieren aus verschiedenen Gründen nur langsam, u. a. aufgrund einer großen Anzahl von CPU-Konflikten, langer Bereinigungszyklen oder weil einfach nicht genügend Worker-Threads vorhanden sind. Services müssen so konfiguriert werden, dass für die zeitnahe Reaktion auf Zustandsprüfungen noch genügend Reserven bereitstehen, anstatt dass zu viele zusätzliche Anforderungen übernommen werden.

Zum Glück gibt es einfache und bewährte Methoden, mit deren Hilfe wir derartige Abwärtsspiralen vermeiden. Tools wie iptables und auch einige Load Balancer unterstützen eine Begrenzung der "max. Anzahl an Verbindungen". In diesem Fall beschränkt das Betriebssystem (oder der Load Balancer) die Anzahl der Verbindungen zum Server. Auf diese Weise wird der Serverprozess nicht mit gleichzeitigen Anfragen überflutet, die ihn nur verlangsamt hätten.

Wenn vor einem Service ein Proxy oder Load Balancer geschaltet wird, der die max. Anzahl an Verbindungen begrenzt, erscheint es nur logisch, dass die Anzahl der Worker-Threads auf dem HTTP-Server mit der max. Anzahl an Verbindungen im Proxy übereinstimmt. Diese Konfiguration würde jedoch in einem Brownout-Szenario zu einer Abwärtsspirale führen. Proxy-Zustandsprüfungen benötigen ebenfalls Verbindungen. Deshalb muss der Worker-Pool eines Servers groß genug sein, dass auch zusätzlich angeforderte Zustandsprüfungen verarbeitet werden können. Worker im Leerlauf sind kostengünstig, deshalb neigen wir dazu, zusätzliche zu konfigurieren: Irgendwo im Bereich einiger weniger bis hin zur doppelten Anzahl der konfigurierten max. Proxy-Verbindungen.

Als weitere Strategie, wie wir Zustandsprüfungen priorisieren, lassen wir die Server selbst die maximale Anzahl gleichzeitiger Verbindungen beschränken. In diesem Fall sind die Zustandsprüfungen durch Load Balancer immer zulässig, aber normale Anfragen werden abgelehnt, wenn der Server bereits in einem bestimmten Schwellenbereich arbeitet. Die Implementierungen bei Amazon reichen von einfachen Semaphoren in Java zu komplexeren Trendanalysen der CPU-Auslastung.

Damit die Services rechtzeitig auf Ping-Anforderungen der Zustandsprüfungen reagieren, kann als weitere Möglichkeit die Logik der Zustandsprüfung in einem Hintergrundprozess ausgeführt und das beim Ping überprüfte Label "isHealthy" aktualisiert werden. In diesem Fall reagieren die Server schnell auf Zustandsprüfungen und die Prüfung von Abhängigkeiten ist für die externen Systeme, mit denen interagiert wird, eine kalkulierbare Last. Wenn Teams diese Methode einsetzen, achten sie besonders darauf, dass Fehler im Thread der Zustandsprüfungen erkannt werden. Wenn dieser Thread im Hintergrund beendet wird, erkennt der Server keine zukünftigen Serverausfälle (oder Wiederherstellungen!) mehr.

Ein Gleichgewicht zwischen abhängigkeitsbezogenen Zustandsprüfungen und dem Ausmaß der Auswirkungen herstellen

Abhängigkeitsbezogene Zustandsprüfungen sind deshalb so attraktiv, weil sie einen gründlichen Test des Serverzustands ermöglichen. Sie bergen aber auch eine gewisse Gefahr, weil eine Abhängigkeit über einen Dominoeffekt zum Ausfall des gesamten Systems führen kann.

Gewisse Aufschlüsse über den Umgang mit Abhängigkeiten der Zustandsprüfungen erhalten wir, wenn wir die serviceorientierte Architektur bei Amazon betrachten. Jeder Service bei Amazon ist für wenige Aufgaben konzipiert; es gibt keine monolithischen Strukturen ohne Untergliederung. Für den Aufbau der Services auf diese Weise sprechen viele Vorteile, unter anderem schnellere Innovationen mit kleinen Teams und geringere Auswirkungen, wenn ein Problem bei einem Service besteht. Diese Architekturgestaltung kann auch für Zustandsprüfungen gelten.

Wenn ein Service einen anderen aufruft, besteht eine Abhängigkeit zu diesem Service. Ruft ein Service die Abhängigkeit nur manchmal auf, betrachten wir die Abhängigkeit als "schwach", da der Service bestimmte Arbeiten trotzdem ausführen kann, selbst wenn die Kommunikation mit der Abhängigkeit nicht möglich ist. Ohne Fail-Open-Schutz wird diese Abhängigkeit durch die Implementierung einer Zustandsprüfung, die eine Abhängigkeit prüft, zu einer "starken" Abhängigkeit. Ist diese Abhängigkeit nicht aufrufbar, gilt dasselbe für den Service. Das führt zu einem Dominoeffekt mit immer größeren Auswirkungen.

Selbst wenn die Funktionen auf unterschiedliche Services aufgeteilt werden, bedient jeder Service wahrscheinlich mehrere APIs. Manchmal haben die APIs des Services eigene Abhängigkeiten. Wenn eine API betroffen ist, wird allgemein bevorzugt, dass der Service weiterhin die anderen APIs bedient. Ein Service kann sich beispielsweise gleichzeitig auf der Steuerebene (z. B. die gelegentlich aufgerufenen CRUD APIs bei langlebigen Ressourcen) und auf der Datenebene (extrem geschäftskritische APIs mit hohem Durchsatz) befinden. Wir möchten, dass die APIs auf der Datenebene auch dann noch funktionieren, wenn die APIs der Steuerebene Probleme mit dem Aufruf ihrer Abhängigkeiten haben.

Auch eine einzige API kann sich abhängig von der Eingabe oder dem Status der Daten anders verhalten. Ein gemeinsames Muster ist eine API für Leseaufrufe, die Datenbanken abruft, aber die Antworten gewisse Zeit lokal im Cache speichert. Ist die Datenbank nicht aufrufbar, kann der Service weiterhin die im Cache gespeicherten Leseaufrufe verwenden, bis die Datenbank wieder online ist. Werden Zustandsprüfungen bei nur einem beschädigten Codepfad nicht bestanden, wirkt sich ein Problem bei der Kommunikation mit Abhängigkeiten stärker aus.

Bei der Diskussion darüber, für welche Abhängigkeit eine Zustandsprüfung durchgeführt werden soll, stellt sich eine interessante Frage über die Kompromisse zwischen Mikroservices und relativ monolithischen Services. Es gibt selten eine ganz eindeutige Regel, auf wie viele implementierbare Einheiten oder Endpunkte ein Service aufgeteilt werden sollte. Aber die Fragen "für welche Abhängigkeiten die Zustandsprüfungen durchgeführt werden sollen" und "ob ein Fehler die Auswirkungen verschlimmert" sind interessant für die Auslegung als Mikro- oder Makroservice. 

Probleme bei Zustandsprüfungen in der Realität

In der Theorie hört sich das alles schön und gut an, aber was passiert mit den Systemen im Praxiseinsatz, wenn die Zustandsprüfung nicht korrekt erfolgt? Wir haben nach Mustern in Erfahrungsberichten von AWS-Kunden und Amazon gesucht, um den größeren Zusammenhang erfassen zu können. Dabei haben wir auch kompensierende Faktoren betrachtet – die Hilfsmittel, mit denen Teams verhindern, dass eine Schwäche der Zustandsprüfung zu einem weit verbreiteten Problem führt.

Bereitstellungen

Ein Problemfeld bei den Zustandsprüfungen betrifft die Bereitstellungen. Bereitstellungssysteme wie AWS CodeDeploy pushen neuen Code nacheinander auf jeweils einen Teil der Flotte. Dabei wird abgewartet, bis eine Bereitstellungsphase abgeschlossen ist, bevor mir der nächsten fortgefahren wird. Bei diesem Prozess müssen sich die Server beim Bereitstellungssystem melden, nachdem sie mit dem neuen Code hochgefahren wurden und ausgeführt werden. Erfolgt keine Rückmeldung, erkennt das Bereitstellungssystem, dass mit dem neuen Code ein Problem besteht, und führt ein Rollback durch.

Das grundlegendste Startskript würde den Severprozess einfach aufteilen und dem Bereitstellungssystem umgehend "Bereitstellung ausgeführt" melden. Das ist aber gefährlich, weil bei neuem Code so großes Fehlerpotenzial besteht: Der neue Code könnte direkt nach dem Start abstürzen, hängen bleiben und nicht auf einen Server-Socket lauschen, die Konfiguration nicht laden, die für eine erfolgreiche Verarbeitung der Anfragen nötig ist, oder es könnte ein Bug bestehen. Wenn ein Bereitstellungssystem nicht für eine abhängigkeitsbezogene Zustandsprüfung konfiguriert ist, wird nicht erkannt, dass die Bereitstellung fehlerhaft ist. Der Prozess wird fortgesetzt, wobei ein Server nach dem anderen ausfällt.

In der Praxis implementieren die Teams von Amazon mehrere Schutzmechanismen, die verhindern sollen, dass dieses Szenario ihre ganze Flotte außer Betrieb setzt. Bei einem dieser Mechanismen werden Alarme konfiguriert, die immer dann ausgelöst werden, wenn die Gesamtgröße der Flotte für den Betrieb bei hoher Last nicht ausreicht oder eine hohe Latenz oder Fehlerrate besteht. Wird einer dieser Alarme ausgelöst, hält das Bereitstellungssystem den Vorgang an und führt ein Rollback durch.

Ein weiterer Schutzmechanismus ist die phasenweise Bereitstellung. Anstatt die ganze Flotte in einem einzigen Schritt umzustellen, kann der Service so konfiguriert werden, dass die Bereitstellung nur für einen Teil der Server erfolgt, zum Beispiel eine Availability Zone. Dann wird die Bereitstellung pausiert und erst einmal alle Integrationstests für diese Zone ausgeführt. Diese Bereitstellung nach Availability Zone ist komfortabel, weil die Services bereits so konzipiert wurden, dass sie weiterhin ausgeführt werden, selbst wenn in einer Availability Zone Probleme auftreten.

Bevor die Bereitstellung in der Produktionsumgebung erfolgt, erproben die Teams von Amazon diese Änderungen selbstverständlich in Testumgebungen und führen automatische Integrationstests durch, bei denen derartige Fehler erkannt würden. Es können jedoch trotzdem geringe und unvermeidbare Unterschiede zwischen den Produktions- und Testumgebungen bestehen. Deshalb sollten für die Bereitstellung viele Sicherheitsebenen implementiert werden, damit alle Arten von Problemen erfasst werden, bevor sie sich auf die Produktion auswirken. Zustandsprüfungen sind zwar ein wichtiger Faktor zum Schutz von Services vor fehlerhaften Bereitstellungen, sie alleine sind uns aber noch nicht genug. Wir denken auch an andere Hilfsmittel, die als Schutzmechanismen dienen, um Flotten vor diesen und anderen Fehlern zu schützen.

Asynchrone Prozessoren

Ein weiteres Fehlermuster betrifft die asynchrone Verarbeitung von Nachrichten, zum Beispiel bei einem Service, der seine Workload durch den Abruf einer SQS-Warteschlange oder eines Amazon Kinesis Streams erhält. Anders als bei Systemen, die Anfragen von Load Balancers erhalten, werden keine automatischen Zustandsprüfungen durchgeführt, die Server außer Betrieb nehmen.

Wenn die Zustandsprüfungen der Services nicht tief genug greifen, können einzelne Server für die Warteschlangenverarbeitung ausfallen, falls der Speicherplatz auf den Festplatten voll wird oder nicht genügend Dateideskriptoren vorhanden sind. Das Problem hindert den Server nicht daran, die in der Warteschlange anstehenden Arbeiten auszuführen, der Server kann jedoch Nachrichten nicht mehr erfolgreich bearbeiten. Das führt zu einer verzögerten Verarbeitung der Nachrichten, wobei der fehlerhafte Server schnell Arbeiten aus der Warteschlange holt, diese aber nicht ausführen kann.

In derartigen Situationen gibt es häufig mehrere Schutzmechanismen, welche die Auswirkungen begrenzen. Wenn ein Server die Nachricht nicht verarbeiten kann, die er aus einer Warteschlange zieht, stellt SQS die Nachricht nach einer konfigurierten Zeitbeschränkung für die Sichtbarkeit der Nachricht erneut einem anderen Server zu. Das steigert die Latenz von einem Ende zum anderen, aber es gehen keine Nachrichten verloren. Ein weiterer Schutzmechanismus ist ein Alarm, der ausgelöst wird, wenn beim Verarbeiten der Nachrichten zu viele Fehler auftreten, die einen Techniker zur Prüfung veranlassen.

Volllaufende Festplatten

Eine andere Fehlerquelle sind volllaufende Festplatten, die dazu führen können, dass Daten nicht mehr verarbeitet werden oder die Anmeldung fehlschlägt. Dieser Fehler führt zu einer Lücke in der Zustandsprüfung, denn der betroffene Server ist möglicherweise gar nicht dazu in der Lage, das Problem zu melden.

Auch hier sorgen verschiedene Sicherheitsmaßnahmen dafür, dass Services "blind fliegen" und ermöglichen eine schnelle Eindämmung des Problems. Bei Systemen, die über einen Proxy angebunden sind, zum Beispiel ein Application Load Balancer oder ein API Gateway, generiert der Proxy Metriken zur Fehlerrate und Latenz. Auf diese Weise wird ein Alarm ausgelöst, obwohl der Server selbst kein Problem meldet. Bei warteschlangenbasierten Systemen liefern Dienste wie der Amazon Simple Queue Service (Amazon SQS) Metriken, die auf eine verzögerte Verarbeitung einiger Nachrichten hinweisen.

Alle diese Lösungen haben gemein, dass immer mehrere Kontrollebenen eingebunden sind, um Fehler zu erkennen und zu vermeiden. Der Server meldet Fehler, ebenso wie ein externes System. Dasselbe Prinzip ist bei der Zustandsprüfung wirksam. Ein externes System kann den Funktionszustand eines anderen Systems genauer messen als seinen eigenen. Aus diesem Grund definieren Teams in AWS Auto Scaling einen Load Balancer, der externe Ping-Zustandsprüfungen durchführt.

Teams schreiben auch oft eigene Zustandsprüfungssysteme, die jeden Server regelmäßig auf Verfügbarkeit prüfen und Fehlerzustände an AWS Auto Scaling melden. Eine der häufigsten Implementierungen umfasst eine Lambda-Funktion, die im Minutentakt über den Zustand jedes Servers informiert. Oft speichern diese Checks den Zustand zwischen den Ausführungen, zum Beispiel in DynamoDB, um zu verhindern, dass unabsichtlich zu viele Server auf einmal als nicht funktionsfähig gemeldet werden.

Zombies

Ein anderes Problemfeld sind Zombie-Server. Diese Server wurden vorübergehend vom Netzwerk getrennt, laufen aber weiter, oder sie wurden für einen längeren Zeitraum heruntergefahren und später neu gestartet.

Wenn Zombie-Server reaktiviert werden, sind sie meist nicht mehr mit den übrigen Systemen in der Infrastruktur synchron, was zu ernsthaften Problemen führen kann. Angenommen, auf einem Zombie-Server wird eine wesentlich ältere, nicht mehr kompatible Softwareversion ausgeführt, dann kann es zu Fehlern kommen, wenn der Server mit einem anderen Schema auf eine Datenbank zugreift, oder der Server verwendet eine falsche Konfiguration.

Um Zombies zu erkennen, melden Systeme bei der Systemdiagnose oft ihre aktuell ausgeführte Softwareversion. Ein zentraler Monitoring-Agent vergleicht die Rückmeldungen aus der gesamten Flotte und hält nach veralteten Versionen Ausschau. So wird verhindert, dass diese Server wieder in den regulären Betrieb übernommen werden.

Fazit

Server und die darauf ausgeführte Software fallen aus allen möglichen und unmöglichen Gründen aus. Hardware geht kaputt. Oder uns Softwareentwicklern entgeht ein Bug – wie der von mir oben beschriebene, der das System komplette System lahmlegt. Um alle Arten von unerwarteten Fehlerzuständen abzufangen, bedarf es einer mehrstufigen Zustandsprüfung, die von einfachen Funktionschecks bis hin zur passiven Überwachung von Servermetriken reicht.

Wenn es zu einem Fehler kommt, geht es darum, schnell zu reagieren und die betroffenen Server außer Betrieb zu nehmen. Für unsichere oder extreme Situationen ist es außerdem notwendig, die Automatisierung abschalten zu können, damit Techniker sich des Problems annehmen können. Und wie bei jeder Flottenautomatisierung müssen Verbindungsbegrenzungen und Schwellenwerte festgelegt werden. Fail Open und die Implementierung zentraler Akteure sind Strategien, mit denen sich die Vorteile einer tiefgehenden Zustandsprüfung mit der Sicherheit einer verbindungsbegrenzten Automatisierung kombinieren lassen.

Praktische Übung

Probieren Sie einige der Prinzipien aus, die Sie in den praktischen Übungen erlernt haben.


Über den Autor

David Yanacek ist Senior Principal Engineer und arbeitet an AWS Lambda. David ist seit 2006 Softwareentwickler bei Amazon. Zuvor arbeitete er an Amazon DynamoDB und AWS IoT sowie an internen Web-Service-Frameworks und Automatisierungssystemen für den Flottenbetrieb. Eine von Davids Lieblingsaktivitäten bei der Arbeit ist die Durchführung von Protokollanalysen und das Durchsuchen von Betriebsmetriken, um Wege zu finden, wie Systeme im Laufe der Zeit immer reibungsloser funktionieren.

Timeouts, Wiederholungsversuche und Backoff mit Jitter