Tipy a triky Kubernetes: funkcie elegantného vypnutia v NGINX a PHP-FPM

Typická podmienka pri implementácii CI/CD v Kubernetes: aplikácia musí vedieť neprijímať nové požiadavky klientov pred úplným zastavením a hlavne úspešne dokončiť existujúce.

Tipy a triky Kubernetes: funkcie elegantného vypnutia v NGINX a PHP-FPM

Dodržanie tejto podmienky umožňuje dosiahnuť nulové prestoje počas nasadenia. Avšak aj pri použití veľmi populárnych balíkov (ako NGINX a PHP-FPM) sa môžete stretnúť s ťažkosťami, ktoré povedú k nárastu chýb pri každom nasadení...

teória. Ako žije pod

O životnom cykle lusku sme už podrobne publikovali tento článok. V kontexte zvažovanej témy nás zaujíma nasledovné: v momente, keď pod vchádza do stavu ukončuje, prestanú sa mu odosielať nové požiadavky (pod odstránený zo zoznamu koncových bodov služby). Aby sme teda predišli výpadkom pri nasadzovaní, stačí, aby sme správne vyriešili problém so zastavením aplikácie.

Mali by ste tiež pamätať na to, že predvolená doba odkladu je 30 sekúnd: po tomto bude modul ukončený a aplikácia musí mať čas na spracovanie všetkých požiadaviek pred týmto obdobím. Poznámka: hoci každá požiadavka, ktorá trvá viac ako 5-10 sekúnd, je už problematická a ladné vypnutie jej už nepomôže...

Ak chcete lepšie pochopiť, čo sa stane, keď modul skončí, pozrite si nasledujúci diagram:

Tipy a triky Kubernetes: funkcie elegantného vypnutia v NGINX a PHP-FPM

A1, B1 - Príjem zmien o stave ohniska
A2 - Odlet SIGTERM
B2 - Odstránenie modulu z koncových bodov
B3 - Prijímanie zmien (zoznam koncových bodov sa zmenil)
B4 - Aktualizácia pravidiel iptables

Poznámka: odstránenie koncového bodu a odoslanie SIGTERM neprebieha postupne, ale paralelne. A vzhľadom na skutočnosť, že Ingress nedostane okamžite aktualizovaný zoznam koncových bodov, nové požiadavky od klientov budú odoslané na modul, čo spôsobí chybu 500 počas ukončenia modulu. (Podrobnejší materiál k tejto problematike nájdete my preložené). Tento problém je potrebné vyriešiť nasledujúcimi spôsobmi:

  • Odoslať pripojenie: zatvorte hlavičky odpovede (ak ide o aplikáciu HTTP).
  • Ak nie je možné vykonať zmeny v kóde, nasledujúci článok popisuje riešenie, ktoré vám umožní spracovávať požiadavky až do konca obdobia odkladu.

teória. Ako NGINX a PHP-FPM ukončujú svoje procesy

Nginx

Začnime s NGINX, pretože s ním je všetko viac-menej zrejmé. Keď sa ponoríme do teórie, zistíme, že NGINX má jeden hlavný proces a niekoľko „pracovníkov“ - to sú podradené procesy, ktoré spracúvajú požiadavky klientov. Poskytuje sa pohodlná možnosť: pomocou príkazu nginx -s <SIGNAL> ukončiť procesy buď v režime rýchleho vypnutia alebo elegantného vypnutia. Je zrejmé, že nás zaujíma práve posledná možnosť.

Potom je všetko jednoduché: musíte pridať preStop-hák príkaz, ktorý odošle signál elegantného vypnutia. Dá sa to urobiť v Nasadení, v kontajnerovom bloku:

       lifecycle:
          preStop:
            exec:
              command:
              - /usr/sbin/nginx
              - -s
              - quit

Teraz, keď sa modul vypne, v protokoloch kontajnera NGINX uvidíme nasledovné:

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

A to bude znamenať to, čo potrebujeme: NGINX čaká na dokončenie požiadaviek a potom proces ukončí. Nižšie však zvážime aj bežný problém, kvôli ktorému aj s príkazom nginx -s quit proces sa nesprávne ukončí.

A v tejto fáze sme s NGINX hotoví: aspoň z protokolov môžete pochopiť, že všetko funguje tak, ako má.

Ako je to s PHP-FPM? Ako zvláda elegantné vypnutie? Poďme na to.

PHP-FPM

V prípade PHP-FPM je informácií o niečo menej. Ak sa zameriate na oficiálny manuál podľa PHP-FPM to povie, že sú akceptované nasledujúce signály POSIX:

  1. SIGINT, SIGTERM - rýchle vypnutie;
  2. SIGQUIT — elegantné vypnutie (čo potrebujeme).

Zvyšné signály nie sú v tejto úlohe potrebné, preto ich analýzu vynecháme. Na správne ukončenie procesu budete musieť napísať nasledujúci predbežný zastavovací hák:

        lifecycle:
          preStop:
            exec:
              command:
              - /bin/kill
              - -SIGQUIT
              - "1"

Na prvý pohľad je to všetko, čo je potrebné na elegantné vypnutie v oboch kontajneroch. Úloha je však ťažšia, ako sa zdá. Nižšie sú uvedené dva prípady, v ktorých ladné vypnutie nefungovalo a spôsobilo krátkodobú nedostupnosť projektu počas nasadenia.

Prax. Možné problémy s elegantným vypnutím

Nginx

V prvom rade je užitočné si zapamätať: okrem vykonania príkazu nginx -s quit Je tu ešte jedna etapa, ktorá stojí za pozornosť. Narazili sme na problém, keď NGINX stále odosielal SIGTERM namiesto signálu SIGQUIT, čo spôsobilo, že požiadavky sa nedokončili správne. Podobné prípady možno nájsť napr. tu. Bohužiaľ sa nám nepodarilo určiť konkrétny dôvod tohto správania: existovalo podozrenie na verziu NGINX, ale nepotvrdilo sa. Príznakom bolo, že v protokoloch kontajnera NGINX boli pozorované správy: "otvorená zásuvka #10 zostala v spojení 5", po ktorom sa lusk zastavil.

Takýto problém môžeme pozorovať napríklad z odpovedí na Ingress, ktoré potrebujeme:

Tipy a triky Kubernetes: funkcie elegantného vypnutia v NGINX a PHP-FPM
Indikátory stavových kódov v čase nasadenia

V tomto prípade od samotného Ingress dostávame iba chybový kód 503: nemôže získať prístup ku kontajneru NGINX, pretože už nie je prístupný. Ak sa pozriete na protokoly kontajnerov s NGINX, obsahujú nasledovné:

[alert] 13939#0: *154 open socket #3 left in connection 16
[alert] 13939#0: *168 open socket #6 left in connection 13

Po zmene signálu zastavenia sa kontajner začne správne zastavovať: potvrdzuje to skutočnosť, že chyba 503 sa už nepozoruje.

Ak sa stretnete s podobným problémom, má zmysel zistiť, aký stop signál sa používa v kontajneri a ako presne vyzerá hák preStop. Je celkom možné, že dôvod spočíva práve v tomto.

PHP-FPM... a ďalšie

Problém PHP-FPM je opísaný triviálne: nečaká na dokončenie podradených procesov, ale ich ukončuje, preto sa pri nasadzovaní a iných operáciách vyskytuje 502 chýb. Od roku 2005 je na bugs.php.net niekoľko hlásení o chybách (napr tu и tu), ktorý popisuje tento problém. S najväčšou pravdepodobnosťou však v protokoloch nič neuvidíte: PHP-FPM oznámi dokončenie svojho procesu bez akýchkoľvek chýb alebo upozornení tretích strán.

Je vhodné objasniť, že samotný problém môže v menšej či väčšej miere závisieť od samotnej aplikácie a nemusí sa prejaviť napríklad pri monitorovaní. Ak sa s tým stretnete, ako prvé vám napadne jednoduché riešenie: pridajte háčik preStop s sleep(30). Umožní vám to dokončiť všetky požiadavky, ktoré boli predtým (a nové neprijímame, pretože pod schopný ukončuje) a po 30 sekundách sa modul sám ukončí signálom SIGTERM.

Ukazuje sa, že lifecycle pretože kontajner bude vyzerať takto:

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

Avšak vzhľadom na 30-sek sleep sme silne predĺžime čas nasadenia, pretože každý modul bude ukončený minimum 30 sekúnd, čo je zlé. Čo sa s tým dá robiť?

Obráťme sa na stranu zodpovednú za priame vyhotovenie žiadosti. V našom prípade je PHP-FPMže štandardne nemonitoruje vykonávanie svojich podriadených procesov: Hlavný proces sa okamžite ukončí. Toto správanie môžete zmeniť pomocou smernice process_control_timeout, ktorá špecifikuje časové limity pre podriadené procesy, aby čakali na signály od hlavného. Ak nastavíte hodnotu na 20 sekúnd, pokryje to väčšinu dopytov spustených v kontajneri a po ich dokončení zastaví hlavný proces.

S týmto poznaním sa vráťme k nášmu poslednému problému. Ako už bolo spomenuté, Kubernetes nie je monolitická platforma: komunikácia medzi jej rôznymi komponentmi nejaký čas trvá. To platí najmä vtedy, keď vezmeme do úvahy fungovanie Ingresses a ďalších súvisiacich komponentov, pretože v dôsledku takéhoto oneskorenia v čase nasadenia je ľahké získať nárast 500 chýb. Chyba sa môže napríklad vyskytnúť vo fáze odosielania požiadavky do upstreamu, ale „časové oneskorenie“ interakcie medzi komponentmi je pomerne krátke – menej ako sekundu.

Z tohto dôvodu Spolu s už spomínanou smernicou process_control_timeout môžete použiť nasledujúcu konštrukciu lifecycle:

lifecycle:
  preStop:
    exec:
      command: ["/bin/bash","-c","/bin/sleep 1; kill -QUIT 1"]

V tomto prípade oneskorenie kompenzujeme príkazom sleep a výrazne nepredlžujte čas nasadenia: je viditeľný rozdiel medzi 30 sekundami a jednou?... V skutočnosti ide o process_control_timeoutA lifecycle používa sa len ako „záchranná sieť“ v prípade oneskorenia.

Všeobecne povedané popísané správanie a zodpovedajúce riešenie sa nevzťahujú len na PHP-FPM. Podobná situácia môže tak či onak nastať pri používaní iných jazykov/rámcov. Ak nemôžete vyriešiť elegantné vypnutie iným spôsobom - napríklad prepísaním kódu tak, aby aplikácia správne spracovávala signály ukončenia - môžete použiť opísanú metódu. Možno to nie je najkrajšie, ale funguje to.

Prax. Záťažové testovanie na kontrolu činnosti modulu

Záťažové testovanie je jedným zo spôsobov, ako skontrolovať, ako kontajner funguje, pretože tento postup ho približuje skutočným bojovým podmienkam, keď používatelia navštívia stránku. Ak chcete otestovať vyššie uvedené odporúčania, môžete použiť Yandex.Tankom: Dokonale pokrýva všetky naše potreby. Nasledujú tipy a odporúčania na vykonávanie testovania s jasným príkladom z našich skúseností vďaka grafom Grafana a samotnej Yandex.Tank.

Najdôležitejšia vec je tu skontrolujte zmeny krok za krokom. Po pridaní novej opravy spustite test a zistite, či sa výsledky zmenili v porovnaní s posledným spustením. V opačnom prípade bude ťažké identifikovať neefektívne riešenia a z dlhodobého hľadiska to môže len uškodiť (napríklad predĺžiť čas nasadenia).

Ďalšou nuansou je pozrieť sa na protokoly kontajnera počas jeho ukončenia. Je tam zaznamenaná informácia o ladnom vypnutí? Existujú nejaké chyby v protokoloch pri prístupe k iným zdrojom (napríklad k susednému kontajneru PHP-FPM)? Chyby v samotnej aplikácii (ako v prípade NGINX popísaného vyššie)? Dúfam, že úvodné informácie z tohto článku vám pomôžu lepšie pochopiť, čo sa deje s kontajnerom pri jeho ukončení.

Prvá skúšobná jazda teda prebehla bez lifecycle a bez ďalších direktív pre aplikačný server (process_control_timeout v PHP-FPM). Účelom tohto testu bolo zistiť približný počet chýb (a či nejaké sú). Z dodatočných informácií by ste tiež mali vedieť, že priemerný čas nasadenia každého modulu bol približne 5 až 10 sekúnd, kým bol úplne pripravený. Výsledky sú:

Tipy a triky Kubernetes: funkcie elegantného vypnutia v NGINX a PHP-FPM

Informačný panel Yandex.Tank zobrazuje nárast 502 chýb, ktoré sa vyskytli v čase nasadenia a trvali v priemere do 5 sekúnd. Pravdepodobne to bolo preto, že existujúce požiadavky na starú pod boli ukončené, keď bola ukončená. Potom sa objavilo 503 chýb, čo bolo výsledkom zastaveného kontajnera NGINX, ktorý tiež prerušil pripojenia kvôli backendu (čo bránilo Ingress pripojiť sa k nemu).

Pozrime sa ako process_control_timeout v PHP-FPM nám pomôže počkať na dokončenie detských procesov, t.j. opraviť takéto chyby. Znova nasaďte pomocou tejto smernice:

Tipy a triky Kubernetes: funkcie elegantného vypnutia v NGINX a PHP-FPM

Počas 500. nasadenia už nie sú žiadne chyby! Nasadenie je úspešné, funguje ladné vypnutie.

Je však potrebné pripomenúť si problém s kontajnermi Ingress, malým percentom chýb, ku ktorým sa môžeme dostať v dôsledku časového oneskorenia. Aby ste sa im vyhli, zostáva len pridať štruktúru s sleep a zopakujte nasadenie. V našom konkrétnom prípade však neboli viditeľné žiadne zmeny (opäť žiadne chyby).

Záver

Aby sme mohli proces elegantne ukončiť, od aplikácie očakávame nasledovné správanie:

  1. Počkajte niekoľko sekúnd a potom prestaňte prijímať nové pripojenia.
  2. Počkajte na dokončenie všetkých požiadaviek a zatvorte všetky udržiavacie pripojenia, ktoré nevykonávajú požiadavky.
  3. Ukončite proces.

Nie všetky aplikácie však môžu fungovať týmto spôsobom. Jedným z riešení problému v realite Kubernetes je:

  • pridanie predbežného háku, ktorý počká niekoľko sekúnd;
  • preštudovaním konfiguračného súboru nášho backendu, kde nájdete príslušné parametre.

Príklad s NGINX objasňuje, že dokonca aj aplikácia, ktorá by mala na začiatku správne spracovať ukončovacie signály, to nemusí urobiť, takže je dôležité skontrolovať 500 chýb počas nasadenia aplikácie. To vám tiež umožňuje pozrieť sa na problém širšie a nezameriavať sa na jeden modul alebo kontajner, ale pozrieť sa na celú infraštruktúru ako celok.

Ako testovací nástroj môžete použiť Yandex.Tank v spojení s akýmkoľvek monitorovacím systémom (v našom prípade boli údaje na test prevzaté z Grafany s backendom Prometheus). Problémy s plynulým vypnutím sú jasne viditeľné pri veľkom zaťažení, ktoré môže benchmark generovať, a monitorovanie pomáha podrobnejšie analyzovať situáciu počas testu alebo po ňom.

V reakcii na spätnú väzbu k článku: stojí za zmienku, že problémy a riešenia sú tu opísané vo vzťahu k NGINX Ingress. Pre iné prípady existujú iné riešenia, ktoré môžeme zvážiť v nasledujúcich materiáloch série.

PS

Ďalšie zo série tipov a trikov K8s:

Zdroj: hab.com

Pridať komentár