Kushtet standarde në zbatimin e CI/CD në Kubernetes: aplikacioni duhet të jetë në gjendje të mos pranojë kërkesat e reja të klientëve para se të ndalojë plotësisht, dhe më e rëndësishmja — të përfundojë me sukses kërkesat ekzistuese.

Respektimi i këtij kushti lejon arritjen e ndalimit zero gjatë shpërndarjes. Megjithatë, edhe me përdorimin e grupeve shumë të njohura (si NGINX dhe PHP-FPM) mund të hasni vështirësi që do të çojnë në rritjen e gabimeve gjatë çdo shpërndarjeje…
Teoria. Si jeton pod
Kemi publikuar më parë me detaje mbi ciklin e jetës së një podi . Në kontekstin e temës së diskutuar, na intereson e volgende: në momentin kur pod-i kalon në gjendjen Duke u përfunduar, ai nuk pranon më kërkesa të reja (pod nga lista e endpoints për shërbimin). Pra, për të evituar ndalimin gjatë shpërndarjes, nga ana jonë mjafton të zgjidhim problemin e ndalimit të saktë të aplikacionit.
Gjithashtu duhet të mbani mend se periudha e miratimit (grace period) në mënyrë standarde është : pas kësaj, pod-i do të terinohet dhe aplikacioni duhet të ketë kohë për të përfunduar të gjitha kërkesat para kësaj periudhe. Shënim: megjithatë, çdo kërkesë që ekzekutohet më shumë se 5-10 sekonda, tashmë paraqet një problem, dhe ndalimi i butë nuk do t'i ndihmojë…
Për të kuptuar më mirë çfarë ndodh kur pod-i përfundohet, mjafton të shqyrtoni skemën e mëposhtme:

A1, B1 — Marrja e njoftimeve mbi gjendjen e pod-it
A2 — Dërgimi i SIGTERM
B2 — Shkëputja e pod-it nga endpoints
B3 — Marrja e njoftimeve (lista e endpoints është ndryshuar)
B4 — Përditësimi i rregullave të iptables
Vini re: Shkëputja e endpoint të pod-it dhe dërgimi i SIGTERM ndodhin jo në mënyrë të renditur, por paralelisht. Dhe për shkak se Ingress merr listën e përditësuar të Endpoints jo menjëherë, në pod do të dërgohen kërkesa të reja nga klientët, gjë që do të shkaktojë gabime 500 gjatë terinimit të pod-it (materiali më i detajuar mbi këtë çështje ne ). Kjo çështje duhet zgjidhur në mënyrë të mëposhtme:
- Dërgoni në titujt e përgjigjes Connection: close (nëse lidhet me aplikacionin HTTP).
- Nëse nuk ka mundësi që të bëni ndryshime në kod, atëherë në këtë artikull përshkruhet një zgjidhje që do të lejojë përpunimin e kërkesave deri në fund të periudhës së butë.
Teoria. Si i përfundojnë proceset NGINX dhe PHP-FPM
NGINX
Të fillojmë me NGINX, pasi me të gjithçka është më shumë se sa e qartë. Duke u zhytur në teori, mësojmë se NGINX ka një proces kryesor dhe disa "punëtorë" — këta janë proceset nëntime që përpunojnë kërkesat e klientëve. Është parashikuar një mundësi e përshtatshme: me ndihmën e komandës nginx -s mund të përfundoni proceset ose në mënyrë të shpejtë, ose në mënyrë të butë. Është e qartë se ne na intereson pikërisht varianti i fundit.
Pas kësaj, gjithçka është e thjeshtë: duhet të shtoni në një komandë që do të dërgojë sinjalin për ndalimin e butë. Kjo mund të bëhet në Deployment, në bllokun e kontejnerëve:
lifecycle:
preStop:
exec:
command:
- /usr/sbin/nginx
- -s
- quitTani, në momentin e përfundimit të punës së pod-it, në log-et e kontejnerit NGINX do të shohim sa vijon:
2018/01/25 13:58:31 [notice] 1#1: sinjali 3 (SIGQUIT) i marrë, po mbyllet
2018/01/25 13:58:31 [notice] 11#11: po mbyllet në mënyrë të butë Dhe kjo do të thotë atë që na nevojitet: NGINX pret përfundimin e përpunimit të kërkesave, pas të cilit vret procesin. Megjithatë, më poshtë do të shqyrtohet një problem i zakonshëm, për shkak të të cilit edhe me komandën nginx -s quit procesi mbyllet në mënyrë të gabuar.
Dhe në këtë fazë me NGINX kemi përfunduar: të paktën nga log-et mund të kuptojmë që gjithçka funksionon siç duhet.
Si janë gjërat me PHP-FPM? Si e përpunon ai mbylljen e butë? Le të shqyrtojmë.
PHP-FPM
Në rastin e PHP-FPM, informacioni është pak më i kufizuar. Nëse drejtohemi nga për PHP-FPM, do të flitet se pranohet sinjali i mëposhtëm POSIX:
-
SIGINT,SIGTERM— mbyllje e shpejtë; -
SIGQUIT— mbyllje e butë (atë që na nevojitet).
Sinjalet e tjera në këtë detyrë nuk kërkojmë, prandaj do ta lëmë këtë pjesë pa shqyrtim. Për përfundimin e saktë të procesit, do të nevojitet të shkruani preStop-hook-in e mëposhtëm:
lifecycle:
preStop:
exec:
command:
- /bin/kill
- -SIGQUIT
- "1"Në pamje të parë, kjo është gjithçka që kërkohet për të realizuar ndalimin e butë në të dy kontejnerët. Megjithatë, detyra është më e komplikuar se sa duket. Më poshtë janë trajtuar dy raste në të cilat ndalimi i butë nuk funksionoi dhe shkaktoi një mungesë të përkohshme të projektit gjatë shpërndarjes.
Praktika. Problemet e mundshme me ndalimin e butë
NGINX
Së pari, është e dobishme të mbani mend: përveç ekzekutimit të komandës nginx -s quit ka edhe një fazë tjetër mbi të cilën duhet të përqendroheni. Ne kemi hasur një problem, kur NGINX në vend të sinjalit SIGQUIT ende dërgonte SIGTERM, duke bërë që kërkesat të mos përfundonin në mënyrë të saktë. Raste të ngjashme mund të gjenden, për shembull, . fatke, ne arritëm të kuptojmë arsyen e këtij qëndrimi: kishte dyshime për versionet e NGINX, por kjo nuk u konfirmua. Simptomat i përkasin faktit që në logjet e kontenierit NGINX, u vërejtën mesazhe «open socket #10 left in connection 5», pas së cilës pod-i ndalej.
Mund të vërejmë një problem të tillë, për shembull, përmes përgjigjeve në Ingress-in tonë:

Të dhënat e status kodit në momentin e implementimit
Në këtë rast, marrim pikërisht kodin 503 të gabimit nga Ingress-i: ai nuk mund të arrijë në kontenierin NGINX, pasi ky i fundit nuk është më i disponueshëm. Nëse shohim në logjet e kontenierit me NGINX, ato përmbajnë këtë:
[alert] 13939#0: *154 open socket #3 left in connection 16
[alert] 13939#0: *168 open socket #6 left in connection 13Pas ndryshimit të sinjalit të ndalimit, kontenieri fillon të ndalet siç duhet: kjo konfirmohet nga fakti që nuk vërehet më gabimi 503.
Nëse keni hasur një problem të ngjashëm, ka kuptim të kuptoni se cili sinjal ndalimi përdoret në kontenier dhe si duket preStop-huk. Është e mundur që shkaku qëndron pikërisht aty.
PHP-FPM… dhe jo vetëm
Problemi me PHP-FPM përshkruhet thjesht: ai nuk pret përfundimin e proceseve të fëmijëve, i terminon ata, çka sjell gabime 502 gjatë implementimeve dhe operacioneve të tjera. Në bugs.php.net ka disa raporte gabimi që datojnë që nga viti 2005 (p.sh., dhe ), ku përshkruhet ky problem. Ndërsa, në logje ndoshta nuk do të shihni asgjë: PHP-FPM do të shpallë përfundimin e procesit të tij pa ndonjë gabim ose njoftime të tjera.
Vlen të theksohet se problemi mund të varet më shumë ose më pak nga vetë aplikacioni dhe të mos shfaqet, për shembull, në monitorim. Nëse gjithsesi hasni një të tillë, një zgjidhje e thjeshtë është të shtoni një preStop-huk me sleep(30). Ai do të lejojë që të përfundojnë të gjitha kërkesat që ishin deri atëherë (me e re nuk pranojmë, pasi pod-i janë ndodhet në gjendjen Duke u përfunduar), dhe pas 30 sekundash vetë pod-i do të mbyllet me sinjalin SIGTERM.
Kështu, lifecycle për kontenierin do të duket si më poshtë:
lifecycle:
preStop:
exec:
command:
- /bin/sleep
- "30" Megjithatë, për shkak të caktimit të 30 sekondave gjumë ne do të rrisim ndjeshëm kohën e implementimit, pasi çdo pod do të mbyllet minimumi për 30 sekonda, që është e keqe. Çfarë mund të bëjmë për këtë?
Le të kthehemi te ana që është përgjegjëse për realizimin e aplikacionit. Në rastin tonë, kjo PHP-FPM, i cili në mënyrë të paracaktuar nuk monitoron ekzekutimin e proceseve të fëmijëve:procesi kryesor mbyllet menjëherë. Mund ta ndryshoni këtë sjellje me anë të direktivës process_control_timeout, e cila përcakton kufijtë e kohës për pritjen e sinjaleve nga procesi kryesor nga proceset e fëmijëve. Nëse e vendosni vlerën në 20 sekonda, atëherë do të mbuloni shumicën e kërkesave që ekzekutohen në kontenier dhe pas përfundimit të tyre, procesi kryesor do të ndalet.
Me këtë njohuri, kthehemi te problemi ynë të fundit. Siç u përmend, Kubernetes nuk është një platformë monolitike: ndërveprimi midis komponentëve të saj kërkon pak kohë. Kjo është veçanërisht e rëndësishme kur shqyrtojmë funksionimin e Ingress-it dhe komponenteve të tjera përreth, pasi për shkak të një vonese të tillë në momentin e implementimit është lehtë të hasësh në shpërthime të gabimeve 500. Për shembull, gabimi mund të ndodhë në fazën e dërgimit të një kërkese te upstream, por vetë “vonesa” e ndërveprimit midis komponentëve është mjaft e shkurtër - më pak se një sekondë.
Prandaj, në përmbledhje me direktivën e përmendur më parë process_control_timeout mund të përdorni ndërtimin e mëposhtëm për lifecycle:
lifecycle:
preStop:
exec:
command: ["/bin/bash","-c","/bin/sleep 1; kill -QUIT 1"] Në këtë rast, ne kompensojmë vonesën me komandën gjumë dhe nuk e rrisim ndjeshëm kohën e implementimit: sepse ka një ndryshim të dukshëm midis 30 sekondave dhe një… Në thelb, “punën kryesore” e merr veten process_control_timeout, dhe lifecycle përdoret vetëm si “siguri” në rast të vonesës.
Në përgjithësi, sjellja e përshkruar dhe zgjidhja përkatëse nuk i përkasin vetëm PHP-FPM. Një situatë e ngjashme mund të ndodhë në lidhje me përdorimin e gjuhëve të tjera programimi/framework-eve. Nëse nuk mund të korrigjoni ndalimin e mbarë në mënyra të tjera - për shembull, duke e ridizajnuar kodin që aplikacioni të trajtojë siç duhet sinjalet e përfundimit - mund të aplikoni mënyrën e përshkruar. Megjithëse nuk është zgjidhja më e bukur, funksionon.
Praktika. Testimi i ngarkesës për të kontrolluar funksionimin e pod-it
Testimi i ngarkesës është një nga mënyrat për të kontrolluar se si funksionon kontenieri, pasi ky proces e afron në kushte reale, kur përdoruesit vizitojnë faqen. Për të testuar rekomandimet e mësipërme, mund të përdorni : ai përmbush të gjitha nevojat tona. Më poshtë janë disa këshilla dhe rekomandime për testimin me një shembull të qartë - falë grafikëve të Grafana dhe Yandex.Tank - nga përvoja jonë.
E rëndësishmja këtu është të kontrolloni ndryshimet në faza. Pas shtimit të një korrigjimi të ri, filloni testimin dhe shikoni nëse rezultatet kanë ndryshuar në krahasim me lançimin e kaluar. Në rast të kundërt, do të jetë e vështirë të identifikoni zgjidhjet joefikase, dhe në perspektivë mund të dëmtoni (p.sh., të rrisni kohën e lansimit).
Një tjetër nuancë është - kontrolloni logët e kontejnerit gjatë terminimit të tij. A regjistrohet atje informacioni për mbylljen e qetë? A ka gabime në logë gjatë aksesit në burime të tjera (p.sh., në kontejnerin fqinj PHP-FPM)? Gabimet e aplikacioneve vetë (siç ndodhi në rastin e përmendur më sipër me NGINX)? Shpresoj që informacioni fillestar nga ky artikull do të ndihmojë në kuptimin më mirë se çfarë ndodh me kontejnerin gjatë terminimit të tij.
Pra, lansimi i parë i testimit ndodhi pa lifecycle dhe pa udhëzime shtesë për serverin e aplikacioneve (process_control_timeout në PHP-FPM). Qëllimi i kësaj testi ishte të identifikonte numrin e përafërt të gabimeve (dhe nëse ka ndonjë). Po ashtu, nga informacioni shtesë, duhet të dimë se koha mesatare e lansimit të çdo pod-i ishte rreth 5-10 sekonda deri në gjendjen e plotë të gatishmërisë. Rezultatet janë si më poshtë:

Në panelin informativ të Yandex.Tank, shihet një shpërthim i gabimeve 502, i cili ndodhi në momentin e lansimit dhe vazhdoi në mesatare deri në 5 sekonda. Supozohet se regjistrimet ekzistuese të kërkesave te pod-i i vjetër u ndërprenë, kur ai u terminua. Pas kësaj dolën gabimet 503, që ishin rezultat i ndalimit të kontejnerit NGINX, i cili gjithashtu ndërpreu lidhjet për shkak të backend-it (për shkak të cilit Ingress nuk arriti të lidhej me të).
Të shohim si process_control_timeout në PHP-FPM do të na ndihmojë të presim përfundimin e proceseve të fëmijëve, dmth., të korrigjojmë këto gabime. Rilansimi tashmë duke përdorur këtë udhëzim:

Gjatë lansimit, nuk kishte më gabime 500! Lansimi kalon me sukses, mbyllja e qetë funksionon.
Megjithatë, është e rëndësishme të kujtojmë momentin me kontejnerët Ingress, një përqindje e vogël e gabimeve në të cilat mund të përjetojmë për shkak të vonesave të përkohshme. Në mënyrë për t'i shmangur ato, mbetet të shtojmë një konstrukcion me gjumë dhe të përsërisim lansimin. Megjithatë, në rastin tonë konkret, nuk pati ndryshime të dukshme (përsëri s'ka gabime).
Përfundimi
Për një përfundim të duhur të procesit, presim nga aplikacioni sjelljen e mëposhtme:
- Të presë disa sekonda, pasi të ndalojë pranimin e lidhjeve të reja.
- Të presë përfundimin e të gjitha kërkesave dhe të mbyllë të gjitha lidhjet e mbajtjes aktive, të cilat kërkesat nuk i realizojnë.
- Të përfundojë procesin e tij.
Megjithatë, jo të gjitha aplikacionet e dinë si të punojnë kështu. Një nga zgjidhjet e problemit në realitetin e Kubernetes është:
- shtimi i një hooks pre-stop, i cili do të presë disa sekonda;
- shqyrtimi i skedarit të konfigurimit të backend-it tonë përparë për parametra përkatës.
Shembulli me NGINX tregon se edhe një aplikacion, i cili fillimisht duhet të reagojë siç duhet ndaj sinjaleve për mbyllje, mund të mos e bëjë këtë, prandaj është kritik të kontrolloni praninë e gabimeve 500 gjatë lansimit të aplikacionit. Kjo gjithashtu lejon të shikohet problemi në mënyrë më të gjerë dhe të mos përqendrohemi në një pod të vetëm apo kontejner, por të shikojmë gjithë infrastrukturën si një tërësi.
Si një mjet për testim, mund të përdorim Yandex.Tank në bashkëpunim me çdo sistem monitorimi (në rastin tonë për testin u morën të dhënat nga Grafana me backend-in në formë Prometheus). Problemet me mbylljet e qeta janë të dukshme gjatë ngarkesave të mëdha që mund të gjenerohet nga benchmark, dhe monitorimi ndihmon për të analizuar situatën në detaje gjatë ose pas testit.
Duke u përgjigjur në feedback-un mbi artikullin: duhet të saktësohet se problemet dhe zgjidhjet e tyre janë përshkruar këtu në lidhje me NGINX Ingress. Për raste të tjera, ka zgjidhje të tjera, të cilat ndoshta do t'i shqyrtojmë në materialet e ardhshme të ciklit.
P.S.
Diçka tjetër nga cikli K8s tips & tricks:
- «»;
- «»;
- «»;
- «».
Burimi: habr.com
