Фаллинг Довн тхе Раббит Холе: Прича о једној грешци при поновном покретању лака - Први део

Гхостинусханка, након што је претходних 20 минута лупао по дугмадима као да му живот зависи од тога, окреће се према мени са полудивљим изразом у очима и лукавим осмехом - "Човече, мислим да разумем."

„Погледајте овде“, каже он, показујући на један од знакова на екрану, „кладим се у свој црвени шешир да ако овде додамо оно што сам вам управо послао“ – показујући на други део кода – „грешка више неће бити бити приказан."

Мало збуњен и уморан, мењам сед изјаву на којој смо радили неко време, чувам датотеку и покрећем systemctl varnish reload. Порука о грешци је нестала...

„Имејлови које сам разменио са кандидатом“, наставио је мој колега, док се његов осмех претвара у искрен осмех пун радости, „Одједном ми је синуло да је ово потпуно исти проблем!“

Како је све почело

Чланак претпоставља разумевање како басх, авк, сед и системд раде. Познавање лакова је пожељно али није обавезно.
Временске ознаке у исечцима су промењене.
Вриттен витх Гхостинусханка.
Овај текст је превод оригинала објављеног на енглеском пре две недеље; превод боиикоден.

Сунце сија кроз панорамске прозоре још једног топлог јесењег јутра, шоља свеже скуваног пића са кофеином стоји поред тастатуре, омиљена симфонија звукова свира у слушалицама преко шуштања механичких тастатура, а први улазак у листа заосталих тикета на канбан табли заиграно сија са судбоносним насловом „Инвестигате варнисхрелоад сх: ецхо: И/О еррор ин стагинг” (Инвестигате „варнисхрелоад сх: ецхо: И/О еррор” ин стагинг). Када је у питању лак, грешке нема и не може бити, чак и ако не резултирају проблемима, као у овом случају.

За оне који нису упознати са лакирање, ово је једноставна схелл скрипта која се користи за поновно учитавање конфигурације лакирати - такође се зове ВЦЛ.

Као што наслов тикета сугерише, грешка се догодила на једном од сервера у фази, а пошто сам био уверен да рутирање лака у бини ради исправно, претпоставио сам да би то била мања грешка. Дакле, само порука која је ушла у већ затворени излазни ток. Узимам карту за себе, у пуном поверењу да ћу је означити спремном за мање од 30 минута, тапшам се по рамену да очистим таблу од следећег смећа и враћам се важнијим стварима.

Ударање у зид при 200 км/х

Отварање датотеке varnishreload, на једном од сервера који користе Дебиан Стретцх, видео сам схелл скрипту мању од 200 редова.

Пролазећи кроз скрипту, нисам видео ништа што би могло да изазове проблеме када сам је покренуо више пута директно са терминала.

На крају крајева, ово је позорница, чак и ако се поквари, нико се неће жалити, па ... не превише. Покрећем скрипту и видим шта ће бити исписано на терминал, али грешке више нису видљиве.

Још неколико покрета да бих се уверио да не могу да репродукујем грешку без додатног напора, и почињем да схватам како да променим ову скрипту и да она и даље испушта грешку.

Може ли скрипта да блокира СТДОУТ (користећи > &-)? Или СТДЕРР? Ни једно ни друго није успело на крају.

Очигледно је да системд на неки начин мења окружење за покретање, али како и зашто?
Укључујем вим и уређујем varnishreload, додајући set -x тачно испод схебанга, у нади да ће отклањање грешака у излазу скрипте бацити мало светла.

Фајл је поправљен, па поново учитавам лак и видим да је промена потпуно све покварила... Ауспух је потпуни неред, са тонама кода налик на Ц. Чак ни померање у терминалу није довољно да се пронађе где почиње. потпуно сам збуњен. Може ли режим за отклањање грешака утицати на рад програма који се покрећу у скрипти? Без зезања. Буг у љусци? Неколико могућих сценарија лети ми у глави као бубашвабе у различитим правцима. Шоља пића пуног кофеина се одмах испразни, брзи одлазак у кухињу по залихе и... идемо. Отварам сценарио и пажљивије погледам схебанг: #!/bin/sh.

/bin/sh - ово је само басх симболична веза, тако да се скрипта тумачи у ПОСИКС-компатибилном режиму, зар не? Није га било! Подразумевана љуска на Дебиан-у је дасх, што је управо оно односи се /bin/sh.

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

Ради суђења, променио сам схебанг у #!/bin/bash, уклоњено set -x и покушао поново. Коначно, при накнадном поновном учитавању лака, у излазу се појавила толерантна грешка:

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, ево га!

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 }

Али, како се испоставило, ред 124 је прилично празан и не занима га. Могао сам само да претпоставим да се грешка догодила као део вишелинијског померања на линији 116.
Шта је коначно записано у променљиву VCL_FILE као резултат извршавања горње под-љуске?

На почетку шаље садржај променљиве VLC_SHOW, креиран на линији 115, на следећу команду кроз цев. И шта се онда тамо дешава?

Прво, користи varnishadm, који је део инсталационог пакета лака, да конфигуришете лак без поновног покретања.

подкоманда vcl.show -v се користи за излаз целе ВЦЛ конфигурације наведене у ${VCL_NAME}, на СТДОУТ.

Да бисте приказали тренутно активну ВЦЛ конфигурацију, као и неколико претходних верзија конфигурација рутирања лака које су још у меморији, можете користити команду varnishadm vcl.list, чији ће излаз бити сличан следећем:

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

Вредност променљиве ${VCL_NAME} постављен у другом делу сценарија varnishreload на име тренутно активног ВЦЛ-а, ако постоји. У овом случају то ће бити „релоад_20190101_120000_12397“.

У реду, променљиво. ${VCL_SHOW} садржи комплетну конфигурацију за лак, до сада јасан. Сада коначно разумем зашто цртица излаз са set -x испоставило се да је тако покварен - укључивао је садржај настале конфигурације.

Важно је разумети да се комплетна ВЦЛ конфигурација често може спојити из више датотека. Коментари у Ц стилу се користе да дефинишу где је једна конфигурациона датотека укључена у другу, и управо о томе говори следећи ред исечка кода.
Синтакса за коментаре који описују укључене датотеке има следећи формат:

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

Бројеви у овом контексту нису битни, занима нас назив фајла.

Дакле, шта се дешава у мочвари команди која почиње на линији 116?
Будимо искрени.
Команда се састоји из четири дела:

  1. Једноставно echo, који приказује вредност променљиве ${VCL_SHOW}
    echo "$VCL_SHOW"
  2. awk, који тражи линију (запис), где ће прво поље, након поделе текста, бити „//“, а друго ће бити „ВЦЛ.СХОВ“.
    Авк ће исписати први ред који одговара овим обрасцима, а затим ће одмах прекинути обраду.

    awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}'
  3. Блок кода који чува вредности поља у пет варијабли, раздвојених размацима. Пета варијабла ФИЛЕ прима остатак линије. Коначно, последњи ехо исписује садржај променљиве ${FILE}.
    { read -r DELIM VCL_SHOW INDEX SIZE FILE; echo "$FILE" }
  4. Пошто су сви кораци од 1 до 3 затворени у подљуску, излаз вредности $FILE биће уписан у променљиву VCL_FILE.

Као што коментар у реду 119 сугерише, ово служи јединој сврси поузданог руковања случајевима у којима ће се ВЦЛ позивати на датотеке са знаковима размака у њиховим именима.

Прокоментарисао сам оригиналну логику обраде за ${VCL_FILE} и покушао да промени редослед команди, али то није довело ни до чега. Код мене је све функционисало чисто, а у случају покретања сервиса дало је грешку.

Чини се да се грешка једноставно не може поновити када се скрипта покреће ручно, док се процењених 30 минута већ завршило шест пута, а поред тога се појавио задатак вишег приоритета који је гурао остале случајеве у страну. Остатак недеље био је испуњен разним задацима и био је само мало разводњен разговором о сед-у и интервјуом са кандидатом. Проблем са грешком у varnishreload неповратно изгубљен у песку времена.

Твој такозвани сед-фу... заправо... смеће

Следеће недеље је био један прилично слободан дан, па сам одлучио да поново узмем ову карту. Надао сам се да је у мом мозгу неки позадински процес све ово време тражио решење за овај проблем, а овај пут ћу свакако разумети шта није у реду.

Пошто прошли пут само промена кода није помогла, одлучио сам да га препишем почевши од 116. реда. У сваком случају, постојећи код је био глуп. И апсолутно нема потребе да се користи read.

Гледајући поново грешку:
sh: echo: broken pipe - у овој команди ехо је на два места, али претпостављам да је прво вероватнији кривац (па, или бар саучесник). Авк такође не улива поверење. И у случају да заиста јесте awk | {read; echo} дизајн доводи до свих ових проблема, зашто га не заменити? Ова команда у једном реду не користи све карактеристике авк-а, па чак ни ову додатну read у додатку.

Од прошле недеље је био извештај о sedЖелео сам да испробам своје новостечене вештине и поједноставим echo | awk | { read; echo} у разумљивији echo | sed. Иако ово дефинитивно није најбољи приступ да се ухвати грешка, мислио сам да ћу бар испробати свој сед-фу и можда научити нешто ново о проблему. Успут сам замолио свог колегу, писца сед талк-а, да ми помогне да смислим ефикаснији сед скрипт.

Испустио сам садржај varnishadm vcl.show -v "$VCL_NAME" у датотеку тако да се могу фокусирати на писање сед скрипте без икаквих проблема са поновним покретањем услуге.

Кратак опис како тачно сед управља уносом може се наћи у његов ГНУ приручник. У изворима сед, симбол n експлицитно наведен као сепаратор линија.

У неколико пролаза, и уз савет мог колеге, написали смо сед скрипту која је дала исти резултат као цео оригинални ред 116.

Испод је пример датотеке са улазним подацима:

> 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

Можда то није очигледно из горњег описа, али нас занима само први коментар // VCL.SHOW, а може их бити неколико у улазним подацима. Због тога се оригинални авк прекида након првог меча.

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

Дакле, садржај скрипте за учитавање лака би изгледао отприлике овако:

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

Горња логика се може сажети на следећи начин:
Ако стринг одговара регуларном изразу // VCL.SHOW, затим похлепно прождире текст који укључује оба броја у том реду и сачувај све што је остало након ове операције. Издајте сачувану вредност и завршите програм.

Једноставно, зар не?

Били смо задовољни сед скриптом и чињеницом да она замењује сав оригинални код. Сви моји тестови су дали жељене резултате, тако да сам променио „учитавање лака“ на серверу и поново покренуо systemctl reload varnish. Прљава грешка echo: write error: Broken pipe поново нам се смејао у лице. Курсор који намигује чекао је да се унесе нова команда у мрачној празнини терминала...

Извор: ввв.хабр.цом

Додај коментар