Совети и трикови на Kubernetes: карактеристики на доброто исклучување во NGINX и PHP-FPM

Типичен услов при имплементирање на CI/CD во Kubernetes: апликацијата мора да може да не прифаќа нови барања на клиентот пред целосно да престане, и што е најважно, успешно да ги заврши постоечките.

Совети и трикови на Kubernetes: карактеристики на доброто исклучување во NGINX и PHP-FPM

Усогласеноста со оваа состојба ви овозможува да постигнете нула прекини за време на распоредувањето. Сепак, дури и кога користите многу популарни пакети (како NGINX и PHP-FPM), може да наидете на тешкотии што ќе доведат до наплив на грешки при секое распоредување...

Теорија. Како живее pod

Веќе детално објавивме за животниот циклус на мешунката овој член,. Во контекст на темата што се разгледува, нè интересира следново: во моментот кога подот влегува во состојба Прекинување, нови барања престануваат да се испраќаат до него (под избришани од листата на крајни точки за услугата). Така, за да избегнеме застој за време на распоредувањето, доволно е да го решиме проблемот со правилно запирање на апликацијата.

Треба да запомните и дека стандардниот грејс период е 30 секунди: после ова, подлогата ќе биде прекината и апликацијата мора да има време да ги обработи сите барања пред овој период. Имајте на ум: иако секое барање кое трае повеќе од 5-10 секунди е веќе проблематично, а благодатното исклучување повеќе нема да му помогне...

За подобро да разберете што се случува кога ќе заврши мешунката, само погледнете го следниов дијаграм:

Совети и трикови на Kubernetes: карактеристики на доброто исклучување во NGINX и PHP-FPM

А1, Б1 - Примање промени за состојбата на огништето
A2 - Поаѓање SIGTERM
Б2 - Отстранување на подлога од крајните точки
Б3 - Примање промени (списокот на крајни точки е променет)
Б4 - Ажурирајте ги правилата за 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). Целта на овој тест беше да се идентификува приближниот број на грешки (и дали ги има). Исто така, од дополнителни информации, треба да знаете дека просечното време на распоредување за секој pod беше околу 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. Завршете го вашиот процес.

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

  • додавање на пред-стоп кука што ќе почека неколку секунди;
  • проучување на конфигурациската датотека на нашиот заден дел за соодветните параметри.

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

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

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

PS

Друго од серијата совети и трикови K8s:

Извор: www.habr.com

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