نکات و ترفندهای Kubernetes: ویژگی های خاموش کردن برازنده در NGINX و PHP-FPM

یک شرایط معمولی هنگام پیاده‌سازی CI/CD در Kubernetes: برنامه باید بتواند قبل از توقف کامل درخواست‌های مشتری جدید را نپذیرد و مهمتر از همه، درخواست‌های موجود را با موفقیت تکمیل کند.

نکات و ترفندهای Kubernetes: ویژگی های خاموش کردن برازنده در NGINX و PHP-FPM

انطباق با این شرط به شما امکان می دهد در حین استقرار به زمان توقف صفر برسید. با این حال، حتی در هنگام استفاده از بسته‌های بسیار محبوب (مانند NGINX و PHP-FPM)، می‌توانید با مشکلاتی مواجه شوید که منجر به موجی از خطاها در هر استقرار می‌شود.

تئوری. غلاف چگونه زندگی می کند

ما قبلاً به طور مفصل در مورد چرخه زندگی یک غلاف منتشر کرده ایم این مقاله. در زمینه موضوع مورد بررسی، ما به موارد زیر علاقه مند هستیم: در لحظه ای که غلاف وارد حالت می شود پایان دادن، درخواست های جدید به آن ارسال نمی شود (pod حذف شده از لیست نقاط پایانی سرویس). بنابراین، برای جلوگیری از خرابی در حین استقرار، کافی است مشکل توقف برنامه را به درستی حل کنیم.

همچنین باید به یاد داشته باشید که دوره مهلت پیش فرض است 30 ثانیه: پس از این مدت، پاد خاتمه می یابد و برنامه باید قبل از این مدت زمان لازم را برای پردازش تمام درخواست ها داشته باشد. یادداشت: اگرچه هر درخواستی که بیش از 5 تا 10 ثانیه طول بکشد در حال حاضر مشکل ساز است و خاموش کردن دلپذیر دیگر به آن کمک نمی کند ...

برای درک بهتر اینکه وقتی یک غلاف خاتمه می یابد چه اتفاقی می افتد، فقط به نمودار زیر نگاه کنید:

نکات و ترفندهای Kubernetes: ویژگی های خاموش کردن برازنده در NGINX و PHP-FPM

A1، B1 - دریافت تغییرات در مورد وضعیت کوره
A2 - خروج SIGTERM
B2 - حذف یک غلاف از نقاط پایانی
B3 - دریافت تغییرات (لیست نقاط پایانی تغییر کرده است)
B4 - قوانین iptables را به روز کنید

لطفا توجه داشته باشید: حذف نقطه پایانی پاد و ارسال SIGTERM به صورت متوالی اتفاق نمی افتد، بلکه به صورت موازی انجام می شود. و با توجه به اینکه Ingress لیست به روز شده Endpoints را بلافاصله دریافت نمی کند، درخواست های جدید از مشتریان به پاد ارسال می شود که باعث ایجاد خطای 500 در هنگام پایان پاد می شود. (برای مطالب دقیق تر در مورد این موضوع، ما ترجمه شده). این مشکل باید به روش های زیر حل شود:

  • ارسال اتصال: سرصفحه‌های پاسخ را ببندید (اگر مربوط به یک برنامه HTTP باشد).
  • اگر امکان ایجاد تغییرات در کد وجود ندارد، مقاله زیر راه حلی را شرح می دهد که به شما امکان می دهد تا درخواست ها را تا پایان دوره برازنده پردازش کنید.

تئوری. چگونه NGINX و PHP-FPM فرآیندهای خود را خاتمه می دهند

NGINX

بیایید با NGINX شروع کنیم، زیرا همه چیز با آن کم و بیش واضح است. با غوطه ور شدن در تئوری، می آموزیم که NGINX یک فرآیند اصلی و چندین "کارگر" دارد - اینها فرآیندهای فرزندی هستند که درخواست های مشتری را پردازش می کنند. یک گزینه مناسب ارائه شده است: با استفاده از دستور nginx -s <SIGNAL> فرآیندها را در حالت خاموش کردن سریع یا خاموش کردن برازنده خاتمه دهید. بدیهی است که این گزینه دوم است که مورد علاقه ما است.

سپس همه چیز ساده است: باید به آن اضافه کنید preStop-hook فرمانی که یک سیگنال خاموش شدن برازنده ارسال می کند. این را می توان در Deployment، در بلوک کانتینر انجام داد:

       lifecycle:
          preStop:
            exec:
              command:
              - /usr/sbin/nginx
              - -s
              - quit

اکنون، هنگامی که پاد خاموش می شود، موارد زیر را در گزارش های کانتینر NGINX مشاهده خواهیم کرد:

2018/01/25 13:58:31 [notice] 1#1: signal 3 (SIGQUIT) received, shutting down
2018/01/25 13:58:31 [notice] 11#11: gracefully shutting down

و این بدان معناست که ما به آن نیاز داریم: NGINX منتظر می ماند تا درخواست ها تکمیل شوند و سپس فرآیند را از بین می برد. با این حال، در زیر یک مشکل رایج را نیز در نظر خواهیم گرفت که به دلیل آن، حتی با دستور nginx -s quit فرآیند به اشتباه خاتمه می یابد.

و در این مرحله کار با NGINX تمام شده است: حداقل از روی گزارش ها می توانید بفهمید که همه چیز همانطور که باید کار می کند.

مشکل PHP-FPM چیست؟ چگونه با خاموش شدن برازنده برخورد می کند؟ بیایید آن را بفهمیم.

PHP-FPM

در مورد PHP-FPM، اطلاعات کمی کمتر است. اگر تمرکز کنید دفترچه راهنمای رسمی طبق PHP-FPM، می گوید که سیگنال های POSIX زیر پذیرفته می شوند:

  1. SIGINT, SIGTERM - خاموش شدن سریع؛
  2. SIGQUIT - خاموش شدن برازنده (آنچه ما به آن نیاز داریم).

سیگنال های باقی مانده در این کار مورد نیاز نیستند، بنابراین از تجزیه و تحلیل آنها صرف نظر می کنیم. برای پایان صحیح فرآیند، باید قلاب preStop زیر را بنویسید:

        lifecycle:
          preStop:
            exec:
              command:
              - /bin/kill
              - -SIGQUIT
              - "1"

در نگاه اول، این تمام چیزی است که برای انجام یک خاموشی دلپذیر در هر دو کانتینر لازم است. با این حال، کار دشوارتر از آن چیزی است که به نظر می رسد. در زیر دو مورد وجود دارد که در آنها خاموشی دلپذیر جواب نداد و باعث در دسترس نبودن کوتاه مدت پروژه در حین استقرار شد.

تمرین. مشکلات احتمالی با خاموشی دلپذیر

NGINX

اول از همه، یادآوری این نکته مفید است: علاوه بر اجرای دستور nginx -s quit یک مرحله دیگر وجود دارد که ارزش توجه به آن را دارد. ما با مشکلی مواجه شدیم که در آن NGINX همچنان SIGTERM را به جای سیگنال SIGQUIT ارسال می‌کند و باعث می‌شود درخواست‌ها به درستی تکمیل نشود. موارد مشابهی را می توان یافت، برای مثال، اینجا. متأسفانه، ما نتوانستیم دلیل خاص این رفتار را تعیین کنیم: یک سوء ظن در مورد نسخه NGINX وجود داشت، اما تأیید نشد. علامت این بود که پیام‌هایی در گزارش‌های کانتینر NGINX مشاهده شد: "سوکت شماره 10 را در اتصال 5 باز کنید"، پس از آن غلاف متوقف شد.

ما می‌توانیم چنین مشکلی را مشاهده کنیم، برای مثال، از پاسخ‌های Ingress که نیاز داریم:

نکات و ترفندهای Kubernetes: ویژگی های خاموش کردن برازنده در NGINX و PHP-FPM
نشانگر کدهای وضعیت در زمان استقرار

در این مورد، ما فقط یک کد خطای 503 را از خود Ingress دریافت می کنیم: نمی تواند به ظرف NGINX دسترسی داشته باشد، زیرا دیگر در دسترس نیست. اگر به لاگ های کانتینر با NGINX نگاه کنید، آنها حاوی موارد زیر هستند:

[alert] 13939#0: *154 open socket #3 left in connection 16
[alert] 13939#0: *168 open socket #6 left in connection 13

پس از تغییر سیگنال توقف، ظرف شروع به توقف صحیح می کند: این با این واقعیت تأیید می شود که خطای 503 دیگر مشاهده نمی شود.

اگر با مشکل مشابهی مواجه شدید، منطقی است که بفهمید از چه سیگنال توقفی در ظرف استفاده شده است و قلاب preStop دقیقاً چه شکلی است. کاملاً ممکن است که دلیل دقیقاً در این باشد.

PHP-FPM... و بیشتر

مشکل PHP-FPM به روشی بی اهمیت توضیح داده شده است: منتظر تکمیل فرآیندهای فرزند نمی ماند، آنها را خاتمه می دهد، به همین دلیل است که خطاهای 502 در حین استقرار و سایر عملیات رخ می دهد. از سال 2005 چندین گزارش اشکال در bugs.php.net وجود دارد (به عنوان مثال اینجا и اینجا) که این مشکل را شرح می دهد. اما به احتمال زیاد چیزی در گزارش‌ها نخواهید دید: PHP-FPM تکمیل فرآیند خود را بدون هیچ خطا یا اعلان‌های شخص ثالث اعلام می‌کند.

شایان ذکر است که خود مشکل ممکن است تا حدی کمتر یا بیشتر به خود برنامه بستگی داشته باشد و مثلاً در نظارت ظاهر نشود. اگر با آن مواجه شدید، ابتدا یک راه حل ساده به ذهنتان می رسد: یک قلاب preStop اضافه کنید sleep(30). به شما این امکان را می‌دهد که تمام درخواست‌هایی را که قبلاً بوده‌اند تکمیل کنید (و ما درخواست‌های جدید را قبول نمی‌کنیم، از زمان pod قبلا قادر به پایان دادن، و پس از 30 ثانیه خود غلاف با یک سیگنال به پایان می رسد SIGTERM.

معلوم است که lifecycle برای ظرف به شکل زیر خواهد بود:

    lifecycle:
      preStop:
        exec:
          command:
          - /bin/sleep
          - "30"

با این حال، با توجه به 30 ثانیه sleep ما شدیدا ما زمان استقرار را افزایش خواهیم داد، زیرا هر pod خاتمه خواهد یافت کمترین 30 ثانیه که بد است. در این باره چه میتوان کرد؟

بیایید به طرف مسئول اجرای مستقیم برنامه بپردازیم. در مورد ما اینطور است PHP-FPMکه به طور پیش فرض بر اجرای فرآیندهای فرزند خود نظارت نمی کند: فرآیند اصلی فوراً خاتمه می یابد. شما می توانید این رفتار را با استفاده از دستورالعمل تغییر دهید process_control_timeout، که محدودیت های زمانی را برای پردازش های فرزند برای منتظر سیگنال های Master مشخص می کند. اگر مقدار را روی 20 ثانیه تنظیم کنید، این بیشتر کوئری‌های در حال اجرا در ظرف را پوشش می‌دهد و پس از تکمیل، فرآیند اصلی متوقف می‌شود.

با این آگاهی، به آخرین مشکل خود برگردیم. همانطور که گفته شد، Kubernetes یک پلت فرم یکپارچه نیست: ارتباط بین اجزای مختلف آن مدتی طول می کشد. این امر به ویژه زمانی صادق است که عملکرد Ingresses و سایر اجزای مرتبط را در نظر بگیریم، زیرا به دلیل چنین تاخیری در زمان استقرار، به راحتی می توان 500 خطا را دریافت کرد. به عنوان مثال، ممکن است در مرحله ارسال درخواست به بالادست خطا رخ دهد، اما "تأخیر زمانی" تعامل بین اجزا بسیار کوتاه است - کمتر از یک ثانیه.

بنابراین، در مجموع با بخشنامه ای که قبلا ذکر شد process_control_timeout می توانید از ساختار زیر استفاده کنید lifecycle:

lifecycle:
  preStop:
    exec:
      command: ["/bin/bash","-c","/bin/sleep 1; kill -QUIT 1"]

در این صورت تاخیر را با دستور جبران می کنیم sleep و زمان استقرار را تا حد زیادی افزایش ندهید: آیا تفاوت محسوسی بین 30 ثانیه و یک ثانیه وجود دارد؟.. در واقع، این process_control_timeoutو lifecycle در صورت تأخیر فقط به عنوان "شبکه ایمنی" استفاده می شود.

به طور کلی رفتار توصیف شده و راه حل مربوطه نه تنها برای PHP-FPM اعمال می شود. وضعیت مشابهی ممکن است در هنگام استفاده از زبان ها/چارچوب های دیگر به یک شکل به وجود بیاید. اگر نمی توانید خاموشی دلپذیر را به روش های دیگر برطرف کنید - به عنوان مثال، با بازنویسی کد به طوری که برنامه به درستی سیگنال های خاتمه را پردازش کند - می توانید از روش توصیف شده استفاده کنید. شاید زیباترین نباشد، اما کار می کند.

تمرین. تست بارگذاری برای بررسی عملکرد غلاف

تست بار یکی از راه‌های بررسی نحوه عملکرد کانتینر است، زیرا این روش هنگام بازدید کاربران از سایت، آن را به شرایط واقعی جنگ نزدیک‌تر می‌کند. برای تست توصیه های بالا می توانید از Yandex.Tankom: تمام نیازهای ما را کاملاً پوشش می دهد. در زیر نکات و توصیه هایی برای انجام آزمایش با یک مثال واضح از تجربه ما به لطف نمودارهای Grafana و Yandex.Tank آورده شده است.

مهمترین چیز اینجاست گام به گام تغییرات را بررسی کنید. پس از افزودن یک اصلاح جدید، تست را اجرا کنید و ببینید آیا نتایج نسبت به آخرین اجرا تغییر کرده است یا خیر. در غیر این صورت، شناسایی راه حل های ناکارآمد دشوار خواهد بود و در دراز مدت فقط می تواند آسیب برساند (مثلاً افزایش زمان استقرار).

نکته ظریف دیگر این است که به سیاهههای مربوط به کانتینر در طول خاتمه آن نگاه کنید. آیا اطلاعات مربوط به خاموشی برازنده در آنجا ثبت شده است؟ آیا هنگام دسترسی به منابع دیگر (مثلاً به یک کانتینر PHP-FPM همسایه) در گزارش ها خطایی وجود دارد؟ خطا در خود برنامه (مانند مورد NGINX که در بالا توضیح داده شد)؟ امیدوارم اطلاعات مقدماتی این مقاله به شما کمک کند تا درک بهتری از اتفاقاتی که برای کانتینر در حین خاتمه آن می‌افتد را داشته باشد.

بنابراین، اولین اجرای آزمایشی بدون انجام شد lifecycle و بدون دستورالعمل های اضافی برای سرور برنامه (process_control_timeout در PHP-FPM). هدف از این آزمایش شناسایی تعداد تقریبی خطاها (و وجود یا خیر) بود. همچنین از اطلاعات تکمیلی باید بدانید که میانگین زمان استقرار برای هر پاد حدود 5-10 ثانیه تا آماده شدن کامل آن بوده است. نتایج عبارتند از:

نکات و ترفندهای Kubernetes: ویژگی های خاموش کردن برازنده در NGINX و PHP-FPM

پانل اطلاعات Yandex.Tank 502 خطا را نشان می دهد که در زمان استقرار رخ داده و به طور متوسط ​​تا 5 ثانیه طول می کشد. احتمالاً این به این دلیل بود که درخواست‌های موجود به پاد قدیمی در زمان خاتمه خاتمه داده می‌شد. پس از این، 503 خطا ظاهر شد، که نتیجه یک کانتینر NGINX متوقف شده بود، که همچنین به دلیل backend، اتصالات را قطع کرد (که مانع از اتصال Ingress به آن شد).

بیایید ببینیم چگونه process_control_timeout در PHP-FPM به ما کمک می کند تا منتظر تکمیل فرآیندهای فرزند باشیم. اینگونه خطاها را تصحیح کنید استقرار مجدد با استفاده از این دستورالعمل:

نکات و ترفندهای Kubernetes: ویژگی های خاموش کردن برازنده در NGINX و PHP-FPM

در طول استقرار 500 دیگر هیچ خطایی وجود ندارد! استقرار موفقیت آمیز است، خاموش کردن برازنده است.

با این حال، لازم است مشکل کانتینرهای Ingress را به خاطر بسپاریم، درصد کمی از خطاهایی که ممکن است به دلیل تاخیر زمانی دریافت کنیم. برای اجتناب از آنها، تنها چیزی که باقی می ماند اضافه کردن یک ساختار با آن است sleep و استقرار را تکرار کنید. با این حال، در مورد خاص ما، هیچ تغییری قابل مشاهده نیست (دوباره، بدون خطا).

نتیجه

برای خاتمه دادن به این فرآیند، ما رفتار زیر را از برنامه انتظار داریم:

  1. چند ثانیه صبر کنید و سپس پذیرش اتصالات جدید را متوقف کنید.
  2. منتظر بمانید تا تمام درخواست‌ها تکمیل شوند و تمام اتصالات keepalive که درخواست‌ها را اجرا نمی‌کنند ببندید.
  3. روند خود را تمام کنید

با این حال، همه برنامه ها نمی توانند به این روش کار کنند. یک راه حل برای مشکل در واقعیت های Kubernetes این است:

  • اضافه کردن یک قلاب پیش از توقف که چند ثانیه صبر می کند.
  • مطالعه فایل پیکربندی باطن ما برای پارامترهای مناسب.

مثال با NGINX روشن می کند که حتی برنامه ای که در ابتدا باید سیگنال های خاتمه را به درستی پردازش کند، ممکن است این کار را انجام ندهد، بنابراین بررسی 500 خطا در طول استقرار برنامه بسیار مهم است. این همچنین به شما این امکان را می دهد که به مشکل به طور گسترده نگاه کنید و روی یک غلاف یا ظرف واحد تمرکز نکنید، بلکه به کل زیرساخت به عنوان یک کل نگاه کنید.

به عنوان یک ابزار تست، می توانید از Yandex.Tank در ارتباط با هر سیستم نظارتی استفاده کنید (در مورد ما، داده ها از Grafana با یک Backend Prometheus برای آزمایش گرفته شده است). مشکلات مربوط به خاموشی دلپذیر تحت بارهای سنگینی که معیار می تواند ایجاد کند به وضوح قابل مشاهده است و نظارت به تجزیه و تحلیل وضعیت با جزئیات بیشتر در حین یا بعد از آزمایش کمک می کند.

در پاسخ به بازخورد مقاله: شایان ذکر است که مشکلات و راه حل ها در رابطه با NGINX Ingress در اینجا شرح داده شده است. برای موارد دیگر، راه حل های دیگری نیز وجود دارد که ممکن است در مطالب زیر این مجموعه را در نظر بگیریم.

PS

موارد دیگر از سری نکات و ترفندهای K8s:

منبع: www.habr.com

اضافه کردن نظر