Falling Down the Rabbit Hole. Լաքի վերագործարկման մեկ սխալի պատմություն - Մաս 1

Ղոստինուշանկա, նախորդ 20 րոպեների կոճակները խփելուց հետո, ասես իր կյանքը դրանից էր կախված, աչքերի կիսավիժական արտահայտությամբ ու խորամանկ քմծիծաղով շրջվում է դեպի ինձ.

«Նայիր այստեղ», - ասում է նա՝ մատնացույց անելով էկրանի հերոսներից մեկին, «Ես գրազ եմ գալիս իմ կարմիր գլխարկի վրա, որ եթե այստեղ ավելացնենք այն, ինչ ես ուղարկել եմ քեզ», - մատնացույց անելով կոդի մեկ այլ բաժին, - «սխալն այլևս չի լինի: ցուցադրվի»։

Մի փոքր շփոթված և հոգնած, ես փոխում եմ sed հայտարարությունը, որի վրա մենք որոշ ժամանակ աշխատել ենք, պահում եմ ֆայլը և գործարկում systemctl varnish reload. Սխալի հաղորդագրությունն անհետացել է...

«Այն նամակները, որոնք ես փոխանակեցի թեկնածուի հետ,- շարունակեց իմ գործընկերը, երբ նրա ժպիտը վերածվում էր ուրախությամբ լի անկեղծ ժպիտի,- հանկարծ հասկացա, որ սա նույն խնդիրն է»:

Ինչպես սկսվեց ամեն ինչ

Հոդվածը ենթադրում է հասկանալ, թե ինչպես են աշխատում bash-ը, awk-ը, sed-ը և systemd-ը: Լաքի իմացությունը նախընտրելի է, բայց պարտադիր չէ։
Հատվածներում ժամանակի դրոշմակնիքները փոխվել են:
Գրված է Ղոստինուշանկա.
Այս տեքստը երկու շաբաթ առաջ անգլերեն հրատարակված բնագրի թարգմանությունն է. թարգմանությունը բոյիկոդեն.

Արևը շողում է համայնապատկերային պատուհանների միջով աշնանային մեկ այլ տաք առավոտ, մի բաժակ թարմ կոֆեինացված ըմպելիքը նստած է ստեղնաշարի կողքին, ականջակալներում հնչում է հնչյունների սիրված սիմֆոնիան՝ մեխանիկական ստեղնաշարի խշխշոցի վրա, և առաջին մուտքը Կանբանի տախտակի վրա մնացած տոմսերի ցուցակը զվարճալի կերպով փայլում է «Հետազոտել լաքի բեռնումը sh. echo: I/O սխալ բեմադրության մեջ» (հետազոտել «varnishreload sh: echo: I/O error» բեմադրության մեջ) ճակատագրական վերնագրով: Ինչ վերաբերում է լաքին, ապա սխալներ չկան և չեն կարող լինել, նույնիսկ եթե դրանք խնդիրներ չեն առաջացնում, ինչպես այս դեպքում:

Для тех, кто не знаком с լաքապատում, սա պարզ shell script է, որն օգտագործվում է կազմաձևումը վերաբեռնելու համար լաք - կոչվում է նաև VCL:

Ինչպես հուշում է տոմսի վերնագիրը, սխալը տեղի է ունեցել բեմի սերվերներից մեկի վրա, և քանի որ ես վստահ էի, որ լաքի երթուղիչը բեմում ճիշտ է աշխատում, ես ենթադրեցի, որ դա աննշան սխալ կլիներ: Այսպիսով, ընդամենը մի հաղորդագրություն, որն արդեն փակված ելքային հոսքի մեջ է մտել: Ես տոմս եմ վերցնում ինձ համար՝ լիովին վստահ լինելով, որ կնշեմ այն ​​պատրաստ է 30 րոպեից պակաս ժամանակում, թփթփացնում եմ ուսս՝ հաջորդ անպետք նյութերից տախտակը մաքրելու համար և կվերադառնամ ավելի կարևոր բաներին:

200 կմ/ժ արագությամբ բախվելով պատին

Ֆայլի բացում varnishreload, Debian Stretch-ով աշխատող սերվերներից մեկում ես տեսա 200 տողից պակաս երկարությամբ shell script:

Անցնելով սցենարը, ես չտեսա որևէ բան, որը կարող էր խնդիրներ առաջացնել այն մի քանի անգամ անմիջապես տերմինալից գործարկելիս:

Ի վերջո, սա փուլ է, եթե նույնիսկ կոտրվի, ոչ ոք չի բողոքի, լավ ... ոչ շատ: Ես գործարկում եմ սցենարը և տեսնում եմ, թե ինչ է դուրս գրվելու տերմինալում, բայց սխալներն այլևս չեն երևում:

Եվս մի քանի վազք՝ համոզվելու համար, որ չեմ կարող վերարտադրել սխալն առանց լրացուցիչ ջանքերի, և ես սկսում եմ պարզել, թե ինչպես փոխել այս սցենարը և այնպես անել, որ այն դեռ սխալվի:

Կարո՞ղ է սցենարն արգելափակել STDOUT-ը (օգտագործելով > &-)? Կամ STDERR? Երկուսն էլ վերջում չաշխատեցին:

Ակնհայտորեն systemd-ը ինչ-որ կերպ փոխում է գործարկման միջավայրը, բայց ինչպես և ինչու:
Միացնում եմ vim-ը և խմբագրում varnishreload, ավելացնելով set -x հենց շեբանգի տակ՝ հուսալով, որ սցենարի արդյունքի վրիպազերծումը որոշակի լույս կսփռի:

Ֆայլը ֆիքսված է, այնպես որ ես վերաբեռնում եմ լաքը և տեսնում եմ, որ փոփոխությունն ամբողջությամբ կոտրել է ամեն ինչ... Արտանետումը լրիվ խառնաշփոթ է, դրա մեջ C-ի նման տոննա կոդ կա: Անգամ տերմինալում պտտելը բավարար չէ՝ գտնելու, թե որտեղից է այն սկսվում: Ես լրիվ շփոթված եմ։ Կարո՞ղ է վրիպազերծման ռեժիմն ազդել սցենարով աշխատող ծրագրերի աշխատանքի վրա: Ոչ, հիմարություն: Սխա՞լ է պատյանում: Մի քանի հնարավոր սցենարներ գլխումս ուտիճների պես թռչում են տարբեր ուղղություններով։ Մի բաժակ կոֆեինով լի ըմպելիքն ակնթարթորեն դատարկվում է, արագ ուղևորություն դեպի խոհանոց՝ լիցքավորման համար և… արի գնանք: Ես բացում եմ սցենարը և ավելի մոտիկից նայում շեբանգին. #!/bin/sh.

/bin/sh - սա պարզապես bash symlink է, այնպես որ սցենարը մեկնաբանվում է POSIX-ի հետ համատեղելի ռեժիմով, այնպես չէ՞: Այն չկար։ Debian-ի լռելյայն կեղևը dash է, ինչը հենց այն է վերաբերում է /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} պարունակում է լաքի ամբողջական կոնֆիգուրացիան՝ առայժմ պարզ: Այժմ ես վերջապես հասկանում եմ, թե ինչու է dash արտադրանքը 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 րոպեն արդեն ավարտվել է վեց անգամ և, բացի այդ, առաջացել է ավելի առաջնահերթ խնդիր՝ մի կողմ մղելով մնացած դեպքերը։ Շաբաթվա մնացած մասը հագեցած էր մի շարք առաջադրանքներով և միայն թեթևակի թուլացավ sed-ի մասին զրույցով և թեկնածուի հետ հարցազրույցով: Սխալ խնդիր է varnishreload անվերադարձ կորած ժամանակի ավազների մեջ:

Ձեր այսպես կոչված sed-fu... իրականում... զիբիլ

Հաջորդ շաբաթը բավական ազատ օր ունեցավ, ուստի ես որոշեցի նորից վերցնել այս տոմսը: Ես հույս ունեի, որ իմ ուղեղում ինչ-որ ֆոնային գործընթաց այս ամբողջ ընթացքում փնտրում էր այս խնդրի լուծումը, և այս անգամ ես անպայման կհասկանամ, թե ինչն է սխալ:

Քանի որ նախորդ անգամ պարզապես կոդը փոխելը չօգնեց, ես պարզապես որոշեցի այն վերաշարադրել՝ սկսած 116-րդ տողից։ Ամեն դեպքում, գոյություն ունեցող օրենսգիրքը հիմար էր։ Եվ բացարձակապես կարիք չկա օգտագործելու read.

Կրկին նայելով սխալին.
sh: echo: broken pipe - այս հրամանում արձագանքը երկու տեղ է, բայց ես կասկածում եմ, որ առաջինն ավելի հավանական մեղավորն է (կամ գոնե հանցակիցը): Awk-ը նույնպես վստահություն չի ներշնչում: Իսկ այն դեպքում, երբ դա իսկապես այդպես է awk | {read; echo} դիզայնը հանգեցնում է այս բոլոր խնդիրների, ինչու չփոխարինել այն: Այս մեկ տող հրամանը չի օգտագործում awk-ի բոլոր հնարավորությունները և նույնիսկ այս լրացուցիչը read հավելվածում։

Անցած շաբաթվանից հաշվետվություն կար sedԵս ուզում էի փորձել իմ նոր ձեռք բերած հմտությունները և պարզեցնել echo | awk | { read; echo} ավելի հասկանալի ձևով echo | sed. Թեև սա, անկասկած, սխալը բռնելու լավագույն մոտեցումը չէ, ես մտածեցի, որ գոնե փորձեմ իմ sed-fu-ն և միգուցե նոր բան սովորեմ խնդրի մասին: Ճանապարհին ես խնդրեցի իմ գործընկերոջը՝ sed talk գրողին, օգնել ինձ ավելի արդյունավետ sed սցենար ստեղծելու համար:

Ես բաց թողեցի բովանդակությունը varnishadm vcl.show -v "$VCL_NAME" ֆայլի վրա, որպեսզի կարողանամ կենտրոնանալ sed սցենարը գրելու վրա՝ առանց ծառայության վերագործարկման դժվարությունների:

Հակիրճ նկարագրություն, թե ինչպես է sed-ը կարգավորում մուտքագրումը նրա GNU ձեռնարկը. Սեդ աղբյուրներում խորհրդանիշը n հստակորեն նշված է որպես գծի բաժանարար:

Մի քանի անցումներով և իմ գործընկերոջ խորհրդով մենք գրեցինք sed սցենար, որը տվեց նույն արդյունքը, ինչ ամբողջ բնօրինակ 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, և դրանցից մի քանիսը կարող են լինել մուտքային տվյալների մեջ: Ահա թե ինչու սկզբնական awk-ն ավարտվում է առաջին հանդիպումից հետո:

# шаг первый, вывести только строки с комментариями
# используя возможности 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, այնուհետև ագահորեն խժռեք տեքստը, որը ներառում է այդ տողում երկու թվերն էլ, և պահպանեք այն, ինչ մնացել է այս գործողությունից հետո: Թողարկեք պահված արժեքը և ավարտեք ծրագիրը:

Պարզ, այնպես չէ՞:

Մենք գոհ էինք sed script-ից և այն փաստից, որ այն փոխարինում է ամբողջ բնօրինակ կոդը: Իմ բոլոր թեստերը տվեցին ցանկալի արդյունքները, ուստի ես փոխեցի «varnishreload» սերվերի վրա և նորից վազեցի systemctl reload varnish. Կեղտոտ սխալ echo: write error: Broken pipe նորից ծիծաղեց մեր դեմքին: Աչքով անող կուրսորը սպասում էր տերմինալի մութ դատարկության մեջ նոր հրամանի մուտքագրմանը...

Source: www.habr.com

Добавить комментарий