„Pozrite sa sem,“ hovorí a ukazuje na jeden zo symbolov na obrazovke, „stavím sa, že ak sem pridáme to, čo som vám práve poslal,“ ukazuje na inú časť kódu, „chyba už nebude sa zobrazí."
Trochu zmätený a unavený upravím výraz sed, na ktorom sme chvíľu pracovali, uložím súbor a spustím systemctl varnish reload
. Chybové hlásenie zmizlo...
"E-maily, ktoré som si vymenil s kandidátom," pokračoval môj kolega, keď jeho úsmev prerástol do skutočného úsmevu radosti, "Zrazu mi došlo, že je to presne ten istý problém!"
Ako to všetko začalo
Článok predpokladá pochopenie toho, ako fungujú bash, awk, sed a systemd. Znalosť laku je preferovaná, nie však nutná.
Časové pečiatky v úryvkoch boli zmenené.
Napísané s
Tento text je prekladom originálu uverejneného v angličtine pred dvoma týždňami; preklad
Slnko svieti cez panoramatické okná v ďalšie teplé jesenné ráno, šálka čerstvo pripraveného nápoja bohatého na kofeín odpočíva od klávesnice, v slúchadlách znie vaša obľúbená symfónia zvukov, ktorá prehluší šuchot mechanických klávesníc, a prvý vstup v zozname backlog tiketov na nástenke Kanban hravo žiari osudným názvom “Investigatelakreload” sh: echo: I/O error in staging” (Investigate “varnishreload sh: echo: I/O error” in staging). Pri lakoch je a nemôže byť priestor na chyby, aj keď nespôsobujú žiadne problémy ako v tomto prípade.
Pre tých, ktorí nie sú oboznámení
Ako už názov tiketu napovedá, chyba nastala na jednom zo serverov na pódiu a keďže som si bol istý, že smerovanie laku na pódiu funguje správne, predpokladal som, že pôjde o drobnú chybu. Takže len správa, ktorá skončila v už uzavretom výstupnom toku. Beriem si lístok pre seba, s plnou dôverou, že ho označím za pripravený za menej ako 30 minút, potľapkám sa po pleci, že som vyčistil tabuľu od ďalšieho odpadu a vraciam sa k dôležitejším veciam.
Náraz do steny v rýchlosti 200 km/h
Otváranie súboru varnishreload
, na jednom zo serverov so systémom Debian Stretch som videl skript shellu dlhý menej ako 200 riadkov.
Po prejdení skriptu som si nevšimol nič, čo by mohlo spôsobiť problémy pri jeho viacnásobnom spustení priamo z terminálu.
Toto je predsa etapa, aj keď sa to zlomí, nikto sa nebude sťažovať, no... nie príliš. Spustím skript a uvidím, čo sa zapíše do terminálu, ale chyby už nie sú viditeľné.
Ešte pár spustení, aby som sa uistil, že chybu nemôžem reprodukovať bez ďalšieho úsilia, a začínam zisťovať, ako zmeniť tento skript, aby stále vyvolával chybu.
Môže skript prepísať STDOUT (pomocou > &-
)? Alebo STDERR? Ani jedno z toho nakoniec nefungovalo.
Systemd zrejme nejakým spôsobom upravuje spúšťacie prostredie, ale ako a prečo?
Otvorím vim a upravím varnishreload
, pridávajúc set -x
priamo pod šebangom, dúfajúc, že ladiaci výstup skriptu vrhne trochu svetla.
Pilník je opravený, tak znovu načítam lak a vidím, že výmena úplne všetko rozbila... Výfuk je úplný neporiadok, v ktorom sú tony céčkového kódu. Ani rolovanie v termináli nestačí na to, aby ste zistili, kde začína. Som úplne zmätený. Môže režim ladenia ovplyvniť činnosť programov spúšťaných v skripte? Nie, je to nezmysel. Chyba v škrupine? Ako šváby sa mi hlavou preháňa niekoľko možných scenárov rôznymi smermi. Šálka kofeínového nápoja sa okamžite vyprázdni, rýchly výlet do kuchyne doplniť zásoby a... ideme. Otváram scenár a bližšie sa pozerám na shebang: #!/bin/sh
.
/bin/sh
- toto je len symbolický odkaz na bash, takže skript je interpretovaný v režime kompatibilnom s POSIX, však? Nie tak! Predvolený shell v Debiane je dash a presne tak to vyzerá. /bin/sh
.
# ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Jan 24 2017 /bin/sh -> dash
Ako test som zmenil shebang na #!/bin/bash
, vymazané set -x
a skúsil to znova. Nakoniec sa po následnom reštarte laku objavila vo výstupe tolerovateľná chyba:
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
Linka 124, tu je!
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 # all this ceremony to handle blanks in FILE
120 read -r DELIM VCL_SHOW INDEX SIZE FILE
121 echo "$FILE"
122 }
123 ) || :
124
125 if [ -z "$VCL_FILE" ]
126 then
127 echo "$VCL_SHOW" >&2
128 fail "failed to get the VCL file name"
129 fi
130
131 echo "$VCL_FILE"
132 }
Ako sa však ukázalo, linka 124 je celkom prázdna a bez záujmu. Mohol som len predpokladať, že chyba nastala ako súčasť viacriadkového reťazca začínajúceho na riadku 116.
Čo sa nakoniec zapíše do premennej? VCL_FILE
v dôsledku vykonania vyššie uvedeného sub-shell?
Na začiatku odošle obsah premennej VLC_SHOW
, vytvorený na riadku 115 po príkaze cez potrubie. A čo sa tam potom stane?
Po prvé, tam sa používa varnishadm
, ktorý je súčasťou inštalačného balíka laku, na nastavenie laku bez reštartovania.
Sub-tím vcl.show -v
používa sa na výstup celej konfigurácie VCL špecifikovanej v ${VCL_NAME}
, do STDOUT.
Ak chcete zobraziť aktuálnu aktívnu konfiguráciu VCL, ako aj niekoľko predchádzajúcich verzií konfigurácií smerovania laku, ktoré sú stále v pamäti, môžete použiť príkaz varnishadm vcl.list
, ktorého výstup bude podobný nižšie uvedenému:
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
Variabilná hodnota ${VCL_NAME}
je nainštalovaný v inej časti skriptu varnishreload
na názov aktuálne aktívneho VCL, ak existuje. V tomto prípade to bude „reload_20190101_120000_12397“.
Skvelé, variabilné ${VCL_SHOW}
obsahuje kompletnú konfiguráciu pre lak, zatiaľ číra. Teraz konečne chápem, prečo je výstup pomlčky set -x
sa ukázalo byť tak rozbité - zahŕňalo obsah výslednej konfigurácie.
Je dôležité pochopiť, že úplná konfigurácia VCL môže byť často zostavená z niekoľkých súborov. Komentáre v štýle C sa používajú na identifikáciu toho, kde boli určité konfiguračné súbory zahrnuté do iných, a o tom je celý nasledujúci riadok úryvku kódu.
Syntax komentárov popisujúcich zahrnuté súbory je v nasledujúcom formáte:
// VCL.SHOW <NUM> <NUM> <FILENAME>
Čísla v tomto kontexte nie sú dôležité, nás zaujíma názov súboru.
Čo sa nakoniec stane v bažine príkazov začínajúcich na riadku 116?
Priznajme si to.
Tím sa skladá zo štyroch častí:
- prostý
echo
, ktorý vypíše hodnotu premennej${VCL_SHOW}
echo "$VCL_SHOW"
awk
, ktorý hľadá riadok (záznam), kde prvé pole je po rozdelení textu „//“ a druhé je „VCL.SHOW“.
Awk vypíše prvý riadok zodpovedajúci týmto vzorom a potom okamžite zastaví spracovanie.awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}'
- Blok kódu, ktorý ukladá hodnoty polí do piatich premenných oddelených medzerami. Piata premenná FILE prijíma zvyšok riadku. Nakoniec posledné echo vypíše obsah premennej
${FILE}
.{ read -r DELIM VCL_SHOW INDEX SIZE FILE; echo "$FILE" }
- Pretože všetky kroky 1 až 3 sú uzavreté v subshell, výstup hodnoty
$FILE
sa zapíše do premennejVCL_FILE
.
Ako naznačuje komentár na riadku 119, slúži to výlučne na spoľahlivé riešenie prípadov, keď VCL bude odkazovať na súbory s medzerami v ich názvoch.
Pôvodnú logiku spracovania som okomentoval ${VCL_FILE}
a pokúsil sa zmeniť postupnosť príkazov, ale k ničomu to neviedlo. Všetko mi fungovalo dobre, ale keď som spustil službu, vyskytla sa chyba.
Zdá sa, že chyba pri manuálnom spustení skriptu jednoducho nie je reprodukovateľná, pričom predpokladaných 30 minút už šesťkrát uplynulo a navyše sa objavila úloha s vyššou prioritou, ktorá odsúva ostatné záležitosti bokom. Zvyšok týždňa bol naplnený rôznymi úlohami a bol len mierne preriedený reportážou o sed a pohovorom s kandidátom. Problém s chybou v varnishreload
bol nenávratne stratený v piesku času.
Tvoje takzvané sed-fu... je vlastne... svinstvo
Ďalší týždeň som mal jeden celkom voľný deň, tak som sa rozhodol opäť riešiť tento lístok. Dúfal som, že v mojom mozgu nejaký proces na pozadí celý ten čas hľadal riešenie tohto problému a tentoraz už určite pochopím, čo sa deje.
Keďže minule jednoduchá zmena kódu nepomohla, rozhodol som sa ho prepísať od riadku 116. V každom prípade bol existujúci kód hlúpy. A vôbec nie je potrebné ho používať read
.
Znova sa pozrite na chybu:
sh: echo: broken pipe
— ozvena sa v tomto príkaze objavuje na dvoch miestach, ale mám podozrenie, že prvé je pravdepodobnejší vinník (alebo aspoň spolupáchateľ). Awk tiež nevzbudzuje dôveru. A v prípade, že to tak naozaj je awk | {read; echo}
dizajn vedie k všetkým týmto problémom, prečo ho nenahradiť? Tento jednoriadkový príkaz nevyužíva všetky funkcie awk a dokonca ani túto navyše read
navyše.
Od minulého týždňa tu bola správa o sed
, chcel som si vyskúšať svoje novonadobudnuté zručnosti a zjednodušiť echo | awk | { read; echo}
do zrozumiteľnejšieho echo | sed
. Aj keď to rozhodne nie je najlepší prístup k identifikácii chyby, povedal som si, že aspoň vyskúšam svoje sed-fu a možno sa o probléme dozviem niečo nové. Cestou som požiadal kolegu, autora sed talku, aby mi pomohol vymyslieť efektívnejší sed scenár.
Vypustil som obsah varnishadm vcl.show -v "$VCL_NAME"
do súboru, takže som sa mohol sústrediť na písanie skriptu sed bez akýchkoľvek problémov s reštartovaním služby.
Krátky popis toho, ako presne vstup sed procesov nájdete v n
explicitne špecifikované ako oddeľovač riadkov.
V niekoľkých prechodoch a s odporúčaniami môjho kolegu sme napísali sed skript, ktorý dal rovnaký výsledok ako celý pôvodný riadok 116.
Nižšie je uvedený vzorový súbor so vstupnými údajmi:
> 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
Z vyššie uvedeného popisu to nemusí byť zrejmé, ale nás zaujíma len prvý komentár // VCL.SHOW
a vo vstupných údajoch ich môže byť niekoľko. To je dôvod, prečo pôvodný awk končí po prvom zápase.
# шаг первый, вывести только строки с комментариями
# используя возможности sed, определяется символ-разделитель с помощью конструкции '#' вместо обычно используемого '/', за счёт этого не придётся экранировать косые в искомом комментарии
# определяется регулярное выражение “// VCL.SHOW”, для поиска строк с определенным шаблоном
# флаг -n позаботится о том, чтобы sed не выводил все входные данные, как он это делает по умолчанию (см. ссылку выше)
# -E позволяет использовать расширенные регулярные выражения
> cat vcl-processor-1.sed
#// VCL.SHOW#p
> sed -En -f vcl-processor-1.sed vcl-example.vcl
// VCL.SHOW 0 1578 file with 3 spaces.vcl
// VCL.SHOW 0 1578 file.vcl
// VCL.SHOW 0 1578 file with TWOspaces.vcl
# шаг второй, вывести только имя файла
# используя команду “substitute”, с группами внутри регулярных выражений, отображается только нужная группa
# и это делается только для совпадений, ранее описанного поиска
> cat vcl-processor-2.sed
#// VCL.SHOW# {
s#.* [0-9]+ [0-9]+ (.*)$#1#
p
}
> sed -En -f vcl-processor-2.sed vcl-example.vcl
file with 3 spaces.vcl
file.vcl
file with TWOspaces.vcl
# шаг третий, получить только первый из результатов
# как и в случае с awk, добавляется немедленное завершения после печати первого найденного совпадения
> cat vcl-processor-3.sed
#// VCL.SHOW# {
s#.* [0-9]+ [0-9]+ (.*)$#1#
p
q
}
> sed -En -f vcl-processor-3.sed vcl-example.vcl
file with 3 spaces.vcl
# шаг четвертый, схлопнуть всё в однострочник, используя двоеточия для разделения команд
> sed -En -e '#// VCL.SHOW#{s#.* [0-9]+ [0-9]+ (.*)$#1#p;q;}' vcl-example.vcl
file with 3 spaces.vcl
Takže obsah skriptu lakreload bude vyzerať asi takto:
VCL_FILE="$(echo "$VCL_SHOW" | sed -En '#// VCL.SHOW#{s#.*[0-9]+ [0-9]+ (.*)$#1#p;q;};')"
Vyššie uvedená logika sa dá stručne vyjadriť takto:
Ak sa reťazec zhoduje s regulárnym výrazom // VCL.SHOW
, potom hltavo zhltnite text, ktorý obsahuje obe čísla v tomto riadku, a uložte si všetko, čo po tejto operácii zostane. Vyšlite uloženú hodnotu a ukončite program.
Jednoduché, však?
Boli sme spokojní so skriptom sed a skutočnosťou, že nahradil celý pôvodný kód. Všetky moje testy poskytli požadované výsledky, takže som zmenil „varnishreload“ na serveri a znova som ho spustil systemctl reload varnish
. Zlá chyba echo: write error: Broken pipe
opäť sa nám vysmial do tváre. Žmurkajúci kurzor čakal v tmavej prázdnote terminálu na zadanie nového príkazu...
Zdroj: hab.com