Falling Down the Rabbit Hole: Tarina yhdestä lakan uudelleenkäynnistysvirheestä - Osa 1

Ghostinushanka, painanut nappeja edelliset 20 minuuttia ikään kuin hänen elämänsä riippuisi siitä, kääntyy minuun puolivilli ilme silmissään ja ovela virne - "Kaveri, luulen ymmärtäväni."

"Katso tänne", hän sanoo ja osoittaa yhtä näytöllä olevista symboleista. "Luon vetoa, että jos lisäämme sen, mitä juuri lähetin sinulle tänne", osoittaa toista koodin osaa, "virhettä ei enää näytetä."

Hieman hämmentyneenä ja väsyneenä vaihdan sed-lausetta, jota olemme työstäneet jonkin aikaa, tallennan tiedoston ja suoritan systemctl varnish reload. Virheilmoitus on kadonnut...

"Sähköpostit, joita vaihdoin ehdokkaan kanssa", kollegani jatkoi, kun hänen virnistyksensä muuttuu aidoksi hymyksi, joka on täynnä iloa, "Yhtäkkiä tajusin, että tämä on täsmälleen sama ongelma!"

Kuinka kaikki alkoi

Artikkelissa oletetaan ymmärtävän, kuinka bash, awk, sed ja systemd toimivat. Lakkatuntemus katsotaan eduksi, mutta ei pakollinen.
Katkelmien aikaleimat on muutettu.
Kirjoitettu kanssa Ghostinushanka.
Tämä teksti on käännös alkuperäisestä, joka julkaistiin englanniksi kaksi viikkoa sitten; käännös boyikoden.

Aurinko paistaa panoraamaikkunoista toisena lämpimänä syysaamuna, kuppi tuoretta kofeiinipitoista juomaa lepää erossa koskettimistosta, suosikki äänten sinfonia soi mekaanisten koskettimien kahinaa kuulokkeissa, ja kanbantaulun backlog lippulistan ensimmäinen merkintä hehkuu leikkisästi kohtalokkaalla otsikolla "Investigate sh:Ovarnegate nishreload sh : echo: I/O error" vaiheessa). Lakkauksessa ei ole eikä voi olla virheitä, vaikka ne eivät aiheuta ongelmia, kuten tässä tapauksessa.

Niille, jotka eivät tunne lakan uudelleenlataus, tämä on yksinkertainen komentosarja, jota käytetään kokoonpanon uudelleen lataamiseen lakka - kutsutaan myös VCL:ksi.

Kuten lipun otsikko viittaa, virhe tapahtui yhdellä lavan palvelimista ja koska olin varma, että lakan reititys lavalla toimi oikein, oletin tämän olevan pieni virhe. Joten, vain viesti, joka joutui jo suljettuun lähtövirtaan. Otan lipun itselleni, täysin luottavaisin mielin, että merkitsen sen valmiiksi alle 30 minuutissa, taputan itseäni olkapäälle seuraavan roskalaudan poistamiseksi ja palaan tärkeämpiin asioihin.

Törmäsi seinään nopeudella 200 km/h

Tiedoston avaaminen varnishreload, yhdellä Debian Stretchiä käyttävistä palvelimista näin alle 200 rivin pituisen komentotulkkikomentosarjan.

Suorittaessani komentosarjaa en nähnyt mitään, mikä voisi aiheuttaa ongelmia, kun sitä suoritettiin useita kertoja suoraan päätteestä.

Loppujen lopuksi tämä on vaihe, vaikka se rikkoutuisi, kukaan ei valita, no ... ei liikaa. Suoritan skriptin ja katson, mitä päätelaitteelle kirjoitetaan, mutta virheet eivät enää näy.

Vielä pari kertaa varmistaakseni, etten voi toistaa virhettä ilman ylimääräistä vaivaa, ja alan miettiä, kuinka muuttaa tätä komentosarjaa ja saada se silti aiheuttamaan virheen.

Voiko komentosarja estää STDOUT (käyttäen > &-)? Tai STDERR? Kumpikaan ei toiminut lopulta.

Ilmeisesti systemd muuttaa ajoympäristöä jollain tavalla, mutta miten ja miksi?
Laitan vimin päälle ja muokkaan varnishreload, lisäämällä set -x aivan shebangin alla, toivoen, että käsikirjoituksen tulosten virheenkorjaus valaisee hieman.

Tiedosto on korjattu, joten lataan lakan uudelleen ja näen, että muutos rikkoi kaiken... Pakoputki on täydellinen sotku, jossa on tonnia C-kaltaista koodia. Edes terminaalin vierittäminen ei riitä löytämään, mistä se alkaa. Olen täysin hämmentynyt. Voiko debug-tila vaikuttaa komentosarjassa suoritettavien ohjelmien toimintaan? Ei hevonpaskaa. Bugi kuoressa? Päässäni lentää useita mahdollisia skenaarioita kuin torakoita eri suuntiin. Kupillinen kofeiinia täynnä olevaa juomaa tyhjenee heti, pikamatka keittiöön lisäravintoa varten ja… mennään. Avaan käsikirjoituksen ja katson tarkemmin shebangia: #!/bin/sh.

/bin/sh - Tämä on vain bash-symlink, joten skripti tulkitaan POSIX-yhteensopivassa tilassa, eikö niin? Se ei ollut siellä! Debianin oletuskuori on dash, mikä on juuri sitä viittaa /bin/sh.

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

Kokeilun vuoksi vaihdoin shebangin muotoon #!/bin/bash, poistettu set -x ja yritti uudelleen. Lopuksi myöhemmän lakan uudelleenlatauksen yhteydessä tulostukseen ilmestyi siedettävä virhe:

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

Linja 124, tässä se 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 }

Mutta kuten kävi ilmi, rivi 124 on melko tyhjä eikä kiinnosta. Voisin vain olettaa, että virhe tapahtui osana riviltä 116 alkavaa monirivistä.
Mitä muuttujaan lopulta kirjoitetaan VCL_FILE yllä olevan alikulkon suorittamisen seurauksena?

Alussa se lähettää muuttujan sisällön VLC_SHOW, luotu rivillä 115, seuraavaan komentoon putken kautta. Ja mitä siellä sitten tapahtuu?

Ensinnäkin se käyttää varnishadm, joka on osa lakan asennuspakettia, jotta voit määrittää lakan ilman uudelleenkäynnistystä.

alakomento vcl.show -v käytetään koko kohdassa määritellyn VCL-kokoonpanon tulostamiseen ${VCL_NAME}, STDOUT.

Voit näyttää tällä hetkellä aktiivisen VCL-kokoonpanon sekä useita aiempia versioita lakan reitityskokoonpanoista, jotka ovat vielä muistissa, käyttämällä komentoa varnishadm vcl.list, jonka tulos on seuraavanlainen:

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

Muuttuva arvo ${VCL_NAME} asetettu käsikirjoituksen toisessa osassa varnishreload tällä hetkellä aktiivisen VCL:n nimeen, jos sellainen on. Tässä tapauksessa se on "reload_20190101_120000_12397".

Okei, vaihteleva. ${VCL_SHOW} sisältää täydellisen lakan kokoonpanon, toistaiseksi selkeä. Nyt olen vihdoin ymmärtänyt, miksi dash-tulostus set -x osoittautui niin rikki - se sisälsi tuloksena olevan kokoonpanon sisällön.

On tärkeää ymmärtää, että täydellinen VCL-kokoonpano voidaan usein koota yhteen useista tiedostoista. C-tyylisiä kommentteja käytetään määrittämään, missä yksi asetustiedosto on sisällytetty toiseen, ja juuri siitä seuraavassa koodinpätkärivissä on kyse.
Mukana olevia tiedostoja kuvaavien kommenttien syntaksi on seuraavassa muodossa:

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

Numerot eivät tässä yhteydessä ole tärkeitä, olemme kiinnostuneita tiedoston nimestä.

Mitä sitten tapahtuu komentojen suossa, joka alkaa riviltä 116?
Totta puhuen.
Komento koostuu neljästä osasta:

  1. yksinkertainen echo, joka näyttää muuttujan arvon ${VCL_SHOW}
    echo "$VCL_SHOW"
  2. awk, joka etsii riviä (tietuetta), jossa ensimmäinen kenttä tekstin jakamisen jälkeen on "//" ja toinen on "VCL.SHOW".
    Awk kirjoittaa ensimmäisen rivin, joka vastaa näitä kuvioita, ja lopettaa käsittelyn välittömästi.

    awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}'
  3. Koodilohko, joka tallentaa kentän arvot viiteen muuttujaan välilyönnillä erotettuna. Viides muuttuja FILE vastaanottaa loput rivistä. Lopuksi viimeinen kaiku kirjoittaa muuttujan sisällön ${FILE}.
    { read -r DELIM VCL_SHOW INDEX SIZE FILE; echo "$FILE" }
  4. Koska kaikki vaiheet 1 - 3 on suljettu alikuoreen, arvon tulos $FILE kirjoitetaan muuttujaan VCL_FILE.

Kuten rivin 119 kommentti ehdottaa, tämä palvelee ainoaa tarkoitusta käsitellä luotettavasti tapauksia, joissa VCL viittaa tiedostoihin, joiden nimissä on välilyöntejä.

Olen kommentoinut alkuperäistä käsittelylogiikkaa ${VCL_FILE} ja yritti muuttaa komentojen järjestystä, mutta se ei johtanut mihinkään. Kaikki toimi minulle siististi, ja palvelun käynnistämisen tapauksessa se antoi virheen.

Näyttää siltä, ​​että virhe ei yksinkertaisesti ole toistettavissa käsin skriptiä ajettaessa, kun taas arvioidut 30 minuuttia ovat päättyneet jo kuusi kertaa ja lisäksi on ilmaantunut korkeamman prioriteetin tehtävä, joka työntää muut tapaukset sivuun. Loppuviikko oli täynnä erilaisia ​​tehtäviä, ja sitä laimensi vain hieman sed-keskustelu ja haastattelu ehdokkaan kanssa. Virhe ongelma sisään varnishreload peruuttamattomasti eksynyt ajan hiekkaan.

Sinun niin kutsuttu sed-fu... itse asiassa... roskaa

Seuraavalla viikolla oli yksi melko vapaa päivä, joten päätin ostaa tämän lipun uudelleen. Toivoin, että aivoissani jokin taustaprosessi koko tämän ajan etsi ratkaisua tähän ongelmaan, ja tällä kertaa ymmärrän varmasti, mikä on vialla.

Koska viime kerralla pelkkä koodin vaihtaminen ei auttanut, päätin vain kirjoittaa sen uudelleen 116. riviltä alkaen. Joka tapauksessa nykyinen koodi oli typerä. Eikä ole mitään tarvetta käyttää read.

Katsotaanpa virhettä uudelleen:
sh: echo: broken pipe - tässä komennossa kaiku on kahdessa paikassa, mutta epäilen, että ensimmäinen on todennäköisempi syyllinen (no, tai ainakin rikoskumppani). Awk ei myöskään herätä luottamusta. Ja jos se todella on awk | {read; echo} suunnittelu johtaa kaikkiin näihin ongelmiin, miksi ei korvata sitä? Tämä yksirivinen komento ei käytä kaikkia awkin ominaisuuksia eikä edes tätä ylimääräistä read lisäyksessä.

Viime viikosta lähtien on ollut raporttia sedHalusin kokeilla äskettäin hankittuja taitojani ja yksinkertaistaa echo | awk | { read; echo} ymmärrettävämpään suuntaan echo | sed. Vaikka tämä ei todellakaan ole paras tapa saada vikaa kiinni, ajattelin ainakin kokeilla sed-fuani ja ehkä oppia jotain uutta ongelmasta. Pyysin matkan varrella kollegaani, sed talk -kirjoittajaa, auttamaan minua löytämään tehokkaampi sed-käsikirjoitus.

Pudotin sisällön varnishadm vcl.show -v "$VCL_NAME" tiedostoon, jotta voin keskittyä sed-skriptin kirjoittamiseen ilman palvelun uudelleenkäynnistymisen vaivaa.

Lyhyt kuvaus siitä, kuinka sed käsittelee syötettä, löytyy kohdasta hänen GNU-käsikirjaansa. Sed-lähteissä symboli n nimenomaisesti määritelty rivierottimeksi.

Kirjoitimme useissa kierroksissa ja kollegani neuvoista sed-käsikirjoituksen, joka antoi saman tuloksen kuin koko alkuperäinen rivi 116.

Alla on esimerkkitiedosto syötetiedoilla:

> 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

Se ei ehkä ole selvää yllä olevasta kuvauksesta, mutta olemme kiinnostuneita vain ensimmäisestä kommentista // VCL.SHOW, ja niitä voi olla useita syöttötiedoissa. Tästä syystä alkuperäinen awk päättyy ensimmäisen ottelun jälkeen.

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

Joten varnishreload-skriptin sisältö näyttäisi suunnilleen tältä:

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

Yllä oleva logiikka voidaan tiivistää seuraavasti:
Jos merkkijono vastaa säännöllistä lauseketta // VCL.SHOW, niele sitten ahneesti teksti, joka sisältää molemmat numerot kyseisellä rivillä, ja tallenna kaikki tämän toiminnon jälkeen jäljellä olevat. Anna tallennettu arvo ja lopeta ohjelma.

Yksinkertaista, eikö?

Olimme tyytyväisiä sed-skriptiin ja siihen, että se korvaa kaiken alkuperäisen koodin. Kaikki testini antoivat halutut tulokset, joten vaihdoin "lakan uudelleenlatauksen" palvelimella ja juoksin uudelleen systemctl reload varnish. Likainen virhe echo: write error: Broken pipe nauroi taas päin naamaa. Silmäilevä kohdistin odotti uutta komentoa terminaalin pimeässä tyhjiössä...

Lähde: will.com

Lisää kommentti