Standard requirement when implementing CI/CD in Kubernetes: the application must be able to stop accepting new client requests before full shutdown, and, importantly, successfully complete any existing requests.

Compliance with this condition allows for zero downtime during deployment. However, even when using very popular stacks (like NGINX and PHP-FPM), one can encounter difficulties that lead to a spike in errors during each deploymentâŠ
Theory. How a pod lives
We have already published details about the lifecycle of a pod . In the context of the topic at hand, we are interested in the following: at the moment when the pod transitions to a state Termineeritud, it stops receiving new requests (the pod is removed from the endpoint list for the service). Thus, to avoid downtime during deployment, it is sufficient for us to resolve the issue of correctly shutting down the application.
It is also important to remember that the grace period by default is : after this, the pod will be terminated and the application must complete all requests within this period. MĂ€rkus: however, any request that takes longer than 5-10 seconds is already problematic, and graceful shutdown won't help itâŠ
To better understand what happens when a pod shuts down, one can study the following diagram:

A1, B1 â Receiving changes about the pod's state
A2 â Sending SIGTERM
B2 â Removing the pod from the endpoints
B3 â Receiving changes (the list of endpoints has changed)
B4 â Updating iptables rules
Note: the removal of the pod's endpoint and sending SIGTERM do not occur sequentially, but in parallel. Because the Ingress does not immediately receive the updated list of Endpoints, new requests from clients will be sent to the pod, resulting in 500 errors during the pod's termination. (we have translated more detailed material on this issue) )Sending Connection: close in response headers (if it is related to an HTTP application).
- If it is not possible to make changes to the code, then the article describes a solution that will allow requests to be fully processed until the end of the graceful period.
- Theory. How NGINX and PHP-FPM terminate their processes
Teooria. Kuidas NGINX ja PHP-FPM oma protsesse lÔpetavad
NGINX
Alustame NGINXiga, kuna selle kasutamine on enam-vĂ€hem selge. Vaatame teooriat, et teada saada, et NGINX-il on ĂŒks meistrikĂ€sk ja mitu âtöötajatâ â need on alamprotsessid, mis töötlevad kliendi pĂ€ringuid. On olemas mugav vĂ”imalus: kĂ€su abil nginx -s protsesside lĂ”petamiseks, kas kiire lĂ”ppemise reĆŸiimis vĂ”i sujuvas lĂ”petamises. Ilmselgelt huvitab meid viimane variant.
Edasi on kÔik lihtne: tuleb lisada kÀsk, mis saadab signaali sujuvaks lÔpetamiseks. Seda saab teha paigutuses, konteineri plokis:
lifecycle:
preStop:
exec:
command:
- /usr/sbin/nginx
- -s
- quitNĂŒĂŒd, kui pod lĂ”petab töö, nĂ€eme NGINX konteineri logides jĂ€rgmist:
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 Ja see tÀhendaks, et kÔik on nagu peab: NGINX ootab pÀringute tÀitmist, mille jÀrel lÔpetab protsessi. Siiski, allpool kÀsitletakse tavalist probleemi, mille tÔttu isegi kÀsu nginx -s quit protsess ei lÔpe Ôigesti.
NĂŒĂŒd oleme NGINX-iga Ă€rajÀÀnud; logidest on vĂ€hemalt selge, et kĂ”ik töötab nagu peab.
Kuidas on asjad PHP-FPM-iga? Kuidas see teostab sujuvat lÔpetamist? Uurime lÀhemalt.
PHP-FPM
PHP-FPM-i puhul on teave veidi vÀhem. Kui toetuda PHP-FPM-i kohta, siis seal rÀÀgitakse, et aktsepteeritakse jÀrgmisi POSIX-signaale:
-
SIGINT,SIGTERMâ kiire lĂ”petamine; -
SIGQUITâ sujuv lĂ”petamine (see, mis meid huvitab).
ĂlejÀÀnud signaale ei ole siin vaja, seega jĂ€tame need kĂ€sitlemata. Protsessi korrektseks lĂ”petamiseks on vajalik kirjutada jĂ€rgmine preStop-hĂŒppamine:
lifecycle:
preStop:
exec:
command:
- /bin/kill
- -SIGQUIT
- "1"Esmapilgul nĂ€ib, et see on kĂ”ik, mis vajalik sujuva lĂ”petamise teostamiseks mĂ”lemas konteineris. Siiski, ĂŒlesanne on keerulisem, kui tundub. Edasi vaatame kahte juhtumit, kus sujuv lĂ”petamine ei toiminud ja pĂ”hjustas projekti ajutise kĂ€ttesaamatuse juurutamise ajal.
Praktika. VÔimalikud probleemid sujuva lÔpetamisega
NGINX
Esiteks on kasulik meeles pidada: lisaks kĂ€su tĂ€itmisele nginx -s quit on veel ĂŒks etapp, millele tasub tĂ€helepanu pöörata. Oleme kokku puutunud probleemiga, kus NGINX saatis SIGTERM signaali hoopis SIGQUIT'i asemel, mistĂ”ttu pĂ€ringud ei lĂ”petatud Ă”igesti. Selliseid juhtumeid vĂ”ib leida, nĂ€iteks, . Kahjuks ei Ă”nnestunud meil selle kĂ€itumise konkreetset pĂ”hjust tuvastada: kahtlustati NGINX'i versioonides, kuid see ei leidnud kinnitust. SĂŒmptomaatika seisnes selles, et NGINX-i konteineri logides ilmusid jĂ€rgmised teated «open socket #10 left in connection 5», pĂ€rast mida pod peatati.
Sellist probleemi vÔime jÀlgida nÀiteks meie vajaliku Ingress'i vastustes:

Oleku koodide nÀitajad juurutamise hetkel
Antud juhul saame 503 veakoodi Ingress'ilt: see ei saa pöörduda NGINX-i konteineri poole, kuna see on juba mittesaadav. Kui vaadata NGINX-i konteineri logisid, on seal jÀrgmine:
[alert] 13939#0: *154 open socket #3 left in connection 16
[alert] 13939#0: *168 open socket #6 left in connection 13PÀrast stopp-signaali muutmist hakkab konteiner Ôigesti peatuma: seda tÔendab, et 503 viga ei kordu enam.
Kui olete sarnase probleemiga silmitsi seisnud, on mĂ”ttekas vĂ€lja selgitada, milline stopp-signaal kasutatakse konteineris ja kuidas tĂ€pselt preStop-hĂŒĂŒk vĂ€lja nĂ€eb. TĂ”enĂ€oliselt peitub probleem just selles.
PHP-FPM⊠ja mitte ainult
PHP-FPM-i probleem on kirjeldatud triviaalsetena: see ei oota lasteprotsesside lĂ”petamist, terminiseerib need, mistĂ”ttu tekivad juurutamise ja muude toimingute kĂ€igus 502 vead. bugs.php.net lehelt on alates 2005. aastast mitmeid veateateid (nĂ€iteks, ja ), mis kirjeldavad seda probleemi. Logides, tĂ”enĂ€oliselt, midagi ei nĂ€e: PHP-FPM teatab oma protsessi lĂ”petamisest ilma igasuguste vigadeta vĂ”i kĂŒlgnevate teavitusteta.
Tuleb tĂ€psustada, et probleem ise vĂ”ib sĂ”ltuda vĂ€hemal vĂ”i suuremal mÀÀral rakendusest ning ei pruugi nĂ€iteks jĂ€lgimises vĂ€lja paista. Kui te siiski kokku puutute, siis tuleb kĂ”igepealt meelde lihtne lahendus: lisada preStop-hĂŒĂŒk koos sleep(30). See vĂ”imaldab lĂ”petada kĂ”ik eelnevad pĂ€ringud (ja me ei aktseptee uusi, kuna pod juba on seisundis Termineeritud), ja 30 sekundi möödudes lĂ”petab pod end signaaliga SIGTERM.
Selgub, et lifecycle konteineri jaoks nÀeb vÀlja jÀrgmiselt:
lifecycle:
preStop:
exec:
command:
- \/bin\/sleep
- "30" Kuid 30-sekundilise aja mÀÀramine sleep meie suuresti pikendab juurutamise aega, kuna iga pod lÔpetatakse minimaalselt 30 sekundi pÀrast, mis on halb. Mida sellega teha?
Suundume poole, kes vastutab rakenduse otsese tÀitmise eest. Meie puhul on see PHP-FPM, mis vaikimisi ei jÀlgi oma child-protsesse: peaprotsess lÔpetatakse kohe. Seda kÀitumist saab muuta juhisega process_control_timeout, mis mÀÀrab ajapiirangud signaalide ootamiseks peast alaealistele protsessidele. Kui seada vÀÀrtus 20 sekundile, katab see enamiku kaasasolevatest pÀringutest konteineris, ja pÀrast nende lÔpetamist lÔpetatakse peaprotsess.
Selle teadmisega pöördume tagasi meie viimase probleemi juurde. Nagu juba mainitud, ei ole Kubernetes monoliitne platvorm: erinevate komponentide vahelise suhtlemise jaoks kulub aega. See kehtib eriti siis, kui vaatame Ingresside ja teiste seotud komponentide tööd, kuna juurutamise ajal vĂ”ib isegi tekkida 500-ndate vigu. NĂ€iteks vĂ”ib viga tekkida pĂ€ringu saatmisel upstream'i, kuid tegelik 'ajalugu' komponentide vahelise suhtlemise vahel on ĂŒsna lĂŒhike â vĂ€hem kui sekund.
Seega, kokkuvÔttes koos juba mainitud juhisega process_control_timeout vÔib kasutada jÀrgmist konstruktsiooni lifecycle:
lifecycle:
preStop:
exec:
command: ["/bin/bash","-c","/bin/sleep 1; kill -QUIT 1"] Sellisel juhul kompenseerime viivituse kĂ€sklusega sleep ja ei pikenda juurutamise aega: kas pole vahe 30 sekundi ja ĂŒhe vahel mĂ€rgatav? PĂ”himĂ”tteliselt on "peamine töö" just see, process_control_timeout, vaid lifecycle on lihtsalt kasutatud "kindlustuseks" viivituse korral.
Ăldiselt kĂ€itumine ja vastav lahendus ei puuduta ainult PHP-FPM. Sarnane olukord vĂ”ib tekkida ka teiste programmikeelte/raamistikudega. Kui teised meetodid ei aita graceful shutdown'i parandada â nĂ€iteks, kui kood on ĂŒmber kirjutatud nii, et rakendus töötleb lĂ”petamis signaale Ă”igesti â vĂ”ib rakendada kirjeldatud viisi. Olgu see mitte kĂ”ige ilusam, kuid see töötab.
Praktika. Koormustestimine podi töö kontrollimiseks
Koormustestimine on ĂŒks meetod, millega kontrollitakse, kuidas konteiner töötab, kuna see protseduur simuleerib reaalseid lahingutingimusi, kui saidile pÀÀsevad juurde kasutajad. Ălaltoodud soovituste testimiseks saab kasutada : see katab suurepĂ€raselt kĂ”ik meie vajadused. Edasi antakse nĂ€punĂ€iteid ja soovitusi testimise lĂ€biviimiseks koos selge â tĂ€nu Grafana diagrammidele ja Yandex.Tanki nĂ€idenditele â nĂ€itega meie kogemusest.
Peamine siin on kontrollida muutusi etappidena. PÀrast uue paranduse lisamist kÀivitage testimine ja vaadake, kas tulemused on vÔrreldes eelmise kÀivitamisega muutunud. Vastasel juhul on raske tuvastada ebaefektiivseid lahendusi ja tulevikus vÔivad need isegi kahju tekitada (nt suurendades juurutamise aega).
Teine nĂŒanss on vaadata konteineri logisid selle lĂ”petamise ajal. Kas seal fikseeritakse teavet graceful shutdowni kohta? Kas logides on vigu, kui pöörduda teistele ressurssidele (nt naaberkonteiner PHP-FPM)? Kas rakenduse enda vead (nagu ĂŒlalpool kirjeldatud NGINX-i puhul)? Loodan, et selle artikli sissejuhatav teave aitab paremini mĂ”ista, mis siis juhtub konteineriga selle lĂ”petamise ajal.
Nii et esimene testimine toimus ilma lifecycle ja ilma tĂ€iendavate direktiivide jaoks rakendusserveris (process_control_timeout PHP-FPM-is). Selle testi eesmĂ€rk oli tuvastada ligikaudne vigade arv (ja kas neid ĂŒldse on). Samuti tuleb tĂ€iendavast teabest teada, et iga podi juurutamise keskmine aeg oli umbes 5-10 sekundit kuni tĂ€ieliku valmiduseni. Tulemused on jĂ€rgmised:

Yandex.Tanki teabepaneelil on nĂ€ha 502 vigade tĂ”us, mis toimus juurutamise hetkel ja kesta keskmiselt kuni 5 sekundit. Eeldatakse, et see katkestas olemasolevad pĂ€ringud vanale podâile, kui see lĂ”petati. PĂ€rast seda ilmnesid 503 vead, mis tulenesid peatatud NGINX-konteinerist, mis samuti katkestas ĂŒhendused pĂ”hiteenusega (mille tĂ”ttu ei saanud Ingress sellele ĂŒhenduda).
Vaadakem, kuidas process_control_timeout PHP-FPM aitab meil oodata child-protsesside lÔpetamist, s.t. sellised vead parandada. Kordusjuurutamine juba selle direktiivi kasutamisega:

500-nd vigu ajal vigu enam ei ole! Deployimine lÀheb edukalt ja graceful shutdown toimib.
Siiski tasub meenutada hetke Ingress-konteineritega, kus me vÔime saada vÀikese protsendi vigu ajutise viivituse tÔttu. Nende vÀltimiseks jÀÀb alles struktuuri lisamine, sleep ja deployimine kordamine. Kuid meie konkreetses olukorras ei olnud muutusi nÀha (vigu ei ole jÀlle).
KokkuvÔte
Protsessi korrektselt lÔpetamiseks ootame rakenduselt jÀrgmist kÀitumist:
- Oodata paar sekundi, pĂ€rast mida lĂ”petada uute ĂŒhenduste vastuvĂ”tmine.
- Oodata, kuni kĂ”ik pĂ€ringud on lĂ”petatud ning sulgeda kĂ”ik keepalive-ĂŒhendused, mis ei tĂ€ida pĂ€ringuid.
- LÔpetada oma protsess.
Kuid mitte kĂ”ik rakendused ei oska nii töötada. Ăhe lahendusena Kuberneteses on:
- pre-stop hooki lisamine, mis ootab paar sekundit;
- meie backend'i konfiguratsioonifaili uurimine vastavate parameetrite osas.
NÀide NGINX-ist aitab mÔista, et isegi rakendus, mis algselt peaks korrektselt lÔpetamissignaale töötlema, ei pruugi seda teha, seega on kriitiline kontrollida 500 vigu rakenduse deployimise ajal. Samuti vÔimaldab see probleemile laiemalt vaadata ja mitte keskenduda eraldi pod'ile vÔi konteinerile, vaid vaadata kogu infrastruktuuri tervikuna.
Testimise tööriigina vĂ”ib kasutada Yandex.Tank'i koos mis tahes monitooringusĂŒsteemiga (meie juhul kasutati testimiseks andmeid Grafanast, mille backend oli Prometheus). Probleemid graceful shutdowniga on selgelt nĂ€htavad suurte koormate korral, mida vĂ”ib genereerida benchmark, ja monitooring aitab olukorda tĂ€psemalt analĂŒĂŒsida testi ajal vĂ”i pĂ€rast seda.
Vastates artikli tagasisidele: tuleb mÀrkida, et probleemid ja nende lahendused on siin kirjeldatud NGINX Ingress'i kontekstis. Teiste juhtumite jaoks on olemas teised lahendused, mida me vÔib-olla kÀsitleme jÀrgmistes materjalides.
P.S.
Teine osa K8s nÀpunÀidetest ja nippidest:
- «»;
- «»;
- «»;
- «».
Allikas: habr.com
