Sicherheitsnetz statt Geländer - Umgang mit Fehlern in IT-Infrastruktur-Plattformen

(This article is also available in English)

Es gibt ein weit verbreitetes Sprichwort: "Irren ist menschlich". Die Aussage dahinter ist einfach: Wir als Menschen sind nicht perfekt und machen Fehler. Das gilt natürlich auch für Software und Platform Engineers wie ich einer bin. Daher haben wir auf unserem Gebiet verschiedene Maßnahmen, um die Wahrscheinlichkeit und Auswirkungen von Fehlern zu reduzieren.

Um es kurz klar zu definieren: Mit Fehlern meine ich in diesem Kontext zwei verschiedene Dinge: Zum einen das was allgemein als Bug bezeichnet wird, also Code, der nicht das tut was er soll (z.B. eine falsche Berechnung oder eine fehlende Eingabe-Prüfung), zum anderen nicht erwünschte Handlungen, etwa aus Versehen Einträge in einer Datenbank löschen oder einen neuen Codestand statt in einer Test- in die Produktionsumgebung ausrollen. Es wird auch gerne zwischen Irrtum und Fehler unterschieden, mit dem Unterschied, dass ein Irrtum besteht, wenn ich das Ergebnis vorher nicht wirklich hätte wissen können, bei einem Fehler aber schon. Für meine Zwecke fasse ich beide als Fehler zusammen.

Wahrscheinlichkeit von Fehlern reduzieren

Die bekannteste und am meisten verbreitete Methode, um die Wahrscheinlichkeit von Fehler zu reduzieren, ist Testing. Von Unit Testing bis System Testing geht es immer um das gleiche: In automatisierter Form Einheiten eines Systems oder das ganze System zu testen, ob es sich wie erwartet verhält. Damit können wir Bugs abfangen, bevor sie ein End-User zu Gesicht bekommt oder sie Schäden anrichten können. Es gibt hier aber mehrere Probleme: Eine vollständige 100% Testabdeckung ist schwierig und wäre (für die meisten Fälle) unpraktikabel aufwändig und teuer. Außerdem werden auch die Tests wieder von Menschen geschrieben, haben also Fehler.

Ein weiterer verbreiteter Prozess um Bugs frühzeitig zu finden, sind Code Reviews. Getreu dem Motto "Vier Augen sehen mehr als zwei" dient ein Code Review unter anderem dazu mögliche Bugs zu finden. Code Reviews helfen aber auch noch dabei, Lesbarkeit und Verständlichkeit des Codes zu validieren und Wissen zu verteilen.

Für diese Maßnahmen gilt: Sie reduzieren die Wahrscheinlichkeit von Fehlern, können sie aber nicht komplett ausschließen. Und wenn wir uns auf Platform Engineering konzentrieren, ist Testing sogar noch deutlich aufwändiger als bei Software (Infrastruktur ist aufwändiger aufzusetzen als eine Test-Funktion in Software), damit weniger praktikabel und deckt damit, wenn es überhaupt gemacht wird, im Zweifel weniger mögliche Fehler-Fälle ab.

Wenn wir also die Wahrscheinlichkeit von Fehlern nicht ausreichend reduzieren können, müssen wir zumindest deren Auswirkungen minimieren.

Auswirkungen von Fehlern minimieren

Das wichtigste Mittel für Plattformen ist dabei meiner Meinung nach: Schnelle Infrastructure-as-code(IaC)-Automatisierung.

Wenn wir unsere Infrastruktur deklarativ beschrieben haben und automatisiert und schnell auf einen definierten Stand bringen können, können wir problemlos wieder auf einen funktionierenden Stand zurückrollen oder einen Fix ausspielen. Weil wir eben nicht den Weg beschreiben, sondern das Ziel. IaC-Tools wie Terraform oder Kubernetes sind dann in der Lage von jedem beliebigen (verkorksten) Ausgangszustand unseren beschriebenen Zielzustand zu erreichen. Dabei sollte es keine Rolle spielen, wie wir den fehlerhaften Zustand erzeugt haben. Sei es weil wir in der deklarativen Beschreibung einen Bug eingebaut oder im Rahmen manueller Betriebstätigkeiten einen Fehler gemacht haben und dabei etwas falsch konfiguriert oder gar gelöscht haben.

Reden wir von zustandsloser Infrastruktur wie Containern oder Konfiguration wie z.B. Firewall-Regeln, ist dieses Zurückrollen schnell und leicht möglich. Doch andere Komponenten wie Datenbanken haben einen Zustand, der nicht so einfach zurückgerollt oder wiederhergestellt werden kann. Daher gehören für mich zu einer sauber automatisierten Infrastruktur auch automatisierte Backups. Und, was gerne vergessen wird, auch ein automatisierter (und einfacher) Weg diese Backups wieder einzuspielen.

Das Prinzip, das für mich hier dahintersteckt, hat der Youtuber und Tech-Influencer Theo Browne in Videos ganz passend als "safety nets instead of guard rails" (Sicherheitsnetz statt Geländer) beschrieben. Auch wenn er auf Software- statt auf Plattform-Entwicklung abgezielt hat.

Das Geländer ist für mich dabei der Unit Test und das Code Review. Es verhindert in vielen Fällen, dass ich in die Tiefe stürze. Aber wenn ich ausrutsche und drüber falle stürze ich trotzdem ab. Hier greift das Sicherheitsnetz. Es verhindert nicht, dass ich abstürze, fängt mich aber ab, sodass ich nicht tief falle und unverletzt wieder hochkomme. Das ist für mich die Automatisierung und das Backup. Beide verhindern nicht, dass ich einen Fehler mache, fangen aber die Auswirkungen ab, indem sie mir ermöglichen, diesen schnell wieder zu korrigieren.

Ein Beispiel

Um das Ganze etwas greifbarer zu machen, möchte ich eine Situation beschreiben, die mir vor einigen Jahren in einem Kundenprojekt passiert ist.

Der Kontext: Ein größeres IoT-Projekt mit tausenden Geräten im Feld, die täglich viele Millionen Nachrichten produzieren. In dem Teil-Projekt, in dem ich unterwegs war, ging es im Kern um eine Datenverarbeitungs- und -analyse-Pipeline, an deren Ende vorberechnete Daten unter anderem in ElasticSearch abgelegt wurden, um sie für graphische Auswertungen mit dem integrierten Dashboarding-Tool Kibana nutzbar zu machen. Im Rahmen eines Umbaus habe ich Indices (quasi die Tabellen in ElasticSearch) umbenannt und Daten transformiert, was damals nur durch Kopieren der Daten in einen neuen Index mit dem gewünschtem Namen und anschließendem Löschen des alten ging. Dabei ist mir das Malheur passiert: Anstatt einen älteren bereits transformierten Index zu entfernen, habe ich den Index des aktuellen Tages gelöscht.

Hätte ich den Fehler durch "Geländer" verhindern können? Ich hätte die ganze Aktion vorher komplett automatisieren und testen können. Dann wäre mein irrtümlich gelöschter Index wahrscheinlich aufgefallen, hätte aber in Summe viel mehr Zeit und Aufwand gekostet als für einen eigentlich kleinen Umbau an nicht kritischen und auch nicht Endkunden-sichtbaren Daten praktikabel gewesen wäre.

Dank "Sicherheitsnetz" ist aber trotzdem nicht viel passiert. Wir hatten natürlich Backups der Daten in ElasticSearch. Diese wurden der Effizienz wegen aber nur nachts im Batch angefertigt. Für die Daten des aktuellen Tages also keine Hilfe. Aber weil wir beim Design der Architektur davon ausgegangen sind, dass Fehler passieren können (seien sie technischer oder menschlicher Art), haben wir Schutzmechanismen eingebaut. Alle Daten wurden, sobald sie in der Plattform eintreffen, so schnell wie möglich unverarbeitet und in Rohform mit Hilfe von Kafka in Object Storage (AWS S3) persistiert.

Damit konnten wir jederzeit alte Rohdaten aus S3 auslesen und erneut durch die Verarbeitungspipeline schieben. Dazu hatten wir die Pipeline, bestehend aus einer Reihe von Apache Spark Streams (verbunden mit Kafka), mit einem Reprocessing-Modus ausgestattet, sodass wir leicht Daten eines bestimmten Zeitraums neu verarbeiten konnten. Wahlweise aus S3 ausgelesen oder, für neuere Daten wenn noch vorhanden, direkt aus den Kafka-Topics.

Dank dieser Mechanismen war es mir also leicht möglich, die aus Versehen gelöschten Daten neu berechnen zu lassen. Außer einem Schrecken für mich und zeitweilig nicht verfügbaren Auswertungen ist am Ende nichts passiert.

Diese Situation illustriert sehr gut was ich mit Sicherheitsnetz meine: Davon ausgehen, dass Fehler passieren, und Mechanismen vorsehen, damit diese Fehler schnell und einfach korrigiert werden können. In diesem Fall durch Backups an den richtigen Stellen und Tools um gelöschte Daten neu zu berechnen.

Wenn Auswirkungen teuer sind

Die Denkweise, die ich in den letzten Absätzen beschrieben habe, hat eine implizite Annahme: Fehler sind nicht schlimm (im Sinne des Schadens den sie anrichten) und lassen sich leicht korrigieren. In Industrial-IoT- und SmartFactory-Projekten, in denen ich normalerweise unterwegs bin, gilt diese Annahme fast immer. Use cases auf diesen Plattformen drehen sich oft um die Optimierung der Produktion oder überhaupt erst mal die Visualisierung einer digitalisierten Produktion. Gehen hier Daten verloren oder haben Applikationen Ausfälle, hat das keine größeren finanziellen Auswirkungen.

Es gibt aber IT-Bereiche, in denen das ganz anders ausschaut: Bugs in Steuergeräten z.B. von Fahrzeugen (insbesondere bei denen mit autonomen Funktionen) können im schlimmsten Fall zu Todesfällen führen, ähnliches gilt für medizinische Geräte. Oder, um nahe der SmartFactory zu bleiben, Steuergeräte für Produktionsmaschinen. Hier können Fehler schnell Millionenschäden verursachen (fehlerhafte Produkte, gestoppte Produktion oder beschädigte Maschinen).

In all diesen Fällen braucht es eine andere Herangehensweise um Fehler soweit irgendwie möglich auszuschließen. Ausführlichste Tests reichen da schon nicht mehr und wir haben es ganz schnell mit Feldern wie formaler Verifikation zu tun. Das ist entsprechend teuer und aufwändig, aber immer noch besser und günstiger als die möglichen Schäden.

Die Mischung macht's

In Projekten rangieren die Auswirkungen von Fehlern irgendwo zwischen diesen beiden Extremen (keine Folgen vs Todesfälle). Erfahrungsgemäß und nüchtern betrachtet in den meisten Projekten eher mit Tendenz zu geringen Folgen. In der Praxis gilt daher: Die Mischung macht's. Manche Fehler kann man gut vorab abfangen, bei anderen ist es einfacher sie nachträglich zu korrigieren.

Alle Beteiligten sollten sich (am besten schon vor Projektstart) klar machen, dass Fehler passieren werden und gemeinsam festlegen, was die möglichen Auswirkungen und deren Kosten sind. Basierend darauf kann man sich dann überlegen, welcher Aufwand für die Verringerung der Wahrscheinlichkeit von Fehlern sinnvoll ist, und was man eher hinnimmt. Am Ende ist das eine Kosten-Nutzen-Frage.

Meiner Erfahrung nach werden diese Überlegungen oft nicht bewusst gemacht, fließen aber natürlich implizit dennoch in Entscheidungen und Vorgehensweisen ein. Hier bietet es sich an, sich dieses Spannungsfeld hin und wieder mal bewusst zu machen und das eigene Projekt zu verorten.

Fazit

"Sicherheitsnetze" in unsere Plattformen einzubauen ist meiner Meinung nach ein sehr viel praktikabler Schutz vor Fehlern als immer höheren "Geländern" hinterherzujagen.

Die Jagd nach immer noch höherer Testabdeckung kostet nur unnötig Zeit und Nerven. Entwickler und Betriebsteams mit ausschweifenden Sicherheitsprozessen von den Systemen fernzuhalten hält diese nur von der Arbeit ab und führt im Zweifel zu unsicheren Workarounds. Ein Mehraugenprinzip fängt zwar viele Fehler ab, aber eben nicht alle.

Am Ende passieren Fehler trotz aller Bemühungen dennoch, wir können nur ihre Wahrscheinlichkeit verringern. Und wenn sie passieren, sind wir froh ein Sicherheitsnetz zu haben, das uns auffängt.