Kapitel 1. Die Anwendungsplattform
Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com
Martin Fowler und James Lewis, die den Begriff Microservices ursprünglich vorgeschlagen haben, definieren die Architektur in ihrem bahnbrechenden Blogpost wie folgt:
...eine besondere Art, Softwareanwendungen als Suiten von unabhängig voneinander einsetzbaren Diensten zu entwickeln. Es gibt zwar keine genaue Definition dieses Architekturstils, aber es gibt bestimmte gemeinsame Merkmale, die sich auf die Organisation rund um die Geschäftsfähigkeit, die automatisierte Bereitstellung, die Intelligenz in den Endpunkten und die dezentralisierte Kontrolle von Sprachen und Daten beziehen.
Die Einführung von Microservices verspricht, die Softwareentwicklung zu beschleunigen, indem Anwendungen in unabhängig voneinander entwickelte und eingesetzte Komponenten aufgeteilt werden, die von unabhängigen Teams produziert werden. Die Notwendigkeit, große Software-Releases zu koordinieren und zu planen, verringert sich dadurch. Jeder Microservice wird von einem unabhängigen Team entwickelt, um einen bestimmten Geschäftsbedarf (für interne oder externe Kunden) zu decken. Microservices werden redundant und horizontal skaliert auf verschiedenen Cloud-Ressourcen bereitgestellt und kommunizieren über das Netzwerk mit unterschiedlichen Protokollen miteinander.
Diese Architektur bringt eine Reihe von Herausforderungen mit sich, die bei monolithischen Anwendungen bisher nicht aufgetreten sind. Monolithische Anwendungen wurden früher hauptsächlich auf demselben Server bereitgestellt und nur selten als sorgfältig choreografiertes Ereignis freigegeben. Der Software-Release-Prozess war die Hauptquelle für Veränderungen und Instabilität im System. Bei Microservices führen die Kommunikations- und Datenübertragungskosten zu zusätzlichen Latenzen und können das Nutzererlebnis beeinträchtigen. Eine Kette von Dutzenden oder Hunderten von Microservices arbeitet nun miteinander, um dieses Erlebnis zu schaffen. Microservices werden unabhängig voneinander veröffentlicht, aber jeder einzelne kann sich unbeabsichtigt auf andere Microservices auswirken und damit auch auf das Endnutzererlebnis.
Die Verwaltung dieser Art von verteilten Systemen erfordert neue Praktiken, Werkzeuge und eine neue Entwicklungskultur. Die Beschleunigung von Software-Releases muss nicht auf Kosten von Stabilität und Sicherheit gehen. Vielmehr gehen sie Hand in Hand. Dieses Kapitel stellt die Kultur eines effektiven Plattform-Engineering-Teams vor und beschreibt die grundlegenden Bausteine für zuverlässige Systeme.
Plattform Engineering Kultur
Um Microservices zu verwalten, muss ein Unternehmen bestimmte Kommunikationsprotokolle und unterstützende Frameworks standardisieren. Wenn jedes Team seine eigene Full-Stack-Entwicklung pflegen muss, entstehen viele Ineffizienzen und Reibungen bei der Kommunikation mit anderen Teilen einer verteilten Anwendung. In der Praxis führt die Standardisierung zu einem Plattformteam, das sich auf die Bereitstellung dieser Dienste für die übrigen Teams konzentriert, die sich wiederum auf die Entwicklung von Software zur Erfüllung der Geschäftsanforderungen konzentrieren.
Wir wollen Leitplanken bieten, keine Tore.
Dianne Marsh, Direktorin für technische Tools bei Netflix
Anstatt Gates zu bauen, solltest du den Teams die Möglichkeit geben, Lösungen zu entwickeln, die zuerst für sie funktionieren, von ihnen zu lernen und sie dann auf den Rest der Organisation zu übertragen.
Organisationen, die Systeme entwerfen, sind gezwungen, Entwürfe zu erstellen, die Kopien der Kommunikationsstrukturen dieser Organisationen sind.
Conway's Gesetz
Abbildung 1-1 zeigt eine technische Organisation, die nach Spezialgebieten aufgebaut ist. Eine Gruppe ist auf das Design von Benutzeroberflächen und -erfahrungen spezialisiert, eine andere auf die Entwicklung von Backend-Diensten, eine weitere auf die Verwaltung der Datenbank, eine weitere auf die Automatisierung von Geschäftsprozessen und eine weitere auf die Verwaltung von Netzwerkressourcen.
Die Lehre, die oft aus Conways Gesetz zieht, ist, dass funktionsübergreifende Teams, wie in Abbildung 1-2, schneller iterieren können. Denn wenn die Teamstruktur auf die technische Spezialisierung ausgerichtet ist, muss jede neue Geschäftsanforderung mit all diesen Spezialisierungen koordiniert werden.
Natürlich gibt es auch in diesem System eine Verschwendung, nämlich dass die Spezialisten in den einzelnen Teams unabhängig voneinander Fähigkeiten entwickeln. Netflix hat keine eigenen Site Reliability Engineers pro Team, wie es Google in Site Reliability Engineering (herausgegeben von Betsy Beyer et al., O'Reilly) propagiert. Vielleicht war die Zentralisierung der Softwareentwicklung effizienter, weil die Art der Software, die von den Produktteams geschrieben wurde (meist Java, meist zustandslose, horizontal skalierte Microservices), homogener war. Ist dein Unternehmen eher mit Google vergleichbar, das an sehr unterschiedlichen Produkten arbeitet, von automatisierten Autos über die Suche bis hin zu mobiler Hardware und Browsern? Oder ähnelt es eher Netflix, das aus einer Reihe von Geschäftsanwendungen besteht, die in einer Handvoll Sprachen geschrieben wurden und auf einer begrenzten Anzahl von Plattformen laufen?
Funktionsübergreifende Teams und völlig abgeschottete Teams befinden sich an den entgegengesetzten Enden eines Spektrums. Ein effektives Plattform-Engineering kann den Bedarf an einem Spezialisten pro Team für eine Reihe von Problemen verringern. Eine Organisation mit dediziertem Platform Engineering ist eher ein Hybrid, wie in Abbildung 1-3. Ein zentrales Plattform-Engineering-Team ist am stärksten, wenn es die Produktteams als Kunden betrachtet, die ständig überzeugt werden müssen, und wenig bis keine Kontrolle über das Verhalten seiner Kunden ausübt.
Wenn zum Beispiel die Überwachungsinstrumente im gesamten Unternehmen als gemeinsame Bibliothek in jedem Microservice enthalten sind, wird das hart erarbeitete Wissen über Verfügbarkeitsindikatoren, von denen bekannt ist, dass sie allgemein anwendbar sind, gemeinsam genutzt. Jedes Produktteam braucht nur wenig Zeit, um Verfügbarkeitsindikatoren hinzuzufügen, die für seinen Geschäftsbereich spezifisch sind. Es kann mit dem zentralen Überwachungsteam kommunizieren, um Informationen und Ratschläge zu erhalten, wie es wirksame Signale erstellen kann, falls erforderlich.
Bei Netflix war die stärkste kulturelle Strömung "Freiheit und Verantwortung", die in einem etwas berühmten Kulturdeck aus dem Jahr 2001 definiert wurde. Ich gehörte zum Team für technische Tools, aber wir konnten nicht verlangen, dass alle anderen ein bestimmtes Build-Tool übernehmen. Ein kleines Team von Ingenieuren verwaltete Cassandra-Cluster im Auftrag vieler Produktteams. Diese Konzentration von Build-Tool- oder Cassandra-Kenntnissen hat eine gewisse Effizienz, denn sie ist ein natürlicher Kommunikationsknotenpunkt, über den undifferenzierte Probleme mit diesen Produkten an die produktorientierten Teams weitergegeben werden.
Das Build-Tools-Team bei Netflix bestand zu seiner kleinsten Zeit nur aus zwei Ingenieuren, die die Interessen von etwa 700 anderen Ingenieuren vertraten, während sie zwischen empfohlenen Build-Tools (Ant zu Gradle) wechselten und zwei große Java-Upgrades (Java 6 zu 7 und dann Java 7 zu 8) durchführten, neben anderen täglichen Routinen. Jedes Produktteam war vollständig für seinen Build verantwortlich. Aufgrund von "Freiheit und Verantwortung" konnten wir kein festes Datum festlegen, wann wir die Ant-basierten Build-Tools vollständig abschaffen würden. Wir konnten kein festes Datum festlegen, an dem jedes Team seine Java-Version aktualisieren musste (es sei denn, ein neues Oracle-Lizenzierungsmodell übernahm dies für uns). Der kulturelle Imperativ trieb uns dazu, uns so stark auf die Erfahrung der Entwickler zu konzentrieren, dass die Produktteams mit uns migrieren wollten. Das erforderte ein Maß an Aufwand und Einfühlungsvermögen, das nur dadurch gewährleistet werden konnte, dass wir absolut keine harten Anforderungen stellen konnten.
Wenn ein Plattform-Ingenieur wie ich die Interessen so vieler verschiedener Produktteams in einem fokussierten technischen Fachgebiet wie dem Build-Tooling bedient, tauchen zwangsläufig Muster auf. Mein Team sah, dass sich dasselbe Skript immer und immer wieder abspielte, z. B. bei Problemen mit binären Abhängigkeiten, der Versionierung von Plug-ins oder dem Release-Workflow. Wir haben zunächst daran gearbeitet, die Entdeckung dieser Muster zu automatisieren und Warnungen in der Build-Ausgabe auszugeben. Ohne die Kultur der Freiheit und Verantwortung hätten wir vielleicht die Warnungen übersprungen und den Build einfach fehlgeschlagen, sodass die Produktteams die Probleme hätten beheben müssen. Das wäre für das Build-Tool-Team zufriedenstellend gewesen - wir wären nicht für die Beantwortung von Fragen im Zusammenhang mit Fehlern verantwortlich gewesen, vor denen wir die Teams warnen wollten. Aber aus der Sicht des Produktteams wäre jede "Lektion", die das Build-Tool-Team gelernt hat, für sie zu beliebigen Zeitpunkten störend gewesen, und besonders störend, wenn sie dringendere (wenn auch vorübergehende) Prioritäten hatten.
Der sanftere, nicht fehlschlagende Warnansatz war schockierend ineffektiv. Die Teams schenkten den erfolgreichen Build-Protokollen kaum Beachtung, unabhängig davon, wie viele Warnungen ausgegeben wurden. Und selbst wenn sie die Warnungen sahen, war der Versuch, sie zu beheben, mit Risiken verbunden: Ein funktionierendes Build mit Warnungen ist besser als ein fehlerhaftes Build ohne Warnungen. Daher konnten sorgfältig formulierte Verwerfungswarnungen über Monate oder Jahre hinweg ignoriert werden.
Der "Leitplanken statt Tore"-Ansatz verlangte von unserem Build-Tools-Team, darüber nachzudenken, wie wir unser Wissen mit den Produktteams auf eine Art und Weise teilen können, die für sie sichtbar ist, wenig Zeit und Aufwand erfordert und das Risiko verringert, mit uns auf den gepflasterten Weg zu kommen. Das Tool, das dabei herauskam, war in seinem Fokus auf die Erfahrung der Entwickler fast schon überragend.
Zuerst haben wir ein Tool geschrieben, das den Groovy-Code von Gradle-Builds umschreiben kann, um häufige Muster zu erkennen. Das war viel schwieriger, als nur Warnungen im Log auszugeben. Es erforderte Änderungen am abstrakten Syntaxbaum, um die Einrückung zu erhalten. Das ist im Allgemeinen ein unlösbares Problem, aber in bestimmten Fällen erstaunlich effektiv. Die Autoremediation erfolgte jedoch auf freiwilliger Basis durch einen einfachen Befehl, den die Produktteams ausführen konnten, um Empfehlungen zu akzeptieren .
Als Nächstes schrieben wir Überwachungsinstrumente, die Muster meldeten, die potenziell behebbar waren, für die die Produktteams aber keine Empfehlung annahmen. Wir konnten jedes schädliche Muster in der Organisation im Laufe der Zeit überwachen und beobachten, wie die Auswirkungen abnahmen, wenn die Teams Abhilfemaßnahmen akzeptierten. Wenn wir eine kleine Anzahl von Teams erreichten, die sich einfach nicht beteiligen wollten, wussten wir, wer sie waren, so dass wir zu ihren Schreibtischen gehen und mit ihnen unter vier Augen arbeiten konnten, um ihre Bedenken zu hören und ihnen zu helfen, weiterzukommen. (Ich habe das so oft gemacht, dass ich anfing, meine eigene Maus mit mir herumzutragen. Es gab eine verdächtige Korrelation zwischen Netflix-Ingenieuren, die Trackballs benutzten, und Netflix-Ingenieuren, die bei der Annahme von Abhilfemaßnahmen auf der Strecke blieben.) Letztendlich hat diese proaktive Kommunikation eine Vertrauensbasis geschaffen, die zukünftige Empfehlungen von uns weniger riskant erscheinen ließ.
Wir haben uns ziemlich viel Mühe gegeben, um die Sichtbarkeit der Empfehlungen zu verbessern, ohne die Builds zu unterbrechen, um die Aufmerksamkeit der Entwickler zu gewinnen. Die Build-Ausgabe wurde sorgfältig eingefärbt und stilisiert, manchmal mit visuellen Indikatoren wie Unicode-Häkchen und X-Markierungen, die kaum zu übersehen waren. Empfehlungen erschienen immer am Ende des Builds, weil wir wussten, dass sie das letzte waren, was auf dem Terminal ausgegeben wurde, und unsere CI-Tools scrollten standardmäßig zum Ende der Log-Ausgabe, wenn Ingenieure die Build-Ausgabe untersuchten. Wir haben Jenkins beigebracht, sich als TTY-Terminal zu tarnen, um die Build-Ausgabe einzufärben, aber die Escape-Sequenzen der Cursorbewegung zu ignorieren, um den Fortschritt der Build-Aufgabe zu serialisieren.
Diese Art von Erlebnis zu schaffen war technisch aufwändig, aber vergleiche es mit den beiden anderen Optionen:
- Kultur der Freiheit und Verantwortung
-
Er hat uns dazu gebracht, eine Selbsthilfe-Automediation mit Monitoring zu entwickeln, die uns hilft, die Teams, die Schwierigkeiten haben, zu verstehen und mit ihnen zu kommunizieren.
- Zentralisierte Kontrollkultur
-
Wir hätten uns wahrscheinlich dazu verleiten lassen, Builds eifrig abzubrechen, weil uns die Build-Erfahrung "gehörte". Die Teams wären von ihren anderen Prioritäten abgelenkt worden, um unserem Wunsch nach einem einheitlichen Build-Erlebnis nachzukommen. Jede Änderung hätte aufgrund der fehlenden Autorisierung viel mehr Fragen an uns als das Build-Tool-Team aufgeworfen. Der Gesamtaufwand für jede Änderung wäre viel größer gewesen.
Ein effektives Plattform-Engineering-Team kümmert sich intensiv um das Entwicklererlebnis, und zwar mindestens genauso intensiv wie die Produktteams, die sich auf das Kundenerlebnis konzentrieren. Das sollte nicht überraschen: In einer gut aufgestellten Plattform-Engineering-Organisation sind die Entwickler der Kunde! Ein gesundes Produktmanagement, erfahrene User-Experience-Designer und UI-Ingenieure und -Designer, denen ihr Handwerk am Herzen liegt, sollten ein Indiz dafür sein, dass das Team für die Entwicklung von Plattformen auf die Bedürfnisse der Entwickler ausgerichtet ist.
Nähere Einzelheiten zur Teamstruktur können in diesem Buch nicht behandelt werden, aber in Team Topologies von Matthew Skelton und Manuel Pais (IT Revolution Press) findest du eine gründliche Behandlung des Themas.
Sobald das Team kulturell kalibriert ist, stellt sich die Frage, wie man die Fähigkeiten priorisiert, die ein Plattform-Engineering-Team seinem Kundenstamm bieten kann. Der Rest dieses Buches ist ein Aufruf zum Handeln, geordnet nach Fähigkeiten, die (meiner Meinung nach) am wichtigsten sind und weniger wichtig.
Überwachung
Die Überwachung deiner Anwendungsinfrastruktur erfordert von allen Etappen auf dem Weg zu stabileren Systemen den geringsten organisatorischen Aufwand. Wie wir in den folgenden Kapiteln zeigen werden, sind die Überwachungsinstrumente auf Framework-Ebene inzwischen so ausgereift, dass du sie nur noch einschalten musst, um die Vorteile zu nutzen. Das Kosten-Nutzen-Verhältnis hat sich so stark zugunsten des Nutzens verschoben, dass du, wenn du nichts anderes in diesem Buch tust, jetzt mit der Überwachung deiner Produktionsanwendungen beginnen solltest. In Kapitel 2 geht es um die Bausteine der Metriken und in Kapitel 4 um die spezifischen Diagramme und Warnmeldungen, die du einsetzen kannst, meist auf der Grundlage von Instrumenten, die Java-Frameworks bereitstellen, ohne dass du zusätzliche Arbeit leisten musst.
Metriken, Logs und verteiltes Tracing sind drei Formen der Beobachtbarkeit, die es ermöglichen, die Verfügbarkeit von Diensten zu messen und die Fehlersuche bei komplexen Problemen mit verteilten Systemen zu erleichtern. Bevor wir uns näher mit den einzelnen Funktionen befassen, ist es sinnvoll zu verstehen, welche Möglichkeiten sie bieten.
Überwachung der Verfügbarkeit
Verfügbarkeitssignale messen den Gesamtzustand des Systems und ob das System im Großen und Ganzen wie vorgesehen funktioniert. Sie wird durch Service Level Indicators (SLIs) quantifiziert. Zu diesen Indikatoren gehören Signale für den Zustand des Systems (z. B. Ressourcenverbrauch) und Geschäftskennzahlen wie die Anzahl der verkauften Sandwiches oder der Starts von Video-Streams pro Sekunde. SLIs werden gegen einen Schwellenwert getestet, der als Service Level Objective (SLO) bezeichnet wird und eine Ober- oder Untergrenze für den Bereich eines SLIs festlegt. SLOs wiederum sind eine etwas restriktivere oder konservativere Schätzung als der Schwellenwert, den du mit deinen Geschäftspartnern über das von dir erwartete Serviceniveau vereinbart hast ( ), oder das, was als Service Level Agreement (SLA) bekannt ist. Die Idee ist, dass ein SLO eine gewisse Vorwarnung vor einer drohenden Verletzung eines SLA bieten sollte, damit du nicht tatsächlich an den Punkt kommst, an dem du das SLA verletzt.
Metriken sind das wichtigste Beobachtungsinstrument zur Messung der Verfügbarkeit. Sie sind ein Maß für SLIs. Metriken sind das gebräuchlichste Verfügbarkeitssignal, weil sie eine Zusammenfassung aller Aktivitäten im System darstellen. Sie sind billig genug, um keine Stichproben zu erfordern (d.h. einen Teil der Daten zu verwerfen, um den Aufwand zu begrenzen), wodurch die Gefahr besteht, dass wichtige Indikatoren für die Nichtverfügbarkeit vernachlässigt werden.
Metriken sind numerische Werte, die in einer Zeitreihe angeordnet sind und eine Stichprobe zu einem bestimmten Zeitpunkt oder ein Aggregat von Einzelereignissen darstellen, die in einem Intervall aufgetreten sind:
- Metriken
-
Metriken sollten unabhängig vom Durchsatz einen festen Preis haben. Eine Metrik, die die Ausführung eines bestimmten Codeblocks zählt, sollte beispielsweise nur die Anzahl der in einem Intervall beobachteten Ausführungen übermitteln, unabhängig davon, wie viele es sind. Damit meine ich, dass eine Metrik zum Veröffentlichungszeitpunkt "N Anfragen wurden beobachtet" liefern sollte und nicht "Ich habe eine Anfrage N-mal gesehen" während des Veröffentlichungsintervalls.
- Metrik-Daten
-
Metrikdaten können nicht verwendet werden, um Rückschlüsse auf die Leistung oder Funktion einer einzelnen Anfrage zu ziehen. Die Metrik-Telemetrie bietet einen Kompromiss zwischen der Aussagekraft einer einzelnen Anfrage und dem Verhalten der Anwendung über alle Anfragen in einem Intervall.
Um die Verfügbarkeit eines Java-Microservices effektiv zu überwachen, muss eine Vielzahl von Verfügbarkeitssignalen überwacht werden. Gängige Signale werden in Kapitel 4 beschrieben, aber im Allgemeinen fallen sie in vier Kategorien, die zusammen als L-USE Methode bekannt sind:1
- Latenz
-
Dies ist ein Maß dafür, wie viel Zeit für die Ausführung eines Codeblocks aufgewendet wurde. Für einen gewöhnlichen REST-basierten Microservice ist die Latenz der REST-Endpunkte ein nützliches Maß für die Verfügbarkeit der Anwendung, insbesondere für die maximale Latenz. Dies wird im Abschnitt "Latenz" ausführlicher behandelt .
- Inanspruchnahme
-
Ein Maß dafür, wie viel von einer endlichen Ressource verbraucht wird. Die Prozessorauslastung ist ein gängiger Indikator für die Auslastung. Siehe "CPU-Auslastung".
- Sättigung
-
Sättigung ist ein Maß für zusätzliche Arbeit, die nicht erledigt werden kann. "Garbage Collection Pause Times" zeigt, wie man den Java-Heap misst, der in Zeiten von übermäßigem Speicherdruck zu einer Anhäufung von Arbeit führt, die nicht erledigt werden kann. Es ist auch üblich, Pools wie Datenbankverbindungspools, Anfragepools usw. zu überwachen.
- Fehler
-
Neben der Betrachtung rein leistungsbezogener Aspekte ist es wichtig, einen Weg zu finden, um die Fehlerquote im Verhältnis zum Gesamtdurchsatz zu quantifizieren. Zu den Fehlermessungen gehören unvorhergesehene Ausnahmen, die zu erfolglosen HTTP-Antworten an einem Service-Endpunkt führen (siehe "Fehler"), aber auch indirektere Messgrößen wie das Verhältnis von Anfragen, die gegen einen offenen Circuit Breaker gerichtet sind (siehe "Circuit Breakers").
Auslastung und Sättigung mögen auf den ersten Blick ähnlich erscheinen, aber wenn du den Unterschied verinnerlichst, wird sich das auf deine Denkweise bei der Erstellung von Diagrammen und der Alarmierung bei Ressourcen auswirken, die auf beide Arten gemessen werden können. Ein gutes Beispiel ist der JVM-Speicher. Du kannst den JVM-Speicher als Auslastungskennzahl messen, indem du die Anzahl der Bytes angibst, die in jedem Speicherbereich verbraucht werden. Du kannst den JVM-Speicher auch anhand des Anteils der Zeit messen, die für die Speicherbereinigung aufgewendet wird, was ein Maß für die Sättigung ist. In den meisten Fällen, in denen sowohl die Auslastung als auch die Sättigung gemessen werden können, führt die Sättigungsmetrik zu besser definierten Warnschwellen. Wenn die Arbeitsspeicherauslastung 95 % eines Bereichs übersteigt, ist es schwierig, einen Alarm auszulösen (weil die Speicherbereinigung die Auslastung wieder unter diesen Schwellenwert senkt). Wenn die Speicherauslastung jedoch routinemäßig und häufig 95 % übersteigt, wird der Garbage Collector häufiger aktiviert und mehr Zeit mit der Speicherbereinigung als mit anderen Aufgaben verbracht, so dass die Sättigungsmessung höher ausfällt.
Einige gängige Verfügbarkeitssignale sind in Tabelle 1-1 aufgeführt.
SLI | SLO | L-USE Kriterien |
---|---|---|
Prozess-CPU-Nutzung |
<80% |
Sättigung |
Heap-Nutzung |
<80% des verfügbaren Heap-Speicherplatzes |
Sättigung |
Fehlerquote für einen REST-Endpunkt |
<1% der gesamten Anfragen an den Endpunkt |
Fehler |
Maximale Latenz für einen REST-Endpunkt |
<100 ms |
Latenz |
Google hat eine viel präskriptivere Sichtweise, wie SLOs zu verwenden sind.
Googles Ansatz für SLOs
Site Reliability Engineering von Betsy Beyer et al. (O'Reilly) stellt die Serviceverfügbarkeit als ein Spannungsfeld zwischen konkurrierenden organisatorischen Anforderungen dar: neue Funktionen zu liefern und die bestehenden Funktionen zuverlässig zu betreiben. Er schlägt vor, dass sich Produktteams und engagierte Site Reliability Engineers auf ein Fehlerbudget einigen, das ein messbares Ziel dafür vorgibt, wie unzuverlässig ein Dienst innerhalb eines bestimmten Zeitfensters sein darf. Wenn dieses Ziel überschritten wird, sollte das Team die Zuverlässigkeit über die Entwicklung von Funktionen stellen, bis das Ziel erreicht ist.
Die Sichtweise von Google auf SLOs wird im Kapitel "Alerting on SLOs" in dem von Betsy Beyer et al. herausgegebenen The Site Reliability Workbook (O'Reilly) detailliert erläutert. Die Google-Ingenieure erkennen die Wahrscheinlichkeit, dass ein Fehlerbudget innerhalb eines bestimmten Zeitraums erschöpft sein wird, und reagieren darauf, indem sie bei Bedarf Ressourcen von der Entwicklung von Funktionen auf die Zuverlässigkeit umschichten. Das Wort "Fehler" bedeutet in diesem Fall die Überschreitung eines SLO. Das kann das Überschreiten eines akzeptablen Anteils an Serverfehlschlägen in einem RESTful-Microservice bedeuten, aber auch das Überschreiten eines akzeptablen Latenzschwellenwerts, das Überschreiten der Grenze zur Überlastung der Dateideskriptoren auf dem zugrunde liegenden Betriebssystem oder jede andere Kombination von Messungen. Nach dieser Definition ist die Zeit, in der ein Dienst in einem vorgegebenen Zeitfenster unzuverlässig ist, der Anteil, in dem ein oder mehrere SLOs nicht erfüllt wurden.
In deinem Unternehmen muss es keine getrennten Funktionen für Produktingenieure und Zuverlässigkeitsingenieure geben, damit die Fehlerbudgetierung ein nützliches Konzept ist. Selbst ein einzelner Ingenieur, der ganz allein an einem Produkt arbeitet und für dessen Betrieb verantwortlich ist, kann davon profitieren, wenn er darüber nachdenkt, wo er die Entwicklung von Funktionen zugunsten der Verbesserung der Zuverlässigkeit unterbrechen sollte und umgekehrt.
Ich denke, dass der Overhead des Google-Fehlerbudgets für viele Unternehmen zu groß ist. Fange an zu messen, finde heraus, wie die Warnfunktionen in dein Unternehmen passen und überlege dir dann, ob du das Google-Verfahren übernehmen willst oder nicht, wenn du darin geübt bist.
Das Sammeln, Visualisieren und Alarmieren von Anwendungsmetriken ist eine Übung, um die Verfügbarkeit deiner Dienste kontinuierlich zu testen. Manchmal enthält ein Alert genug kontextbezogene Daten, um ein Problem zu beheben. In anderen Fällen musst du eine fehlschlagende Instanz in der Produktion isolieren (z. B. indem du sie aus dem Load Balancer herausnimmst) und weitere Debugging-Techniken anwenden, um das Problem zu finden. Zu diesem Zweck werden andere Formen der Telemetrie verwendet.
Ein weniger formeller Ansatz für SLOs
Bei Netflix hat sich ein weniger formelles System bewährt, bei dem die einzelnen Ingenieurteams für die Verfügbarkeit ihrer Dienste verantwortlich waren, es keine Trennung der Verantwortung zwischen SRE und Produktingenieur in den einzelnen Produktteams gab und es keine so formalisierte Reaktion auf Fehlerbudgets gab, zumindest nicht organisationsübergreifend. Keines der Systeme ist richtig oder falsch; finde ein System, das für dich gut funktioniert.
In diesem Buch werden wir die Messung der Verfügbarkeit in einfacheren Begriffen beschreiben: als Tests gegen eine Fehlerrate oder Fehlerquote, Latenzen, Sättigung und Auslastungsindikatoren. Wir werden Verstöße gegen diese Tests nicht als bestimmte "Fehler" der Zuverlässigkeit darstellen, die über ein Zeitfenster von einem Fehlerbudget abgezogen werden. Wenn du diese Messungen nutzen und die Fehlerbudgetierung und die organisatorische Dynamik der SRE-Kultur von Google auf dein Unternehmen übertragen möchtest, kannst du dich an den Anleitungen in Googles Schriften zu diesem Thema orientieren.
Überwachung als Debugging-Tool
Logs und verteilte Traces, die in Kapitel 3 ausführlich behandelt werden, dienen vor allem der Fehlersuche, sobald du eine Phase der Nichtverfügbarkeit feststellst. Profiling-Tools sind ebenfalls Signale für die Fehlersuche.
Es ist sehr üblich (und angesichts des unübersichtlichen Marktes auch einfach), dass Unternehmen ihre gesamten Investitionen in das Leistungsmanagement auf Debugging-Tools konzentrieren. Anbieter von Application Performance Management (APM) verkaufen sich manchmal als All-in-One-Lösung, deren Kerntechnologie jedoch ausschließlich auf Tracing oder Logging und der Bereitstellung von Verfügbarkeitssignalen durch Aggregation dieser Debugging-Signale beruht.
Um keinen bestimmten Anbieter hervorzuheben, betrachten wir YourKit, ein wertvolles Profiling- (Debugging-) Tool, das diese Aufgabe gut erfüllt, ohne sich als mehr zu verkaufen. YourKit zeichnet sich dadurch aus, dass es rechen- und speicherintensive Hotspots im Java-Code hervorhebt (siehe Abbildung 1-4). Einige beliebte kommerzielle APM-Lösungen haben einen ähnlichen Fokus, der zwar nützlich ist, aber kein Ersatz für ein gezieltes Verfügbarkeitssignal.
Diese Lösungen sind granularer und zeichnen auf unterschiedliche Weise auf, was während einer bestimmten Interaktion mit dem System passiert ist. Diese höhere Granularität ist mit Kosten verbunden, die häufig durch Downsampling oder sogar durch die vollständige Abschaltung der Signale gemildert werden, bis sie benötigt werden.
Wenn du versuchst, die Verfügbarkeit anhand von Log- oder Tracing-Signalen zu messen, musst du in der Regel einen Kompromiss zwischen Genauigkeit und Kosten eingehen, und beides kann nicht optimiert werden. Dieser Kompromiss besteht bei Traces, weil sie in der Regel abgetastet werden. Der Speicherplatzbedarf für Traces ist höher als für Metriken.
Lernen, Misserfolge zu erwarten
Wenn du deine Anwendungen noch nicht nutzerorientiert überwachst, wirst du, sobald du damit anfängst, wahrscheinlich mit der ernüchternden Realität deiner heutigen Software konfrontiert werden. Dein erster Impuls wird sein, wegzuschauen. Die Realität wird wahrscheinlich hässlich sein.
Bei einem mittelgroßen Schaden- und Unfallversicherer fügten wir der Hauptanwendung, die die Versicherungsvertreter des Unternehmens für ihre normalen Geschäfte nutzen, eine Überwachung hinzu. Trotz strenger Freigabeprozesse und einer einigermaßen gesunden Testkultur meldete die Anwendung über 5 Fehler pro Minute bei etwa 1.000 Anfragen pro Minute. Aus einer Perspektive ist das nur eine Fehlerquote von 0,5 % (vielleicht akzeptabel, vielleicht auch nicht), aber die Fehlerquote war dennoch ein Schock für ein Unternehmen, das dachte, sein Dienst sei gut getestet.
Die Erkenntnis, dass das System nicht perfekt sein wird, verlagert den Schwerpunkt von dem Versuch, perfekt zu sein, auf die Überwachung, Alarmierung und schnelle Lösung von Problemen, die im System auftreten. Keine noch so gute Prozesskontrolle in Bezug auf die Veränderungsrate wird zu perfekten Ergebnissen führen.
Bevor du den Auslieferungs- und Freigabeprozess weiter entwickelst, ist der erste Schritt auf dem Weg zu einer belastbaren Software die Überwachung deiner Software, wie sie jetzt freigegeben wird.
Mit dem Übergang zu Microservices und den sich ändernden Anwendungspraktiken und der Infrastruktur ist die Überwachung noch wichtiger geworden. Viele Komponenten liegen nicht direkt unter der Kontrolle eines Unternehmens. Latenzzeiten und Fehler können zum Beispiel durch Ausfälle in der Netzwerkschicht, der Infrastruktur und durch Komponenten und Dienste von Drittanbietern verursacht werden. Jedes Team, das einen Microservice produziert, hat das Potenzial, andere Teile des Systems, die nicht unter seiner direkten Kontrolle stehen, negativ zu beeinflussen.
Auch Endnutzer von Software erwarten keine Perfektion, aber sie wollen, dass ihr Dienstleister in der Lage ist, Probleme effektiv zu beheben. Das ist das so genannte Service-Recovery-Paradoxon, bei dem die Nutzer/innen eines Dienstes nach einem Ausfall mehr Vertrauen in den Dienst haben als vor dem Ausfall.
Unternehmen müssen das Nutzererlebnis, das sie ihren Endnutzern bieten wollen, verstehen und erfassen - welche Art von Systemverhalten Probleme für das Unternehmen verursacht und welche Art von Verhalten für die Nutzer akzeptabel ist. In Site Reliability Engineering und The Site Reliability Workbook erfährst du mehr darüber, wie du diese für dein Unternehmen auswählen kannst.
Sobald du sie identifiziert und gemessen hast, kannst du dich an Google orientieren, wie in "Googles Ansatz für SLOs", oder an Netflix' informellerem "Kontext und Leitplanken"-Stil, oder irgendwo dazwischen, um deine Software oder die nächsten Schritte zu überdenken. Im ersten Kapitel über Netflix in Seeking SRE von David N. Blank-Edelman (O'Reilly) erfährst du mehr über Kontext und Leitplanken. Ob du der Google-Praxis folgst oder einer einfacheren, hängt von deiner Organisation, der Art der Software, die du entwickelst, und der Entwicklungskultur ab, die du fördern willst.
Wenn das Ziel, niemals fehlzuschlagen, durch das Ziel ersetzt wird, die SLAs zu erfüllen, kann die Technik damit beginnen, mehrere Ebenen der Ausfallsicherheit in die Systeme einzubauen, um die Auswirkungen von Fehlschlägen auf die Erfahrung der Endnutzer zu minimieren.
Effektive Überwachung schafft Vertrauen
In manchen Unternehmen wird die Technik immer noch als Dienstleistungsunternehmen und nicht als Kernkompetenz angesehen. Bei einer Versicherungsgesellschaft mit einer Fehlerquote von fünf Fehlern pro Minute ist dies die vorherrschende Einstellung. In vielen Fällen, in denen die technische Abteilung die Versicherungsvertreter des Unternehmens im Außendienst unterstützte, bestand die Hauptinteraktion zwischen ihnen in der Meldung und Verfolgung von Softwareproblemen durch ein Call Center.
Die Technik hat die Behebung von Fehlern auf der Grundlage der vom Call Center gemeldeten Fehler routinemäßig gegenüber neuen Funktionsanforderungen priorisiert und bei jeder Softwareversion ein wenig von beidem getan. Ich fragte mich, wie oft die Außendienstmitarbeiter Probleme einfach nicht meldeten, entweder weil ein wachsender Bug-Backlog darauf hindeutete, dass sie ihre Zeit nicht effektiv nutzen würden, oder weil es für das Problem eine ausreichende Lösung gab. Das Problem, wenn man Probleme hauptsächlich über das Call Center erfährt, ist, dass die Beziehung dadurch nur einseitig ist. Die Geschäftspartner melden das Problem und die Ingenieure reagieren (irgendwann).
Eine nutzerzentrierte Überwachungskultur stellt diese Beziehung in beide Richtungen her. Eine Warnmeldung kann genügend Kontextinformationen liefern, um zu erkennen, dass die Bewertung einer bestimmten Fahrzeugklasse bei Agenten in einer bestimmten Region heute fehlschlägt. Die Technik hat die Möglichkeit, die Agenten proaktiv mit genügend Kontextinformationen zu erreichen, um ihnen zu erklären, dass das Problem bereits bekannt ist.
Lieferung
Die Verbesserung der Softwareauslieferungspipeline verringert die Wahrscheinlichkeit, dass du weitere Fehler in ein bestehendes System einführst (oder hilft dir zumindest, solche Änderungen schnell zu erkennen und rückgängig zu machen). Es zeigt sich, dass eine gute Überwachung eine nicht selbstverständliche Voraussetzung für die Entwicklung sicherer und effektiver Lieferpraktiken ist.
Die Trennung zwischen Continuous Integration (CI) und Continuous Delivery (CD) wird oft dadurch verwischt, dass Teams häufig Skripte zur Automatisierung der Bereitstellung erstellen und diese Skripte als Teil der Continuous Integration Builds ausführen. Es ist einfach, ein CI-System als flexibles, universell einsetzbares Workflow-Automatisierungstool zu nutzen. Um eine klare konzeptionelle Abgrenzung zwischen den beiden zu schaffen, unabhängig davon, wo die Automatisierung läuft, sagen wir, dass die kontinuierliche Integration mit der Veröffentlichung eines Microservice-Artefakts in einem Artefakt-Repository endet und die Auslieferung an diesem Punkt beginnt. In Abbildung 1-5 ist der Lebenszyklus der Softwarebereitstellung als eine Abfolge von Ereignissen dargestellt, die von der Codeübergabe bis zur Bereitstellung reicht.
Die einzelnen Schritte unterliegen unterschiedlichen Häufigkeiten und organisatorischen Bedürfnissen für Kontrollmaßnahmen. Sie haben auch grundsätzlich unterschiedliche Ziele. Das Ziel der kontinuierlichen Integration ist es, das Feedback der Entwickler zu beschleunigen, durch automatisierte Tests schnell fehlzuschlagen und eine eifrige Zusammenführung zu fördern, um eine vielseitige Integration zu verhindern. Das Ziel der Lieferautomatisierung ist es, den Release-Zyklus zu beschleunigen, die Einhaltung von Sicherheits- und Compliance-Maßnahmen zu gewährleisten, sichere und skalierbare Bereitstellungspraktiken zu bieten und zu einem Verständnis der bereitgestellten Landschaft für die Überwachung der bereitgestellten Ressourcen beizutragen.
Die besten Bereitstellungsplattformen fungieren auch als Inventar der aktuell eingesetzten Assets, was den Effekt einer guten Überwachung noch verstärkt: Sie helfen dabei, die Überwachung in die Tat umzusetzen. In Kapitel 6 werden wir darüber sprechen, wie du ein durchgängiges Inventar erstellen kannst, das mit einem Inventar der eingesetzten Ressourcen endet und das es dir ermöglicht, die kleinsten Details deines Codes bis hin zu den eingesetzten Ressourcen (d.h. Container, virtuelle Maschinen und Funktionen) zu verstehen.
Continuous Delivery bedeutet nicht zwangsläufig Continuous Deployment
Ein wirklich kontinuierliches Deployment (jeder Commit, der die automatischen Prüfungen durchläuft, geht automatisch in die Produktion) kann ein Ziel für dein Unternehmen sein, muss es aber nicht. Alles in allem ist eine engere Feedbackschleife einer längeren Feedbackschleife vorzuziehen, aber sie ist mit technischen, betrieblichen und kulturellen Kosten verbunden. Alle Themen, die in diesem Buch behandelt werden, gelten sowohl für Continuous Delivery im Allgemeinen als auch für Continuous Deployment im Besonderen.
Sobald eine wirksame Überwachung eingerichtet ist und weniger Fehler durch weitere Änderungen am Code in das System eingebracht werden, können wir uns darauf konzentrieren, die Zuverlässigkeit des laufenden Systems durch die Entwicklung von Verkehrsmanagementpraktiken zu erhöhen.
Verkehrsmanagement
Die Ausfallsicherheit eines verteilten Systems basiert zu einem großen Teil auf der Erwartung von und der Kompensation für Ausfälle. Die Verfügbarkeitsüberwachung deckt diese tatsächlichen Fehlerpunkte auf, die Fehlersuchbarkeitsüberwachung hilft, sie zu verstehen, und die Auslieferungsautomatisierung verhindert, dass du in jeder neuen Version zu viele neue Fehler einführst. Traffic-Management-Muster helfen Live-Instanzen dabei, mit der allgegenwärtigen Realität von Ausfällen umzugehen.
In Kapitel 7 werden wir besondere Strategien zur Lastverteilung (Plattform, Gateway und Client-Seite) und zur Ausfallsicherheit (Wiederholungsversuche, Ratenbegrenzer, Abschottung und Unterbrecher) vorstellen, die ein Sicherheitsnetz für laufende Systeme bieten.
Dieser Schritt wird als letzter behandelt, weil er projektbezogen den höchsten manuellen Programmieraufwand erfordert und weil die Investition, die du in diese Arbeit steckst, von den Erkenntnissen aus den vorherigen Schritten beeinflusst werden kann.
Nicht abgedeckte Fähigkeiten
Bestimmte Fähigkeiten, die häufig im Fokus von Plattform-Engineering-Teams stehen, werden in diesem Buch nicht behandelt. Ich möchte ein paar von ihnen herausgreifen, nämlich das Testen und das Konfigurationsmanagement, und erklären, warum.
Testautomatisierung
Ich bin der Meinung, dass die in Open Source verfügbare Testautomatisierung dich bis zu einem gewissen Punkt weiterbringt. Jede Investition, die darüber hinausgeht, wird sich wahrscheinlich nicht mehr lohnen. Im Folgenden sind einige Probleme aufgeführt, die bereits gut gelöst sind:
-
Einheitstests
-
Verspottung/Stoppung
-
Grundlegende Integrationstests, einschließlich Testcontainer
-
Vertragsprüfung
-
Werkzeuge entwickeln, die helfen, rechenintensive und kostengünstige Testsuiten zu trennen
Es gibt noch ein paar andere Probleme, die es meiner Meinung nach zu vermeiden gilt, es sei denn, du hast wirklich eine Menge Ressourcen (sowohl rechnerisch als auch in Form von Entwicklungszeit) zu investieren. Die Vertragsprüfung ist ein Beispiel für eine Technik, die einiges von dem abdeckt, was die beiden anderen prüfen, aber auf eine viel billigere Weise:
-
Downstream-Tests (d.h. bei jeder Änderung einer Bibliothek werden alle anderen Projekte, die direkt oder indirekt von dieser Bibliothek abhängen, gebaut, um festzustellen, ob die Änderung zu Fehlern im Downstream führt)
-
End-to-End-Integrationstests für ganze Suiten von Microservices
Ich bin ein großer Befürworter von automatisierten Tests verschiedener Art und stehe dem ganzen Unternehmen sehr misstrauisch gegenüber. Unter dem sozialen Druck der Testenthusiasten um mich herum habe ich mich manchmal für eine kurze Zeit der aktuellen Testmode angeschlossen: 100%ige Testabdeckung, verhaltensorientierte Entwicklung, Einbeziehung von Geschäftspartnern, die keine Ingenieure sind, in die Testspezifikation, Spock usw. Einige der cleversten Entwicklungen im Open-Source-Java-Ökosystem haben in diesem Bereich stattgefunden: Man denke nur an Spocks kreativen Einsatz von Bytecode-Manipulationen, um Datentabellen und Ähnliches zu erstellen.
Traditionell wurden bei der Arbeit mit monolithischen Anwendungen die Software-Releases als Hauptquelle für Veränderungen im System und damit als potenzielle Fehlerquelle angesehen. Der Schwerpunkt lag darauf, sicherzustellen, dass der Software-Release-Prozess nicht fehlschlägt. Es wurde viel Aufwand betrieben, um sicherzustellen, dass die Umgebungen der unteren Ebenen mit der Produktion übereinstimmten, um zu überprüfen, ob die anstehenden Software-Releases stabil waren. Wenn das System einmal eingeführt und stabil ist, sollte es auch stabil bleiben.
Realistisch betrachtet, war das noch nie der Fall. Ingenieurteams führen automatisierte Testverfahren als Heilmittel gegen Misserfolge ein und verdoppeln sie, nur damit sich die Misserfolge hartnäckig halten. Das Management steht dem Testen von vornherein skeptisch gegenüber. Wenn die Tests fehlschlagen, ist das letzte bisschen Vertrauen dahin. Produktionsumgebungen haben die hartnäckige Angewohnheit, auf subtile und scheinbar immer katastrophale Weise von den Testumgebungen abzuweichen. Wenn du mich jetzt vor die Wahl stellen würdest, zwischen einer nahezu 100%igen Testabdeckung und einem weiterentwickelten Produktionsüberwachungssystem zu wählen, würde ich mich für das Überwachungssystem entscheiden. Das liegt nicht daran, dass ich weniger von Tests halte, sondern daran, dass selbst in einigermaßen gut definierten traditionellen Unternehmen, deren Praktiken sich nicht schnell ändern, eine 100%ige Testabdeckung ein Mythos ist. Die Produktionsumgebung wird sich einfach anders verhalten. Wie Josh Long zu sagen pflegt: "There is no place like it."
Eine wirksame Überwachung warnt uns, wenn ein System aufgrund vorhersehbarer Umstände (z. B. Hardwareausfälle oder Nichtverfügbarkeit nachgelagerter Dienste) nicht richtig funktioniert. Außerdem wird unser Wissen über das System ständig erweitert, was zu Tests führen kann, die Fälle abdecken, die wir uns vorher nicht vorstellen konnten.
Mehrstufige Prüfverfahren können das Auftreten von Fehlern einschränken, aber niemals ausschließen, selbst in Branchen mit den strengsten Qualitätskontrollverfahren. Die aktive Messung der Ergebnisse in der Produktion verkürzt die Zeit bis zur Entdeckung und schließlich bis zur Behebung von Fehlern. Testen und Überwachen sind also komplementäre Praktiken, um die Fehlerquote für die Endnutzer/innen zu verringern. Im besten Fall verhindert das Testen ganze Klassen von Rückschritten, und die Überwachung identifiziert schnell diejenigen, die unvermeidlich bleiben.
Unsere automatisierten Testsuiten beweisen (sofern sie nicht selbst logische Fehler enthalten), was wir über das System wissen. Die Produktionsüberwachung zeigt uns, was passiert. Die Einsicht, dass automatisierte Tests nicht alles abdecken können, sollte eine große Erleichterung sein.
Da der Anwendungscode immer Fehler enthalten wird, die auf unvorhergesehene Wechselwirkungen, Umgebungsfaktoren wie Ressourcenbeschränkungen und unvollkommene Tests zurückzuführen sind, ist eine effektive Überwachung sogar noch wichtiger als das Testen einer Produktionsanwendung. Ein Test beweist, was wir denken, was passieren wird. Die Überwachung zeigt, was tatsächlich passiert.
Chaos Engineering und kontinuierliche Verifizierung
Es gibt eine ganze Disziplin, die sich damit beschäftigt, kontinuierlich zu überprüfen, ob sich deine Software so verhält, wie du es erwartest, indem du kontrollierte Ausfälle (Chaos-Experimente) einführst und überprüfst. Da verteilte Systeme komplex sind, können wir nicht alle ihre unzähligen Wechselwirkungen vorhersehen. Diese Form des Testens hilft dabei, unerwartete, neu entstehende Eigenschaften komplexer Systeme aufzudecken.
Die gesamte Disziplin des Chaos Engineering ist breit gefächert, und da sie in Chaos Engineering von Casey Rosenthal und Nora Jones (O'Reilly) ausführlich behandelt wird, werde ich in diesem Buch nicht näher darauf eingehen.
Konfiguration als Code
Die 12-Faktoren-App lehrt, dass die Konfiguration vom Code getrennt werden sollte. Die Grundform dieses Konzepts - die Konfiguration wird als Umgebungsvariable gespeichert oder beim Start von einem zentralen Konfigurationsserver wie dem Spring Cloud Config Server abgerufen - ist meiner Meinung nach so einfach, dass sie hier nicht weiter erläutert werden muss.
Der kompliziertere Fall der dynamischen Konfiguration - bei dem sich Änderungen an einer zentralen Konfigurationsquelle auf laufende Instanzen ausbreiten und deren Verhalten beeinflussen - ist in der Praxis äußerst gefährlich und muss mit Vorsicht behandelt werden. Zusammen mit dem quelloffenen Netflix Archaius-Konfigurationsclient (der u. a. in den Spring Cloud Netflix-Abhängigkeiten enthalten ist) wurde ein proprietärer Archaius-Server entwickelt, der diesen Zweck erfüllt. Unbeabsichtigte Konsequenzen, die sich aus der Weitergabe dynamischer Konfigurationen an laufende Instanzen ergaben, verursachten eine Reihe von Produktionsvorfällen von solchem Ausmaß, dass die Delivery Engineers einen ganzen Canary-Analyseprozess entwickelten, um dynamische Konfigurationsänderungen einzugrenzen und schrittweise auszurollen. Dies würde den Rahmen dieses Buches sprengen, da viele Unternehmen niemals einen so großen Nutzen aus der automatisierten Canary-Analyse von Codeänderungen ziehen werden, dass sich der Aufwand lohnt.
Die deklarative Bereitstellung ist eine ganz andere Form der Konfiguration als Code, die durch den Aufstieg von Kubernetes und seinen YAML-Manifesten wieder populär geworden ist. Meine frühe Karriere hat mich mit einem permanenten Misstrauen gegenüber der Vollständigkeit von rein deklarativen Lösungen zurückgelassen. Ich denke, dass es immer einen Platz sowohl für imperative als auch für deklarative Konfiguration gibt. Ich habe für eine Versicherungsgesellschaft an einem System zur Verwaltung von Versicherungspolicen gearbeitet, das aus einer Backend-API, die XML-Antworten zurückgibt, und einem Frontend mit XSLT-Transformationen dieser API-Antworten in statisches HTML/JavaScript für die Darstellung im Browser bestand.
Es war eine bizarre Art von Templating-Schema. Seine Befürworter argumentierten, dass die XSLT der Darstellung jeder Seite einen deklarativen Charakter verleiht. Und doch stellt sich heraus, dass XSLT selbst Turing-komplett ist und einen überzeugenden Existenzbeweis hat. Das typische Argument für eine deklarative Definition ist die Einfachheit, die zu einer Automatisierbarkeit wie statischer Analyse und Fehlerbehebung führt. Aber wie im Fall von XSLT haben diese Technologien einen scheinbar unvermeidlichen Weg, sich in Richtung Turing-Vollständigkeit zu entwickeln. Die gleichen Kräfte sind auch bei JSON(Jsonnet) und Kubernetes(Kustomize) im Spiel. Diese Technologien sind zweifelsohne nützlich, aber ich kann nicht eine weitere Stimme im Chor sein, die nach einer rein deklarativen Konfiguration ruft. Abgesehen von diesem Punkt kann dieses Buch meiner Meinung nach nicht viel dazu beitragen.
Fähigkeiten kapseln
So sehr die objektorientierte Programmierung (OOP) heute auch unter Beschuss steht, eines ihrer grundlegenden Konzepte ist die Kapselung. In der OOP geht es bei der Kapselung darum, Zustand und Verhalten in einer Einheit zu bündeln, z. B. einer Klasse in Java. Ein zentraler Gedanke ist, den Zustand eines Objekts vor der Außenwelt zu verbergen, das sogenannte Information Hiding. In gewisser Weise ist es die Aufgabe des Plattform-Engineering-Teams, eine ähnliche Kapselungsaufgabe für die bewährten Methoden für die Entwicklerteams seiner Kunden zu erfüllen, indem es Informationen nicht unkontrolliert versteckt, sondern sie von der Verantwortung entlastet, damit umzugehen. Das höchste Lob, das ein zentrales Team von einem Produktingenieur erhalten kann, ist vielleicht: "Ich muss mich nicht darum kümmern, was ihr macht."
In den folgenden Kapiteln werden wir eine Reihe bewährter Methoden vorstellen, wie ich sie verstehe. Die Herausforderung für dich als Plattform-Ingenieur besteht darin, sie in deinem Unternehmen so wenig wie möglich einzuschränken, also "Leitplanken statt Tore" zu bauen. Denke beim Lesen darüber nach, wie du das hart erarbeitete Wissen, das auf jede Geschäftsanwendung anwendbar ist, in dein Unternehmen einbringen kannst.
Wenn der Plan darin besteht, die Zustimmung einer einflussreichen Führungskraft einzuholen und eine E-Mail an das gesamte Unternehmen zu senden, in der die Einführung bis zu einem bestimmten Datum gefordert wird, ist das ein Tor. Du willst zwar immer noch die Zustimmung deiner Führung, aber du musst die gemeinsamen Funktionen auf eine Weise bereitstellen, die sich eher wie eine Leitplanke anfühlt:
- Explizite Laufzeit-Abhängigkeiten
-
Wenn du eine Kernbibliothek hast, die jeder Microservice als Laufzeitabhängigkeit enthält, ist dies mit Sicherheit dein Bereitstellungsmechanismus. Aktiviere die wichtigsten Metriken, füge allgemeine Telemetrie-Tags hinzu, konfiguriere Tracing, füge Muster für das Verkehrsmanagement hinzu usw. Wenn du Spring stark nutzt, verwende Autokonfigurationsklassen. Wenn du Java EE verwendest, kannst du die Konfiguration auf ähnliche Weise mit CDI konditionieren.
- Servicekunden als Abhängigkeiten
-
Vor allem bei den Mustern für das Verkehrsmanagement (Fallbacks, Wiederholungslogik usw.) sollte das Team, das den Dienst erstellt, auch für die Erstellung eines Dienstclients verantwortlich sein, der mit dem Dienst interagiert. Schließlich weiß das Team, das den Dienst erstellt und betreibt, am besten, wo seine Schwachstellen und potenziellen Fehlerquellen liegen. Diese Ingenieure sind wahrscheinlich am besten in der Lage, dieses Wissen in einer Client-Abhängigkeit zu formalisieren, so dass jeder Nutzer des Dienstes ihn auf die zuverlässigste Weise nutzen kann.
- Injizieren einer Laufzeit-Abhängigkeit
-
Wenn der Bereitstellungsprozess relativ standardisiert ist, hast du die Möglichkeit, Laufzeitabhängigkeiten in die bereitgestellte Umgebung zu integrieren. Mit diesem Ansatz hat das Cloud Foundry Buildpack-Team eine Plattformmetrik-Implementierung in Spring Boot-Anwendungen integriert, die auf Cloud Foundry laufen. Du kannst etwas Ähnliches tun.
Bevor du zu eifrig kapselst, suche dir eine Handvoll Teams und übe diese Disziplin explizit im Code in einer Handvoll Anwendungen. Verallgemeinere, was du lernst.
Service Mesh
Als letzten Ausweg kannst du gemeinsame Plattformfunktionen in Sidecar-Prozessen (oder Containern) neben der Anwendung kapseln, die in Verbindung mit einer Steuerungsebene, die sie verwaltet, als Service Mesh bezeichnet werden.
Das Service Mesh ist eine Infrastrukturschicht außerhalb des Anwendungscodes, die die Interaktion zwischen Microservices verwaltet. Eine der bekanntesten Implementierungen ist heute Istio. Diese Sidecars übernehmen Funktionen wie Traffic Management, Service Discovery und Monitoring für den Anwendungsprozess, so dass sich die Anwendung um diese Dinge nicht kümmern muss. Im besten Fall vereinfacht dies die Anwendungsentwicklung, während die Komplexität und die Kosten für die Bereitstellung und den Betrieb des Dienstes steigen.
Über einen ausreichend langen Zeitraum hinweg sind die Trends in der Softwareentwicklung oft zyklisch. Im Fall der Standortzuverlässigkeit schwingt das Pendel von der zunehmenden Verantwortung der Anwendungen und Entwickler (z. B. Netflix OSS, DevOps) zur zentralen Verantwortung des Betriebsteams. Das zunehmende Interesse an Service Mesh bedeutet eine Rückkehr zur zentralen Verantwortung des Betriebsteams.
Istio fördert das Konzept der Verwaltung und Weitergabe von Richtlinien für eine Reihe von Microservices von der zentralisierten Steuerungsebene aus, auf Geheiß eines organisatorisch zentralisierten Teams, das darauf spezialisiert ist, die Auswirkungen dieser Richtlinien zu verstehen.
Die altehrwürdige Netflix OSS-Suite (deren wichtige Teile in alternativen Versionen wie Resilience4j für das Verkehrsmanagement, HashiCorp Consul für die Erkennung, Micrometer für die Instrumentierung von Metriken usw. erhältlich sind) hat diese Anwendungsprobleme gelöst. Im Großen und Ganzen bestand die Auswirkung auf den Anwendungscode jedoch nur in der Hinzufügung einer oder mehrerer binärer Abhängigkeiten, woraufhin eine Form der Autokonfiguration übernahm und die ansonsten unberührte Anwendungslogik verzierte. Der offensichtliche Nachteil dieses Ansatzes ist die Sprachunterstützung, denn die Unterstützung für jedes Muster der Standortzuverlässigkeit erfordert Bibliotheksimplementierungen in jeder Sprache/jedem Framework, das das Unternehmen verwendet.
Abbildung 1-6 zeigt eine optimistische Sicht auf die Auswirkungen dieses Entwicklungszyklus auf den abgeleiteten Wert. Mit etwas Glück können wir bei jedem Übergang von der Dezentralisierung zur Zentralisierung und zurück von den Vorteilen des vorherigen Zyklus lernen und diese vollständig einkapseln. So ist es beispielsweise denkbar, dass Istio die Vorteile des OSS-Stacks von Netflix vollständig ausschöpft, nur um beim nächsten Dezentralisierungsschub Potenziale freizusetzen, die in der Istio-Implementierung nicht realisierbar waren. In Resilience4j wird bereits über adaptive Formen von Mustern wie Bulkheads diskutiert, die auf anwendungsspezifische Indikatoren reagieren.
Die Dimensionierung von Sidecars ist angesichts des fehlenden domänenspezifischen Wissens ebenfalls schwierig. Woher weiß ein Sidecar, ob ein Anwendungsprozess 10.000 Anfragen pro Sekunde benötigt oder nur eine? Wie können wir die Größe der Sidecar-Kontrollebene im Voraus festlegen, wenn wir nicht wissen, wie viele Sidecars es letztendlich geben wird?
Seitenwagen sind auf das Wissen des kleinsten gemeinsamen Nenners beschränkt
Ein Sidecar-Proxy ist immer dann am schwächsten, wenn das domänenspezifische Wissen über die Anwendung der Schlüssel zur nächsten Stufe der Ausfallsicherheit ist. Da Sidecars per Definition von der Anwendung getrennt sind, können sie kein anwendungsspezifisches Wissen kodieren, ohne dass eine Koordination zwischen der Anwendung und dem Sidecar erforderlich ist. Das ist wahrscheinlich mindestens genauso schwierig wie die Implementierung der vom Sidecar bereitgestellten Funktionen in einer sprachspezifischen Bibliothek, die von der Anwendung eingebunden werden kann.
Ich glaube, dass die in Open Source verfügbare Testautomatisierung dich in eine bestimmte Richtung bringt. Jede darüber hinaus gehende Investition ist wahrscheinlich mit abnehmenden Erträgen verbunden, wie in "Service Mesh Tracing" besprochen, und gegen die Verwendung von Sidecars für das Verkehrsmanagement, wie in "Implementierung im Service Mesh", so unpopulär diese Meinungen auch sein mögen. Diese Implementierungen sind verlustbehaftet im Vergleich zu dem, was du mit einer Binärabhängigkeit erreichen kannst, die entweder explizit enthalten ist oder in die Laufzeit injiziert wird - beides fügt ein weitaus größeres Maß an Funktionalität hinzu, das nur dann kostspielig wird, wenn du eine beträchtliche Anzahl verschiedener Sprachen zu unterstützen hast (und selbst dann bin ich nicht überzeugt).
Zusammenfassung
In diesem Kapitel haben wir Plattform-Engineering zumindest als Platzhalter für die Funktionen des Zuverlässigkeits-Engineerings definiert, die wir im weiteren Verlauf dieses Buches besprechen werden. Das Plattform-Engineering-Team ist am effektivsten, wenn es einen kundenorientierten Fokus hat (wobei der Kunde die anderen Entwickler im Unternehmen sind) und nicht den der Kontrolle. Teste die Werkzeuge, den Einführungspfad für diese Werkzeuge und alle Prozesse, die du entwickelst, anhand der Regel "Leitplanken statt Tore".
Letztendlich ist die Gestaltung deiner Plattform auch ein Teil der Gestaltung deiner Organisation. Wofür wollt ihr bekannt sein?
Get SRE mit Java Microservices now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.