Съвети и трикове на 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 - Сигнал за заминаване
B2 - Премахване на под от крайни точки
B3 - Получаване на промени (списъкът с крайни точки е променен)
B4 - Актуализирайте правилата на iptables

Моля, обърнете внимание: изтриването на групата на крайната точка и изпращането на SIGTERM не се случва последователно, а паралелно. И поради факта, че Ingress не получава незабавно актуализирания списък с крайни точки, нови заявки от клиенти ще бъдат изпратени до групата, което ще доведе до грешка 500 по време на прекратяването на групата (за по-подробни материали по този въпрос, ние преведено). Този проблем трябва да бъде решен по следните начини:

  • Изпращане на връзка: затворете в заглавките на отговора (ако това се отнася за HTTP приложение).
  • Ако не е възможно да направите промени в кода, следната статия описва решение, което ще ви позволи да обработвате заявки до края на гратисния период.

Теория. Как NGINX и PHP-FPM прекратяват своите процеси

Nginx

Да започнем с NGINX, тъй като с него всичко е повече или по-малко очевидно. Гмуркайки се в теорията, научаваме, че NGINX има един главен процес и няколко „работници“ - това са дъщерни процеси, които обработват клиентски заявки. Предлага се удобна опция: използване на командата nginx -s <SIGNAL> прекратява процесите или в режим на бързо изключване, или в режим на изящно изключване. Очевидно последният вариант ни интересува.

Тогава всичко е просто: трябва да добавите към preStop-кука команда, която ще изпрати грациозен сигнал за изключване. Това може да се направи в 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 по време на внедряване и други операции. Има няколко доклада за грешки на bugs.php.net от 2005 г. (напр тук и тук), който описва този проблем. Но най-вероятно няма да видите нищо в регистрационните файлове: PHP-FPM ще обяви завършването на своя процес без никакви грешки или известия от трети страни.

Струва си да се изясни, че самият проблем може да зависи в по-малка или по-голяма степен от самото приложение и може да не се прояви, например, при наблюдение. Ако го срещнете, първо ви идва на ум едно просто решение: добавете кука preStop с sleep(30). Това ще ви позволи да завършите всички заявки, които са били преди (и не приемаме нови, тъй като под вече способен Прекратяване), а след 30 секунди самата капсула ще приключи със сигнал SIGTERM.

Оказва се, че lifecycle за контейнера ще изглежда така:

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

Въпреки това, поради 30-сек sleep ние сме силно ще увеличим времето за разгръщане, тъй като всяка група ще бъде прекратена минимум 30 секунди, което е лошо. Какво може да се направи по въпроса?

Нека се обърнем към страната, отговорна за прякото изпълнение на приложението. В нашия случай е така PHP-FPMКойто по подразбиране не следи изпълнението на своите дъщерни процеси: Главният процес се прекратява незабавно. Можете да промените това поведение с помощта на директивата process_control_timeout, който определя времевите граници за дъщерните процеси да чакат сигнали от главния. Ако зададете стойността на 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 контейнер, който също прекъсна връзките поради бекенда (което попречи на Ingress да се свърже с него).

Да видим как process_control_timeout в PHP-FPM ще ни помогне да изчакаме завършването на дъщерните процеси, т.е. коригирайте подобни грешки. Повторно внедряване с помощта на тази директива:

Съвети и трикове на Kubernetes: функции за елегантно изключване в NGINX и PHP-FPM

Няма повече грешки по време на 500-то внедряване! Внедряването е успешно, грациозното изключване работи.

Въпреки това си струва да си припомним проблема с контейнерите на Ingress, малък процент грешки, в които може да получим поради забавяне във времето. За да ги избегнете, остава само да добавите структура с sleep и повторете разгръщането. В нашия конкретен случай обаче не се виждаха промени (отново няма грешки).

Заключение

За да прекратим процеса елегантно, очакваме следното поведение от приложението:

  1. Изчакайте няколко секунди и след това спрете да приемате нови връзки.
  2. Изчакайте всички заявки да завършат и затворете всички поддържащи активност връзки, които не изпълняват заявки.
  3. Прекратете процеса си.

Не всички приложения обаче могат да работят по този начин. Едно решение на проблема в реалностите на Kubernetes е:

  • добавяне на кука за предварително спиране, която ще изчака няколко секунди;
  • изучаване на конфигурационния файл на нашия бекенд за подходящите параметри.

Примерът с NGINX показва ясно, че дори приложение, което първоначално трябва да обработва правилно сигналите за прекратяване, може да не го направи, така че е критично да проверите за 500 грешки по време на внедряването на приложението. Това също така ви позволява да погледнете проблема по-широко и да не се фокусирате върху един пакет или контейнер, а да разгледате цялата инфраструктура като цяло.

Като инструмент за тестване можете да използвате Yandex.Tank във връзка с всяка система за мониторинг (в нашия случай данните бяха взети от Grafana с бекенд на Prometheus за теста). Проблемите с плавното изключване са ясно видими при големи натоварвания, които бенчмаркът може да генерира, а мониторингът помага да се анализира по-подробно ситуацията по време на или след теста.

В отговор на отзивите за статията: заслужава да се спомене, че проблемите и решенията са описани тук във връзка с NGINX Ingress. За останалите случаи има други решения, които може да разгледаме в следващите материали от поредицата.

PS

Други от серията съвети и трикове на K8s:

Източник: www.habr.com

Добавяне на нов коментар