Standardvillkor för implementering av CI/CD i Kubernetes: applikationen mÄste kunna sluta ta emot nya kundförfrÄgningar innan den stÀngs ner helt, och viktigast av allt - framgÄngsrikt slutföra redan pÄgÄende.

Att följa detta villkor gör det möjligt att uppnĂ„ noll driftstopp under distributionen. Men Ă€ven vid anvĂ€ndning av mycket populĂ€ra kombinationer (som NGINX och PHP-FPM) kan man stöta pĂ„ svĂ„righeter som leder till en ökning av fel vid varje distributionâŠ
Teori. Hur en pod lever
Vi har redan publicerat detaljer om livscykeln för en pod. . I kontexten av det aktuella Àmnet Àr vi intresserade av följande: i det ögonblicket nÀr en pod gÄr in i tillstÄndet Terminating, slutar nya förfrÄgningar att skickas till den (pod frÄn listan över endpoints för tjÀnsten). DÀrför rÀcker det för att undvika driftstopp under distributionen att lösa problemet med korrekt nedstÀngning av applikationen.
Det Ă€r ocksĂ„ viktigt att komma ihĂ„g att graceperioden som standard Ă€r : efter det kommer podden att termineras och applikationen mĂ„ste hinna behandla alla förfrĂ„gningar före denna period. AnmĂ€rkning: Ă€ven om varje förfrĂ„gan som tar mer Ă€n 5-10 sekunder redan Ă€r problematisk, och gracefull nedstĂ€ngning kommer inte att hjĂ€lpa denâŠ
För att bÀttre förstÄ vad som hÀnder nÀr en pod avslutar sin verksamhet Àr det tillrÀckligt att studera följande schema:

A1, B1 â Mottagande av förĂ€ndringar i podens tillstĂ„nd
A2 â Skicka SIGTERM
B2 â Ta bort podden frĂ„n endpoints
B3 â Mottagande av förĂ€ndringar (listan över endpoints har Ă€ndrats)
B4 â Uppdatering av iptables-regler
Observera att borttagning av endpointen för podden och skickande av SIGTERM inte sker sekventiellt, utan parallellt. Eftersom Ingress inte fÄr den uppdaterade listan över endpoints omedelbart, kommer nya förfrÄgningar frÄn kunder att skickas till podden, vilket kommer att orsaka 500 fel under termineringen av podden. (mer detaljerat material om denna frÄga har vi ). För att lösa detta problem finns det följande metoder:
- Skicka i svarshuvudena Connection: close (om det rör sig om en HTTP-applikation).
- Om det inte Àr möjligt att göra Àndringar i koden, beskrivs i artikeln nedan en lösning som gör det möjligt att behandla förfrÄgningar tills slutet av graceperioden.
Teori. Hur NGINX och PHP-FPM avslutar sina processer
NGINX
Vi börjar med NGINX, eftersom allt Ă€r relativt uppenbart med det. Genom att dyka ner i teorin lĂ€r vi oss att NGINX har en masterprocess och flera "workers" â dessa Ă€r barnprocesser som hanterar klientförfrĂ„gningar. Det finns en praktisk möjlighet: med hjĂ€lp av kommandot nginx -s avsluta processer antingen i fast shutdown-lĂ€ge eller i graceful shutdown-lĂ€ge. Det Ă€r uppenbart att vi Ă€r intresserade av det senare alternativet.
Sedan Àr det enkelt: vi mÄste lÀgga till i ett kommando som kommer att skicka en signal om graceful shutdown. Det kan göras i Deployment, i containerblocket:
lifecycle:
preStop:
exec:
command:
- /usr/sbin/nginx
- -s
- quitNu, vid nedstÀngning av podden, kommer vi att se följande i loggarna frÄn NGINX-kontainern:
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 Och det kommer att betyda det vi vill: NGINX vÀntar pÄ att slutföra begÀrningar, varefter det dödar processen. Nedan kommer vi dock att titta pÄ ett vanligt problem som gör att Àven med kommandot nginx -s quit processen avslutas onormalt.
Och vid detta stadium har vi avslutat med NGINX: Ätminstone kan vi förstÄ av loggarna att allt fungerar som det ska.
Hur ser det ut med PHP-FPM? Hur hanterar den graceful shutdown? LÄt oss reda ut det.
PHP-FPM
NÀr det gÀller PHP-FPM finns det mindre information. Om vi orienterar oss pÄ för PHP-FPM, kommer det att berÀttas att följande POSIX-signaler tas emot:
-
SIGINT,SIGTERMâ fast shutdown; -
SIGQUITâ graceful shutdown (det vi behöver).
De övriga signalerna Àr inte nödvÀndiga för denna uppgift, sÄ vi hoppar över dem. För att korrekt avsluta processen behöver vi skriva följande preStop-hake:
lifecycle:
preStop:
exec:
command:
- /bin/kill
- -SIGQUIT
- "1"Vid första anblicken Àr detta allt som krÀvs för att utföra graceful shutdown i bÄda containrarna. Trots detta Àr uppgiften mer komplex Àn vad som verkar. Nedan diskuteras tvÄ fall dÀr graceful shutdown inte fungerade och orsakade kortvarig otillgÀnglighet av projektet under deployment.
Praxis. Möjliga problem med graceful shutdown
NGINX
Först och frÀmst Àr det bra att komma ihÄg: utöver att utföra kommandot nginx -s quit finns det en annan steg som Àr vÀrt att uppmÀrksamma. Vi har stött pÄ ett problem dÀr NGINX, istÀllet för signalen SIGQUIT, fortfarande skickade SIGTERM, vilket gjorde att begÀrningarna inte avslutades korrekt. Liknande fall kan hittas, till exempel, . TyvÀrr kunde vi inte faststÀlla den specifika orsaken till detta beteende: vi misstÀnkte NGINX-versionerna, men det bekrÀftades inte. Symtomen var att loggarna frÄn NGINX-kontainern visade meddelanden «open socket #10 left in connection 5», efter vilket podden stannar.
Vi kan observera ett sÄdant problem, till exempel frÄn svaren pÄ vÄr önskade Ingress:

Statuskoders indikatorer vid distribueringsögonblicket
I det hÀr fallet fÄr vi faktiskt 503-felkod frÄn sjÀlva Ingress: den kan inte komma Ät NGINX-containern eftersom den redan Àr otillgÀnglig. Om vi tittar i loggarna för NGINX-containern ser vi följande:
[alert] 13939#0: *154 open socket #3 left in connection 16
[alert] 13939#0: *168 open socket #6 left in connection 13Efter att stoppsignalen har Àndrats börjar containern stÀngas av korrekt: detta bekrÀftas av att 503-felet inte lÀngre observeras.
Om du har stött pÄ ett liknande problem kan det vara vÀrt att ta reda pÄ vilken stoppsignal som anvÀnds i containern och hur preStop-hooken ser ut. Det Àr möjligt att orsaken ligger just hÀr.
PHP-FPM⊠och inte bara
Problemet med PHP-FPM beskrivs enkelt: det vÀntar inte pÄ att barnprocesserna ska avslutas, utan terminerar dem, vilket leder till 502-fel under distribution och vid andra operationer. PÄ bugs.php.net har det sedan 2005 funnits flera felrapporter (till exempel, och ), som beskriver detta problem. I loggarna kommer du förmodligen inte att se nÄgot: PHP-FPM kommer att meddela att dess process har avslutats utan nÄgra fel eller externa meddelanden.
Det Àr vÀrt att klargöra att sjÀlva problemet kan i mindre eller större utstrÀckning bero pÄ sjÀlva applikationen och kanske inte kommer att visas, till exempel i övervakningen. Om du ÀndÄ stöter pÄ det, sÄ Àr den första tanken en enkel workaround: att lÀgga till en preStop-hook med sleep(30). Det lÄter alla tidigare begÀrningar avslutas (och vi accepterar inga nya, eftersom podden redan Àr i tillstÄndet Terminating), och efter 30 sekunder kommer podden sjÀlv att avslutas med signalen SIGTERM.
Det visar sig att lifecycle för containern kommer att se ut som följer:
lifecycle:
preStop:
exec:
command:
- /bin/sleep
- "30" Men pÄ grund av den angivna 30-sekunders sleep anvÀnder vi ökar vi distributionstiden, eftersom varje pod kommer att termineras minimum i 30 sekunder, vilket Àr dÄligt. Vad kan vi göra Ät detta?
Vi vÀnder oss till den del som ansvarar för den direkta körningen av applikationen. I vÄrt fall Àr det PHP-FPM, som i standardinstÀllningen inte övervakar sina barnprocesser: huvudprocessen termineras omedelbart. Detta beteende kan Àndras med hjÀlp av direktivet process_control_timeout, som anger tidsgrÀnser för att vÀnta pÄ signaler frÄn masterprocessen frÄn underprocesser. Om vÀrdet sÀtts till 20 sekunder tÀcker det de flesta förfrÄgningar som utförs i containern, och efter deras slutförande kommer masterprocessen att stoppas.
Med denna kunskap Ă„tervĂ€nder vi till vĂ„rt senaste problem. Som nĂ€mnts Ă€r Kubernetes inte en monolitisk plattform: det tar viss tid för interaktion mellan dess olika komponenter. Detta Ă€r sĂ€rskilt relevant nĂ€r vi övervĂ€ger arbetet med Ingress och andra relaterade komponenter, eftersom en sĂ„dan fördröjning vid distribution lĂ€tt kan leda till en topp av 500-fel. Till exempel kan ett fel uppstĂ„ vid steget att skicka en förfrĂ„gan till upstream, men den faktiska 'tidsfördröjningen' i interaktionen mellan komponenterna Ă€r ganska kort â mindre Ă€n en sekund.
DÀrför, totalt med den redan nÀmnda direktiven process_control_timeout kan följande konstruktion anvÀndas för lifecycle:
lifecycle:
preStop:
exec:
command: ["/bin/bash","-c","/bin/sleep 1; kill -QUIT 1"] I sÄ fall kompenserar vi för fördröjningen med kommandot sleep och ökar inte i stor grad distributionstiden: det Àr ju en mÀrkbar skillnad mellan 30 sekunder och en?.. I huvudsak tar process_control_timeout, och lifecycle endast anvÀnds som en 'sÀkerhetsÄtgÀrd' i hÀndelse av lag.
Generellt sett, det beskrivna beteendet och motsvarande workaround gĂ€ller inte bara PHP-FPM. En liknande situation kan uppstĂ„ vid anvĂ€ndning av andra sprĂ„k/ramverk. Om det inte gĂ„r att Ă„tgĂ€rda en korrekt avslutning pĂ„ andra sĂ€tt â till exempel genom att skriva om koden sĂ„ att applikationen korrekt hanterar avslutningssignaler â kan den beskrivna metoden anvĂ€ndas. Den kanske inte Ă€r den snyggaste, men den fungerar.
Praktik. Belastningstestning för att kontrollera poddens funktion.
Belastningstestning Ă€r ett av sĂ€tten att kontrollera hur containern fungerar, dĂ„ denna procedur nĂ€rmar sig verkliga stridsvillkor nĂ€r anvĂ€ndare besöker webbplatsen. För att testa de ovan nĂ€mnda rekommendationerna kan man anvĂ€nda : den tĂ€cker perfekt alla vĂ„ra behov. HĂ€r följer rĂ„d och rekommendationer för att genomföra tester med en tydlig â tack vare diagrammen frĂ„n Grafana och Yandex.Tank â exempel frĂ„n vĂ„r erfarenhet.
Det viktigaste hĂ€r Ă€r â att kontrollera förĂ€ndringar steg för steg.. Efter att ha lagt till en ny korrigering, kör testerna och se om resultaten har förĂ€ndrats jĂ€mfört med tidigare körningar. Annars kan det bli svĂ„rt att identifiera ineffektiva lösningar, och pĂ„ sikt kan det rentav skada (t.ex. öka tiden för deployment).
En annan aspekt Àr att kontrollera loggarna för containern under dess terminering. Registreras dÀr information om graceful shutdown? Finns det nÄgra fel i loggarna nÀr det gÀller andra resurser (t.ex. en nÀrstÄende PHP-FPM-container)? Fel frÄn sjÀlva applikationen (som i det ovan beskrivna fallet med NGINX)? Jag hoppas att den inledande informationen i denna artikel hjÀlper till att förstÄ vad som hÀnder med containern under dess terminering.
SÄ, den första testen kördes utan lifecycle och utan ytterligare direktiv för applikationsservern (process_control_timeout i PHP-FPM). MÄlet med detta test var att identifiera det ungefÀrliga antalet fel (och om det finns nÄgra överhuvudtaget). Dessutom bör det noteras att den genomsnittliga tiden för deployment av varje pod var cirka 5-10 sekunder tills det var helt redo. Resultaten Àr följande:

PÄ informationspanelen i Yandex.Tank syns en ökning av 502-fel som intrÀffade vid tidpunkten för deployment och pÄgick i genomsnitt i upp till 5 sekunder. Antagligen avbröts befintliga förfrÄgningar till den gamla podden nÀr den terminerades. DÀrefter uppstod 503-fel, vilket blev resultatet av den stoppade NGINX-containern som ocksÄ bröt anslutningar pÄ grund av backend (vilket gjorde att Ingress inte kunde ansluta).
LÄt oss se hur process_control_timeout i PHP-FPM kan hjÀlpa oss att vÀnta pÄ att barnprocesserna ska avslutas, det vill sÀga att ÄtgÀrda sÄdana fel. En omdeployment med anvÀndning av detta direktiv:

Under deployment finns det inga fler 500-fel! Deployment genomförs framgÄngsrikt, graceful shutdown fungerar.
Det Àr dock vÀrt att nÀmna aspekten med Ingress-containrar, dÀr en liten procentandel av felen kan uppstÄ pÄ grund av tillfÀlliga fördröjningar. För att undvika dem gÀller det att lÀgga till en konstruktion med sleep och upprepa deployment. I vÄrt specifika fall syntes dock inga förÀndringar (felen Àr Äterigen borta).
Slutsats
För att processen ska avslutas korrekt förvÀntar vi oss följande beteende frÄn applikationen:
- VÀnta nÄgra sekunder, efter vilket den slutar ta emot nya anslutningar.
- VÀnta tills alla förfrÄgningar Àr klara och stÀng alla keepalive-anslutningar som inte utför förfrÄgningarna.
- Avsluta din process.
Men inte alla applikationer kan fungera sÄ hÀr. En lösning pÄ problemet i Kubernetes Àr:
- tillÀgg av en pre-stop hook som vÀntar nÄgra sekunder;
- granskning av konfigurationsfilen för vÄr backend med avseende pÄ relevanta parametrar.
Ett exempel med NGINX visar att Àven en applikation som ursprungligen ska hantera avslutningssignaler korrekt, kanske inte gör det, varför det Àr kritiskt att kontrollera förekomsten av 500-fel under distributionen av applikationen. Detta gör det ocksÄ möjligt att se pÄ problemet bredare och inte enbart fokusera pÄ enskild pod eller container, utan att betrakta hela infrastrukturen som helhet.
För testning kan Yandex.Tank anvÀndas tillsammans med vilket övervakningssystem som helst (i vÄrt fall togs data frÄn Grafana med en backend som Àr Prometheus). Problem med graceful shutdown syns tydligt vid hög belastning, vilket benchmark kan generera, och övervakningen hjÀlper till att analysera situationen mer ingÄende under eller efter testet.
Som svar pÄ feedback frÄn artikeln: det bör nÀmnas att problem och lösningar som beskrivs hÀr gÀller NGINX Ingress. För andra fall finns det andra lösningar, som vi kanske kommer att gÄ igenom i framtida material i cykeln.
P.S.
NÄgot frÄn serien K8s tips & tricks:
- «»;
- «»;
- «»;
- «».
KĂ€lla: habr.com
