14. August 2026 · 9 Min. Lesezeit
Der Plan war grün, weil zwei von drei Sichten übereinstimmten. Die dritte war die Wahrheit.
Ein terraform plan kam für einen verwalteten Observability-Stack mit null zurück. Der On-Call hatte ihn während eines Incidents vor zwei Wochen von Hand gefixt. Der Fix war live. Der Fix war in der Datei. Nur der State war hinterher. Ein Apply hätte jede incident-mildernde Zeile in einem Schritt reverten können. Das “nichts zu tun” des Plans war in diesem Fall nicht zu unterscheiden von “keine Drift”. Das ist keine Geschichte über einen grünen Plan, der ein echtes Problem gefangen hat; es ist eine Geschichte über einen grünen Plan, der eines versteckt hat.
Ein Plan ist ein Diff. Das System hat drei Sichten
Ein typischer Plan ist ein Diff zwischen zwei Dingen: State und Datei. “State” ist die letzte erfolgreiche Apply; “Datei” ist das, was du schreiben würdest, wenn du jetzt anfingst. Der Plan rendert unverändert, wann immer State und Datei übereinstimmen, was das Grün ist, das jeder Operator zu wollen lernt.
Das System hat drei Quellen der Wahrheit, und nur zwei davon sind im Diff. Live ist das laufende Ding, der Cluster oder die Workload oder der Storage-Bucket. State ist das, was Terraform oder Helm zuletzt aufgezeichnet haben. Datei ist das, was das Repo gerade sagt. Jede wird separat editiert. Jede kann driften. Ein Plan ignoriert die erste.
Die drei Quellen können aus sehr unterschiedlichen Gründen übereinstimmen. Sie können übereinstimmen, weil alle drei aus derselben Datei abgeleitet sind (der kanonische Fall, den ein grüner Plan korrekt meldet). Sie können übereinstimmen, weil der State hinterher ist und Datei und Live bereits aufgeholt haben (der Fall, der die gefährlichste Drift versteckt, weil State-hinterher aus der Perspektive eines terraform plan identisch aussieht mit keine-Drift). Sie können übereinstimmen, weil die Datei hinterher ist und Live und State übereinstimmen (der “ich habe die Änderung nie aufgeschrieben”-Fall, den der Plan als Drift in die falsche Richtung liest). Sie können übereinstimmen, weil Live hinterher ist und State und Datei übereinstimmen (der “die Platform hat meine Änderung reverted, weil sie abgestürzt ist”-Fall, den der Plan wieder als Drift zur Datei hin liest).
Zwei Sichten, die miteinander übereinstimmen während sie mit der dritten divergieren, ist der Fall, den ein grüner Plan stillschweigend zuklebt. Ein Plan ist ein Diff über einen Vergleich zwischen zwei Dingen, und das dritte Ding bekommt keine Stimme.
Das erste Exponat: der verwaltete Observability-Stack
Es war mitten in einem Incident. Der On-Call hatte ein Hand-helm upgrade gegen den Live-Release gefahren; Values waren im Cluster gelandet, aber die Datei im Repo war nicht aktualisiert worden. Der Incident schloss sich, das Team holte später in der Woche nach und aktualisierte die Datei passend, und an der Oberfläche glich sich alles aus. Dann kam die Planungsphase der nächsten Änderung, und terraform plan kam sauber zurück.
Der Fall war die worst-case-Form für die stillschweigende Übereinstimmung: Live passte zur Datei (beide reflektierten den Fix), State war vor dem Hand-Upgrade geschrieben und nie aktualisiert worden, und der Plan rendere State-vs-Datei als kein Diff, weil State und die Datei in allem übereinstimmten außer im Post-Incident-Fix; in der neuen Datei war der Fix der einzige Diff, und State hielt immer noch die Pre-Incident-Form. Nichts davon war die Aufgabe des Plans. Die Aufgabe des Plans ist es, den Diff zwischen State und Datei zu berechnen, und der einzige Diff dort war der neue Fix, den die Datei eingebaut hatte und den der State nie hatte. Also berichtete der Plan zu Recht: diese Datei will diesen Fix anwenden.
Das ist der Moment, in dem das Grün gefährlich wird. “Nichts zu tun” wäre die richtige Lesart gewesen, wenn State die Wahrheit gewesen wäre und die Datei ihm entsprochen hätte. “Nichts zu tun” wäre die richtige Lesart gewesen, wenn Live, State und Datei alle die Pre-Incident-Form gehalten hätten. “Nichts zu tun” war die falsche Lesart in dem Moment, in dem Live die Post-Incident-Form hielt und nur State divergierte, weil ein Apply jede Zeile des Incident-Fixes in einem Schritt revertet hätte.
Das Fenster, in dem Live und Datei gegen einen State-hinterher-Backend übereinstimmten, ist der Bug. Er ist leicht zu übersehen, weil Live und Datei wirklich übereinstimmen, und ein grüner Plan ist der Traum. Der Postmortem hätte gelesen “Apply hat den Produktions-Fix während der nächsten Änderung revertet”, und er hätte recht gehabt, und er wäre die teuerstmögliche Rahmung eines Missverständnisses gewesen, das der Plan selbst nicht hätte fangen können.
Das zweite Exponat: der verwaltete Kubernetes-Cluster, einen Tag später
Dieselbe Form, andere Oberfläche. Das terraform plan des Repos für einen verwalteten Kubernetes-Cluster kam mit einer einzigen konkreten Änderung zurück: eine Node-Group, die general_arm von 6 auf 4 resize’t, ein echter Kapazitäts-Drain, der absichtlich angefragt war. Das war der einzige Diff in der Ausgabe. Das war auch nicht die ganze Wahrheit.
Drei der anderen Node-Groups auf demselben Cluster waren State-hinterher. Ein Code-Sync war eine Woche zuvor gelandet; Live war durch Hand-Out-of-Band-Edits während einer Upstream-Änderung aktualisiert worden, Live passte bereits zur Datei; nur der State hatte die neue Config nie gesehen. Der Plan rendere State-vs-Datei als kein Diff, weil State und die Datei übereinstimmten, und die nächste Apply hätte die Live-Form zurück auf das reveret, was State dachte, dass der Cluster vor einer Woche ausgesehen hat.
Der eine laute Diff dominierte die Operator-Aufmerksamkeit, was genau der Punkt ist. Operatoren neigen dazu, den ersten Diff zu lesen, den sie sehen, diesen Diff mental auf Sicherheit zu prüfen und den Rest unter “keine Änderungen” abzulegen. Die stillen tauchen nie im Diff auf, weil sie nicht im Diff sind. Der Cluster hätte eine echte Kapazitätsänderung appliziert und still drei andere Groups auf eine Form revertet, auf die niemand geschaut hat. Der Postmortem hätte gelesen “das Apply hat unbeabsichtigte Änderungen mitgezogen”, und er hätte recht gehabt, und er hätte die Ursache dem lauten Diff zugeschrieben statt den drei leisen.
Beide Exponate gehen denselben Mechanismus durch. Was sich ändert, ist welche zwei Sichten übereinstimmten. Das erste Exponat war Live-und-Datei-einig, State-hinterher. Das zweite Exponat war Live-und-State-einig, Datei-hinterher. Der Mechanismus ist derselbe: zwei-einig-einer-divergent stillschweigende Übereinstimmung, grüner Plan, Apply revertet das Ding, das das Live-System hatte und das der State nicht kannte.
Warum ein Plan das nicht sehen kann
terraform plan konsultiert State und Datei. Es konsultiert nicht Live zur Apply-Zeit. helm template konsultiert nicht Live zur Plan-Zeit. Keines der beiden Tools wurde dafür entworfen, das Live-System zu verifizieren; beide wurden dafür entworfen zu verifizieren, was sich ändern würde gegeben den letzten bekannten State. Der Drei-Wege-Merge, den die Platform nicht macht, ist der, den du selbst machen musst, vor dem Plan, nicht als Teil davon.
Die Form der Limitation ist über Werkzeuge hinweg konsistent. Was auch immer außerhalb des Modells der Platform irgendwo aufgezeichnet wird, ist das Ding, das der Plan nicht sehen kann. Für Terraform ist das die Live-API. Für Helm ist es das Live-Release, das helm template nicht abfragt. Für kubectl diff gegen einen Server-Side-Apply ist es welcher Admission-Controller auch immer zuletzt modifiziert hat, was der lokale Dry-Run nie sieht. Das Plan-Werkzeug und die Platform beachten zwei verschiedene Quellen; die dritte Quelle ist die, an die der Benutzer nicht zu denken pflegt.
Ein Plan, der sauber zurückkommt, ist keine Aussage, dass Live zur Datei passt. Es ist eine Aussage, dass State zur Datei passt, was Information ist, die der Operator in eine andere Frage umwandeln muss, die Frage, was Live tut, von dem keine der beiden weiß.
Wie man es tatsächlich liest
Drei billige Reads gegen die drei Quellen, in dieser Reihenfolge: State vom Backend, Datei von der Platte, Live von der API. Leg sie nebeneinander. Der Fall, in dem zwei übereinstimmen und einer divergiert, ist der Fall, den ein grüner Plan stillschweigend zuklebt. Der Fall, in dem alle drei übereinstimmen, ist der Fall, den ein grüner Plan korrekt meldet. Sie zu unterscheiden kostet einen State-Pull und einen Read, kein Apply, keine Review-Latenz.
Für Terraform:
# Read 1: State
terraform state pull
# Read 2: Datei
terraform plan -no-color
# Read 3: Live
# Was auch immer die Platform exposed (z.B. aws eks describe-cluster ...)
Der Punkt sind nicht die Befehle. Der Punkt ist, dass die drei Reads zusammen beantworten, “welche Sicht hinten ist”, was die Frage ist, die ein einzelner grüner Plan nicht beantworten kann.
Für Helm:
# Read 1: State
helm get values <release> -n <ns>
# Read 2: Datei
helm template <release> <chart> -f values.yaml
# Read 3: Live (nach Dry-Run)
helm diff upgrade <release> <chart> -f values.yaml
Jedes Paar von Reads lässt sich mit dem Auge abgleichen. Jedes Paar, das divergiert gegen einen dritten Read, der mit keinem von beiden übereinstimmt, ist der Fall, bei dem es sich lohnt innezuhalten. Die Reads dauern beim ersten Mal länger einzurichten als zu laufen; nach dem ersten Mal sind sie ein Skript, und das Skript ist ein Check, der vor jedem Plan läuft, und der Check ist das, was den grünen Diff von einer Vermutung in eine Entscheidung verwandelt.
Der wertvollste Moment, das zu tun, ist der Moment, in dem du dabei bist, einen Plan “nichts zu tun” zu nennen, nachdem es kürzlich Hand-Fixes am selben System gab. Das ist der einzige Moment, in dem ein grüner Diff wahrscheinlicher falsch als richtig ist, und es ist der einzige Moment, in dem das Lesen der dritten Sicht billig ist. Die Kosten sind fix; der Zeitpunkt ist alles.
Die Regel und die Zwillinge
Lies alle drei Quellen, bevor du planst; der Plan ist ein Diff über die Lücke, keine Aussage der Wahrheit.
Der Beitrag des ersten Exponats ist, dass der grünste Plan als “nichts zu tun” las, und die Kosten des Falschliegens darin bestanden, den Fix zu reverten, der den Cluster überlebensfähig gemacht hatte. Der Beitrag des zweiten Exponats ist, dass der lauteste Diff im Plan die leisen übertönte, und Operatoren neigen dazu, den ersten Diff zu lesen, den sie sehen. Sie haben dieselbe Form, weil der Mechanismus derselbe ist; was sich unterscheidet, ist welche Sicht hinterher war.
Das nächste Mal, wenn ein Plan nach einem Hand-Fix oder einer Out-of-Band-Änderung am selben System unauffällig zurückkommt, ist die Frage nicht “ist das richtig”. Die Frage ist welche der drei Sichten die ist, die hinterher ist. Die Antwort ist das, was die dritte Quelle sagt, und sie zu lesen dauert weniger Zeit, als dieser Beitrag zu schreiben.