Nachdem er in den letzten 20 Minuten die Tastatur gedrückt hat, als würde er für sein Leben tippen, dreht sich Ghostinushanka mit einem halb verrückten Blick in den Augen und einem schlauen Lächeln zu mir. „Alter, ich glaube, ich habe es verstanden.
Sehen Sie sich das an “- als er auf eines der Zeichen auf dem Bildschirm zeigt -„ Ich wette, mein roter Hut, wenn wir hinzufügen, was ich Ihnen gerade hierher geschickt habe “- als er auf eine andere Stelle im Code zeigt -„ wird es geben kein Fehler mehr. "
Etwas verwirrt und müde systemctl varnish reload
ich den Sed-Ausdruck, den wir seit einiger Zeit herausfinden, speichere die Datei und systemctl varnish reload
das systemctl varnish reload
. Fehlermeldung weg ...
"Diese E-Mails, die ich mit dem Kandidaten ausgetauscht habe", fährt mein Kollege fort, während sich sein Lächeln in ein breites und echtes Grinsen verwandelt. "Mir ist plötzlich aufgefallen, dass dies genau dasselbe Problem ist!"
Wie alles begann
Dieser Artikel setzt voraus, dass Sie mit bash, awk und systemd vertraut sind. Einige Lackkenntnisse sind von Vorteil, aber nicht erforderlich.
Zeitstempel in Beispiel-Snippets wurden überarbeitet.
Zusammen mit ghostinushanka verfasst.
An einem weiteren warmen Herbstmorgen scheint die Sonne durch die wandgroßen Fenster, eine Tasse frisch gebrühte koffeinhaltige Flüssigkeit steht neben der Tastatur, Kopfhörer sprechen die geliebte Symphonie von Klängen, die das Rascheln mechanischer Tastaturen überdecken, und der erste Eintrag im Rückstand auf der Kanbantafel wird spielerisch der Titel des schicksalhaften Tickets angezeigt sh: echo: I/O error
„Untersuche das Laden von Daten sh: echo: I/O error
bei der Bereitstellung“. Immer wenn es um Lack geht, gibt es keinen Platz für Fehler, auch wenn dieser keine wirklichen Probleme zu verursachen schien.
Für diejenigen unter Ihnen, die mit varnishreload nicht vertraut sind , ist es einfach ein Shell-Skript, das zum erneuten Laden der Konfiguration - auch VCL genannt - des Varnish-Caching-Servers verwendet wird .
Wie der Titel des Tickets andeutet, ist der Fehler auf einem der Staging-Computer aufgetreten, und ich war mir ziemlich sicher, dass das Varnish-Routing in der Staging-Umgebung funktioniert. Daher war meine Annahme, dass dies ein geringfügiges Problem sein muss. Nur eine benutzerfreundliche Ausgabenachricht, die in einen geschlossenen Stream geschrieben wird. Ich greife nach dem Ticket und bin fest davon überzeugt, dass ich es in weniger als 30 Minuten als erledigt markieren kann. Ich klopfe mir auf den Rücken, um eine weitere weltliche Aufgabe zu erledigen und mich wieder wichtigeren Dingen zuzuwenden.
Mit 200 km / h gegen die Wand
Beim Öffnen der Datei varnishreload
auf einem der betroffenen Server unter Debian Stretch varnishreload
ich fest, dass ein Shell-Skript weniger als 200 Zeilen lang ist. Wenn ich es kurz durchlese, sehe ich nichts Gefährliches, das mich daran hindern würde, das Skript immer wieder vom Terminal aus auszuführen. Immerhin ist dies eine Inszenierung, auch wenn sie kaputt geht, niemand wird sich beschweren, na ja ... nicht zu viel. Ich starte das Skript und beobachte, nur um herauszufinden, dass keine Fehler zu sehen sind. Ein paar weitere Durchläufe stellen sicher, dass ich den Fehler nicht ohne zusätzlichen Aufwand reproduzieren kann, und ich beginne Pläne zu entwickeln, um die Umgebung des Skripts zu optimieren und zu verbessern. Hilft es überhaupt, STDOUT für das Skript zu schließen (mit > &-
)? Oder stderr? Weder noch.
Offensichtlich beeinträchtigt systemd die Umwelt auf irgendeine Weise, aber wie und warum? Ich varnishreload
vim und bearbeite das varnishreload
des Systems, varnishreload
set -x
direkt unter dem varnishreload
hinzu und hoffe, dass die detaillierte Skriptausgabe etwas Licht ins Dunkel bringt.
Da die Datei gepatcht ist, lade ich den Firnis neu, nur um zu sehen, dass die Änderung das Skript vollständig zerstört hat ... Die Ausgabe ist ein komplettes Durcheinander, in dem jede Menge C-Code angezeigt wird und der standardmäßige Scrollback-Puffer nicht ausreicht, um herauszufinden, woher er kommt. Ich fühle mich verwirrt. Könnte das Festlegen der Debug-Option für das Shell-Skript das aufgerufene Programm beschädigen? Nein, kann nicht sein. Ein Fehler in der Shell? In meinem Kopf laufen mehrere mögliche Szenarien wild in verschiedene Richtungen. Eine Tasse koffeinhaltiges Getränk ist sofort fertig, schnelle Fahrt in die Küche zum Nachfüllen und schon geht es wieder los. Ich öffne die Datei und schaue mir den shebang genau an: #!/bin/sh
.
Aber /bin/sh
ist sicherlich nur ein Symlink zu bash, so dass das Skript im POSIX-kompatiblen Modus interpretiert wird, oder? Falsch! Die nicht interaktive Standard-Shell unter Debian ist dash, und genau darauf zeigt /bin/sh
.
Wenn auch nur zum Debuggen, habe ich den shebang in #!/bin/bash
geändert, die set -x
und es erneut versucht. Zum Schluss noch ein vernünftiger Fehler beim nächsten Nachladen des Lacks:
Jan 01 12:00:00 hostname varnishreload[32604]: /usr/sbin/varnishreload: line 124: echo: write error: Broken pipe Jan 01 12:00:00 hostname varnishreload[32604]: VCL 'reload_20190101_120000_32604' compiled
Linie 124, jetzt reden wir!
114 find_vcl_file() { 115 VCL_SHOW=$(varnishadm vcl.show -v "$VCL_NAME" 2>&1) || : 116 VCL_FILE=$( 117 echo "$VCL_SHOW" | 118 awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}' | { 119
Aber wie sich herausstellt, ist die Linie 124 ziemlich ereignislos. Ich konnte nur vermuten, dass der Fehler als Teil des in Zeile 116 ausgeführten mehrzeiligen Befehls erzeugt wurde.
Was erzeugt die obige Subshell überhaupt, um sie in der Variablen VCL_FILE zu speichern? Im ersten Teil sendet es den Inhalt der VCL_SHOW
Variablen, die in der Zeile 115 erstellt wurde, in die Pipe. Was passiert dann dort?
Zunächst wird varnishadm
, ein Standardbestandteil einer Varnish-Installation, mit der Varnish konfiguriert wird, ohne dass ein Neustart erforderlich ist. Mit dem Unterbefehl vcl.show -v
wird die gesamte durch ${VCL_NAME}
angegebene VCL-Konfiguration an ${VCL_NAME}
ausgegeben.
Um die aktuell aktive VCL-Konfiguration sowie mehrere frühere Versionen des varnishadm vcl.list
, die sich noch im Speicher befinden, können Sie einen anderen Befehl varnishadm vcl.list
, dessen Ausgabe der folgenden ähnelt:
discarded cold/busy 1 reload_20190101_120000_11903 discarded cold/busy 2 reload_20190101_120000_12068 discarded cold/busy 16 reload_20190101_120000_12259 discarded cold/busy 16 reload_20190101_120000_12299 discarded cold/busy 28 reload_20190101_120000_12357 active auto/warm 32 reload_20190101_120000_12397 available auto/warm 0 reload_20190101_120000_12587
Die Variable ${VCL_NAME}
wird an anderer Stelle im varnishreload
Skript auf den Namen der derzeit aktiven VCL gesetzt, sofern vorhanden. In diesem Fall wäre das "reload_20190101_120000_12397".
Toll, also enthält ${VCL_SHOW}
jetzt eine vollständige Konfiguration für Varnish, die bisher ${VCL_SHOW}
einfach war. Jetzt habe ich endlich verstanden, warum die Dash-Ausgabe mit set -x
so kaputt zu sein schien - sie enthielt den Inhalt der resultierenden Lackkonfiguration.
Wichtig hierbei ist, dass die vollständige VCL-Konfiguration häufig aus mehreren Dateien zusammengefügt wird. Mit Kommentaren im C-Stil wird festgelegt, wo Konfigurationsdateien in andere Konfigurationsdateien eingefügt wurden. Genau darum geht es in der nächsten Zeile des Code-Snippets.
Die Syntax der dateibezogenen Kommentare hat das folgende Format
Die Zahlen sind hier nicht wichtig, was uns interessiert, ist der Dateiname.
Was in aller Welt passiert also in der Menge der Befehle, die in Zeile 116 beginnen?
Lass es uns auseinander nehmen.
Der Befehl besteht aus vier Teilen:
- Ein einfaches
echo
, das den Wert von ${VCL_SHOW}
echo "$VCL_SHOW"
awk
sucht nach einer Zeile (Datensatz), in der das erste Feld '//' und das zweite "VCL.SHOW" ist.
Awk wird angewiesen, die erste Zeile zu drucken, die mit diesen Mustern übereinstimmt, und die Verarbeitung dann sofort zu beenden.
awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}'
- Ein Codeblock, der die durch Leerzeichen getrennten Felder in fünf Variablen einliest. Die fünfte Variable FILE erhält den Rest der Zeile. Schließlich gibt ein letztes Echo den Inhalt der Variablen
${FILE}
.
{ read -r DELIM VCL_SHOW INDEX SIZE FILE; echo "$FILE" }.
- Da die Schritte 1 bis 3 alle in eine Subshell eingeschlossen sind, wird die Ausgabe von
$FILE
in der Variablen VCL_FILE
.
Wie aus dem Kommentar in Zeile 119 hervorgeht, dient diese Vorgehensweise einem einzigen Zweck: dem zuverlässigen Behandeln des Falls, in dem VCL auf Dateinamen mit Leerzeichen verweist.
Ich habe die ursprüngliche Verarbeitungslogik für ${VCL_FILE}
und versucht, die Befehlskette zu optimieren, aber ohne vernünftiges Ende. Alles hat in meiner Shell funktioniert, aber nie als Dienst ausgeführt.
Es scheint, dass der Fehler bei meiner Ausführung überhaupt nicht reproduzierbar ist - mittlerweile sind die geschätzten 30 Minuten sechs Mal vergangen, und eine neue Aufgabe mit hoher Priorität hat alles beiseite gelegt. Der Rest der Woche war ziemlich voll mit verschiedenen Aufgaben, die beiden Ausnahmen waren ein internes Gespräch unseres Teams über die Verwendung von sed
und ein Interview mit einem vielversprechenden Kandidaten. Das Problem, dass der varnishreload
Fehler verschwunden ist, ist für die ganze Zeit verloren gegangen.
Dein sogenanntes Sed-Fu ... ist wirklich ... ziemlich erbärmlich
Einer der folgenden Wochentage war ziemlich frei, also nahm ich die Aufgabe wieder auf. Ich hatte gehofft, dass vielleicht immer noch ein Hintergrundprozess in meinem Gehirn das Problem in den Griff bekommt und ich es endlich knacken kann.
Da das letzte Mal das Verbiegen des Codes nicht geholfen hat, habe ich mich für eine Neufassung von Zeile 116 entschieden. Der vorhandene Code war sowieso verrückt. Es ist absolut nicht nötig, hier zu read
.
Schauen Sie sich den Fehler noch einmal an:
sh: echo: broken pipe
- Echo ist an zwei Stellen in diesem Befehl, aber ich vermute, dass der erste ein wahrscheinlicherer Täter (oder Komplize) ist. Awk schafft auch kein Vertrauen. Nun, falls es sich wirklich um die awk | {read; echo}
awk | {read; echo}
awk | {read; echo}
construct verursacht all diese Probleme, warum nicht etwas anderes verwenden? Awk ist auf diesem Einzeiler nicht wirklich voll ausgelastet, und dann ist da noch dieser Überschuss.
Da wir in der sed
Woche ein internes Gespräch über sed
, wollte ich meine neu erworbenen Fähigkeiten ausprobieren und das echo | awk | { read; echo }
optimieren echo | awk | { read; echo }
in ein einfacheres echo | sed
echo | sed
. Obwohl dies definitiv nicht der richtige Weg ist, um das Debuggen in Angriff zu nehmen, dachte ich, ich würde zumindest mein Sed-Fu ausprobieren und vielleicht etwas Neues über das Problem in diesem Prozess erfahren. Dabei habe ich meinen Kollegen - den Autor des Sed-Talks - gebeten, mir zu helfen, einen effizienteren Sed-Befehl zu entwickeln.
Ich habe die Datei varnishadm vcl.show -v "$VCL_NAME"
in eine Datei geschrieben, damit ich mich auf das Schreiben von sed konzentrieren kann, ohne den ganzen Aufwand für das Neuladen von Diensten.
Eine kurze Einführung, wie genau sed Prozesse eingegeben werden, findet sich in seinem GNU-Handbuch . In sed sources wird das Zeichen \n
explizit als Zeilentrennzeichen angegeben.
Nach mehreren Iterationen und Eingaben meines Kollegen haben wir einen sed-Ausdruck erstellt, der genau das gleiche Ergebnis wie die ursprüngliche Zeile 116 liefert.
Lassen Sie uns hier eine Beispiel-Eingabedatei erstellen,
> cat vcl-example.vcl Text // VCL.SHOW 0 1578 file with 3 spaces.vcl More text // VCL.SHOW 0 1578 file.vcl Even more text // VCL.SHOW 0 1578 file with TWOspaces.vcl Final text
Es ist aus der obigen Beschreibung möglicherweise nicht ersichtlich, aber wir interessieren uns nur für den ersten // VCL.SHOW
Kommentar, und bei der Eingabe sind möglicherweise mehrere vorhanden. Genau deshalb gibt awk nach dem ersten Match auf.
Der Inhalt des varnishreload-Skripts würde also ungefähr so aussehen:
VCL_FILE="$(echo "$VCL_SHOW" | sed -En '\#// VCL.SHOW#{s#.*[0-9]+ [0-9]+ (.*)$#\1#p;q;};')"
Die obige Logik kann prägnant ausgedrückt werden durch:
Wenn eine Zeile mit dem // VCL.SHOW
, // VCL.SHOW
gierig mit dem Text einschließlich der beiden Zahlen in dieser Zeile // VCL.SHOW
, und erfassen Sie, was danach kommt. Capture ausgeben und beenden.
Einfach, nicht wahr?
Wir waren mit dem sed-Skript und der Tatsache, welchen Originalcode es ersetzt, zufrieden. Alle von mir durchgeführten varnishreload
ergaben die gewünschten Ergebnisse. varnishreload
habe ich den varnishreload
auf dem Server geändert und den systemctl reload varnish
erneut systemctl reload varnish
. Das gefürchtete echo: write error: Broken pipe
lächelte in unseren Gesichtern. Der blinkende Cursor erwartete eine neue Befehlseingabe in der dunklen Leere des Terminals ...