13. August 2026 · 6 Min. Lesezeit
Die Config war Byte-für-Byte identisch. Das bewies das Falsche.
Wir haben einen OpenTelemetry Collector kodifiziert, der von Hand installiert und nie in einem Repo festgehalten worden war. Der sichere Weg dafür ist nicht, eine neue Config zu erfinden, sondern die bereits laufende zu reproduzieren. Also haben wir sie neu abgeleitet und unsere Arbeit auf die strengste Art geprüft, die wir kannten: wir haben das Ergebnis Byte für Byte gegen die live-produktive Config verglichen. Sie stimmte exakt überein. Das YAML parste. terraform validate war sauber, das Linting war sauber, und mehrere unabhängige Reviews kamen ohne Befund zurück.
Dann startete sie und lief in eine Crash-Loop.
Alles, was eine kaputte Config hätte fangen sollen, war gelaufen und hatte bestanden, und die Config war trotzdem kaputt. Die Lücke zwischen diesen beiden Tatsachen ist der ganze Punkt dieses Beitrags, und sie ist nicht spezifisch für Collectors oder Kafka. Es geht darum, was “Byte-identisch” tatsächlich beweist, und das stellt sich als etwas anderes heraus als das, was alle im Raum annahmen, dass es beweise.
Was “Byte-identisch” verifiziert hat
Der Vergleich war echt, und er war nützlich. Er bewies, dass die Transformation von der laufenden Config zur kodifizierten treu war. Nichts fiel weg, nichts wurde umformuliert, kein Schlüssel driftete in der Übersetzung. Wenn deine Sorge lautet “haben wir die Quelle akkurat reproduziert”, dann ist ein Byte-für-Byte-Diff eine vollständige und ehrliche Antwort.
Aber das ist eine Aussage über die Kopie. Sie setzt die neue Config in Beziehung zur alten und sagt, sie seien gleich. Sie sagt überhaupt nichts über eine zweite, völlig getrennte Eigenschaft: ob diese Config gültig ist für das, wogegen sie jetzt laufen muss. Das sind zwei verschiedene Fragen. Treue der Kopie, und Gültigkeit für das Ziel. Sie ergeben zufällig denselben Grünton, deshalb ist es leicht, die erste zu prüfen und zu glauben, man habe die zweite beantwortet.
Hier waren sie böse auseinandergelaufen.
Die Lücke, die die Kopie nicht sehen konnte
Die Config war für eine bestimmte Version des Collectors geschrieben worden. Das Image, das die Unit pinnte, war rund zwei Dutzend Minor-Releases neuer. Ein config-getriebenes Binary und seine Config sind ein Vertrag, und dieser Vertrag hatte sich unter einer Config verschoben, die eingefroren blieb. Drei Dinge waren über diese Releases hinweg kaputtgegangen:
- Ein Telemetrie-Feld, das die Config noch referenzierte, war entfernt worden. Für das neuere Binary ist es schlicht ein unbekannter Schlüssel, und ein unbekannter Schlüssel beim Start ist fatal, nicht ignoriert.
- Das
topicdes Kafka-Receivers war von einem einzelnen String zu einer Liste geworden, während der Kafka-Exporter die Einzahlform behielt. Diese Asymmetrie ist die fieseste Sorte: ein naives Suchen-und-Ersetzen über die Datei “repariert” beide Receiver-Stellen und zerbricht alle acht Exporter-Stellen, oder umgekehrt, und sieht in beiden Fällen ordentlich aus. - Eine Producer-Nachrichtengröße überschritt jetzt ein neueres broker-seitiges Schreiblimit, sodass selbst ein sauberer Start beim ersten Sendeversuch gescheitert wäre.
Nichts davon war Drift in unserer Kopie. Jedes einzelne war treu und korrekt aus einer Quelle reproduziert, die selbst nicht mehr gültig war für das Ziel. Der Diff tat exakt seinen Job und sagte uns nichts, was wir hören mussten.
Warum jeder grüne Check grün war
Das ist der Teil, den man verinnerlichen sollte, denn die Checks waren nicht nachlässig. Sie beantworteten ihre eigenen Fragen korrekt.
terraform validate prüft, ob deine Konfiguration und dein Plan wohlgeformt sind. Es instanziiert nicht die Komponente, die die Config beschreibt, es lädt nicht das gepinnte Image, und es hat keine Ahnung, welche Felder das Binary dieses Images akzeptieren wird. YAML-Validierung prüft Struktur: ist das eine Map, ist jenes eine Liste, sind die Anführungszeichen ausgeglichen. Sie kann nicht wissen, dass topic in dieser Version eine Liste sein muss, wenn es in der letzten ein String war, denn “welche Version” steht nicht im YAML. Die Reviews prüften, dass die kodifizierte Config der Produktion entsprach, was sie tat, makellos.
Und der tiefste Grund liegt eine Schicht darunter. Diese Config wird vom Collector-Binary, beim Start gelesen, nicht von einem Admission-Webhook und nicht von irgendeinem Control-Plane-Check. Eine Config, die für dieses Binary ungültig ist, wird also trotzdem sauber angewendet. Die Ressource wird erstellt, das Objekt wird akzeptiert, alles meldet Erfolg, und dann liest der Prozess eine Sekunde später seine eigene Config und stirbt. Der Fehler lebt jenseits jedes Gates, das eine Chance hatte, ihn zu stoppen, weil keines dieser Gates jemals die tatsächlich gepinnte Komponente startet.
Byte-identisch, gültiges YAML, sauberer Plan, freigegebenes Review. Vier wahre Aussagen, von denen keine “das wird starten” lautet.
Der einzige Check, der etwas bedeutet
Die Lösung war kein besserer Linter. Sie war, das echte Ding laufen zu lassen: das tatsächlich gepinnte Binary beschaffen, die richtige Distribution (ein anderer Build desselben Collectors bringt einen anderen Satz an Komponenten mit, der falsche gibt dir also ein selbstsicheres, falsches Bestehen), es auf die echte Config mit der echten Umgebung zeigen und starten lassen. Das ist der erste Check der ganzen Kette, der die von der Config genannten Komponenten instanziiert und den Vertrag durchsetzt, den die gepinnte Version tatsächlich definiert. Er fängt das entfernte Feld, die Receiver-Listen-Änderung und die Fehler der Config-Sprache, die validate strukturell nicht fangen kann, weil validate nie eine Komponente baut.
So wurde “das gepinnte Binary mit dieser Config starten” ein verpflichtender Schritt vor jedem Apply, und er gilt für mehr als die erstmalige Kodifizierung. Jedes Mal, wenn du das Image anhebst, hast du den Vertrag verschoben und die Config eingefroren. Jedes Mal, wenn du eine Spec-Datei von einem Ort an einen anderen kopierst, hast du eine Config über eine Versionsgrenze getragen, die dir vielleicht nicht aufgefallen ist. Beides ist derselbe Aufbau wie der ursprüngliche Bug.
Es gibt eine kleinere Variante davon, die als Warnung statt als Absturz auftaucht. Wenn das neuere Binary loggt, dass ein Feld oder Komponentenname zugunsten seiner aktuellen Form veraltet ist, dann ist das kein Rauschen zum Wegscrollen. Es ist derselbe Fehler, ein Release zu früh, der sich ankündigt, solange er noch gefahrlos zu beheben ist. Benenne es um, während das Ding im Leerlauf ist, und ein Neustart kostet nichts; lass es liegen, und das Release, das den Alias schließlich entfernt, verwandelt eine Config, die niemand angefasst hat, in eine, die nicht mehr startet.
Was man wirklich mitnehmen sollte
Pinne eine Version und kopiere eine Config, und du hast leise zwei Dinge erschaffen, die übereinstimmen müssen und getrennt verifiziert wurden: eine Kopie, die ihrer Quelle treu ist, und eine Quelle, die für ihr Ziel vielleicht nicht mehr gültig ist. Ein Byte-für-Byte-Diff, ein bestandenes validate, ein grünes Linting und ein sauberes Review können allesamt das Erste bestätigen, während jedes einzelne davon für das Zweite blind bleibt.
Verifiziere gegen das, was du gepinnt hast, nicht gegen das, wovon du kopiert hast. Das überzeugendste grüne Licht in der Pipeline war eine ehrliche Antwort auf eine Frage, die niemand im Raum tatsächlich stellte.