Kritimas per triušio skylę: vieno lako perkrovimo nesėkmės istorija – 1 dalis

ghostinushanka20 minučių mušdamas mygtukus taip, lyg nuo to priklausytų jo gyvybė, kreipiasi į mane pusiau laukiniu žvilgsniu akyse ir gudriu išsišiepimu: „Bičiuli, aš manau, kad supratau“.

„Pažiūrėk“, – sako jis, rodydamas į vieną iš simbolių ekrane, – „Lažinuosi, mano raudona kepurė, kad jei pridėsime čia tai, ką ką tik tau atsiunčiau“, rodydamas į kitą kodo skyrių, „klaida nebebus. bus rodomas“.

Šiek tiek sutrikęs ir pavargęs pakeičiau sed išraišką, prie kurios kurį laiką dirbome, išsaugosiu failą ir paleidžiu systemctl varnish reload. Klaidos pranešimas dingo...

„El. laiškai, kuriais apsikeičiau su kandidatu“, – tęsė kolega, o jo šypsena peraugo į tikrą džiaugsmo šypseną, – staiga supratau, kad tai lygiai ta pati problema!

Kaip viskas prasidėjo

Straipsnyje daroma prielaida, kad suprantama, kaip veikia bash, awk, sed ir systemd. Žinios apie laką pageidautinos, bet neprivalomos.
Fragmentų laiko žymos buvo pakeistos.
Parašyta su ghostinushanka.
Šis tekstas yra prieš dvi savaites anglų kalba išleisto originalo vertimas; vertimas boikoden.

Dar vieną šiltą rudens rytą pro panoraminius langus šviečia saulė, nuo klaviatūros ilsisi šviežiai paruošto kofeino prisotinto gėrimo puodelis, ausinėse skamba mėgstama garsų simfonija, užgniaužusi mechaninių klaviatūrų ošimą, ir pirmasis įėjimas. atsilikusių bilietų sąraše „Kanban“ lentoje žaismingai švyti lemtingu pavadinimu „Investigate lakonreload“ sh: echo: I/O error in stage“ (Instaging „varnishreload sh: echo: I/O error“ instaging). Kalbant apie laką, klaidoms vietos yra ir negali būti, net jei jos nesukelia jokių problemų, kaip šiuo atveju.

Tiems, kurie nėra susipažinę su lako perkrovimas, tai paprastas apvalkalo scenarijus, naudojamas konfigūracijai iš naujo įkelti lakas - dar vadinamas VCL.

Kaip rodo bilieto pavadinimas, klaida įvyko viename iš scenos serverių, o kadangi buvau tikras, kad lako išdėstymas scenoje veikia tinkamai, maniau, kad tai bus nedidelė klaida. Taigi, tik pranešimas, kuris atsidūrė jau uždarytame išvesties sraute. Pasiimu bilietą sau, visiškai pasitikėdamas, kad per mažiau nei 30 minučių pažymėsiu jį paruoštą, paglostysiu sau per nugarą, kad nuvaliau lentą nuo dar vienos šiukšlės ir grįšiu prie svarbesnių reikalų.

200 km/val. greičiu atsitrenkė į sieną

Failo atidarymas varnishreload, viename iš serverių, kuriuose veikia Debian Stretch, pamačiau trumpesnį nei 200 eilučių apvalkalo scenarijų.

Peržiūrėjęs scenarijų, nepastebėjau nieko, kas galėtų sukelti problemų, jei kelis kartus paleisčiau jį tiesiai iš terminalo.

Juk tai etapas, jei ir nutrūks, niekas nesiskųs, na... ne per daug. Paleidžiu scenarijų ir žiūriu, kas bus įrašyta į terminalą, bet klaidų nebesimato.

Dar pora paleidimų, kad įsitikinčiau, jog negaliu atkurti klaidos be jokių papildomų pastangų, ir pradedu sugalvoti, kaip pakeisti šį scenarijų ir priversti jį vis tiek rodyti klaidą.

Ar scenarijus gali nepaisyti STDOUT (naudojant > &-)? Arba STDERR? Nė vienas iš jų galiausiai nepasiteisino.

Matyt, sistema kažkaip pakeičia paleidimo aplinką, bet kaip ir kodėl?
Atidarau vim ir redaguoju varnishreload, pridedant set -x tiesiai po shebang, tikėdamiesi, kad scenarijaus derinimo išvestis parodys šiek tiek šviesos.

Failas ištaisytas, tai perkraunu laką ir matau, kad pakeitimas visiškai viską sulaužė... Išmetimas yra visiška netvarka, kurioje yra tonos C tipo kodo. Net slinkimo terminale neužtenka norint rasti, kur jis prasideda. Aš visiškai sutrikęs. Ar derinimo režimas gali paveikti programų, paleistų pagal scenarijų, veikimą? Ne, tai nesąmonė. Klaida kiaute? Mano galvoje tarsi tarakonai į skirtingas puses laksto keli galimi scenarijai. Gėrimo su kofeinu puodelis akimirksniu ištuštinamas, greita kelionė į virtuvę pasipildyti atsargų ir... važiuojame. Atidarau scenarijų ir atidžiau pažvelgiu į „shebang“: #!/bin/sh.

/bin/sh - tai tik nuoroda į bash, todėl scenarijus interpretuojamas su POSIX suderinamu režimu, tiesa? Ne taip! Numatytasis „Debian“ apvalkalas yra „dash“ ir būtent taip jis atrodo. nurodo /bin/sh.

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

Išbandymui pakeičiau shebangą į #!/bin/bash, ištrinta set -x ir pabandė dar kartą. Galiausiai, vėliau perkraunant laką, išvestyje pasirodė toleruotina klaida:

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 eilutė, štai!

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 }

Tačiau, kaip paaiškėjo, 124 eilutė yra gana tuščia ir neįdomi. Galėjau tik manyti, kad klaida įvyko kaip kelių eilučių eilutės, prasidedančios 116 eilutėje, dalis.
Kas galiausiai įrašoma į kintamąjį? VCL_FILE dėl to, kad buvo vykdomas aukščiau pateiktas sub-apvalkalas?

Pradžioje jis siunčia kintamojo turinį VLC_SHOW, sukurta 115 eilutėje, vykdant komandą per vamzdį. Ir kas tada ten vyksta?

Pirma, jis ten naudojamas varnishadm, kuris yra lako montavimo paketo dalis, skirtas lako nustatymui neperkraunant.

Pogrupis vcl.show -v naudojamas visai VCL konfigūracijai, nurodytai išvesti ${VCL_NAME}, į STDOUT.

Norėdami parodyti dabartinę aktyvią VCL konfigūraciją, taip pat keletą ankstesnių lako nukreipimo konfigūracijų versijų, kurios vis dar yra atmintyje, galite naudoti komandą varnishadm vcl.list, kurio išvestis bus panaši į toliau pateiktą:

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

Kintamoji vertė ${VCL_NAME} yra įdiegta kitoje scenarijaus dalyje varnishreload į šiuo metu aktyvios VCL pavadinimą, jei toks yra. Šiuo atveju tai bus „perkrauti_20190101_120000_12397“.

Puikus, kintamas ${VCL_SHOW} yra visa lako konfigūracija, kol kas aišku. Dabar aš pagaliau suprantu, kodėl yra brūkšnelių išvestis set -x pasirodė toks sugedęs – jame buvo gautos konfigūracijos turinys.

Svarbu suprasti, kad visa VCL konfigūracija dažnai gali būti sujungta iš kelių failų. C stiliaus komentarai naudojami norint nustatyti, kur tam tikri konfigūracijos failai buvo įtraukti į kitus, ir apie tai kalba toliau pateiktoje kodo fragmento eilutėje.
Komentarų, apibūdinančių įtrauktus failus, sintaksė yra tokio formato:

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

Skaičiai šiame kontekste nėra svarbūs, mus domina failo pavadinimas.

Kas galiausiai nutinka komandų liūne, pradedant nuo 116 eilutės?
Leiskite face it.
Komanda susideda iš keturių dalių:

  1. Paprasta echo, kuris išspausdina kintamojo reikšmę ${VCL_SHOW}
    echo "$VCL_SHOW"
  2. awk, kuri ieško eilutės (įrašo), kur pirmas laukas, sulaužius tekstą, yra „//“, o antrasis – „VCL.SHOW“.
    „Awk“ išrašys pirmąją eilutę, atitinkančią šiuos šablonus, ir nedelsdama sustabdys apdorojimą.

    awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}'
  3. Kodo blokas, kuriame saugomos lauko reikšmės į penkis kintamuosius, atskirtus tarpais. Penktasis FILE kintamasis gauna likusią eilutės dalį. Galiausiai paskutinis aidas išrašo kintamojo turinį ${FILE}.
    { read -r DELIM VCL_SHOW INDEX SIZE FILE; echo "$FILE" }
  4. Kadangi visi žingsniai nuo 1 iki 3 yra įtraukti į posluoksnį, išvedama reikšmė $FILE bus įrašytas į kintamąjį VCL_FILE.

Kaip rodo komentaras 119 eilutėje, tai yra vienintelis tikslas – patikimai tvarkyti atvejus, kai VCL nurodys failus, kurių pavadinimuose yra tarpų.

Aš pakomentavau pradinę apdorojimo logiką ${VCL_FILE} ir bandė pakeisti komandų seką, bet tai nieko neprivedė. Pas mane viskas veikė gerai, bet kai pradėjau servisą davė klaidą.

Panašu, kad rankiniu būdu paleidžiant scenarijų klaida tiesiog neatkuriama, o tariamas 30 minučių jau šešis kartus praėjo ir, be to, atsirado aukštesnio prioriteto užduotis, nustumianti į šalį kitus reikalus. Likusią savaitės dalį užpildė įvairios užduotys, o ją tik šiek tiek sumažino ataskaita apie sed ir pokalbis su kandidatu. Problema su klaida varnishreload buvo negrįžtamai pasiklydęs laiko smėlyje.

Jūsų vadinamasis sed-fu... iš tikrųjų... šiukšlės

Kitą savaitę turėjau vieną gana laisvą dieną, todėl nusprendžiau dar kartą imtis šio bilieto. Tikėjausi, kad mano smegenyse kažkoks foninis procesas visą tą laiką ieškojo šios problemos sprendimo, ir šį kartą tikrai suprasiu, kas vyksta.

Kadangi praeitą kartą tiesiog pakeitus kodą nepadėjo, nusprendžiau jį perrašyti nuo 116 eilutės. Bet kokiu atveju esamas kodas buvo kvailas. Ir jo naudoti visiškai nereikia read.

Dar kartą pažiūrėjus į klaidą:
sh: echo: broken pipe — aidas pasirodo dviejose šios komandos vietose, bet įtariu, kad pirmasis yra labiau tikėtinas kaltininkas (ar bent jau bendrininkas). Awk taip pat nekelia pasitikėjimo. Ir tuo atveju, jei tai tikrai yra awk | {read; echo} dizainas sukelia visas šias problemas, kodėl jo nepakeitus? Ši vienos eilutės komanda nenaudoja visų awk funkcijų ir net šios papildomos read papildomai.

Nuo praėjusios savaitės buvo pranešta apie sed, norėjau išbandyti naujai įgytus įgūdžius ir supaprastinti echo | awk | { read; echo} į labiau suprantamą echo | sed. Nors tai tikrai nėra geriausias būdas nustatyti klaidą, maniau, kad bent jau išbandysiu savo sed-fu ir galbūt sužinosiu ką nors naujo apie problemą. Pakeliui paprašiau kolegos, sed pokalbio autoriaus, kad padėtų man sugalvoti efektyvesnį sed scenarijų.

Aš numečiau turinį varnishadm vcl.show -v "$VCL_NAME" į failą, kad galėčiau sutelkti dėmesį į sed scenarijaus rašymą be jokio vargo perkraunant paslaugą.

Trumpas aprašymas, kaip tiksliai galima rasti sed procesų įvestį jo GNU vadovą. Sed šaltiniuose simbolis n aiškiai nurodytas kaip eilučių skyriklis.

Keliais praėjimais ir remdamiesi mano kolegos rekomendacijomis, parašėme sed scenarijų, kuris davė tokį patį rezultatą kaip ir visa originali 116 eilutė.

Žemiau yra pavyzdinis failas su įvesties duomenimis:

> 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

Galbūt tai nėra akivaizdu iš aukščiau pateikto aprašymo, bet mus domina tik pirmasis komentaras // VCL.SHOW, o įvesties duomenyse jų gali būti keletas. Štai kodėl originalus awk baigiasi po pirmojo mačo.

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

Taigi, lako atnaujinimo scenarijaus turinys atrodys maždaug taip:

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

Aukščiau pateiktą logiką galima trumpai išreikšti taip:
Jei eilutė atitinka reguliariąją išraišką // VCL.SHOW, tada godžiai surykite tekstą, kuriame yra abu šios eilutės skaičiai, ir išsaugokite viską, kas liko po šios operacijos. Išleiskite išsaugotą vertę ir užbaikite programą.

Paprasta, ar ne?

Buvome patenkinti sed scenarijumi ir tuo, kad jis pakeitė visą pradinį kodą. Visi mano testai davė norimus rezultatus, todėl pakeičiau „lako įkėlimą“ serveryje ir paleidau dar kartą systemctl reload varnish. Bloga klaida echo: write error: Broken pipe vėl nusijuokė mums į veidus. Mirksintis žymeklis laukė, kol tamsioje terminalo tuštumoje bus įvesta nauja komanda...

Šaltinis: www.habr.com

Добавить комментарий