Falling Down the Rabbit Hole: Egy lakk újraindítási hibájának története – 1. rész

ghostinushanka, miután az előző 20 percben úgy nyomogatta a gombokat, mintha az élete múlna rajta, félig vad arckifejezéssel a szemében és ravasz vigyorral fordul felém - "Haver, azt hiszem, értem."

„Nézz ide” – mondja, és az egyik karakterre mutat a képernyőn –, lefogadom, hogy ha hozzáadjuk ide azt, amit az imént küldtem neked – mutat egy másik kódrészletre –, a hiba többé nem fog megjelenni."

Kicsit tanácstalanul és fáradtan megváltoztatom a sed utasítást, amin már egy ideje dolgozunk, mentem a fájlt, és futok. systemctl varnish reload. A hibaüzenet eltűnt...

– Azok az e-mailek, amelyeket a jelölttel váltottam – folytatta kollégám, miközben mosolya őszinte, örömteli mosolymá alakul át –, hirtelen eszembe jutott, hogy ez pontosan ugyanaz a probléma!

Hogyan kezdődött az egész

A cikk feltételezi a bash, awk, sed és systemd működésének megértését. A lakkozás ismerete előny, de nem kötelező.
A kivonatok időbélyegei módosultak.
-val írva ghostinushanka.
Ez a szöveg a két hete angol nyelven megjelent eredeti fordítása; fordítás boyikoden.

A panorámaablakokon besüt a nap egy újabb meleg őszi reggelen, egy csésze frissen főzött koffeines ital pihen a klaviatúra oldalán, a fülhallgatóban a hangok kedvenc szimfóniája szólal meg a mechanikus billentyűzetek susogása mellett, és az első bejegyzés a az elmaradt jegyek listája a kanban táblán játékosan világít a sorsdöntő címen: „Investigate varnishreload sh: echo: I/O error in stageing” (Investigate „varnishreload sh: echo: I/O error” in staging). Ha a lakkozásról van szó, akkor nincs és nem is lehet hiba, még akkor sem, ha ezek nem eredményeznek problémát, mint ebben az esetben.

Azoknak, akik nem ismerik lakk újratöltés, ez egy egyszerű shell-szkript, amelyet a konfiguráció újratöltésére használnak lakk - más néven VCL.

Ahogy a jegy címe is sugallja, a hiba a színpad egyik szerverén lépett fel, és mivel biztos voltam benne, hogy a lakk továbbítása a színpadon megfelelően működik, feltételeztem, hogy ez kisebb hiba lesz. Tehát csak egy üzenet, amely egy már lezárt kimeneti adatfolyamba került. Veszek egy jegyet magamnak, abban a teljes bizalomban, hogy kevesebb, mint 30 percen belül készre jelölöm, megveregetem a vállam, hogy megtisztítom a táblát a következő szeméttől, és visszatérek a fontosabb dolgokhoz.

200 km/órás sebességgel falnak ütközött

Fájl megnyitása varnishreload, az egyik Debian Stretchet futtató szerveren láttam egy 200 sornál rövidebb shell szkriptet.

Miután végigfutottam a szkriptet, nem vettem észre semmi olyat, ami problémát okozhatna, ha többször futtassa közvetlenül a terminálról.

Végül is ez egy színpad, még ha el is törik, senki nem fog panaszkodni, hát... nem túl sokat. Lefuttatom a szkriptet, és megnézem, mi lesz kiírva a terminálra, de a hibák már nem látszanak.

Még néhány futtatás, hogy megbizonyosodjon arról, hogy nem tudom reprodukálni a hibát némi extra erőfeszítés nélkül, és elkezdem kitalálni, hogyan változtassam meg ezt a szkriptet, és állítsam be, hogy továbbra is hibát okozzon.

A szkript blokkolhatja-e az STDOUT (a > &-)? Vagy STDERR? Végül egyik sem működött.

Nyilvánvaló, hogy a systemd valamilyen módon megváltoztatja a futtatási környezetet, de hogyan és miért?
Bekapcsolom a vimet és szerkesztem varnishreload, hozzátéve set -x közvetlenül a shebang alatt, remélve, hogy a forgatókönyv kimenetének hibakeresése némi fényt derít.

A reszelő fix, így újratöltöm a lakkot és látom, hogy a váltás teljesen összetört mindent... A kipufogó teljes káosz, rengeteg C-szerű kód van benne. Még a terminálban való görgetés sem elég ahhoz, hogy megtaláljuk, hol kezdődik. teljesen össze vagyok zavarodva. Befolyásolhatja-e a hibakeresési mód a szkriptben futó programok működését? Nem baromság. Hiba a héjban? Több lehetséges forgatókönyv repül a fejemben, mint a csótányok különböző irányokba. Egy csésze koffein teli ital azonnal kiürül, egy gyors kirándulás a konyhába utánpótlásért és… gyerünk. Megnyitom a forgatókönyvet, és közelebbről megnézem a shebanget: #!/bin/sh.

/bin/sh - ez csak egy bash symlink, tehát a script POSIX-kompatibilis módban értelmeződik, nem? Nem volt ott! A Debian alapértelmezett shellje a dash, ami pontosan az utal /bin/sh.

# ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Jan 24  2017 /bin/sh -> dash

A próba kedvéért megváltoztattam a shebangot #!/bin/bash, eltávolítva set -x és újra próbálkozott. Végül a lakk későbbi újratöltésekor egy tolerálható hiba jelent meg a kimenetben:

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

124. sor, itt van!

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 }

De mint kiderült, a 124-es sor meglehetősen üres és érdektelen. Csak azt tudtam feltételezni, hogy a hiba a 116-os vonalról induló többsor részeként történt.
Amit végül a változóba írunk VCL_FILE a fenti alhéj végrehajtásának eredményeként?

Az elején elküldi a változó tartalmát VLC_SHOW, a 115. sorban létrehozott, a következő parancshoz a csövön keresztül. És akkor mi történik ott?

Először is használ varnishadm, amely a lakktelepítő csomag része, a lakk újraindítás nélküli konfigurálásához.

alparancs vcl.show -v pontban meghatározott teljes VCL konfiguráció kimenetére szolgál ${VCL_NAME}, STDOUT-ra.

A jelenleg aktív VCL-konfiguráció, valamint a még a memóriában lévő lakk útválasztási konfigurációinak több korábbi verziójának megjelenítéséhez használhatja a parancsot. varnishadm vcl.list, amelynek kimenete hasonló lesz a következőhöz:

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

Változó érték ${VCL_NAME} a forgatókönyv másik részében varnishreload az aktuálisan aktív VCL nevére, ha van ilyen. Ebben az esetben a „reload_20190101_120000_12397” lesz.

Oké, változó. ${VCL_SHOW} tartalmazza a lakk teljes konfigurációját, eddig egyértelmű. Most végre értem, hogy miért dash kimenet set -x annyira töröttnek bizonyult - benne volt a kapott konfiguráció tartalma.

Fontos megérteni, hogy egy teljes VCL-konfiguráció gyakran több fájlból is összerakható. A C-stílusú megjegyzések annak meghatározására szolgálnak, hogy az egyik konfigurációs fájl hol szerepel a másikban, és a következő kódrészlet pontosan erről szól.
A mellékelt fájlokat leíró megjegyzések szintaxisa a következő formátumú:

// VCL.SHOW <NUM> <NUM> <FILENAME>

A számok ebben az összefüggésben nem fontosak, minket a fájlnév érdekel.

Tehát mi történik a 116-os vonalon kezdődő parancsok mocsarában?
Nézzünk szembe a tényekkel.
A parancs négy részből áll:

  1. egyszerű echo, amely a változó értékét jeleníti meg ${VCL_SHOW}
    echo "$VCL_SHOW"
  2. awk, amely egy sort (rekordot) keres, ahol az első mező a szöveg felosztása után a „//”, a második pedig a „VCL.SHOW” lesz.
    Az Awk kiírja az első sort, amely megfelel ezeknek a mintáknak, majd azonnal leállítja a feldolgozást.

    awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}'
  3. Egy kódblokk, amely a mezőértékeket öt változóban tárolja, szóközzel elválasztva. Az ötödik FILE változó fogadja a sor többi részét. Végül az utolsó visszhang kiírja a változó tartalmát ${FILE}.
    { read -r DELIM VCL_SHOW INDEX SIZE FILE; echo "$FILE" }
  4. Mivel az 1–3. lépések mindegyike egy alhéjba van zárva, az érték kimenete $FILE változóba lesz írva VCL_FILE.

Ahogy a 119. sor megjegyzése is sugallja, ez az egyetlen célt szolgálja, hogy megbízhatóan kezelje azokat az eseteket, amikor a VCL szóköz karaktereket tartalmazó fájlokra hivatkozik a nevében.

Megjegyeztem az eredeti feldolgozási logikát ${VCL_FILE} és megpróbálta megváltoztatni a parancsok sorrendjét, de nem vezetett semmire. Nálam minden tisztán működött, a szerviz indítása esetén hibát adott.

Úgy tűnik, hogy a hiba egyszerűen nem reprodukálható a szkript kézi futtatásakor, miközben a becsült 30 perc már hatszor véget ért, ráadásul egy magasabb prioritású feladat is megjelent, ami félretolta a többi esetet. A hét hátralévő része változatos feladatokkal telt, és csak kissé felhígult a sed-en folytatott beszélgetés és a jelölttel készült interjú. Hiba probléma be varnishreload helyrehozhatatlanul elveszett az idő homokjában.

Az úgynevezett sed-fu... valójában... szemét

A következő héten volt egy elég szabad nap, ezért úgy döntöttem, hogy újra megveszem ezt a jegyet. Reméltem, hogy az agyamban valami háttérfolyamat mindvégig erre a problémára keresi a megoldást, és ezúttal biztosan megértem, mi a baj.

Mivel a múltkor a kód megváltoztatása nem segített, ezért úgy döntöttem, hogy átírom a 116. sortól kezdve. Mindenesetre a meglévő kód hülyeség volt. És egyáltalán nincs szükség a használatára read.

Még egyszer megnézve a hibát:
sh: echo: broken pipe - ebben a parancsban két helyen van visszhang, de gyanítom, hogy az első a valószínűbb tettes (vagy legalábbis cinkos). Az Awk sem kelt magabiztosságot. És ha tényleg így van awk | {read; echo} a tervezés mindezen problémákhoz vezet, miért ne cserélné ki? Ez az egysoros parancs nem használja az awk összes funkcióját, és még ezt az extrát sem read mellékletben.

Múlt hét óta jelentés készült sedKi akartam próbálni az újonnan megszerzett készségeimet és egyszerűsíteni echo | awk | { read; echo} érthetőbbé echo | sed. Bár határozottan nem ez a legjobb módszer a hiba elkapására, úgy gondoltam, legalább kipróbálom a sed-fu-t, és talán tanulok valami újat a problémáról. Útközben megkértem kollégámat, a sed talk íróját, hogy segítsen egy hatékonyabb sed forgatókönyv kidolgozásában.

Eldobtam a tartalmat varnishadm vcl.show -v "$VCL_NAME" egy fájlba, hogy a sed szkript megírására koncentrálhassak anélkül, hogy a szolgáltatás újraindításával járó gondok kellenek.

Itt található egy rövid leírás arról, hogy a sed pontosan hogyan kezeli a bevitelt a GNU kézikönyvét. A sed forrásokban a szimbólum n kifejezetten sorelválasztóként van megadva.

Több menetben és kollégám tanácsára írtunk egy sed scriptet, ami ugyanazt az eredményt adta, mint a teljes eredeti 116-os sor.

Az alábbiakban egy mintafájl látható a bemeneti adatokkal:

> 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

Lehet, hogy a fenti leírásból nem egyértelmű, de minket csak az első komment érdekel // VCL.SHOW, és több is lehet belőlük a bemeneti adatokban. Ez az oka annak, hogy az eredeti awk az első meccs után véget ér.

# шаг первый, вывести только строки с комментариями
# используя возможности 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

Tehát a varnishreload script tartalma valahogy így nézne ki:

VCL_FILE="$(echo "$VCL_SHOW" | sed -En '#// VCL.SHOW#{s#.*[0-9]+ [0-9]+ (.*)$#1#p;q;};')"

A fenti logika a következőképpen foglalható össze:
Ha a karakterlánc megfelel a reguláris kifejezésnek // VCL.SHOW, majd mohón felfalja azt a szöveget, amely mindkét számot tartalmazza abban a sorban, és mentse el, ami a művelet után megmarad. Adja ki a tárolt értéket, és fejezze be a programot.

Egyszerű, nem?

Elégedettek voltunk a sed szkripttel és azzal a ténnyel, hogy az összes eredeti kódot lecseréli. Minden tesztem a kívánt eredményt adta, ezért megváltoztattam a "lakk újratöltését" a szerveren, és újra futottam systemctl reload varnish. Mocskos hiba echo: write error: Broken pipe - nevetett ismét az arcunkba. Egy kacsintó kurzor új parancsot várt a terminál sötét ürességében...

Forrás: will.com

Hozzászólás