Checkliste: Was musste vor dem Start von Microservices in Prod getan werden?

Dieser Artikel enthält einen kurzen Auszug aus meiner eigenen Erfahrung und der Erfahrung meiner Kollegen, mit denen ich Tage und Nächte damit verbracht habe, Vorfälle zu harken. Und viele Vorfälle wären niemals aufgetreten, wenn alle ihre Microservices geliebt hätten und zumindest ein wenig genauer geschrieben worden wären.


Leider glauben einige Low- Level-Programmierer ernsthaft, dass eine Docker-Datei mit einem Befehl selbst ein Mikroservice ist und bereits jetzt bereitgestellt werden kann. Hafenarbeiter drehen sich, die Bank ist schlammig. Dieser Ansatz ist mit Problemen behaftet, die von einem Leistungsabfall über die Unfähigkeit zum Debuggen und Denial-of-Service bis hin zu einem Albtraum namens Dateninkonsistenz reichen.


Wenn Sie der Meinung sind, dass es an der Zeit ist, eine weitere App in Kubernetes / ECS / was auch immer zu starten, habe ich etwas zu beanstanden.


Die englische Version ist ebenfalls verfügbar .


Ich habe mir eine Reihe von Kriterien für die Beurteilung der Bereitschaft von Anträgen für den Start in der Produktion gebildet. Einige Punkte dieser Checkliste können nicht auf alle Anwendungen angewendet werden, sondern nur auf spezielle. Andere gelten im Allgemeinen für alles. Ich bin sicher, Sie können Ihre Optionen in den Kommentaren hinzufügen oder einige dieser Punkte bestreiten.


Wenn Ihr Mikrodienst nicht mindestens eines der Kriterien erfüllt, kann ich nicht zulassen, dass er sich in meinem idealen Cluster befindet, der in einem 2000 Meter unterirdischen Bunker mit Fußbodenheizung und einem geschlossenen, in sich geschlossenen Internetversorgungssystem gebaut wurde.


Lass uns gehen ...


Hinweis: Die Reihenfolge der Artikel spielt keine Rolle. Jedenfalls für mich.


Readme Kurzbeschreibung


Es enthält eine kurze Beschreibung seiner selbst ganz am Anfang von Readme.md in seinem Repository.

Gott, es scheint so einfach. Aber wie oft bin ich darauf gestoßen, dass das Repository nicht die geringste Erklärung enthält, warum es benötigt wird, welche Aufgaben es löst und so weiter. Es ist nicht erforderlich, über etwas Komplizierteres wie Konfigurationsoptionen zu sprechen.


Integration in ein Überwachungssystem


Sendet Metriken an DataDog, NewRelic, Prometheus usw.

Analyse des Ressourcenverbrauchs, Speicherlecks, Stacktraces, Service-Interdependenz, Fehlerrate - ohne all dies (und nicht nur) zu verstehen, ist es äußerst schwierig zu steuern, was in einer großen verteilten Anwendung geschieht.


Warnungen konfiguriert


Der Service umfasst Warnungen, die alle Standardsituationen sowie bekannte eindeutige Situationen abdecken.

Metriken sind gut, aber niemand wird ihnen folgen. Daher erhalten wir automatisch Anrufe / Push / SMS, wenn:


  • Der CPU- / Speicherverbrauch hat dramatisch zugenommen.
  • Der Verkehr nahm stark zu / ab.
  • Die Anzahl der verarbeiteten Transaktionen pro Sekunde hat sich in jede Richtung dramatisch geändert.
  • Die Größe des Artefakts nach dem Zusammenbau hat sich dramatisch geändert (exe, app, jar, ...).
  • Der Prozentsatz der Fehler oder deren Häufigkeit hat den zulässigen Schwellenwert überschritten.
  • Der Dienst hat das Senden von Metriken eingestellt (häufig übersehene Situation).
  • Die Regelmäßigkeit bestimmter erwarteter Ereignisse wird verletzt (Cron-Job funktioniert nicht, nicht alle Ereignisse werden verarbeitet usw.)
  • ...

Runbooks erstellt


Für den Dienst wurde ein Dokument erstellt, das bekannte oder erwartete Eventualitäten beschreibt.

  • Wie kann sichergestellt werden, dass der Fehler intern ist und nicht von Dritten abhängt?
  • ob es darauf ankommt, wo, an wen und was zu schreiben ist;
  • wie man es sicher neu startet;
  • wie man aus dem Backup wiederherstellt und wo Backups liegen;
  • Welche speziellen Dashboards / Abfragen werden erstellt, um diesen Dienst zu überwachen?
  • Verfügt der Dienst über ein eigenes Admin-Panel und wie kommt man dorthin?
  • Gibt es eine API / CLI und wie kann man damit bekannte Probleme beheben?
  • usw.

Die Liste kann zwischen den Organisationen sehr unterschiedlich sein, aber zumindest grundlegende Dinge sollten vorhanden sein.


Alle Protokolle werden in STDOUT / STDERR geschrieben


Der Dienst erstellt im Produktionsmodus keine Protokolldateien, sendet sie nicht an externe Dienste, enthält keine redundanten Abstraktionen für die Protokollrotation usw.

Wenn eine Anwendung Protokolldateien erstellt, sind diese Protokolle unbrauchbar. Sie werden nicht in 5 Container laufen, die parallel laufen, in der Hoffnung, den Fehler zu finden, den Sie brauchen (und hier sind Sie, weinen ...). Ein Neustart des Containers führt zum vollständigen Verlust dieser Protokolle.


Wenn eine Anwendung ihre eigenen Protokolle in ein System eines Drittanbieters schreibt, z. B. in Logstash, führt dies zu nutzloser Redundanz. Der benachbarte Dienst weiß nicht, wie das geht, weil Hat es einen anderen Rahmen? Du bekommst einen Zoo.


Die Anwendung schreibt einen Teil der Protokolle in Dateien und einen Teil in stdout, da es für den Entwickler praktisch ist, INFO in der Konsole und DEBUG in Dateien zu sehen. Dies ist im Allgemeinen die schlechteste Option. Niemand benötigt Komplexität und vollständig redundanten Code und Konfigurationen, die Sie kennen und warten müssen.


Protokolle sind Json


Jede Protokollzeile ist im Json-Format geschrieben und enthält einen konsistenten Satz von Feldern

Bisher schreibt fast jeder Protokolle im Klartext. Dies ist eine echte Katastrophe. Ich würde mich freuen, nie etwas über Grok Patterns zu erfahren. Ich träume manchmal von ihnen und friere ein, versuche mich nicht zu bewegen, um ihre Aufmerksamkeit nicht zu erregen. Versuchen Sie einfach, Java-Ausnahmen in den Protokollen einmal zu analysieren.


Json ist gut, es ist Feuer vom Himmel. Fügen Sie einfach dort hinzu:


  • Millisekunden- Zeitstempel gemäß RFC 3339 ;
  • Level: Info, Warnung, Fehler, Debug
  • user_id;
  • app_name
  • und andere Felder.

Laden Sie es auf ein geeignetes System herunter (z. B. korrekt konfigurierte ElasticSearch) und genießen Sie es. Verbinden Sie die Protokolle vieler Microservices und fühlen Sie wieder, was gute monolithische Anwendungen waren.


(Und Sie können Request-ID hinzufügen und verfolgen ...)


Protokolle mit Ausführlichkeitsstufen


Die Anwendung muss eine Umgebungsvariable, z. B. LOG_LEVEL, mit mindestens zwei Betriebsmodi unterstützen: ERRORS und DEBUG.

Es ist wünschenswert, dass alle Dienste im selben Ökosystem dieselbe Umgebungsvariable unterstützen. Keine Konfigurationsoption, keine Option in der Befehlszeile (obwohl dies natürlich umkehrbar ist), sondern standardmäßig sofort aus der Umgebung. Sie sollten in der Lage sein, so viele Protokolle wie möglich zu erhalten, wenn etwas schief geht, und so wenige Protokolle wie möglich, wenn alles in Ordnung ist.


Abhängigkeitsversionen behoben


Abhängigkeiten für Paketmanager sind behoben, einschließlich kleinerer Versionen (z. B. cool_framework = 2.5.3).

Dies wurde natürlich schon viel diskutiert. Einige Fix-Abhängigkeiten von Hauptversionen, in der Hoffnung, dass nur kleinere Fehlerkorrekturen und Sicherheitskorrekturen in kleineren Versionen enthalten sind. Das ist nicht richtig.
Jede Änderung in jeder Abhängigkeit sollte in einem separaten Commit widergespiegelt werden . Damit es bei Problemen storniert werden kann. Ist es schwer mit den Händen zu kontrollieren? Es gibt nützliche Roboter wie diesen , die Aktualisierungen verfolgen und Pull-Anfragen für jeden von Ihnen erstellen.


Dockerisiert


Das Repository enthält die produktionsbereite Docker-Datei und die Datei docker-compose.yml

Docker ist seit langem der Standard für viele Unternehmen. Es gibt Ausnahmen, aber selbst wenn Sie Docker nicht in der Produktion haben, sollte jeder Ingenieur in der Lage sein, Docker-Kompositionen durchzuführen und an nichts anderes zu denken, um eine Entwickler-Assembly für die lokale Überprüfung zu erhalten. Und der Systemadministrator muss die Assembly bereits von den Entwicklern mit den erforderlichen Versionen von Bibliotheken, Dienstprogrammen usw. überprüfen lassen, in denen die Anwendung zumindest irgendwie funktioniert , um sie an die Produktion anzupassen.


Umgebungskonfiguration


Alle wichtigen Konfigurationsoptionen werden aus der Umgebung gelesen, und die Umgebung hat Vorrang vor Konfigurationsdateien (jedoch niedriger als Befehlszeilenargumente beim Start).

Niemand wird jemals Ihre Konfigurationsdateien lesen und deren Format studieren wollen. Akzeptiere es einfach.


Weitere Details hier: https://12factor.net/config


Bereitschafts- und Lebendigkeitssonden


Enthält geeignete Endpunkte oder CLI-Befehle, um die Bereitschaft zu testen, Anforderungen beim Start und bei der Verfügbarkeit während des gesamten Lebens zu bearbeiten.

Wenn die Anwendung HTTP-Anforderungen bearbeitet, sollte sie standardmäßig zwei Schnittstellen haben:


  1. Um zu überprüfen, ob die Anwendung aktiv ist und nicht einfriert, wird ein Liveness-Test verwendet. Wenn die Anwendung nicht reagiert, wird sie möglicherweise automatisch von Orchestratoren wie Kubernetes gestoppt, " aber dies ist nicht korrekt ." Tatsächlich kann das Beenden einer eingefrorenen Anwendung einen Dominoeffekt verursachen und Ihren Service dauerhaft beeinträchtigen. Dies ist jedoch kein Entwicklerproblem. Führen Sie einfach diesen Endpunkt aus.


  2. Um zu überprüfen, ob die Anwendung nicht gerade gestartet wurde, sondern bereit ist, Anforderungen anzunehmen, wird ein Bereitschaftstest durchgeführt. Wenn die Anwendung eine Verbindung zur Datenbank, zum Warteschlangensystem usw. hergestellt hat, sollte sie mit einem Status von 200 bis 400 (für Kubernetes) antworten.



Ressourcenlimits


Enthält Beschränkungen für den Verbrauch von Speicher, CPU, Speicherplatz und anderen verfügbaren Ressourcen in einem konsistenten Format.

Die spezifische Implementierung dieses Elements wird in verschiedenen Organisationen und für verschiedene Orchestratoren sehr unterschiedlich sein. Diese Grenzwerte müssen jedoch für alle Dienste in einem einzigen Format festgelegt werden, für verschiedene Umgebungen (prod, dev, test, ...) unterschiedlich sein und sich außerhalb des Repositorys mit Anwendungscode befinden .


Montage und Lieferung erfolgen automatisiert


Das in Ihrer Organisation oder Ihrem Projekt verwendete CI / CD-System ist konfiguriert und kann die Anwendung gemäß dem akzeptierten Workflow an die gewünschte Umgebung liefern.

Nichts wird jemals manuell an die Produktion geliefert.


Unabhängig davon, wie schwierig es ist, die Montage und Lieferung Ihres Projekts zu automatisieren, muss dies erfolgen, bevor dieses Projekt in Produktion geht. Dieser Artikel umfasst das Erstellen und Ausführen von Ansible / Chef-Kochbüchern / Salt / ..., das Erstellen von Anwendungen für mobile Geräte, das Erstellen einer Verzweigung des Betriebssystems und das Erstellen von Images virtueller Maschinen usw.
Kann nicht automatisieren? Sie können dies also nicht in die Welt bringen. Nach dir wird niemand es sammeln.


Anmutiges Herunterfahren - korrektes Herunterfahren


Die Anwendung kann SIGTERM und andere Signale verarbeiten und ihre Arbeit nach dem Ende der Verarbeitung der aktuellen Aufgabe systematisch unterbrechen.

Dies ist ein äußerst wichtiger Punkt. Docker-Prozesse werden verwaist und arbeiten monatelang im Hintergrund, wo niemand sie sieht. Nichttransaktionsvorgänge werden während der Ausführung unterbrochen und führen zu Dateninkonsistenzen zwischen Diensten und Datenbanken. Dies führt zu Fehlern, die nicht vorhersehbar und sehr, sehr teuer sein können.


Wenn Sie keine Abhängigkeiten kontrollieren und nicht garantieren können, dass Ihr Code SIGTERM korrekt verarbeitet, verwenden Sie etwas wie dummes Init .


Mehr Infos hier:



Die Datenbankverbindung wird regelmäßig überprüft


Die Anwendung pingt ständig einen Ping an die Datenbank und reagiert automatisch auf die Ausnahme „Verbindungsverlust“ für alle Anforderungen. Sie versucht, diese selbst wiederherzustellen oder beendet ihre Arbeit ordnungsgemäß

Ich habe viele Fälle gesehen (dies ist nicht nur eine Wendung), in denen Dienste, die für die Verarbeitung von Warteschlangen oder Ereignissen erstellt wurden, ihre Verbindung durch Zeitüberschreitung verloren und endlos Fehler in die Protokolle eingaben, Nachrichten an Warteschlangen zurückgaben, sie an die Warteschlange für tote Briefe schickten oder einfach ihre Arbeit nicht erledigten.


Horizontal skaliert


Mit zunehmender Auslastung reicht es aus, mehr Anwendungsinstanzen auszuführen, um sicherzustellen, dass alle Anforderungen oder Aufgaben verarbeitet werden.

Nicht alle Anwendungen können horizontal skaliert werden. Ein markantes Beispiel sind die Kafka-Verbraucher . Dies ist nicht unbedingt schlecht, aber wenn eine bestimmte Anwendung nicht zweimal gestartet werden kann, müssen alle interessierten Parteien dies im Voraus wissen. Diese Informationen sollten ein Dorn im Auge sein, in der Readme-Datei hängen und wo immer möglich. Einige Anwendungen können im Allgemeinen unter keinen Umständen parallel gestartet werden, was zu ernsthaften Schwierigkeiten bei der Unterstützung führt.


Es ist viel besser, wenn die Anwendung selbst diese Situationen steuert oder ein Wrapper dafür geschrieben wird, der "Konkurrenten" effektiv überwacht und es dem Prozess einfach nicht erlaubt, die Arbeit zu starten oder zu starten, bis ein anderer Prozess seine Arbeit abgeschlossen hat oder bis eine externe Konfiguration N Prozesse gleichzeitig arbeiten lässt.


Warteschlangen für tote Briefe und schlechte Ausfallsicherheit von Nachrichten


Wenn der Dienst auf Warteschlangen wartet oder auf Ereignisse reagiert, führt das Ändern des Formats oder Inhalts von Nachrichten nicht zu deren Ausfall. Nicht erfolgreiche Versuche, die Aufgabe zu verarbeiten, werden N-mal wiederholt. Danach wird die Nachricht an die Dead Letter Queue gesendet.

Oft habe ich gesehen, wie Verbraucher und Leitungen, die so groß geworden sind, dass ihre anschließende Verarbeitung viele Tage dauerte, endlos neu gestartet wurden. Jeder Warteschlangen-Listener sollte darauf vorbereitet sein, das Format zu ändern, zufällige Fehler in der Nachricht selbst (z. B. Eingabe von Daten in JSON) oder wenn diese vom untergeordneten Code verarbeitet werden. Ich bin sogar auf eine Situation gestoßen, in der die Standard-RabbitMQ-Bibliothek für ein äußerst beliebtes Framework keine Wiederholungsversuche, Versuchszähler usw. unterstützte.


Schlimmer noch, wenn eine Nachricht im Fehlerfall einfach zerstört wird.


Begrenzung der Anzahl der verarbeiteten Nachrichten und Aufgaben pro Prozess


Es unterstützt eine Umgebungsvariable, die gezwungen werden kann, die maximale Anzahl verarbeiteter Aufgaben zu begrenzen. Danach wird der Dienst ordnungsgemäß heruntergefahren.

Alles fließt, alles ändert sich, besonders die Erinnerung. Das stetig wachsende Diagramm des Speicherverbrauchs und des am Ende getöteten OOM ist die Norm in modernen kubernetischen Köpfen. Die Implementierung eines primitiven Tests, der Ihnen sogar die Notwendigkeit erspart, all diese Speicherlecks zu untersuchen, würde Ihnen das Leben erleichtern. Ich habe oft gesehen, dass Leute viel Zeit und Mühe (und Geld) aufwenden, um diesen Umsatz zu stoppen, aber es gibt keine Garantie dafür, dass das nächste Engagement Ihres Kollegen ihn nicht verschlimmert. Wenn die Anwendung eine Woche überleben kann, ist dies ein guter Indikator. Lassen Sie es dann einfach von selbst enden und neu gestartet werden. Dies ist besser als SIGKILL (zu SIGTERM siehe oben) oder die Ausnahme "Nicht genügend Speicher". Für ein paar Jahrzehnte reicht dieser Stecker für Sie.


Verwendet keine Drittanbieter-Integration mit Filterung nach IP-Adressen


Wenn die Anwendung Anforderungen an einen Drittanbieter-Dienst stellt, der den Zugriff von begrenzten IP-Adressen ermöglicht, führt der Dienst diese Aufrufe indirekt über einen Reverse-Proxy aus.

Dies ist ein seltener Fall, aber äußerst unangenehm. Es ist sehr unpraktisch, wenn ein winziger Dienst die Möglichkeit blockiert, den Cluster zu ändern oder die gesamte Infrastruktur in eine andere Region zu verlagern. Wenn Sie mit jemandem kommunizieren müssen, der nicht weiß, wie man oAuth oder VPN verwendet, konfigurieren Sie den Reverse-Proxy im Voraus. Implementieren Sie in Ihrem Programm nicht das dynamische Hinzufügen / Entfernen solcher externer Integrationen, da Sie sich dadurch in die einzig verfügbare Laufzeit einnageln. Es ist besser, diese Prozesse sofort zu automatisieren, um Nginx-Konfigurationen zu verwalten, und ihn in Ihrer Anwendung zu kontaktieren.


Offensichtlicher HTTP-Benutzeragent


Der Dienst ersetzt den User-Agent-Header durch einen benutzerdefinierten für alle Anforderungen an eine API, und dieser Header enthält genügend Informationen über den Dienst selbst und seine Version.

Wenn 100 verschiedene Anwendungen miteinander kommunizieren, können Sie verrückt werden, wenn Sie in den Protokollen etwas wie "Go-http-client / 1.1" und die dynamische IP-Adresse des Kubernetes-Containers sehen. Identifizieren Sie Ihre Anwendung und ihre Version immer explizit.


Verstößt nicht gegen die Lizenz


Es enthält keine Abhängigkeiten, die die Anwendung übermäßig einschränken, es ist keine Kopie des Codes einer anderen Person und so weiter.

Dies ist ein selbstverständlicher Fall, aber es stellte sich heraus, dass selbst der Anwalt, der die NDA schrieb, jetzt Schluckauf hat.


Verwendet keine nicht unterstützten Abhängigkeiten


Wenn Sie den Dienst zum ersten Mal starten, enthält er keine bereits veralteten Abhängigkeiten.

Wenn die Bibliothek, die Sie in das Projekt aufgenommen haben, von niemandem mehr unterstützt wird, suchen Sie nach einem anderen Weg, um das Ziel zu erreichen, oder entwickeln Sie die Bibliothek selbst.


Fazit


Es gibt einige sehr spezifische Überprüfungen auf meiner Liste für bestimmte Technologien oder Situationen, aber ich habe nur vergessen, etwas hinzuzufügen. Ich bin sicher, Sie werden auch etwas finden, an das Sie sich erinnern können.

Source: https://habr.com/ru/post/de438064/


All Articles