Ich habe drei Dinge über ein laufendes System abgeleitet. Zwei davon stimmten nicht.

kafka opentelemetry terraform troubleshooting devops

Dreimal in einer Woche, an derselben Migration arbeitend, habe ich eine Aussage über ein laufendes System gemacht, indem ich eine Datei gelesen habe. Zwei der drei Aussagen waren falsch. Die dritte war zufällig falsch und wurde mit geringem Aufwand gerettet. Der vereinende Mechanismus ist, dass das Artefakt und das laufende System zwei Sichten auf dasselbe Ding sind, und sie können aus Gründen übereinstimmen, die das Artefakt nicht verraten kann. Nur die Live-Sicht ist die Quelle der Wahrheit.

Der siebte Consumer

Eine Canary-Migration hatte drei grüne Signale. Der neue Collector hatte seinen ersten Traffic geloggt. Die Span-Prozessoren schrieben in den neuen Broker. Die sechs Dienste, die diese Metriken in Datenbankzeilen verwandeln, wurden versorgt. Der Fix wurde ausgeliefert. Erst danach fand eine breitere Suche einen siebten Dienst, einen Billing-Consumer, der die ganze Woche über auf dieselbe Weise getrennt gewesen war, durch denselben Mechanismus, und von dem Service-Namensscan übersehen worden war, der die Bereinigung getrieben hatte.

Die beiden Wahrheiten galten gleichzeitig. Jedes Signal im Raum war echt, und das System, das gerade geändert wurde, hatte ein Loch, das die Bereinigung zugeklebt hatte. Diese Lücke ist der Beitrag.

Warum der Scan ihn übersah

Der Fix wurde durch ein Consumer-Namenspräfix eingegrenzt. Sechs Dienste passten. Der siebte trug dieses Präfix nicht, also sah der Sweep ihn nicht. Der Mechanismus verdient es, laut ausgesprochen zu werden: die Topics sind der Vertrag, die Namen sind eine Konvention, und eine Konvention kann falsch sein. Consumer-Subscriptions per Service-Namenspräfix zu matchen behandelt den Namensraum als den Vertrag. Der Namensraum hat gelogen.

Die Kosten verstecken sich in Plain Sight, weil der Match auf den Fällen erfolgreich ist, die er sieht, und der Fall, den er nicht sieht, genau der ist, der im Report nicht auftaucht. Ein grüner Sweep ist nicht dasselbe wie ein vollständiger Sweep. Er ist der Beweis, dass die Fälle, die deine Namenskonvention abdeckt, abgedeckt waren. Er schweigt über die Fälle, die die Konvention übersprungen hat, was eine kleinere Menge ist, die keine Verpflichtung hat, leer zu sein.

Eine Revision, die keine Kehrtwende war

Dieselbe Woche, anderes Artefakt, derselbe Fehlermodus. Die Kafka-Consumer-Gruppen des Collectors waren aus Konfigurations-Templates gelesen worden, um daraus zu schließen, dass sie kein Stage-Präfix trügen und daher verweigert würden, sobald Broker-seitige ACLs durchgesetzt würden. Am laufenden Cluster aufgezählt trugen sie alle ein Präfix und waren abgedeckt.

Das allgemeine Prinzip galt weiterhin. Ein Billing-seitiger Consumer ist tatsächlich unpräfixiert und wäre tatsächlich gebrochen, sobald die Regeln verschärft würden. Der Fehler war nicht spekulativ; er entsprang einer spezifischen Behauptung über diese N Gruppen, die die Live-Daten nicht stützten. Templates sehen aus wie Wahrheit, weil sie das Artefakt sind, das dir die Plattform gibt, aber sie sind jemandes Rendering von Absicht, nicht ein Vertrag mit dem Broker. Der Grund, warum die Revision sich selbst fing, war, dass das breitere Bild bereits an einer anderen Stelle derselben Migration gebrochen war, und das Lesen des Live-Clusters bereits der Reflex war.

Die interessante Form des Fehlers ist, dass er billig zu machen und leicht auszuliefern gewesen wäre: ein selbstbewusster Absatz in einem Runbook, ein Häkchen bei einem Review, eine Namenskonventionsänderung, die auf eine Flotte angewendet wird. Die Kosten des Falschliegens waren null bis zu dem Moment, in dem eine andere Consumer-Abfrage auf die unpräfixierte Gruppe traf und die Regel, die tatsächlich nie durchgesetzt worden war, sie still schloss. Die Kosten des Rechtliegens waren ein Absatz, den niemand zweimal gelesen hätte.

Der billige dritte

Ich argumentierte gegen das Inlinen eines Kafka-SASL-Benutzernamens in einer Values-Datei, weil SSM Secrets verschlüsselt speichert, ein vernünftig klingender Einwand, widerrufen nach einer Ein-Zeilen-Prüfung: die Codebase trug bereits exakt ein solches Literal in einer anderen Values-Datei. Die Konvention existierte; eine Policy war aus einem Default abgeleitet worden. Dieser hier war billig zu fangen und fast billiger zu verfehlen, weil es das dritte Mal in drei Tagen war, dass Pattern-Matching der Zug gewesen war, und die dritte Verfehlung einer Woche ist die, die ein müder Operator aufhört, sich selbst zu flaggen, weil das Gehirn sie bereits triagiert.

Die Form von jedem dieser Fehler ist dieselbe: eine selbstbewusste Behauptung über das laufende System, gespeist aus dem Artefakt, die das Artefakt tatsächlich nicht beantworten kann. Dieselbe Form trägt über eine Woche drei verschiedene Kleider.

Warum “ich habe aus der Datei abgeleitet” sich nicht wie ein fertiger Satz anfühlt

Das Artefakt und das laufende System sind zwei Sichten auf dasselbe Ding, und zwei Sichten können aus sehr unterschiedlichen Gründen übereinstimmen. Sie können übereinstimmen, weil sie voneinander abgeleitet sind, was der kanonische Fall ist und der einzige, in dem “ich habe die Datei gelesen” eine vollständige Antwort ist. Sie können übereinstimmen, weil eine von ihnen editiert wurde und die andere nicht, was die Drei-Wege-Merge-Form ist und die ein terraform plan zu einem einzelnen Diff abflacht, das wie normale Drift aussieht. Oder sie können übereinstimmen, weil eine von ihnen auf eine Weise editiert wurde, die die andere nicht sehen konnte, was der byte-identische Cousin dieses Beitrags ist: eine Config, die einer Quelle treu ist, die seither von einer anderen Binary gelesen wurde.

Das Artefakt zu lesen gibt dir was es sein sollte. Nur das laufende System gibt dir was es ist. Beide fühlen sich an wie die Quelle der Wahrheit. Eine von ihnen ist weit weniger oft richtig, als sich eine von ihnen anfühlt.

Wie die Checks konkret aussehen

Das Prinzip aus dem letzten Abschnitt muss in etwas landen, das du tippen kannst. Drei kleine Reads, einer pro Schicht des Kafka-Systems, keiner hängt von einem Template, einem Präfix oder einer Konfigurationsdatei ab. Jeder ist unten mit dem gepaart, was er dir einbringt, was wichtiger ist als der Flag-Satz.

Consumer-Gruppen gegen den Cluster aufzählen. Die Form, die den siebten Consumer findet:

kafka-consumer-groups.sh \
  --bootstrap-server "$BROKER" \
  --describe --all-groups

Das --all-groups-Flag ist der Teil, der den übersehenen Consumer fängt, weil es nicht per Namenspräfix matched; es listet jede Gruppe auf, die der Cluster kennt, einschließlich solcher, deren Namen nicht zur Konvention passen. Paare das mit --all-topics, wenn der Broker über Stages geteilt wird, damit der Read jede Partition jedes Topics umfasst, nicht nur die, die deine Consumer-Config deklariert.

Dieser Read ersetzt den Service-Namenspräfix-Sweep. Beide Durchläufe sehen aus wie Inventur. Nur einer von ihnen nimmt den Cluster als Quelle der Wahrheit.

Die Topic-Liste als Vertrag ziehen. Als das Artefakt sagte, die Consumer seien verschoben, war das Erste, was zu prüfen ist, welche Topics tatsächlich auf dem Cluster existieren und wie ihre Partitionsanzahlen aussehen:

kafka-topics.sh \
  --bootstrap-server "$BROKER" \
  --list --exclude-internal

Gefolgt von --describe für das Topic oder die Topics von Interesse, wenn die Anzahl zählt:

kafka-topics.sh \
  --bootstrap-server "$BROKER" \
  --describe --topic "spans.${STAGE}"

Diese Reads holen den Vertrag vom Broker, wo er lebt. Konfig-Templates sagen dir, was existieren sollte, was dich in den byte-identischen Cousin dieses Beitrags bringt; die Topic-List-Befehle sagen dir, was existiert. Die beiden werden häufig divergieren, und wenn sie es tun, hat der Broker recht.

Lies die ACLs aus dem Live-Cluster. Als die zweite Verfehlung sagte “die Consumer-Gruppen werden verweigert, sobald die ACLs zuziehen”, war der Read, der das in einem Befehl entschieden hätte:

kafka-acls.sh \
  --bootstrap-server "$BROKER" \
  --list --topic "spans.${STAGE}"

Dieser Read ist der, den die Template-basierte Inferenz zu ersetzen versuchte, und er braucht keinen Stage-Präfix und keinen Config-Block, um die Frage zu beantworten. Was auch immer der Cluster sagt, dass dieser Principal tun darf, das ist, was die Regel tatsächlich tut. Paare es mit --resource-pattern-type prefixed, wenn deine Umgebung Präfix-Stil-ACLs verwendet statt literaler Ressourcennamen, weil --list standardmäßig auf literale Mustern steht und die Präfix-Grantings stillschweigend versteckt.

Drei Reads. Keiner von ihnen verlangt mehr Berechtigung als das, was ein Operator mit Read-only-Cluster-Zugriff bereits hat. Keiner von ihnen dauert länger als ein paar Sekunden. Keiner von ihnen ist schwer zu merken. Der schwierige Teil ist, sich daran zu erinnern, nach ihnen zu greifen, bevor man nach der Datei greift.

Die Regel und der Check

Leite aus dem Artefakt ab, verifiziere gegen das laufende System. Behandle das Erste als Hypothese, nicht als Beweis.

Der siebte Consumer ist das, was die Regel dir einbringt, und der Check ist der oben, der ihn gefangen hat: Consumer-Gruppen clusterweit aufzählen, alle Topics, alle Gruppen, keine Berechtigungsanfrage, fünf Sekunden Wandzeit. Das Fragment liest sich fast wie der Präfix-Sweep, den es ersetzt; es nimmt nur der Konvention nicht ab, welche Subscriptions existieren. Das Präfix war eine Vermutung über einen Vertrag; das Topic war der Vertrag.

Der Check ist das, was in die nächste Woche überlebt. Das Beispiel ist das, was ihn einprägsam macht. Beide zusammen zu benennen ist der Teil, der sich die nächsten drei “warte, ist das eins davon”-Momente verdient, wenn das Artefakt und das laufende System aufhören übereinzustimmen, bevor das Gehirn Zeit gehabt hat, auf den Reflex zurückzufallen.

$ cat OPENTELEMETRY .md
· 6 Min. Lesezeit

Die Config war Byte-für-Byte identisch. Das bewies das Falsche.

Wir haben eine Collector-Config neu abgeleitet, sie Byte für Byte gegen die produktive verglichen, sie war identisch, und mit einem Stapel grüner Checks im Rücken ausgeliefert. Sie ist beim Start in eine Crash-Loop gelaufen. Byte-identisch ist eine echte Eigenschaft; sie beantwortet nur eine Frage, die niemand gestellt hat.

opentelemetry kafka terraform devops troubleshooting
$ cat TERRAFORM .md
· 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 sauber zurück, nachdem ein Incident-Fix von Hand angewendet worden war. Der Fix war live, der Fix war in der Datei, und nur der State war hinterher. Ein Plan ist ein Diff zwischen zwei Sichten, aber das System hat drei. Alle drei zu lesen, bevor du planst, ist das, was den grünen Diff von einer Vermutung in eine Entscheidung verwandelt.

terraform helm eks state troubleshooting devops
$ cat CDKTF .md
· 6 Min. Lesezeit

Der Fehler, den ich dem Provider angelastet habe

Wochenlang zeigte ein terraform plan eine Drift, die ich nicht beheben konnte, an einem S3-Bucket, der bereits korrekt konfiguriert war. Ich hakte es unter 'AWS-Provider-Eigenheit' ab. Der Provider war unschuldig. Mein eigener Code log mich an, lange bevor Terraform ihn je zu sehen bekam.

cdktf terraform iac typescript devops troubleshooting