الوقوع في حفرة أرنب: قصة عن خطأ واحد في إعادة تشغيل الورنيش - الجزء الأول

ghostinushanka ، بعد أن سحق الأزرار في الدقائق العشرين الماضية ، كما لو كانت حياته تعتمد عليه ، يلجأ إلي بتعبير نصف متوحش في عينيه وابتسم ابتسامة عريضة - "يا صديق ، أعتقد أنني أفهم".


"انظر هنا" - يقول ، مشيرًا إلى أحد الرموز التي تظهر على الشاشة - "أراهن على قبعتي الحمراء أنه إذا أضفنا هنا ما أرسلته للتو" - يشير إلى جزء آخر من التعليمات البرمجية - "لم يعد الخطأ" سيتم عرضها. "


قليلا من الحيرة والتعب ، أقوم بتغيير تعبير sed الذي كنا نعمل عليه منذ بعض الوقت ، وقم بحفظ الملف وتشغيل systemctl varnish reload . اختفت رسالة الخطأ ...


"الرسائل التي تبادلتها مع المرشح" ، تابع زميلي ، بينما تتحول ابتسامته إلى ابتسامة حقيقية مليئة بالبهجة ، "لقد بزغ فجرًا لي أن هذه هي المشكلة نفسها تمامًا!"


كيف بدأ كل شيء


تفترض هذه المقالة فهم كيفية عمل bash و awk و sed و systemd. معرفة بالورنيش مرحب بها ولكن غير مطلوب.
تم تغيير مقتطفات الطوابع الزمنية.
مكتوب مع ghostinushanka .
هذا النص هو ترجمة للنص الأصلي المنشور باللغة الإنجليزية قبل أسبوعين ؛ ترجمة boikoden .


تشرق الشمس من خلال النوافذ البانورامية في صباح خريف دافئ آخر ، تقع كأس المشروب الطازج المحتوي على الكافيين بعيدًا عن لوحة المفاتيح ، والسمفونية المفضلة للأصوات الأصوات في سماعات الرأس ، وتتداخل مع حفيف لوحات المفاتيح الميكانيكية ، ويلعب العنوان المشؤوم "Investigate varnishre" بهدوء الإدخال الأول في قائمة التذاكر المتراكمة على لوحة kanban. sh: echo: خطأ I / O في التدريج "(تحقق من" varnishreload sh: echo: I / O error "في المرحلة). عندما يتعلق الأمر بالورنيش ، لا توجد أخطاء ولا يمكن أن يكون هناك مكان ، حتى لو لم تترجم إلى أي مشاكل ، كما في هذه الحالة.


بالنسبة لأولئك الذين ليسوا على دراية بالورنيش ، هذا هو برنامج نصي بسيط يستخدم لإعادة تحميل تكوين الورنيش - يسمى أيضًا VCL.


كما يوحي اسم التذكرة ، حدث خطأ على أحد الخوادم على المسرح ، وبما أنني كنت متأكدًا من أن توجيه الورنيش على المسرح يعمل بشكل صحيح ، فقد افترضت أن هذا سيكون خطأً بسيطًا. لذلك ، مجرد رسالة وصلت إلى دفق إخراج مغلق بالفعل. آخذ البطاقة إلى نفسي ، وأنا واثق تمامًا من أنني سأضع علامة عليها جاهزة في أقل من 30 دقيقة ، وأرتب على الكتف لتنظيف اللوحة من القمامة التالية والعودة إلى أمور أكثر أهمية.


تحطيم جدار بسرعة 200 كم / ساعة


بعد أن فتحت ملف varnishreload ، على أحد الخوادم التي تشغّل Debian Stretch ، رأيت varnishreload بطول أقل من 200 سطر.


بعد تشغيل البرنامج النصي ، لم ألاحظ أي شيء يمكن أن يؤدي إلى حدوث مشاكل عند تشغيله عدة مرات مباشرةً من الجهاز الطرفي.


في النهاية ، هذه مرحلة ، حتى لو انهارت ، فلن يشتكي أحد ، حسنًا ... ليس كثيرًا. أقوم بتشغيل البرنامج النصي ومعرفة ما سيتم كتابته إلى الجهاز ، لكن لا يمكنني رؤية أي أخطاء.


يبدأ زوجان آخران في التأكد من أنني لا أستطيع إعادة إنتاج الخطأ دون بذل أي جهد إضافي ، وبدأت في معرفة كيفية تغيير هذا البرنامج النصي وجعله لا يزال يخطئ.


هل يمكن للبرنامج النصي تجاوز STDOUT (باستخدام > &- )؟ أو STDERR؟ لا أحد يعمل نتيجة لذلك.


من الواضح أن systemd يعدل بطريقة ما بيئة بدء التشغيل ، لكن كيف ولماذا؟
أقوم بقطع وتعديل varnishreload ، مضيفًا set -x مباشرةً تحت shebang ، على أمل أن varnishreload إخراج تصحيح البرنامج النصي قليلاً.


تم إصلاح الملف ، لذا أعيد تشغيل الورنيش وأرى أن التغيير قد كسر كل شيء تمامًا ... العادم عبارة عن فوضى كاملة ، حيث يوجد الكثير من الشفرات التي تشبه C. حتى التمرير في المحطة لا يكفي للعثور على المكان الذي تبدأ منه. أنا مرتبك تماما. هل يمكن أن يؤثر وضع التصحيح على عمل البرامج التي يتم تشغيلها في برنامج نصي؟ لا ، هذا هراء. حشرة في الصدفة؟ تندفع عدة سيناريوهات محتملة في رأسي مثل الصراصير في اتجاهات مختلفة. أفرغ كوب من مشروب كامل الكافيين على الفور ، رحلة سريعة إلى المطبخ لتجديد المخزون و ... دعنا نذهب. أفتح البرنامج النصي وننظر إلى shebang: #!/bin/sh .


/bin/sh هو ببساطة bash symlink ، لذلك يتم تفسير البرنامج النصي في وضع متوافق مع POSIX ، أليس كذلك؟ كان هناك! القشرة الافتراضية في دبيان هي شرطة ، وهذا هو بالضبط ما يشير إليه /bin/sh .


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

من أجل المحاكمة ، لقد غيرت shebang إلى #!/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 إلى الأمر التالي من خلال توجيه VLC_SHOW . ثم ماذا يحدث بعد ذلك؟


أولاً ، يستخدم varnishadm ، وهو جزء من حزمة تثبيت الورنيش ، لتكوين الورنيش دون إعادة التشغيل.


يتم استخدام vcl.show -v لإخراج تكوين VCL بأكمله المحدد في ${VCL_NAME} إلى STDOUT.


لعرض تكوين VCL النشط الحالي ، بالإضافة إلى العديد من الإصدارات السابقة من تكوينات توجيه الورنيش التي لا تزال في الذاكرة ، يمكنك استخدام varnishadm vcl.list ، والذي سيكون 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} يحتوي على التكوين الكامل ${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 فو ... حقا ... القمامة


تبين أن الأسبوع التالي كان يومًا مجانيًا ، لذا قررت مرة أخرى تناول هذه التذكرة. كنت آمل أنه في ذهني ، كانت هناك عملية خلفية في كل هذا الوقت تبحث عن حل لهذه المشكلة ، وهذه المرة أفهمها بالتأكيد.


منذ آخر مرة لم يساعد فيها تغيير بسيط في الرمز ، قررت فقط إعادة كتابته بدءًا من السطر 116. في أي حال ، كان الرمز الحالي رديء. وليس هناك حاجة على الإطلاق لاستخدام read .


النظر في الخطأ مرة أخرى:
sh: echo: broken pipe - في هذا الأمر ، يكون الصدى في مكانين ، لكنني أظن أن الأول هو الجاني الأكثر احتمالًا (جيدًا ، أو على الأقل شريك). Awk هو أيضا غير موثوق بها. وفي حال كان الأمر awk | {read; echo} awk | {read; echo} awk | {read; echo} البناء يؤدي إلى كل هذه المشاكل ، لماذا لا تحل محلها؟ لا يستخدم هذا الأمر المكون من سطر واحد جميع ميزات awk ، وحتى هذه read الإضافية في الملحق.


نظرًا لوجود تقرير عن sed الأسبوع الماضي ، أردت تجربة مهاراتي المكتسبة حديثًا وتبسيط echo | awk | { read; echo} echo | awk | { read; echo} echo | awk | { read; echo} في echo | sed أكثر قابلية للفهم echo | sed echo | sed . على الرغم من أن هذا ليس بالتأكيد أفضل طريقة للكشف عن خطأ ما ، فقد اعتقدت أنني على الأقل سأحاول استخدام لعبة sed-فو الخاصة بي وربما أتعلم شيئًا جديدًا حول المشكلة. في هذه العملية ، طلبت من زميلي ، مؤلف التقرير عن sed ، مساعدتي في التوصل إلى نص أكثر فاعلية.


لقد أسقطت محتويات varnishadm vcl.show -v "$VCL_NAME" في الملف ، لذلك يمكنني التركيز على كتابة نص sed بدون أي متاعب مرتبطة بإعادة تحميل الخدمة.


يمكن العثور على وصف موجز لكيفية تعامل sed مع المدخلات في دليل GNU . في مصادر sed ، يتم تحديد الحرف \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 ، وقد يكون هناك العديد منهم في الإدخال. هذا هو السبب في أن 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 ، // VCL.SHOW بتناول النص الذي يتضمن كلا الرقمين في هذا السطر ، ثم احفظ كل ما تبقى بعد هذه العملية. إعطاء القيمة المحفوظة وإنهاء البرنامج.


بسيط ، أليس كذلك؟


لقد سررنا بالكتابة النصية وحقيقة أنها تحل محل الكود الأصلي. أعطت جميع الاختبارات الخاصة بي النتائج المرجوة ، لذلك قمت بتغيير "الورنيش" على الخادم وركضت systemctl reload varnish مرة أخرى. خطأ القذرة echo: write error: Broken pipe ضحك echo: write error: Broken pipe مرة أخرى في وجوهنا. كان هناك مؤشر غمز في انتظار إدخال أمر جديد في الفراغ المظلم للمحطة ...

Source: https://habr.com/ru/post/ar477968/


All Articles