Falling Down the Rabbit Hole: The Story of One Varnish Reload Failure - Diel 1

ghostinushanka, nei't er de foargeande 20 minuten op 'e knoppen hammere hat as oft syn libben derfan ôfhinge, draait him nei my mei in semi-wylde blik yn 'e eagen en in slûchslimme gnizen - "Dude, ik tink dat ik it haw."

"Sjoch hjir," seit er, en wiist nei ien fan 'e symboalen op it skerm, "ik wedzje op myn reade hoed dat as wy hjir tafoegje wat ik jo krekt stjoerde," wiist nei in oare seksje fan koade, "de flater sil net mear wêze sil werjûn wurde."

In bytsje fernuvere en wurch, ik feroarje de sed-ekspresje wêr't wy in skoft oan wurke hawwe, bewarje it bestân en rinne systemctl varnish reload. It flaterberjocht is ferdwûn...

"De e-mails dy't ik útwiksele mei de kandidaat," gie myn kollega troch, doe't syn gnyske útgroeide ta in echte glimke fan freugde, "It kaam my ynienen troch dat dit krekt itselde probleem is!"

Hoe't it allegear begûn

It artikel giet út fan in begryp fan hoe't bash, awk, sed en systemd wurkje. Kennis fan lak is de foarkar, mar net fereaske.
Tiidstempels yn snippets binne feroare.
Skreaun mei ghostinushanka.
Dizze tekst is in oersetting fan it orizjineel dat twa wiken lyn yn it Ingelsk publisearre is; oersetting boikoden.

De sinne skynt troch de panoramyske ruten op in oare waarme hjerstmoarn, in kopke farsk tariede kafee-rike drank rêst fuort fan it toetseboerd, jo favorite symfony fan lûden klinkt yn jo koptelefoan, ferdrinkt it ritseljen fan meganyske toetseboerden, en de earste yngong yn de list fan efterstân kaartsjes op de Kanban board boartlik gloeit mei de needlottige titel "Undersykje varnishreload" sh: echo: I / O flater yn staging" (Undersykje "varnishreload sh: echo: I / O flater" yn staging). As it giet om fernis is en kin der gjin romte wêze foar flaters, sels as se gjin problemen opleverje lykas yn dit gefal.

Foar dyjingen dy't net bekend mei varnishreload, dit is in ienfâldich shell-skript dat brûkt wurdt om de konfiguraasje opnij te laden fernis - ek wol VCL neamd.

As de titel fan it kaartsje suggerearret, barde de flater op ien fan 'e tsjinners op it poadium, en om't ik wie der wis fan dat de lak routing op it poadium wurke goed, Ik oannommen dat dit soe wêze in lytse flater. Dus, gewoan in berjocht dat einige yn in al sletten útfierstream. Ik nim it kaartsje foar mysels, yn fol fertrouwen dat ik it yn minder as 30 minuten klear markearje sil, klopje mysels op 'e rêch foar it skjinmeitsjen fan it boerd fan noch in rommel en kom werom nei wichtiger saken.

Yn in muorre botst mei 200 km/h

It iepenjen fan de triem varnishreload, Op ien fan 'e tsjinners dy't Debian Stretch draaie, seach ik in shellskript fan minder as 200 rigels lang.

Nei't ik it skript trochgien hie, haw ik neat opmurken dat kin resultearje yn problemen by it útfieren fan it meardere kearen direkt fan 'e terminal.

Dit is ommers in poadium, ek al brekt it, gjinien sil kleie, no... net te folle. Ik rinne it skript en sjoch wat sil wurde skreaun oan de terminal, mar de flaters binne net mear sichtber.

In pear mear rint om te soargjen dat ik kin net reprodusearje de flater sûnder ekstra ynspannings , en ik begjin út te finen hoe't te feroarjen dit skript en meitsje it noch smyt in flater.

Kin it skript STDOUT oerskriuwe (mei > &-)? Of STDERR? Gjin fan beide wurke op it lêst.

Blykber feroaret systemd op ien of oare manier de opstartomjouwing, mar hoe, en wêrom?
Ik iepenje vim en bewurkje varnishreload, tafoegjen set -x rjochts ûnder de shebang, yn 'e hoop dat de debug-útfier fan it skript wat ljocht sil smyt.

De triem is korrizjearre, dus ik laad lak opnij en sjoch dat de feroaring alles folslein bruts ... De útlaat is in folsleine puinhoop, wêryn d'r tonnen C-like koade binne. Sels scrollen yn 'e terminal is net genôch om te finen wêr't it begjint. Ik bin hielendal yn de war. Kin debuggen modus ynfloed op de wurking fan programma's lansearre yn in skript? Nee, it is ûnsin. Bug in de shell? Ferskate mooglike senario's rinne troch myn holle as kakkerlakken yn ferskate rjochtingen. De beker fan 'e kafeïnefolle drank is daliks leech, in flugge reis nei de keuken om de foarried oan te foljen en ... we geane. Ik iepenje it skript en besjoch de shebang fan tichterby: #!/bin/sh.

/bin/sh - dit is gewoan in symlink nei bash, dus it skript wurdt ynterpretearre yn POSIX-kompatibele modus, toch? Net sa! De standert shell op Debian is dash, en dat is krekt wat it liket. ferwiist /bin/sh.

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

As test feroare ik de shebang nei #!/bin/bash, wiske set -x en nochris besocht. Uteinlik, by it folgjende opnij opstarten fan lak, ferskynde in tolerabele flater yn 'e útfier:

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

Line 124, hjir is it!

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 }

Mar sa docht bliken, line 124 is frij leech en fan gjin belang. Ik koe allinne mar oannimme dat de flater barde as ûnderdiel fan in multiline string begjinnend op rigel 116.
Wat einiget skreaun nei de fariabele? VCL_FILE as gefolch fan it útfieren fan de boppesteande sub-shell?

Oan it begjin stjoert it de ynhâld fan 'e fariabele VLC_SHOW, makke op rigel 115, nei it kommando troch de piip. En wat bart dêr dan?

Earst wurdt it dêr brûkt varnishadm, dy't diel útmakket fan it fernisynstallaasjepakket, foar it ynstellen fan lak sûnder opnij te begjinnen.

Sub-team vcl.show -v brûkt om de folsleine VCL-konfiguraasje út te fieren spesifisearre yn ${VCL_NAME}, to STDOUT.

Om de aktuele aktive VCL-konfiguraasje wer te jaan, lykas ferskate eardere ferzjes fan fernisroutingkonfiguraasjes dy't noch yn it ûnthâld binne, kinne jo it kommando brûke varnishadm vcl.list, wêrfan de útfier fergelykber is mei de hjirûnder:

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

Fariabele wearde ${VCL_NAME} is ynstallearre yn in oar diel fan it skript varnishreload oan de namme fan de op it stuit aktive VCL, as der ien is. Yn dit gefal sil it "reload_20190101_120000_12397" wêze.

Geweldich, fariabel ${VCL_SHOW} befettet folsleine konfiguraasje foar lak, dúdlik foar no. No begryp ik einliks wêrom't de dash-útfier is set -x die bliken sa brutsen te wêzen - it omfette de ynhâld fan 'e resultearjende konfiguraasje.

It is wichtich om te begripen dat in folsleine VCL-konfiguraasje faaks kin wurde gearstald út ferskate bestannen. Opmerkings yn C-styl wurde brûkt om te identifisearjen wêr't bepaalde konfiguraasjebestannen binne opnommen yn oaren, en dat is wêr't de folgjende rigel fan koadefragment oer giet.
De syntaksis foar opmerkings dy't opnommen bestannen beskriuwe is yn it folgjende formaat:

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

De nûmers binne net wichtich yn dit ferbân, wy binne ynteressearre yn de triemnamme.

Wat bart der úteinlik yn 'e sompe fan kommando's dy't begjinne op rigel 116?
Lit sjen.
It team bestiet út fjouwer dielen:

  1. Simple echo, dy't de wearde fan 'e fariabele printsje ${VCL_SHOW}
    echo "$VCL_SHOW"
  2. awk, dy't siket nei in rigel (rekord) dêr't it earste fjild, nei it brekken fan de tekst, "//", en de twadde is "VCL.SHOW".
    Awk sil de earste rigel skriuwe dy't oerienkomt mei dizze patroanen en stopje dan fuortendaliks mei ferwurkjen.

    awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}'
  3. In blok koade dat fjildwearden opslacht yn fiif fariabelen, skieden troch spaasjes. De fyfde FILE fariabele ûntfangt de rest fan 'e rigel. Uteinlik skriuwt de lêste echo de ynhâld fan 'e fariabele út ${FILE}.
    { read -r DELIM VCL_SHOW INDEX SIZE FILE; echo "$FILE" }
  4. Sûnt alle stappen 1 oant 3 binne ynsletten yn in subshell, útfiert de wearde $FILE sil skreaun wurde nei in fariabele VCL_FILE.

Lykas it kommentaar op rigel 119 suggerearret, tsjinnet dit it ienige doel fan betroubere behanneling fan gefallen wêr't VCL bestannen sil ferwize mei spaasjes yn har nammen.

Ik haw kommentearre út de oarspronklike ferwurkjen logika foar ${VCL_FILE} en besocht te feroarjen it kommando folchoarder, mar it hat net liede ta neat. Alles wurke goed foar my, mar doe't ik begon de tsjinst joech it in flater.

It liket derop dat de flater gewoan net reprodusearber is by it manuell útfieren fan it skript, wylst de sabeare 30 minuten al seis kear binne ferrûn en boppedat is in taak mei hegere prioriteit ferskynde, dy't oare saken oan 'e kant skowe. De rest fan de wike wie fol mei in ferskaat oan taken en waard mar in bytsje ferwettere troch in rapport oer sed en in fraachpetear mei in kandidaat. Probleem mei flater yn varnishreload wie ûnherstelber ferlern yn it sân fan de tiid.

Dyn saneamde sed-fu... is eins... rommel

De oare wike hie ik noch in frij dei, dat ik besleat dit kaartsje wer oan te pakken. Ik hope dat yn myn harsens, guon eftergrûn proses hie socht nei in oplossing foar dit probleem al dizze tiid, en dizze kear soe ik perfoarst begripe wat der bart.

Om't gewoan feroarjen fan de koade de lêste kear net holp, haw ik gewoan besletten om it te herskriuwen fanôf rigel 116. Yn alle gefallen wie de besteande koade dom. En it is perfoarst net nedich om it te brûken read.

Op 'e nij nei de flater sjen:
sh: echo: broken pipe - echo ferskynt op twa plakken yn dit kommando, mar ik fermoedzje dat de earste is de wierskynliker skuldige (of op syn minst in meiwurker). Awk ynspireart ek gjin fertrouwen. En yn it gefal dat it echt is awk | {read; echo} it ûntwerp liedt ta al dizze problemen, wêrom net ferfange? Dit kommando mei ien rigel brûkt net alle funksjes fan awk, en sels dizze ekstra read derneist.

Sûnt ferline wike wie der in rapport oer sed, Ik woe besykje myn nij opdien feardichheden en ferienfâldigje echo | awk | { read; echo} yn in mear begryplik echo | sed. Hoewol dit perfoarst net de bêste oanpak is om de brek te identifisearjen, tocht ik dat ik op syn minst myn sed-fu soe besykje en miskien wat nijs oer it probleem leare. Underweis frege ik myn kollega, de skriuwer fan it sedpraat, om my te helpen mei in effisjinter sedskript te kommen.

Ik liet de ynhâld falle varnishadm vcl.show -v "$VCL_NAME" nei in bestân, dus ik koe my rjochtsje op it skriuwen fan it sed-skript sûnder gedoe mei it opnij opstarten fan 'e tsjinst.

In koarte beskriuwing fan krekt hoe't sed ynfier kin wurde fûn yn syn GNU-hantlieding. Yn de sed boarnen it symboal n eksplisyt oantsjutte as in line separator.

Yn ferskate passaazjes en mei de oanbefellings fan myn kollega hawwe wy in sed-skript skreaun dat itselde resultaat joech as de hiele orizjinele rigel 116.

Hjirûnder is in foarbyldbestân mei ynfiergegevens:

> 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

Dit is miskien net dúdlik út 'e beskriuwing hjirboppe, mar wy binne allinich ynteressearre yn' e earste opmerking // VCL.SHOW, en der kin ferskate fan harren yn de ynfier gegevens. Dit is wêrom de oarspronklike awk einiget nei de earste wedstriid.

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

Dat, de ynhâld fan it varnishreload-skript sil der sa útsjen:

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

De boppesteande logika kin koart as folget wurde útdrukt:
As de tekenrige oerienkomt mei in reguliere ekspresje // VCL.SHOW, fersoargje dan de tekst dy't beide nûmers yn dizze rigel befettet, en bewarje alles dat oerbliuwt nei dizze operaasje. Emit de opsleine wearde en einigje it programma.

Ienfâldich, is it net?

Wy wiene bliid mei it sed-skript en it feit dat it alle orizjinele koade ferfong. Al myn testen joegen de winske resultaten, dus ik feroare de "varnishreload" op 'e tsjinner en rûn it wer systemctl reload varnish. Min flater echo: write error: Broken pipe lake ús wer yn it gesicht. De knipperjende rinnerke wachte op in nij kommando om yn te fieren yn 'e tsjustere leechte fan' e terminal ...

Boarne: www.habr.com

Add a comment