Pad u zečju rupu: Priča o neuspjehu ponovnog punjenja laka - 1. dio

ghostinushanka, koji je prethodnih 20 minuta lupao po gumbima kao da mu život ovisi o tome, okreće se prema meni s poludivljim pogledom u očima i lukavim smiješkom - “Stari, mislim da sam shvatio.”

“Pogledajte ovdje”, kaže, pokazujući na jedan od simbola na ekranu, “kladim se u svoj crveni šešir da ako ovdje dodamo ono što sam vam upravo poslao,” pokazujući na drugi dio koda, “pogreška više neće biti bit će prikazan."

Pomalo zbunjen i umoran, modificiram sed izraz na kojem smo radili neko vrijeme, spremim datoteku i pokrenem systemctl varnish reload. Poruka o pogrešci je nestala...

“E-mailovi koje sam razmijenio s kandidatom,” nastavio je moj kolega, dok je njegov smiješak prerastao u iskren osmijeh radosti, “Odjednom mi je sinulo da je to potpuno isti problem!”

Kako je sve počelo

Članak pretpostavlja razumijevanje načina rada bash, awk, sed i systemd. Poželjno je poznavanje lakiranja, ali nije obavezno.
Vremenske oznake u isječcima su promijenjene.
Napisano sa ghostinushanka.
Ovaj tekst je prijevod originala objavljenog na engleskom prije dva tjedna; prijevod boikoden.

Sunce sija kroz panoramske prozore još jednog toplog jesenskog jutra, šalica svježe pripremljenog napitka bogatog kofeinom odmara se podalje od tipkovnice, omiljena simfonija zvukova zvuči u vašim slušalicama, prigušujući šuštanje mehaničkih tipkovnica, a prvi unos na popisu zaostalih tiketa na Kanban ploči zaigrano svijetli sudbonosnim naslovom “Istražite varnishreload” sh: echo: I/O error in staging” (Istražite “varnishreload sh: echo: I/O error” in staging). Kod lakiranja nema i ne može biti mjesta greškama, čak i ako one ne stvaraju probleme kao u ovom slučaju.

Za one koji nisu upoznati sa varnishreload, ovo je jednostavna skripta ljuske koja se koristi za ponovno učitavanje konfiguracije lak - također se naziva VCL.

Kao što naslov ulaznice sugerira, greška se dogodila na jednom od servera na pozornici, a kako sam bio siguran da rutiranje laka na pozornici radi kako treba, pretpostavio sam da se radi o minornoj grešci. Dakle, samo poruka koja je završila u već zatvorenom izlaznom toku. Uzimam kartu za sebe, u punom uvjerenju da ću je označiti spremnom za manje od 30 minuta, potapšam se po leđima što sam očistio ploču od još jednog smeća i vratim se važnijim stvarima.

Zabijanje u zid brzinom od 200 km/h

Otvaranje datoteke varnishreload, na jednom od poslužitelja koji pokreće Debian Stretch, vidio sam shell skriptu manju od 200 redaka.

Pregledavajući skriptu, nisam primijetio ništa što bi moglo rezultirati problemima pri pokretanju više puta izravno s terminala.

Ipak je ovo pozornica, ako i pukne, nitko se neće buniti, pa... ne previše. Pokrenem skriptu i vidim što će biti upisano na terminal, ali greške se više ne vide.

Još nekoliko pokretanja da se uvjerim da ne mogu reproducirati pogrešku bez ikakvih dodatnih napora i počinjem smišljati kako promijeniti ovu skriptu i učiniti da i dalje izbacuje pogrešku.

Može li skripta nadjačati STDOUT (pomoću > &-)? Ili STDERR? Ni jedno ni drugo na kraju nije uspjelo.

Navodno systemd nekako mijenja okruženje pokretanja, ali kako i zašto?
Otvorim vim i uredim varnishreload, dodajući set -x točno ispod shebanga, nadajući se da će ispravljanje grešaka skripte baciti malo svjetla.

Datoteka je ispravljena, pa ponovno učitavam lak i vidim da je promjena skroz sve pokvarila... Auspuh je potpuna zbrka, u kojoj ima tone C-like koda. Čak ni listanje po terminalu nije dovoljno da biste pronašli gdje počinje. potpuno sam zbunjena. Može li način otklanjanja pogrešaka utjecati na rad programa pokrenutih u skripti? Ne, to su besmislice. Buba u ljusci? Nekoliko mogućih scenarija juri mi glavom poput žohara u različitim smjerovima. Šalica napitka s kofeinom se trenutno isprazni, brzi odlazak u kuhinju da obnovimo zalihe i... idemo. Otvaram skriptu i pobliže gledam shebang: #!/bin/sh.

/bin/sh - ovo je samo simbolička veza za bash, tako da se skripta tumači u POSIX-kompatibilnom načinu, zar ne? Ne tako! Zadana ljuska na Debianu je dash, i točno tako izgleda. upućuje /bin/sh.

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

Kao test, promijenio sam shebang u #!/bin/bash, izbrisano set -x i pokušao ponovno. Konačno, nakon naknadnog ponovnog pokretanja lakiranja, u izlazu se pojavila prihvatljiva pogreška:

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

Linija 124, evo 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 }

Ali kako se pokazalo, linija 124 je prilično prazna i ne predstavlja interes. Mogao sam samo pretpostaviti da se pogreška dogodila kao dio višerednog niza koji počinje u retku 116.
Što se na kraju upisuje u varijablu? VCL_FILE kao rezultat izvršavanja gornje pod-ljuske?

Na početku šalje sadržaj varijable VLC_SHOW, stvoren na liniji 115, slijedeći naredbu kroz cijev. I što se onda tamo događa?

Prvo, tamo se koristi varnishadm, koji je dio instalacijskog paketa lakiranja, za postavljanje lakiranja bez ponovnog pokretanja.

Podtim vcl.show -v koristi se za izlaz cijele VCL konfiguracije navedene u ${VCL_NAME}, na STDOUT.

Za prikaz trenutne aktivne VCL konfiguracije, kao i nekoliko prethodnih verzija konfiguracija usmjeravanja laka koje su još u memoriji, možete koristiti naredbu varnishadm vcl.list, čiji će izlaz biti sličan donjem:

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

Varijabilna vrijednost ${VCL_NAME} instaliran je u drugom dijelu skripte varnishreload na naziv trenutno aktivnog VCL-a, ako postoji. U ovom slučaju to će biti "reload_20190101_120000_12397".

Super, promjenjivo ${VCL_SHOW} sadrži kompletnu konfiguraciju za lak, za sada jasno. Sada konačno razumijem zašto je ispis crtice set -x pokazalo se tako pokvarenim - uključivalo je sadržaj rezultirajuće konfiguracije.

Važno je razumjeti da se kompletna VCL konfiguracija često može sastaviti iz nekoliko datoteka. Komentari u stilu C koriste se za prepoznavanje gdje su određene konfiguracijske datoteke uključene u druge, a to je ono o čemu se radi u sljedećem retku isječka koda.
Sintaksa za komentare koji opisuju uključene datoteke je u sljedećem formatu:

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

Brojevi u ovom kontekstu nisu bitni, zanima nas naziv datoteke.

Što se na kraju događa u močvari naredbi koje počinju na liniji 116?
Idemo vidjeti.
Tim se sastoji od četiri dijela:

  1. jednostavan echo, koji ispisuje vrijednost varijable ${VCL_SHOW}
    echo "$VCL_SHOW"
  2. awk, koji traži liniju (zapis) gdje je prvo polje, nakon razbijanja teksta, “//”, a drugo je “VCL.SHOW”.
    Awk će ispisati prvi redak koji odgovara ovim uzorcima i zatim odmah zaustaviti obradu.

    awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}'
  3. Blok koda koji pohranjuje vrijednosti polja u pet varijabli, odvojenih razmacima. Peta varijabla FILE prima ostatak retka. Na kraju, posljednji echo ispisuje sadržaj varijable ${FILE}.
    { read -r DELIM VCL_SHOW INDEX SIZE FILE; echo "$FILE" }
  4. Budući da su svi koraci od 1 do 3 zatvoreni u podljusku, ispis vrijednosti $FILE bit će zapisano u varijablu VCL_FILE.

Kao što sugerira komentar na retku 119, ovo služi jedinoj svrsi pouzdanog rukovanja slučajevima u kojima će VCL referencirati datoteke s razmacima u njihovim nazivima.

Komentirao sam izvornu logiku obrade za ${VCL_FILE} i pokušao promijeniti redoslijed naredbi, ali to nije dovelo ni do čega. Kod mene je sve radilo u redu, ali kada sam pokrenuo servis, javila se greška.

Čini se da se pogreška jednostavno ne može reproducirati prilikom ručnog pokretanja skripte, dok je navodnih 30 minuta već šest puta isteklo, a uz to se pojavio zadatak višeg prioriteta, gurajući druge stvari u stranu. Ostatak tjedna bio je ispunjen raznim zadacima, a tek malo razvodnjen reportažom o sed-u i razgovorom s kandidatom. Problem s greškom u varnishreload bio nepovratno izgubljen u pijesku vremena.

Tvoj takozvani sed-fu... zapravo je... smeće

Sljedeći tjedan sam imao jedan prilično slobodan dan, pa sam se odlučio ponovno uhvatiti u koštac s ovom kartom. Nadao sam se da je u mom mozgu neki pozadinski proces sve ovo vrijeme tražio rješenje za ovaj problem, a ovaj put ću definitivno shvatiti što se događa.

Budući da jednostavno mijenjanje koda nije pomoglo prošli put, samo sam ga odlučio prepisati počevši od retka 116. U svakom slučaju, postojeći kod je bio glup. I nema apsolutno nikakve potrebe da ga koristite read.

Ponovno gledanje pogreške:
sh: echo: broken pipe — echo se pojavljuje na dva mjesta u ovoj naredbi, ali pretpostavljam da je prvo vjerojatniji krivac (ili barem suučesnik). Ni Awk ne ulijeva povjerenje. A u slučaju da doista jest awk | {read; echo} dizajn dovodi do svih ovih problema, zašto ga ne zamijeniti? Ova jednolinijska naredba ne koristi sve mogućnosti awk-a, pa čak ni ovu dodatnu read u Dodatku.

Od prošlog tjedna bilo je izvješće o sed, htio sam isprobati svoje novostečene vještine i pojednostaviti echo | awk | { read; echo} u razumljiviji echo | sed. Iako ovo definitivno nije najbolji pristup identificiranju buga, mislio sam barem isprobati svoj sed-fu i možda naučiti nešto novo o problemu. Usput sam zamolio kolegu, autora govora o sed-u, da mi pomogne smisliti učinkovitiju skriptu za sed.

Ispustio sam sadržaj varnishadm vcl.show -v "$VCL_NAME" u datoteku, kako bih se mogao usredotočiti na pisanje sed skripte bez gnjavaže ponovnog pokretanja usluge.

Kratak opis kako točno sed obrađuje unos može se pronaći u njegov GNU priručnik. U izvorima sed simbol n eksplicitno naveden kao razdjelnik retka.

U nekoliko prolaza i uz preporuke mog kolege, napisali smo sed skriptu koja je dala isti rezultat kao cijela originalna linija 116.

Ispod je primjer datoteke s ulaznim podacima:

> 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

Ovo možda nije očito iz gornjeg opisa, ali nas zanima samo prvi komentar // VCL.SHOW, a može ih biti više u ulaznim podacima. Zbog toga izvorni awk završava nakon prvog podudaranja.

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

Dakle, sadržaj skripte varnishreload izgledat će otprilike ovako:

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

Gornja logika može se ukratko izraziti na sljedeći način:
Ako niz odgovara regularnom izrazu // VCL.SHOW, zatim pohlepno proždirite tekst koji uključuje oba broja u ovom retku i spremite sve što ostane nakon ove operacije. Emitira pohranjenu vrijednost i završava program.

Jednostavno, zar ne?

Bili smo zadovoljni sed skriptom i činjenicom da je zamijenila sav izvorni kod. Svi moji testovi dali su željene rezultate, pa sam promijenio "varnishreload" na poslužitelju i ponovno ga pokrenuo systemctl reload varnish. Gadna pogreška echo: write error: Broken pipe opet nam se smijao u lice. Kursor koji namiguje čekao je unos nove naredbe u mračnoj praznini terminala...

Izvor: www.habr.com

Dodajte komentar