Falling Down the Rabbit Hole: ühe laki taaskäivitamise vea lugu – 1. osa

Ghostinushanka, olles eelmised 20 minutit nuppudele koputanud, nagu tema elu sellest sõltuks, pöördub minu poole, silmis poolmetsik ilme ja kaval irve – "Kutt, ma arvan, et saan aru."

"Vaata siia," ütleb ta ja osutab ühele ekraanil olevale sümbolile. "Vean kihla, et kui lisame selle, mille ma teile just siia saatsin" - osutades teisele koodijupile - "tõrget enam ei kuvata."

Natuke hämmeldunult ja väsinuna muudan sed-lauset, millega oleme mõnda aega töötanud, salvestan faili ja käivitan systemctl varnish reload. Veateade on kadunud...

"Meilid, mida ma kandidaadiga vahetasin," jätkas kolleeg, kui tema naeratus moondub ehtsaks, rõõmust tulvil naeratuseks. "Mulle jõudis järsku kohale, et see on täpselt sama probleem!"

Kuidas see kõik alguse sai

Artikkel eeldab arusaamist bash, awk, sed ja systemd toimimisest. Laki tundmine on eelistatud, kuid mitte nõutav.
Väljavõtete ajatemplid on muudetud.
Kirjutatud koos Ghostinushanka.
See tekst on kaks nädalat tagasi inglise keeles avaldatud originaali tõlge; tõlge boyikoden.

Päike paistab järjekordsel soojal sügishommikul panoraamakendest, tassike värskelt pruulitud kofeiiniga jooki puhkab klaviatuurist eemal, helide lemmiksümfoonia mängib üle mehaaniliste klaviatuuride kohine kõrvaklappides ning kanbani tahvli mahajäänud piletite nimekirja esimene sissekanne kumab mänguliselt saatusliku pealkirjaga “Investigate varnishreload sh : echo: I/O error” etapis). Kui rääkida lakist, siis vigu ei ole ega saagi olla, isegi kui need ei too kaasa probleeme, nagu antud juhul.

Neile, kes pole tuttavad laki uuesti laadimine, see on lihtne kestaskript, mida kasutatakse konfiguratsiooni uuesti laadimiseks lakk - nimetatakse ka VCL-iks.

Nagu pileti pealkirigi vihjab, tekkis viga lava ühes serveris ja kuna olin kindel, et laki suunamine etapis töötab korralikult, siis eeldasin, et tegemist on väikese veaga. Niisiis, lihtsalt teade, mis sattus juba suletud väljundvoogu. Võtan endale pileti, olles täies kindluses, et märgin selle vähem kui 30 minutiga valmis, patsutan endale õlale, et järgmisest rämpsust tahvli puhastada ja asun tagasi olulisemate asjade juurde.

Kiirusel 200 km/h vastu seina põrganud

Faili avamine varnishreload, ühes serveris, kus töötab Debian Stretch, nägin vähem kui 200 rea pikkust shelliskripti.

Skripti läbides ei näinud ma midagi, mis võiks põhjustada probleeme, kui seda mitu korda otse terminalist käivitada.

Lõppude lõpuks on see lava, isegi kui see puruneb, ei kurda keegi, noh ... mitte liiga palju. Käivitan skripti ja vaatan, mis terminali välja kirjutatakse, aga vigu pole enam näha.

Veel paar korda, et veenduda, et ma ei saa viga ilma täiendava pingutuseta reprodutseerida, ja hakkan nuputama, kuidas seda skripti muuta ja panna see ikkagi vea tekitama.

Kas skript võib blokeerida STDOUT (kasutades > &-)? Või STDERR? Kumbki ei töötanud lõpuks.

Ilmselgelt muudab süsteemne mingil moel käitamiskeskkonda, kuid kuidas ja miks?
Lülitan vimi sisse ja muudan varnishreload, lisades set -x otse shebangi all, lootes, et skripti väljundi silumine heidab valgust.

Fail on fikseeritud, nii et laadin uuesti lakki ja näen, et muudatus lõhkus kõik täielikult ... Heitgaas on täielik jama, seal on tonni C-laadset koodi. Isegi terminalis kerimisest ei piisa, et leida, kust see alguse saab. Olen täiesti segaduses. Kas silumisrežiim võib mõjutada skriptis töötavate programmide tööd? Ilma jamata. Viga kestas? Mitmed võimalikud stsenaariumid lendavad peas nagu prussakad eri suundades. Tass kofeiinirikast jooki tühjeneb koheselt, kiire käik kööki varu saamiseks ja… lähme. Avan skripti ja vaatan shebangi lähemalt: #!/bin/sh.

/bin/sh - see on lihtsalt bash-sümlink, nii et skripti tõlgendatakse POSIX-iga ühilduvas režiimis, eks? Seda polnud seal! Debiani vaikeshell on dash, mis on täpselt see viitab /bin/sh.

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

Kohtuprotsessi huvides muutsin shebangi vastu #!/bin/bash, eemaldatud set -x ja proovis uuesti. Lõpuks ilmnes järgneval laki uuesti laadimisel väljundis talutav viga:

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

Liin 124, siin see on!

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 }

Aga nagu selgus, on rida 124 üsna tühi ja ei paku huvi. Võisin vaid oletada, et tõrge tekkis rida 116 algava mitmerea osana.
Mis lõpuks muutujasse kirjutatakse VCL_FILE ülaltoodud alamkesta täitmise tulemusena?

Alguses saadab see muutuja sisu VLC_SHOW, mis on loodud real 115, järgmise käsu juurde toru kaudu. Ja mis seal siis toimub?

Esiteks, see kasutab varnishadm, mis on osa laki paigalduspaketist, et konfigureerida lakk ilma taaskäivitamiseta.

allkäsk vcl.show -v kasutatakse kogu punktis määratletud VCL-i konfiguratsiooni väljastamiseks ${VCL_NAME}, kuni STDOUT.

Praegu aktiivse VCL-i konfiguratsiooni ja ka mitmete veel mälus olevate laki marsruutimiskonfiguratsioonide varasemate versioonide kuvamiseks võite kasutada varnishadm vcl.list, mille väljund on sarnane järgmisega:

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

Muutuv väärtus ${VCL_NAME} skripti teises osas varnishreload hetkel aktiivse VCL-i nimele, kui see on olemas. Sel juhul on see "reload_20190101_120000_12397".

Olgu, muutuv. ${VCL_SHOW} sisaldab täielikku laki konfiguratsiooni, seni selge. Nüüd saan lõpuks aru, miks dash väljund koos set -x osutus nii katkiseks - see sisaldas saadud konfiguratsiooni sisu.

Oluline on mõista, et täieliku VCL-i konfiguratsiooni saab sageli mitmest failist kokku siduda. C-stiilis kommentaare kasutatakse selleks, et määratleda, kus üks konfiguratsioonifail on teises lisatud, ja see on täpselt see, mida järgmine koodilõigu rida puudutab.
Kaasatud faile kirjeldavate kommentaaride süntaks on järgmises vormingus:

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

Numbrid selles kontekstis pole olulised, meid huvitab failinimi.

Mis siis juhtub käskude mülkas, mis algab realt 116?
Olgem ausad.
Käsk koosneb neljast osast:

  1. Lihtne echo, mis kuvab muutuja väärtuse ${VCL_SHOW}
    echo "$VCL_SHOW"
  2. awk, mis otsib rida (kirjet), kus esimene väli on pärast teksti poolitamist “//” ja teine ​​“VCL.SHOW”.
    Awk kirjutab välja esimese rea, mis neile mustritele sobib, ja lõpetab seejärel töötlemise kohe.

    awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}'
  3. Koodiplokk, mis salvestab välja väärtused, mis on eraldatud tühikutega viieks muutujaks. Viies muutuja FILE võtab vastu ülejäänud rea. Lõpuks kirjutab viimane kaja välja muutuja sisu ${FILE}.
    { read -r DELIM VCL_SHOW INDEX SIZE FILE; echo "$FILE" }
  4. Kuna kõik sammud 1 kuni 3 on ümbritsetud alamkestaga, on väärtuse väljund $FILE kirjutatakse muutujasse VCL_FILE.

Nagu rea 119 kommentaar viitab, teenib see ainus eesmärki usaldusväärselt käsitleda juhtumeid, kus VCL viitab failidele, mille nimedes on tühikud.

Olen kommenteerinud algset töötlemisloogikat ${VCL_FILE} ja püüdis muuta käskude jada, kuid see ei viinud millegini. Minul töötas kõik puhtalt ja teenuse käivitamise puhul andis see vea.

Tundub, et skripti käsitsi käivitamisel pole viga lihtsalt reprodutseeritav, samas kui hinnanguliselt 30 minutit on juba kuuel korral lõppenud ja lisaks on ilmunud kõrgema prioriteediga ülesanne, mis lükkab ülejäänud juhtumid kõrvale. Ülejäänud nädal oli täidetud mitmesuguste ülesannetega ja seda vaid veidi lahjendati sedis peetud vestluse ja kandidaadiga peetud intervjuuga. Viga probleem sisse varnishreload pöördumatult kadunud aja liivadesse.

Sinu nn sed-fu... tegelikult... prügi

Järgmisel nädalal oli üks üsna vaba päev, nii et otsustasin selle pileti uuesti osta. Lootsin, et mu ajus otsis kogu selle aja mingi taustaprotsess sellele probleemile lahendust ja seekord saan kindlasti aru, mis lahti on.

Kuna eelmine kord lihtsalt koodi muutmine ei aidanud, siis otsustasin lihtsalt 116. realt alustades ümber kirjutada. Igal juhul oli olemasolev kood jabur. Ja seda pole absoluutselt vaja kasutada read.

Vaatan viga uuesti:
sh: echo: broken pipe - selles käsus on kaja kahes kohas, kuid kahtlustan, et esimene on tõenäolisem süüdlane (või vähemalt kaasosaline). Awk ei ärata ka usaldust. Ja juhul kui see tõesti nii on awk | {read; echo} disain toob kaasa kõik need probleemid, miks mitte seda asendada? See üherealine käsk ei kasuta kõiki awki funktsioone ja isegi seda lisa read lisas.

Alates eelmisest nädalast on olnud aruanne sedTahtsin proovida oma äsja omandatud oskusi ja lihtsustada echo | awk | { read; echo} arusaadavamaks echo | sed. Kuigi see ei ole kindlasti parim viis vea tabamiseks, mõtlesin, et proovin vähemalt oma sed-fu-d ja võib-olla õpin probleemi kohta midagi uut. Teel palusin oma kolleegil, sed-kõnede kirjutajal, aidata mul välja mõelda tõhusam sed-stsenaarium.

Jätsin sisu maha varnishadm vcl.show -v "$VCL_NAME" faili, et saaksin keskenduda sed-skripti kirjutamisele ilma teenuse taaskäivitamiseta.

Lühikirjelduse selle kohta, kuidas sed täpselt sisendit käsitleb, leiate siit tema GNU käsiraamat. Sedi allikates sümbol n sõnaselgelt määratletud rea eraldajana.

Mitmes läbimises ja kolleegi nõuandel kirjutasime sed-skripti, mis andis sama tulemuse kui kogu algne rida 116.

Allpool on näidisfail sisendandmetega:

> 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

See ei pruugi ülaltoodud kirjeldusest ilmneda, kuid meid huvitab ainult esimene kommentaar // VCL.SHOW, ja neid võib sisendandmetes olla mitu. Seetõttu lõpeb algne awk pärast esimest vastet.

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

Nii et lakkide uuesti laadimise skripti sisu näeks välja umbes selline:

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

Ülaltoodud loogika võib kokku võtta järgmiselt:
Kui string vastab regulaaravaldisele // VCL.SHOW, siis ahnelt õgima teksti, mis sisaldab selle rea mõlemat numbrit, ja salvestada kõik, mis pärast seda toimingut järele jääb. Andke välja salvestatud väärtus ja lõpetage programm.

Lihtne, kas pole?

Olime rahul sed-skripti ja sellega, et see asendab kogu algse koodi. Kõik minu testid andsid soovitud tulemuse, nii et muutsin serveris "laki uuesti laadimist" ja jooksin uuesti systemctl reload varnish. Räpane viga echo: write error: Broken pipe naeris meile jälle näkku. Pilgutav kursor ootas terminali pimedas tühjus uue käsu sisestamist...

Allikas: www.habr.com

Lisa kommentaar