Padanje niz zečju rupu: Priča o jednom neuspjehu ponovnog punjenja laka - 1. dio

ghostinushanka, nakon što je prethodnih 20 minuta lupkao po dugmadima kao da mu život zavisi od toga, okreće se prema meni sa poludivljim pogledom u očima i lukavim osmehom - "Čovječe, mislim da sam shvatio."

“Pogledajte ovdje”, kaže on, 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, “greška više neće biti će biti prikazan."

Pomalo zbunjen i umoran, modifikujem sed izraz na kojem smo radili neko vreme, spremam fajl i pokrećem systemctl varnish reload. Poruka o grešci je nestala...

„Imejlovi koje sam razmenio sa kandidatom“, nastavio je moj kolega, dok je njegov osmeh prerastao u istinski osmeh radosti, „Odjednom mi je sinulo da je ovo potpuno isti problem!“

Kako je sve počelo

Članak pretpostavlja razumijevanje kako bash, awk, sed i systemd rade. Poznavanje lakova je poželjno, ali nije obavezno.
Vremenske oznake u isječcima su promijenjene.
Written with ghostinushanka.
Ovaj tekst je prijevod originala objavljenog na engleskom prije dvije sedmice; prevod boikoden.

Sunce sija kroz panoramske prozore u još jedno toplo jesenje jutro, šolja sveže pripremljenog pića bogatog kofeinom odmara se od tastature, omiljena simfonija zvukova zvuči u vašim slušalicama, prigušujući šuštanje mehaničkih tastatura, i prvi ulazak na listi zaostalih tiketa na Kanban tabli zaigrano svijetli sudbonosnim naslovom “Investigate lakishreload” sh: echo: I/O error in staging” (Investigate “varnishreload sh: echo: I/O error” u stagingu). Kada je lak u pitanju, nema i ne može biti mjesta za greške, čak i ako ne rezultiraju problemima kao u ovom slučaju.

Za one koji nisu upoznati sa lakiranje, ovo je jednostavna shell skripta koja se koristi za ponovno učitavanje konfiguracije lak - takođe se zove VCL.

Kao što naziv tiketa sugeriše, greška se dogodila na jednom od servera na bini, a pošto sam bio siguran da rutiranje laka na bini radi kako treba, pretpostavio sam da bi to bila manja greška. Dakle, samo poruka koja je završila u već zatvorenom izlaznom toku. Uzimam kartu za sebe, u punom povjerenju da ću je označiti spremnom za manje od 30 minuta, tapšam se po ramenu što sam tablu očistio od još jednog smeća i vraćam se važnijim stvarima.

Udaranje u zid pri 200 km/h

Otvaranje datoteke varnishreload, na jednom od servera na kojem je pokrenut Debian Stretch, vidio sam shell skriptu manju od 200 redaka.

Nakon što sam prošao kroz skriptu, nisam primijetio ništa što bi moglo dovesti do problema kada se više puta pokreće direktno s terminala.

Na kraju krajeva, ovo je pozornica, ako i pukne, niko se neće buniti, pa... ne previše. Pokrećem skriptu i vidim šta će biti napisano na terminalu, ali greške se više ne vide.

Još nekoliko pokreta da se uvjerim da ne mogu reproducirati grešku bez ikakvog dodatnog napora, i počinjem shvaćati kako promijeniti ovu skriptu i učiniti da i dalje ispušta grešku.

Može li skripta nadjačati STDOUT (koristeći > &-)? Ili STDERR? Nijedno od ovoga na kraju nije uspjelo.

Očigledno systemd na neki način modificira okruženje za pokretanje, ali kako i zašto?
Otvaram vim i uređujem varnishreload, dodajući set -x odmah ispod shebanga, nadajući se da će izlaz za otklanjanje grešaka skripte baciti malo svjetla.

Fajl je ispravljen, pa ponovo učitavam lak i vidim da je izmjena skroz sve pokvarila... Auspuh je potpuna zbrka, u kojoj ima na tone koda nalik C. Čak ni pomicanje po terminalu nije dovoljno da se pronađe gdje počinje. Potpuno sam zbunjen. Može li način otklanjanja grešaka utjecati na rad programa pokrenutih u skripti? Ne, to je glupost. Bug u ljusci? Nekoliko mogućih scenarija juri mi kroz glavu poput žohara u različitim smjerovima. Šolja s kofeinskim pićem se trenutno prazni, brzi odlazak u kuhinju da dopunimo zalihe i... krećemo. Otvaram skriptu i pobliže gledam shebang: #!/bin/sh.

/bin/sh - ovo je samo simbolična veza za bash, tako da se skripta tumači u POSIX-kompatibilnom modu, zar ne? Nije tako! Zadana ljuska na Debianu je dash, i upravo tako izgleda. se odnosi /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, obrisano set -x i pokušao ponovo. Konačno, nakon naknadnog ponovnog pokretanja lakiranja, u izlazu se pojavila podnošljiva greš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

Red 124, evo ga!

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 ispostavilo, red 124 je poprilično prazan i ne zanima ga. Mogao sam samo pretpostaviti da se greška dogodila kao dio višelinijskog niza koji počinje na liniji 116.
Šta je na kraju zapisano u varijablu? VCL_FILE kao rezultat izvršavanja gornje podljuske?

Na početku šalje sadržaj varijable VLC_SHOW, kreiran na liniji 115, slijedeći naredbu kroz cijevi. I šta se onda tamo dešava?

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

Pod-tim vcl.show -v koristi se za izlaz cijele VCL konfiguracije specificirane 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 onome ispod:

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} je instaliran u drugom dijelu skripte varnishreload na ime trenutno aktivnog VCL-a, ako postoji. U ovom slučaju to će biti “reload_20190101_120000_12397”.

Odlično, varijabilno ${VCL_SHOW} sadrži kompletnu konfiguraciju za lakiranje, za sada čisto. Sada konačno razumijem zašto je crtica izlaz set -x ispostavilo se da je tako pokvaren - uključivao je sadržaj rezultirajuće konfiguracije.

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

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

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

Šta se na kraju dešava u močvari komandi koja počinje na liniji 116?
Hajde da shvatimo.
Tim se sastoji iz četiri dela:

  1. Jednostavno echo, koji ispisuje vrijednost varijable ${VCL_SHOW}
    echo "$VCL_SHOW"
  2. awk, koji traži red (zapis) gdje je prvo polje, nakon prekida teksta, “//”, a drugo je “VCL.SHOW”.
    Awk će ispisati prvi red koji odgovara ovim obrascima, a zatim će odmah prekinuti 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 reda. Konačno, posljednji eho ispisuje sadržaj varijable ${FILE}.
    { read -r DELIM VCL_SHOW INDEX SIZE FILE; echo "$FILE" }
  4. Budući da su svi koraci 1 do 3 zatvoreni u podljusku, izlaz vrijednosti $FILE će biti upisan u varijablu VCL_FILE.

Kao što komentar na liniji 119 sugeriše, ovo služi jedinoj svrsi pouzdanog rukovanja slučajevima u kojima će VCL referencirati datoteke sa razmacima u njihovim imenima.

Prokomentirao sam originalnu logiku obrade za ${VCL_FILE} i pokušao promijeniti redoslijed naredbi, ali to nije dovelo ni do čega. Kod mene je sve funkcionisalo dobro, ali kada sam pokrenuo servis dao je grešku.

Čini se da se greška jednostavno ne može ponoviti prilikom ručnog pokretanja skripte, dok je navodnih 30 minuta već isteklo šest puta, a osim toga pojavio se zadatak višeg prioriteta koji gura druge stvari u stranu. Ostatak sedmice bio je ispunjen raznim zadacima i samo je malo razvodnjen izvještajem o sed-u i intervjuom sa kandidatom. Problem sa greškom u varnishreload bio nepovratno izgubljen u pesku vremena.

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

Sljedeće sedmice imao sam jedan prilično slobodan dan, pa sam odlučio da se ponovo pozabavim ovom kartom. Nadao sam se da je u mom mozgu neki pozadinski proces sve ovo vrijeme tražio rješenje za ovaj problem, i ovaj put ću definitivno razumjeti što se događa.

Pošto jednostavna promjena koda nije pomogla prošli put, odlučio sam da ga prepišem počevši od reda 116. U svakom slučaju, postojeći kod je bio glup. I apsolutno nema potrebe da ga koristite read.

Ponovo pogledam grešku:
sh: echo: broken pipe — eho se pojavljuje na dva mjesta u ovoj komandi, ali pretpostavljam da je prvo vjerovatniji krivac (ili barem saučesnik). Awk takođe ne uliva poverenje. I u slučaju da zaista jeste awk | {read; echo} dizajn dovodi do svih ovih problema, zašto ga ne zamijeniti? Ova komanda u jednom redu ne koristi sve karakteristike awk-a, pa čak ni ovu dodatnu read pored toga.

Od prošle sedmice je bio izvještaj 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 greške, mislio sam da ću barem isprobati svoj sed-fu i možda naučiti nešto novo o problemu. Usput sam zamolio svog kolegu, autora sed razgovora, da mi pomogne da smislim efikasniji sed skript.

Ispustio sam sadržaj varnishadm vcl.show -v "$VCL_NAME" u datoteku, tako da sam mogao da se fokusiram na pisanje sed skripte bez ikakvih problema sa ponovnim pokretanjem servisa.

Kratak opis tačno kako sed obrađuje unos može se naći u njegov GNU priručnik. U sed izvorima simbol n eksplicitno specificirano kao separator redaka.

U nekoliko prolaza i uz preporuke mog kolege, napisali smo sed skriptu koja je dala isti rezultat kao i cijeli originalni red 116.

Ispod je primjer datoteke sa 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čigledno iz gornjeg opisa, ali nas zanima samo prvi komentar // VCL.SHOW, a može ih biti nekoliko u ulaznim podacima. Zbog toga se originalni awk završava nakon prvog meča.

# шаг первый, вывести только строки с комментариями
# используя возможности 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 za učitavanje laka izgledat će otprilike ovako:

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

Gornja logika se može ukratko izraziti na sljedeći način:
Ako string odgovara regularnom izrazu // VCL.SHOW, zatim pohlepno proždire tekst koji uključuje oba broja u ovom redu i sačuvaj sve što ostane nakon ove operacije. Emitirajte pohranjenu vrijednost i završite program.

Jednostavno, zar ne?

Bili smo zadovoljni sed skriptom i činjenicom da je zamijenila sav originalni kod. Svi moji testovi su dali željene rezultate, pa sam promijenio “lakiranje” na serveru i ponovo ga pokrenuo systemctl reload varnish. Loša greška echo: write error: Broken pipe ponovo nam se smejao u lice. Kursor koji namiguje čekao je da se unese nova naredba u mračnoj praznini terminala...

izvor: www.habr.com

Dodajte komentar