Pagbagsak sa Butas ng Kuneho: Ang Kwento ng Isang Pagkabigo sa Isang Varnish Reload - Bahagi 1

ghostinushanka, habang pinipisil ang mga butones sa nakaraang 20 minuto na para bang nakasalalay dito ang kanyang buhay, lumingon sa akin na may medyo ligaw na tingin sa kanyang mga mata at isang matalim na ngiti - "Dude, sa tingin ko nakuha ko na."

"Tingnan mo rito," sabi niya, habang itinuturo ang isa sa mga simbolo sa screen, "Pusta ko ang aking pulang sumbrero na kung idagdag natin dito ang ipinadala ko sa iyo," itinuro ang isa pang seksyon ng code, "ang error ay hindi na ay ipapakita."

Medyo nalilito at pagod, binago ko ang sed expression na matagal na naming ginagawa, i-save ang file at tumakbo systemctl varnish reload. Nawala ang mensahe ng error...

"Ang mga email na ipinakipagpalitan ko sa kandidato," patuloy ng aking kasamahan, habang ang kanyang ngiti ay lumago sa isang tunay na ngiti ng kagalakan, "Biglang naisip ko na ito ay eksaktong parehong problema!"

Kung paano nagsimula ang lahat

Ipinapalagay ng artikulo ang pag-unawa sa kung paano gumagana ang bash, awk, sed at systemd. Ang kaalaman sa barnis ay ginustong, ngunit hindi kinakailangan.
Ang mga timestamp sa mga snippet ay binago.
Nakasulat na may ghostinushanka.
Ang tekstong ito ay pagsasalin ng orihinal na inilathala sa Ingles dalawang linggo na ang nakalipas; pagsasalin boikoden.

Ang araw ay sumisikat sa mga malalawak na bintana sa isa na namang mainit na umaga ng taglagas, isang tasa ng sariwang inihandang inuming mayaman sa caffeine ang natitira sa keyboard, ang iyong paboritong symphony ng mga tunog ay tumutunog sa iyong mga headphone, nilulunod ang kaluskos ng mga mekanikal na keyboard, at ang unang entry sa listahan ng mga backlog ticket sa Kanban board ay mapaglarong kumikinang sa nakamamatay na pamagat na "Imbistigahan ang varnishreload" sh: echo: I/O error sa staging" (Imbistigahan ang "varnishreload sh: echo: I/O error" sa staging). Pagdating sa barnisan, mayroon at hindi maaaring maging anumang silid para sa mga pagkakamali, kahit na hindi sila nagreresulta sa anumang mga problema tulad ng sa kasong ito.

Para sa mga hindi pamilyar sa varnishreload, ito ay isang simpleng shell script na ginamit upang i-reload ang configuration barnisan - tinatawag ding VCL.

Tulad ng iminumungkahi ng pamagat ng tiket, naganap ang error sa isa sa mga server sa entablado, at dahil sigurado akong gumagana nang maayos ang pagruruta ng barnis sa entablado, ipinapalagay ko na ito ay isang maliit na error. Kaya, isang mensahe lamang na napunta sa isang nakasara na stream ng output. Kinukuha ko ang tiket para sa aking sarili, nang buong kumpiyansa na mamarkahan ko itong handa sa loob ng wala pang 30 minuto, tapikin ang aking sarili sa likod para sa paglilinis ng board ng isa pang basura at bumalik sa mas mahahalagang bagay.

Bumagsak sa pader sa bilis na 200 km/h

Pagbukas ng file varnishreload, sa isa sa mga server na nagpapatakbo ng Debian Stretch, nakakita ako ng shell script na wala pang 200 linya ang haba.

Nang dumaan sa script, hindi ko napansin ang anumang bagay na maaaring magresulta sa mga problema kapag pinapatakbo ito nang maraming beses nang direkta mula sa terminal.

Kung tutuusin, ito ay isang yugto, kahit masira, walang magrereklamo, aba... hindi naman masyado. Pinapatakbo ko ang script at tingnan kung ano ang isusulat sa terminal, ngunit ang mga error ay hindi na nakikita.

Ang ilang pares ay tumatakbo upang matiyak na hindi ko mai-reproduce ang error nang walang anumang karagdagang pagsisikap, at nagsisimula akong malaman kung paano baguhin ang script na ito at gawin pa rin itong maghagis ng error.

Maaari bang i-override ng script ang STDOUT (gamit ang > &-)? O STDERR? Wala sa mga ito ang gumana sa huli.

Tila binago ng systemd ang kapaligiran ng pagsisimula, ngunit paano, at bakit?
Binuksan ko ang vim at nag-edit varnishreload, pagdaragdag set -x sa ilalim mismo ng shebang, umaasa na ang debug na output ng script ay magbibigay liwanag.

Ang file ay naitama, kaya nag-reload ako ng barnisan at nakita na ang pagbabago ay ganap na sinira ang lahat... Ang tambutso ay isang kumpletong gulo, kung saan may mga toneladang C-like na code. Kahit na ang pag-scroll sa terminal ay hindi sapat upang mahanap kung saan ito magsisimula. Ako ay lubos na naguguluhan. Maaapektuhan ba ng debugging mode ang pagpapatakbo ng mga program na inilunsad sa isang script? Hindi, ito ay walang kapararakan. Bug sa shell? Maraming posibleng senaryo ang tumatakbo sa aking ulo na parang mga ipis sa iba't ibang direksyon. Ang tasa ng caffeinated na inumin ay agad na naubos, isang mabilis na paglalakbay sa kusina upang lagyang muli ang stock at... umalis na tayo. Binuksan ko ang script at tinitingnang mabuti ang shebang: #!/bin/sh.

/bin/sh - ito ay isang symlink lamang sa bash, kaya ang script ay binibigyang kahulugan sa POSIX-compatible mode, tama ba? Hindi kaya! Ang default na shell sa Debian ay dash, at iyon mismo ang hitsura nito. tumutukoy /bin/sh.

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

Bilang pagsubok, pinalitan ko ang shebang sa #!/bin/bash, tinanggal set -x at sinubukan muli. Sa wakas, sa kasunod na pag-reboot ng barnis, isang matitiis na error ang lumitaw sa output:

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, eto na!

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 }

Ngunit sa lumalabas, ang linya 124 ay medyo walang laman at walang interes. Maaari ko lamang ipagpalagay na ang error ay naganap bilang bahagi ng isang multiline string na nagsisimula sa linya 116.
Ano ang huli na isinulat sa variable? VCL_FILE bilang resulta ng pagsasagawa ng sub-shell sa itaas?

Sa simula, ipinapadala nito ang mga nilalaman ng variable VLC_SHOW, nilikha sa linya 115, kasunod ng utos sa pamamagitan ng pipe. At saka ano ang nangyayari doon?

Una, ito ay ginagamit doon varnishadm, na bahagi ng pakete ng pag-install ng barnis, para sa pag-set up ng barnis nang hindi nagre-restart.

Sub-team vcl.show -v ginamit upang i-output ang buong configuration ng VCL na tinukoy sa ${VCL_NAME}, sa STDOUT.

Upang ipakita ang kasalukuyang aktibong configuration ng VCL, pati na rin ang ilang nakaraang bersyon ng mga configuration ng pagruruta ng barnis na nasa memorya pa rin, maaari mong gamitin ang command varnishadm vcl.list, ang output nito ay magiging katulad ng nasa ibaba:

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

Variable value ${VCL_NAME} ay naka-install sa isa pang bahagi ng script varnishreload sa pangalan ng kasalukuyang aktibong VCL, kung mayroon man. Sa kasong ito, ito ay magiging "reload_20190101_120000_12397".

Mahusay, variable ${VCL_SHOW} naglalaman ng kumpletong configuration para sa barnisan, malinaw sa ngayon. Ngayon sa wakas naiintindihan ko na kung bakit ang dash output ay set -x naging napakasira - kasama nito ang mga nilalaman ng nagresultang pagsasaayos.

Mahalagang maunawaan na ang kumpletong configuration ng VCL ay kadalasang maaaring pagsama-samahin mula sa ilang mga file. Ginagamit ang mga komento sa istilong C upang matukoy kung saan isinama ang ilang partikular na configuration file sa iba, at iyon ang tungkol sa sumusunod na linya ng snippet ng code.
Ang syntax para sa mga komentong naglalarawan ng mga kasamang file ay nasa sumusunod na format:

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

Ang mga numero ay hindi mahalaga sa kontekstong ito, kami ay interesado sa pangalan ng file.

Ano ang mangyayari sa lusak ng mga utos na nagsisimula sa linya 116?
Sabihin Nakaharap ito.
Ang pangkat ay binubuo ng apat na bahagi:

  1. Simple echo, na nagpi-print ng halaga ng variable ${VCL_SHOW}
    echo "$VCL_SHOW"
  2. awk, na naghahanap ng linya (record) kung saan ang unang field, pagkatapos masira ang text, ay “//”, at ang pangalawa ay “VCL.SHOW”.
    Isusulat ng Awk ang unang linya na tumutugma sa mga pattern na ito at pagkatapos ay ihihinto kaagad ang pagproseso.

    awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}'
  3. Isang bloke ng code na nag-iimbak ng mga halaga ng field sa limang variable, na pinaghihiwalay ng mga puwang. Ang ikalimang FILE variable ay tumatanggap ng natitirang bahagi ng linya. Sa wakas, isinusulat ng huling echo ang mga nilalaman ng variable ${FILE}.
    { read -r DELIM VCL_SHOW INDEX SIZE FILE; echo "$FILE" }
  4. Dahil ang lahat ng hakbang 1 hanggang 3 ay nakapaloob sa isang subshell, na naglalabas ng halaga $FILE ay isusulat sa isang variable VCL_FILE.

Tulad ng iminumungkahi ng komento sa linya 119, ito ay nagsisilbi sa tanging layunin ng mapagkakatiwalaang paghawak ng mga kaso kung saan ang VCL ay magre-refer ng mga file na may mga puwang sa kanilang mga pangalan.

Nagkomento ako sa orihinal na lohika ng pagproseso para sa ${VCL_FILE} at sinubukang baguhin ang pagkakasunud-sunod ng command, ngunit hindi ito humantong sa anuman. Naging maayos ang lahat para sa akin, ngunit nang simulan ko ang serbisyo ay nagbigay ito ng error.

Tila ang error ay hindi maaaring kopyahin kapag manu-manong pinapatakbo ang script, habang ang dapat na 30 minuto ay nag-expire na ng anim na beses at, bilang karagdagan, lumitaw ang isang mas mataas na priyoridad na gawain, na itinutulak ang iba pang mga bagay sa isang tabi. Ang natitira sa linggo ay napuno ng iba't ibang mga gawain at bahagyang natunaw ng isang ulat sa sed at isang pakikipanayam sa isang kandidato. Problema sa error sa varnishreload ay hindi na mababawi na nawala sa mga buhangin ng panahon.

Ang iyong tinatawag na sed-fu... ay talagang... basura

Sa susunod na linggo mayroon akong isang medyo libreng araw, kaya nagpasya akong harapin muli ang tiket na ito. Inaasahan ko na sa aking utak, ang ilang proseso sa background ay naghahanap ng solusyon sa problemang ito sa lahat ng oras na ito, at sa pagkakataong ito ay tiyak na mauunawaan ko kung ano ang nangyayari.

Dahil ang simpleng pagbabago ng code ay hindi nakatulong sa huling pagkakataon, napagpasyahan ko na lang na muling isulat ito simula sa linya 116. Sa anumang kaso, ang umiiral na code ay hangal. At talagang hindi na kailangang gamitin ito read.

Tinitingnan muli ang error:
sh: echo: broken pipe — lumilitaw ang echo sa dalawang lugar sa utos na ito, ngunit pinaghihinalaan ko na ang una ay ang mas malamang na salarin (o hindi bababa sa isang kasabwat). Hindi rin nakaka-inspire ng confidence ang Awk. At kung sakali talaga awk | {read; echo} ang disenyo ay humahantong sa lahat ng mga problemang ito, bakit hindi ito palitan? Hindi ginagamit ng one-line command na ito ang lahat ng feature ng awk, at maging ang dagdag na ito read at saka.

Mula noong nakaraang linggo ay may ulat sa sed, gusto kong subukan ang aking mga bagong nakuhang kasanayan at pasimplehin echo | awk | { read; echo} sa isang mas naiintindihan echo | sed. Bagama't tiyak na hindi ito ang pinakamahusay na diskarte sa pagtukoy ng bug, naisip ko na subukan ko man lang ang aking sed-fu at baka matuto ng bago tungkol sa problema. Habang nasa daan, tinanong ko ang aking kasamahan, ang may-akda ng sed talk, na tulungan akong makabuo ng isang mas mahusay na sed script.

Ibinaba ko ang laman varnishadm vcl.show -v "$VCL_NAME" sa isang file, para makapag-focus ako sa pagsusulat ng sed script nang walang abala sa pag-reboot ng serbisyo.

Isang maikling paglalarawan ng eksakto kung paano makikita ang input ng sed ang kanyang GNU manual. Sa sed source ang simbolo n tahasang tinukoy bilang isang line separator.

Sa ilang mga pass at sa mga rekomendasyon ng aking kasamahan, nagsulat kami ng sed script na nagbigay ng parehong resulta sa buong orihinal na linya 116.

Nasa ibaba ang isang sample na file na may input data:

> 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

Maaaring hindi ito halata mula sa paglalarawan sa itaas, ngunit interesado lamang kami sa unang komento // VCL.SHOW, at maaaring may ilan sa mga ito sa input data. Ito ang dahilan kung bakit nagtatapos ang orihinal na awk pagkatapos ng unang laban.

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

Kaya, ang mga nilalaman ng script ng varnishreload ay magiging ganito:

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

Ang lohika sa itaas ay maaaring maipahayag nang maikli tulad ng sumusunod:
Kung ang string ay tumutugma sa isang regular na expression // VCL.SHOW, pagkatapos ay sakim na lalamunin ang teksto na kinabibilangan ng parehong mga numero sa linyang ito, at i-save ang lahat ng natitira pagkatapos ng operasyong ito. Ilabas ang nakaimbak na halaga at tapusin ang programa.

Simple, hindi ba?

Natuwa kami sa sed script at sa katotohanang pinalitan nito ang lahat ng orihinal na code. Ang lahat ng aking mga pagsubok ay nagbigay ng nais na mga resulta, kaya binago ko ang "varnishreload" sa server at pinatakbo itong muli systemctl reload varnish. Masamang pagkakamali echo: write error: Broken pipe nagtawanan na naman kami. Ang kumikislap na cursor ay naghihintay ng bagong utos na ipasok sa madilim na kawalan ng laman ng terminal...

Pinagmulan: www.habr.com

Magdagdag ng komento