ghostinushanka , der die letzten 20 minuten auf die knöpfe gedrückt hatte, als hänge sein leben davon ab, dreht sich mit einem halbwilden ausdruck in den augen und einem schlauen grinsen zu mir um - "alter, ich glaube ich verstehe."
"Schau mal her", sagt er und zeigt auf eines der Symbole auf dem Bildschirm. "Ich wette auf meinen roten Hut, dass, wenn wir hier hinzufügen, was ich dir gerade geschickt habe", der Fehler nicht mehr besteht wird angezeigt. "
Ein wenig verwirrt und müde ändere ich den sed-Ausdruck, an dem wir seit einiger Zeit gearbeitet haben, speichere die Datei und systemctl varnish reload
. Die Fehlermeldung ist verschwunden ...
"Die Mails, die ich mit dem Kandidaten ausgetauscht habe", fuhr mein Kollege fort, während sich sein Grinsen in ein echtes Lächeln voller Freude verwandelte. "Mir wurde plötzlich klar, dass dies genau dasselbe Problem ist!"
Wie hat alles angefangen?
In diesem Artikel wird davon ausgegangen, dass Sie verstehen, wie bash, awk, sed und systemd funktionieren. Lackkenntnisse sind erwünscht, aber nicht erforderlich.
Snippet-Zeitstempel geändert.
Mit ghostinushanka geschrieben .
Dieser Text ist eine Übersetzung des vor zwei Wochen in englischer Sprache veröffentlichten Originals. Boikoden Übersetzung.
An einem anderen warmen Herbstmorgen scheint die Sonne durch die Panoramafenster, die Tasse des frisch zubereiteten koffeinhaltigen Getränks bleibt von der Tastatur entfernt, die Lieblingssymphonie der Klänge im Kopfhörer überlagert das Rascheln der mechanischen Tastaturen und der schicksalhafte Titel „Investigate lackre“ strahlt spielerisch den ersten Eintrag in der Backlog-Ticketliste auf der Kanbantafel. sh: echo: E / A-Fehler beim Staging “(Untersuchen Sie den„ varnishreload sh: echo: E / A-Fehler “in der Phase). Wenn es um Lack geht, gibt es keine Fehler und es kann keinen Platz geben, auch wenn sie nicht zu Problemen führen, wie in diesem Fall.
Für diejenigen, die mit varnishreload nicht vertraut sind , ist dies ein einfaches Shell-Skript, mit dem eine Lackkonfiguration neu geladen wird - auch VCL genannt.
Wie der Name des Tickets andeutet, ist auf einem der Server auf der Bühne ein Fehler aufgetreten, und da ich sicher war, dass das Lackrouting auf der Bühne ordnungsgemäß funktioniert, habe ich angenommen, dass dies ein geringfügiger Fehler ist. Also nur eine Nachricht, die in einen bereits geschlossenen Ausgabestream gelangt ist. Ich nehme das Ticket für mich und vertraue darauf, dass ich es in weniger als 30 Minuten als fertig markieren werde. Ich klopfe mir auf die Schulter, um das Board vom nächsten Müll zu säubern und zu wichtigeren Angelegenheiten zurückzukehren.
Mit 200 km / h gegen eine Wand prallen
Nachdem varnishreload
Datei varnishreload
auf einem der Server mit Debian Stretch geöffnet hatte, sah ich ein Shell-Skript mit einer Länge von weniger als 200 Zeilen.
Nachdem ich das Skript durchgearbeitet hatte, bemerkte ich nichts, was zu Problemen führen könnte, wenn es mehrmals direkt vom Terminal aus ausgeführt wurde.
Am Ende ist dies eine Bühne, auch wenn sie kaputt geht, wird sich niemand beschweren, na ja ... nicht zu viel. Ich führe das Skript aus und sehe, was auf das Terminal geschrieben wird. Es werden jedoch keine Fehler angezeigt.
Ein paar weitere Schritte stellen sicher, dass ich den Fehler nicht ohne zusätzlichen Aufwand reproduzieren kann, und ich beginne herauszufinden, wie dieses Skript geändert werden kann und ob es trotzdem einen Fehler gibt.
Kann ein Skript STDOUT überschreiben (mit > &-
)? Oder STDERR? Keiner von beiden arbeitete infolgedessen.
Offensichtlich verändert systemd irgendwie die Startumgebung, aber wie und warum?
Ich varnishreload
vim und bearbeite varnishreload
, varnishreload
set -x
direkt unter dem varnishreload
hinzu und varnishreload
, dass die Debug-Skript-Ausgabe ein wenig Licht ins Dunkel bringt.
Die Datei ist repariert, also starte ich den Lack neu und stelle fest, dass die Änderung alles kaputt gemacht hat ... Der Auspuff ist ein komplettes Durcheinander, in dem es Tonnen von C-ähnlichem Code gibt. Selbst das Scrollen im Terminal reicht nicht aus, um herauszufinden, wo es beginnt. Ich bin völlig verwirrt. Kann sich der Debug-Modus auf die Arbeit von Programmen auswirken, die in einem Skript gestartet wurden? Nein, Quatsch. Ein Fehler in der Shell? Mehrere mögliche Szenarien rasen durch meinen Kopf wie Kakerlaken in verschiedene Richtungen. Eine Tasse koffeinreiches Getränk war sofort leer, ein kurzer Weg in die Küche, um die Vorräte aufzufüllen, und ... los geht's. Ich öffne das Skript und schaue mir den Shebang an: #!/bin/sh
.
/bin/sh
ist einfach bash symlink, das Skript wird also im POSIX-kompatiblen Modus interpretiert, oder? Da war es! Die Standard-Shell in Debian ist dash, und genau darauf bezieht sich /bin/sh
.
Aus Versuchsgründen habe ich den shebang in #!/bin/bash
geändert, set -x
gelöscht und es erneut versucht. Schließlich trat beim anschließenden Neustart von lack ein tolerierbarer Fehler in der Ausgabe auf:
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, da ist es!
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 herausstellte, ist die Zeile 124 ziemlich leer und nicht von Interesse. Ich konnte nur annehmen, dass der Fehler als Teil einer mehrzeiligen Ausgabe in Zeile 116 auftrat.
Was wird schließlich in die Variable VCL_FILE
als Ergebnis der Ausführung der oben genannten Unterschale geschrieben?
Zu Beginn sendet es den Inhalt der VLC_SHOW
Variablen, die in Zeile 115 erstellt wurde, über die Pipe an den nächsten Befehl. Und was passiert dann?
Zunächst wird varnishadm
, das Teil des varnishadm
ist, um den Lack ohne Neustart zu konfigurieren.
Mit dem vcl.show -v
die gesamte in ${VCL_NAME}
angegebene VCL-Konfiguration an ${VCL_NAME}
ausgegeben.
varnishadm vcl.list
können Sie die derzeit aktive VCL-Konfiguration sowie mehrere frühere Versionen von Lack-Routing-Konfigurationen varnishadm vcl.list
, die sich noch im Speicher varnishadm vcl.list
. Die Ausgabe ähnelt der folgenden:
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
Der Wert der Variablen ${VCL_NAME}
in einem anderen Teil des varnishreload
Skripts auf den Namen der derzeit aktiven VCL gesetzt, sofern vorhanden. In diesem Fall ist dies "reload_20190101_120000_12397".
Großartig, die Variable ${VCL_SHOW}
enthält die vollständige Konfiguration für den Lack, soweit dies klar ist. Jetzt habe ich endlich verstanden, warum die Dash-Ausgabe mit set -x
so kaputt war - sie enthielt den Inhalt der resultierenden Konfiguration.
Es ist wichtig zu verstehen, dass eine vollständige VCL-Konfiguration häufig aus mehreren Dateien zusammengesetzt werden kann. Kommentare im C-Stil werden verwendet, um zu bestimmen, wo einige Konfigurationsdateien in anderen enthalten waren. Genau darum geht es in dem folgenden Code-Snippet in der gesamten Zeile.
Die Syntax der Kommentare, die die enthaltenen Dateien beschreiben, hat das folgende Format:
Die Zahlen sind in diesem Zusammenhang nicht wichtig, wir interessieren uns für den Dateinamen.
Also, was ist los im Sumpf der Mannschaften, die auf der Linie 116 starten?
Lass es uns herausfinden.
Das Team besteht aus vier Teilen:
- Ein einfaches
echo
, das den Wert der Variablen ${VCL_SHOW}
echo "$VCL_SHOW"
awk
sucht nach einer Zeile (Datensatz), in der das erste Feld nach dem Aufbrechen des Texts "//" und das zweite "VCL.SHOW" lautet.
Awk schreibt die erste Zeile, die mit diesen Mustern übereinstimmt, und stoppt dann sofort die Verarbeitung.
awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}'
- Ein Codeblock, der in fünf durch Leerzeichen getrennten variablen Feldwerten gespeichert wird. Die fünfte Variable FILE ruft den Rest der Zeichenfolge ab. Schließlich schreibt das letzte Echo den Inhalt der Variablen
${FILE}
.
{ read -r DELIM VCL_SHOW INDEX SIZE FILE; echo "$FILE" }
- Da alle Schritte 1 bis 3 in einer Unterschale liegen, wird die Ausgabe des Wertes
$FILE
in die Variable VCL_FILE
.
Wie aus dem Kommentar in Zeile 119 hervorgeht, dient dies einem einzigen Zweck: Fälle zuverlässig zu behandeln, in denen die VCL auf Dateien mit Leerzeichen im Namen verweist.
Ich habe die ursprüngliche Verarbeitungslogik für ${VCL_FILE}
und versucht, die ${VCL_FILE}
zu ändern, aber dies hat zu nichts geführt. Bei mir hat alles reibungslos geklappt, und beim Starten des Dienstes ist ein Fehler aufgetreten.
Es scheint, dass der Fehler einfach nicht reproduzierbar ist, wenn Sie das Skript manuell ausführen, während die erwarteten 30 Minuten bereits sechsmal abgelaufen sind und im Anhang eine Aufgabe mit höherer Priorität aufgetreten ist, die den Rest der Fälle beiseite schiebt. Der Rest der Woche war mit einer Vielzahl von Aufgaben gefüllt und wurde nur geringfügig mit einem Bericht über sed und einem Interview mit dem Kandidaten verwässert. Das Problem mit dem varnishreload
ging im Sand der Zeit unwiederbringlich verloren.
Ihr sogenannter Sed-Fu ... wirklich ... Müll
Die nächste Woche war ein ziemlich freier Tag, also entschied ich mich erneut für dieses Ticket. Ich hatte gehofft, dass in meinem Gehirn ein Hintergrundprozess die ganze Zeit nach einer Lösung für dieses Problem suchte, und dieses Mal verstehe ich mit Sicherheit, was es ist.
Da das letzte Mal eine einfache Codeänderung nicht geholfen hat, habe ich mich entschlossen, sie ab der 116. Zeile neu zu schreiben. In jedem Fall war der vorhandene Code mies. Und es ist absolut nicht nötig, read
.
Schauen Sie sich den Fehler noch einmal an:
sh: echo: broken pipe
- in diesem Befehl ist echo an zwei Stellen, aber ich vermute, dass der erste der wahrscheinlichere Schuldige ist (na ja, oder zumindest ein Komplize). Awk ist auch nicht glaubwürdig. Und falls es wirklich awk | {read; echo}
awk | {read; echo}
awk | {read; echo}
konstruktion führt zu all diesen problemen, warum nicht ersetzen? Dieser einzeilige Befehl verwendet nicht alle Funktionen von awk, und selbst dieses Extra wird im Anhang read
.
Da es letzte Woche einen Bericht über sed
, wollte ich meine neu erworbenen Fähigkeiten ausprobieren und echo | awk | { read; echo}
vereinfachen echo | awk | { read; echo}
in ein verständlicheres echo | sed
echo | sed
. Obwohl dies definitiv nicht der beste Ansatz ist, um einen Fehler zu erkennen, dachte ich, ich würde zumindest mein sed-fu ausprobieren und vielleicht etwas Neues über das Problem erfahren. Dabei bat ich meinen Kollegen, den Autor des sed-Berichts, mir zu helfen, ein effektiveres sed-Skript zu entwickeln.
Ich habe den Inhalt von varnishadm vcl.show -v "$VCL_NAME"
in der Datei abgelegt, damit ich mich darauf konzentrieren kann, ein sed-Skript zu schreiben, ohne den Dienst neu zu varnishadm vcl.show -v "$VCL_NAME"
.
Eine kurze Beschreibung, wie sed mit Eingaben umgeht, finden Sie im GNU-Handbuch . In sed-Quellen wird das Zeichen \n
explizit als Zeilentrennzeichen angegeben.
In mehreren Durchgängen und mit den Empfehlungen meines Kollegen haben wir ein sed-Skript geschrieben, das das gleiche Ergebnis wie die gesamte Quelltextzeile 116 lieferte.
Das Folgende ist eine Beispiel-Eingabedatei:
> 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
Dies ist aus der obigen Beschreibung möglicherweise nicht ersichtlich, aber wir interessieren uns nur für den ersten Kommentar // VCL.SHOW
, und möglicherweise sind mehrere davon in der Eingabe enthalten. Deshalb beendet die Original-Awk ihre Arbeit nach dem ersten Match.
Der Inhalt des varnishreload-Skripts sieht also ungefähr so aus:
VCL_FILE="$(echo "$VCL_SHOW" | sed -En '\#// VCL.SHOW#{s#.*[0-9]+ [0-9]+ (.*)$#\1#p;q;};')"
Die obige Logik kann wie folgt zusammengefasst werden:
Wenn die Zeile mit dem regulären Ausdruck // VCL.SHOW
, fressen Sie gierig den Text auf, der beide Zahlen in dieser Zeile enthält, und speichern Sie alles, was nach dieser Operation übrig bleibt. Geben Sie den gespeicherten Wert ein und beenden Sie das Programm.
Einfach, richtig?
Wir waren zufrieden mit dem sed-Skript und der Tatsache, dass es den gesamten Originalcode ersetzt. Alle meine Tests ergaben die gewünschten Ergebnisse, so dass ich den "varnishreload" auf dem Server änderte und systemctl reload varnish
erneut systemctl reload varnish
. Das dreckige echo: write error: Broken pipe
lachte wieder in unseren Gesichtern. Ein blinkender Cursor wartete darauf, dass in der dunklen Leere des Terminals ein neuer Befehl eingegeben wurde ...