Falling Down the Rabbit Hole: Приказната за една грешка при рестартирање на лак - Дел 1

гостинушанка, откако ги чекаше копчињата претходните 20 минути како животот да му зависи од тоа, се свртува кон мене со полудив поглед во очите и итра насмевка - „Друже, мислам дека сфатив“.

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

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

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

Како сето тоа започна

Статијата претпоставува разбирање за тоа како функционираат bash, awk, sed и systemd. Пожелно е познавање на лакот, но не е задолжително.
Временските печати во фрагментите се променети.
Напишано со гостинушанка.
Овој текст е превод на оригиналот објавен на англиски пред две недели; превод боикоден.

Сонцето сјае низ панорамските прозорци во уште едно топло есенско утро, шолја свежо подготвен пијалок богат со кофеин се одмора подалеку од тастатурата, вашата омилена симфонија на звуци звучи во вашите слушалки, го задушува шумолењето на механичките тастатури и првиот влез во списокот со заостанати билети на таблата Канбан разиграно свети со судбоносниот наслов „Истражувај varnishreload“ sh: echo: I/O error in staging“ (Истражувај „varnishreload sh: echo: I/O error“ во инсценирањето). Кога станува збор за лак, простор за грешки има и не може да има, дури и ако тие не резултираат со никакви проблеми како во овој случај.

За оние кои не се запознаени со оптоварување со лак, ова е едноставна скрипта за школка што се користи за повторно вчитување на конфигурацијата лак - исто така наречен VCL.

Како што сугерира насловот на билетот, грешката се случи на еден од серверите на сцената, и бидејќи бев сигурен дека насочувањето на лакот на сцената работи правилно, претпоставив дека ова ќе биде мала грешка. Значи, само порака која заврши во веќе затворен излезен поток. Го земам билетот за себе, со целосна доверба дека ќе го означам подготвен за помалку од 30 минути, се тапкам по рамо за да ја исчистам таблата од уште едно ѓубре и ќе се вратам на поважните работи.

Удар во ѕид со 200 km/h

Отворање на датотеката varnishreload, на еден од серверите со Debian Stretch, видов скрипта за школка долга помалку од 200 линии.

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

На крајот на краиштата, ова е фаза, и да се скрши, никој нема да се жали, добро... не премногу. Ја извршувам скриптата и гледам што ќе биде напишано на терминалот, но грешките веќе не се видливи.

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

Дали скриптата може да го отфрли STDOUT (користејќи > &-)? Или STDERR? Ниту едно од овие не успеало на крајот.

Очигледно systemd некако ја модифицира околината за стартување, но како и зошто?
Отворам вим и уредувам varnishreload, додавајќи set -x веднаш под шебангот, надевајќи се дека излезот за отстранување грешки на сценариото ќе фрли малку светлина.

Датотеката е корегирана, па повторно вчитувам лак и гледам дека промената целосно скрши се... Издувот е целосен хаос, во кој има тони шифра налик на Ц. Дури и лизгањето во терминалот не е доволно за да откриете каде започнува. Целосно сум збунет. Дали режимот за дебагирање може да влијае на работата на програмите стартувани во скрипта? Не, тоа е глупост. Бубачка во школка? Неколку можни сценарија ми се тркаат низ главата како бубашваби во различни правци. Чашата со кофеинскиот пијалок веднаш се испразни, брзо патување до кујната за да се наполни залихата и... тргнуваме. Го отворам сценариото и внимателно го разгледувам шебангот: #!/bin/sh.

/bin/sh - ова е само символик до баш, така што сценариото се толкува во режим компатибилен со POSIX, нели? Не е така! Стандардната обвивка на Debian е цртичка, и токму тоа изгледа. се однесува /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 конфигурација наведена во ${VCL_NAME}, до STDAUT.

За да се прикаже моменталната активна VCL конфигурација, како и неколку претходни верзии на конфигурации за рутирање на лак кои сè уште се во меморија, можете да ја користите командата 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 до името на моментално активниот VCL, доколку постои. Во овој случај, ќе биде „reload_20190101_120000_12397“.

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

Важно е да се разбере дека комплетната VCL конфигурација често може да се состави од неколку датотеки. Коментарите во C-стил се користат за да се идентификува каде одредени конфигурациски датотеки се вклучени во други, и тоа е она за што е следнава линија од фрагментот од кодот.
Синтаксата за коментари што ги опишуваат вклучените датотеки е во следниов формат:

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

Броевите не се важни во овој контекст, нас не интересира името на датотеката.

Што на крајот се случува во мочуриштето команди што започнуваат на линијата 116?
Ајде да видиме.
Тимот се состои од четири дела:

  1. Едноставно echo, кој ја печати вредноста на променливата ${VCL_SHOW}
    echo "$VCL_SHOW"
  2. awk, кој бара линија (запис) каде првото поле, по прекршувањето на текстот, е „//“, а второто е „VCL.SHOW“.
    Awk ќе ја запише првата линија што одговара на овие обрасци, а потоа веднаш ќе престане да ја обработува.

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

Како што сугерира коментарот на линијата 119, ова служи за единствена цел за сигурно справување со случаите каде што VCL ќе упатува на датотеки со празни места во нивните имиња.

Ја коментирав оригиналната логика на обработка ${VCL_FILE} и се обиде да го смени редоследот на наредбите, но тоа не доведе до ништо. Сè ми работеше добро, но кога го започнав сервисот ми даде грешка.

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

Твојот таканаречен сед-фу... е всушност... ѓубре

Следната недела имав еден прилично слободен ден, па решив повторно да се позанимавам со овој билет. Се надевав дека во мојот мозок, некој процес во позадина цело ова време бараше решение за овој проблем и овој пат дефинитивно ќе разберам што се случува.

Бидејќи едноставното менување на кодот не помогна минатиот пат, само решив да го препишам почнувајќи од линијата 116. Во секој случај, постоечката шифра беше глупава. И нема апсолутно никаква потреба да го користите read.

Повторно гледајќи ја грешката:
sh: echo: broken pipe — ехото се појавува на две места во оваа команда, но се сомневам дека првото е поверојатниот виновник (или барем соучесник). Ниту Awk не влева доверба. И во случај навистина да е awk | {read; echo} дизајнот води до сите овие проблеми, зошто да не го замениме? Оваа команда од една линија не ги користи сите карактеристики на awk, па дури и оваа дополнителна read во прилог.

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

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

Краток опис за тоа како точно sed го обработува влезот може да се најде во неговиот прирачник за GNU. Во изворите на сед симболот 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

Значи, содржината на скриптата varnishreload ќе изгледа вака:

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

Горенаведената логика може накратко да се изрази на следниов начин:
Ако низата се совпаѓа со регуларен израз // VCL.SHOW, потоа лакомо проголтајте го текстот што ги вклучува двата броја во оваа линија и зачувајте сè што останува по оваа операција. Емитирајте ја зачуваната вредност и завршете ја програмата.

Едноставно, нели?

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

Извор: www.habr.com

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