Kubernetes wenke en truuks: grasieuse afsluitfunksies in NGINX en PHP-FPM

'n Tipiese toestand by die implementering van CI/CD in Kubernetes: die toepassing moet nie nuwe kliëntversoeke kan aanvaar voordat dit heeltemal gestop word nie, en bowenal, bestaandes suksesvol voltooi.

Kubernetes wenke en truuks: grasieuse afsluitfunksies in NGINX en PHP-FPM

Voldoening aan hierdie voorwaarde stel jou in staat om geen stilstand te bereik tydens ontplooiing nie. Selfs wanneer jy baie gewilde bondels (soos NGINX en PHP-FPM) gebruik, kan jy egter probleme ondervind wat sal lei tot 'n oplewing van foute met elke ontplooiing ...

Teorie. Hoe peul leef

Ons het reeds in detail gepubliseer oor die lewensiklus van 'n peul hierdie artikel. In die konteks van die onderwerp wat oorweeg word, stel ons belang in die volgende: op die oomblik wanneer die peul die staat binnekom Beëindig, nuwe versoeke word nie meer na dit gestuur nie (pod verwyder uit die lys eindpunte vir die diens). Dus, om stilstand tydens ontplooiing te vermy, is dit genoeg vir ons om die probleem om die toepassing korrek te stop, op te los.

Jy moet ook onthou dat die verstek grasietydperk is 30 sekondes: hierna sal die pod beëindig word en die aansoek moet tyd hê om alle versoeke voor hierdie tydperk te verwerk. Let daarop: alhoewel enige versoek wat meer as 5-10 sekondes neem reeds problematies is, en grasieuse afsluiting dit nie meer sal help nie...

Om beter te verstaan ​​wat gebeur wanneer 'n peul eindig, kyk net na die volgende diagram:

Kubernetes wenke en truuks: grasieuse afsluitfunksies in NGINX en PHP-FPM

A1, B1 - Ontvang veranderinge oor die toestand van die vuurherd
A2 - Vertrek SIGTERM
B2 - Verwydering van 'n peul van eindpunte
B3 - Ontvang veranderinge (die lys eindpunte het verander)
B4 - Dateer iptables-reëls op

Neem asseblief kennis: die verwydering van die eindpuntpod en die stuur van SIGTERM gebeur nie opeenvolgend nie, maar parallel. En as gevolg van die feit dat Ingress nie onmiddellik die opgedateerde lys van eindpunte ontvang nie, sal nuwe versoeke van kliënte na die pod gestuur word, wat 'n 500-fout sal veroorsaak tydens podbeëindiging (vir meer gedetailleerde materiaal oor hierdie kwessie, ons vertaal). Hierdie probleem moet op die volgende maniere opgelos word:

  • Stuur verbinding: sluit in reaksie-opskrifte (indien dit 'n HTTP-toepassing betref).
  • As dit nie moontlik is om veranderinge aan die kode aan te bring nie, beskryf die volgende artikel 'n oplossing wat jou sal toelaat om versoeke te verwerk tot aan die einde van die grasieuse tydperk.

Teorie. Hoe NGINX en PHP-FPM hul prosesse beëindig

Nginx

Kom ons begin met NGINX, aangesien alles min of meer duidelik daarmee is. Deur in die teorie te duik, leer ons dat NGINX een meesterproses en verskeie "werkers" het - dit is kinderprosesse wat kliëntversoeke verwerk. 'n Gerieflike opsie word verskaf: gebruik die opdrag nginx -s <SIGNAL> beëindig prosesse óf in vinnige afsluit- of grasieuse afskakelmodus. Uiteraard is dit laasgenoemde opsie wat ons interesseer.

Dan is alles eenvoudig: jy moet byvoeg preStop-haak 'n opdrag wat 'n grasieuse afskakelsein sal stuur. Dit kan gedoen word in Ontplooiing, in die houerblok:

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

Nou, wanneer die peul afskakel, sal ons die volgende in die NGINX-houerlogboeke sien:

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

En dit sal beteken wat ons nodig het: NGINX wag vir versoeke om te voltooi, en maak dan die proses dood. Hier onder sal ons egter ook 'n algemene probleem oorweeg as gevolg daarvan, selfs met die opdrag nginx -s quit die proses eindig verkeerd.

En op hierdie stadium is ons klaar met NGINX: ten minste uit die logs kan jy verstaan ​​dat alles werk soos dit moet.

Wat is die saak met PHP-FPM? Hoe hanteer dit grasieuse afsluiting? Kom ons vind dit uit.

PHP-FPM

In die geval van PHP-FPM is daar 'n bietjie minder inligting. As jy fokus op amptelike handleiding volgens PHP-FPM sal dit sê dat die volgende POSIX-seine aanvaar word:

  1. SIGINT, SIGTERM - vinnige afsluiting;
  2. SIGQUIT — grasieuse afsluiting (wat ons nodig het).

Die oorblywende seine word nie in hierdie taak vereis nie, so ons sal hul analise weglaat. Om die proses korrek te beëindig, sal jy die volgende preStop-haak moet skryf:

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

Met die eerste oogopslag is dit al wat nodig is om 'n grasieuse afsluiting in beide houers uit te voer. Die taak is egter moeiliker as wat dit lyk. Hieronder is twee gevalle waarin grasieuse afsluiting nie gewerk het nie en die korttermyn onbeskikbaarheid van die projek tydens ontplooiing veroorsaak het.

Oefen. Moontlike probleme met grasieuse afsluiting

Nginx

Eerstens is dit nuttig om te onthou: benewens die uitvoering van die opdrag nginx -s quit Daar is nog een stadium wat die moeite werd is om aandag aan te gee. Ons het 'n probleem ondervind waar NGINX steeds SIGTERM in plaas van die SIGQUIT-sein sou stuur, wat veroorsaak dat versoeke nie korrek voltooi nie. Soortgelyke gevalle kan gevind word, bv. hier. Ongelukkig kon ons nie die spesifieke rede vir hierdie gedrag bepaal nie: daar was 'n vermoede oor die NGINX-weergawe, maar dit is nie bevestig nie. Die simptoom was dat boodskappe in die NGINX-houerlogboeke waargeneem is "oop sok #10 oor in verbinding 5", waarna die peul gestop het.

Ons kan so 'n probleem waarneem, byvoorbeeld uit die antwoorde op die Ingress wat ons nodig het:

Kubernetes wenke en truuks: grasieuse afsluitfunksies in NGINX en PHP-FPM
Aanwysers van statuskodes ten tyde van ontplooiing

In hierdie geval ontvang ons net 'n 503-foutkode van Ingress self: dit kan nie toegang tot die NGINX-houer kry nie, aangesien dit nie meer toeganklik is nie. As jy na die houerlogboeke met NGINX kyk, bevat dit die volgende:

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

Nadat die stopsein verander is, begin die houer korrek stop: dit word bevestig deur die feit dat die 503-fout nie meer waargeneem word nie.

As jy 'n soortgelyke probleem teëkom, maak dit sin om uit te vind watter stopsein in die houer gebruik word en hoe presies die preStop-haak lyk. Dit is heel moontlik dat die rede juis hierin lê.

PHP-FPM ... en meer

Die probleem met PHP-FPM word op 'n onbenullige manier beskryf: dit wag nie vir die voltooiing van kinderprosesse nie, dit beëindig dit, en daarom voorkom 502-foute tydens ontplooiing en ander operasies. Daar is verskeie foutverslae op bugs.php.net sedert 2005 (bv hier и hier), wat hierdie probleem beskryf. Maar jy sal heel waarskynlik niks in die logs sien nie: PHP-FPM sal die voltooiing van sy proses aankondig sonder enige foute of derdeparty-kennisgewings.

Dit is die moeite werd om te verduidelik dat die probleem self in 'n mindere of meerdere mate van die toepassing self kan afhang en nie kan manifesteer, byvoorbeeld in monitering nie. As jy dit wel teëkom, kom 'n eenvoudige oplossing eerste by jou op: voeg 'n preStop-haak by sleep(30). Dit sal jou toelaat om alle versoeke te voltooi wat voorheen was (en ons aanvaar nie nuwes nie, aangesien pod reeds in staat is om Beëindig), en na 30 sekondes sal die peul self met 'n sein eindig SIGTERM.

Dit blyk dat lifecycle want die houer sal so lyk:

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

As gevolg van die 30-sekonde sleep ons sterk ons sal die ontplooiingstyd verhoog, aangesien elke peul beëindig sal word minimum 30 sekondes, wat sleg is. Wat kan hieraan gedoen word?

Kom ons draai na die party wat verantwoordelik is vir die direkte uitvoering van die aansoek. In ons geval is dit PHP-FPMWatter monitor by verstek nie die uitvoering van sy kindprosesse nie: Die meesterproses word onmiddellik beëindig. Jy kan hierdie gedrag verander deur die richtlijn te gebruik process_control_timeout, wat die tydsbeperkings spesifiseer vir kinderprosesse om te wag vir seine van die meester. As jy die waarde op 20 sekondes stel, sal dit die meeste van die navrae wat in die houer loop dek en sal die meesterproses stop sodra hulle voltooi is.

Met hierdie kennis, kom ons keer terug na ons laaste probleem. Soos genoem, is Kubernetes nie 'n monolitiese platform nie: kommunikasie tussen sy verskillende komponente neem 'n geruime tyd. Dit is veral waar as ons die werking van Ingresses en ander verwante komponente oorweeg, aangesien dit as gevolg van so 'n vertraging ten tyde van ontplooiing maklik is om 'n toename van 500 foute te kry. Byvoorbeeld, 'n fout kan voorkom in die stadium van die stuur van 'n versoek na 'n stroomop, maar die "tydvertraging" van interaksie tussen komponente is redelik kort - minder as 'n sekonde.

daarom, In totaal met die reeds genoemde richtlijn process_control_timeout jy kan die volgende konstruksie gebruik vir lifecycle:

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

In hierdie geval sal ons vergoed vir die vertraging met die opdrag sleep en ons verhoog nie die ontplooiingstyd noemenswaardig nie: die verskil tussen 30 sekondes en een is immers merkbaar? .. Trouens, dit is die process_control_timeoutEn lifecycle slegs gebruik as 'n "veiligheidsnet" in geval van vertraging.

Oor die algemeen die beskryf gedrag en die ooreenstemmende oplossing is nie net van toepassing op PHP-FPM nie. 'n Soortgelyke situasie kan op een of ander manier ontstaan ​​wanneer ander tale/raamwerke gebruik word. As jy nie grasieuse afsluiting op ander maniere kan regmaak nie - byvoorbeeld deur die kode te herskryf sodat die toepassing beëindigingseine korrek verwerk - kan jy die beskryfde metode gebruik. Dit is dalk nie die mooiste nie, maar dit werk.

Oefen. Ladingstoetsing om die werking van die peul na te gaan

Vragtoetsing is een van die maniere om te kyk hoe die houer werk, aangesien hierdie prosedure dit nader bring aan werklike gevegstoestande wanneer gebruikers die webwerf besoek. Om die bogenoemde aanbevelings te toets, kan jy gebruik Yandex.Tankom: Dit dek al ons behoeftes perfek. Die volgende is wenke en aanbevelings vir die uitvoer van toetse met 'n duidelike voorbeeld uit ons ervaring danksy die grafieke van Grafana en Yandex.Tank self.

Die belangrikste ding hier is kontroleer veranderinge stap vir stap. Nadat u 'n nuwe oplossing bygevoeg het, voer die toets uit en kyk of die resultate verander het in vergelyking met die laaste lopie. Andersins sal dit moeilik wees om ondoeltreffende oplossings te identifiseer, en op die lange duur kan dit net skade berokken (byvoorbeeld verhoog die ontplooiingstyd).

Nog 'n nuanse is om na die houerlogboeke te kyk tydens die beëindiging daarvan. Is inligting oor grasieuse afsluiting daar aangeteken? Is daar enige foute in die logs wanneer toegang tot ander hulpbronne verkry word (byvoorbeeld na 'n naburige PHP-FPM-houer)? Foute in die toepassing self (soos in die geval met NGINX hierbo beskryf)? Ek hoop dat die inleidende inligting uit hierdie artikel jou sal help om beter te verstaan ​​wat met die houer gebeur tydens die beëindiging daarvan.

Dus, die eerste toetslopie het sonder plaasgevind lifecycle en sonder bykomende voorskrifte vir die toepassingsbediener (process_control_timeout in PHP-FPM). Die doel van hierdie toets was om die benaderde aantal foute (en of daar enige is) te identifiseer. Ook, uit bykomende inligting, moet jy weet dat die gemiddelde ontplooiingstyd vir elke peul ongeveer 5-10 sekondes was totdat dit heeltemal gereed was. Die resultate is:

Kubernetes wenke en truuks: grasieuse afsluitfunksies in NGINX en PHP-FPM

Die Yandex.Tank-inligtingspaneel toon 'n styging van 502 foute, wat tydens die ontplooiing plaasgevind het en gemiddeld tot 5 sekondes geduur het. Vermoedelik was dit omdat bestaande versoeke aan die ou pod beëindig is toe dit beëindig is. Hierna het 503 foute verskyn, wat die gevolg was van 'n gestopte NGINX-houer, wat ook verbindings laat val het as gevolg van die agterkant (wat Ingress verhoed het om daaraan te koppel).

Kom ons kyk hoe process_control_timeout in PHP-FPM sal ons help om te wag vir die voltooiing van kinderprosesse, d.w.s. sulke foute reg te stel. Herontplooi met behulp van hierdie richtlijn:

Kubernetes wenke en truuks: grasieuse afsluitfunksies in NGINX en PHP-FPM

Daar is nie meer foute tydens die 500ste ontplooiing nie! Die ontplooiing is suksesvol, grasieuse afsluiting werk.

Dit is egter die moeite werd om die probleem met Ingress-houers te onthou, 'n klein persentasie foute wat ons kan ontvang as gevolg van 'n tydsvertraging. Om hulle te vermy, is al wat oorbly om 'n struktuur by te voeg sleep en herhaal die ontplooiing. In ons spesifieke geval was daar egter geen veranderinge sigbaar nie (weereens geen foute).

Gevolgtrekking

Om die proses grasieus te beëindig, verwag ons die volgende gedrag van die aansoek:

  1. Wag 'n paar sekondes en hou dan op om nuwe verbindings te aanvaar.
  2. Wag vir alle versoeke om alle keepalive-verbindings wat nie versoeke uitvoer nie, te voltooi en toe te maak.
  3. Beëindig jou proses.

Nie alle toepassings kan egter op hierdie manier werk nie. Een oplossing vir die probleem in Kubernetes-realiteite is:

  • die byvoeging van 'n pre-stop-haak wat 'n paar sekondes sal wag;
  • bestudeer die konfigurasielêer van ons backend vir die toepaslike parameters.

Die voorbeeld met NGINX maak dit duidelik dat selfs 'n toepassing wat aanvanklik beëindigingseine korrek moet verwerk, dit dalk nie sal doen nie, daarom is dit van kritieke belang om te kyk vir 500 foute tydens toepassing-ontplooiing. Dit laat jou ook toe om breër na die probleem te kyk en nie op 'n enkele peul of houer te fokus nie, maar na die hele infrastruktuur as 'n geheel te kyk.

As 'n toetsinstrument kan jy Yandex.Tank in samewerking met enige moniteringstelsel gebruik (in ons geval is data van Grafana geneem met 'n Prometheus-agterkant vir die toets). Probleme met grasieuse afsluiting is duidelik sigbaar onder swaar vragte wat die maatstaf kan genereer, en monitering help om die situasie in meer detail tydens of na die toets te ontleed.

In reaksie op terugvoer oor die artikel: dit is die moeite werd om te noem dat die probleme en oplossings hier beskryf word met betrekking tot NGINX Ingress. Vir ander gevalle is daar ander oplossings wat ons in die volgende materiaal van die reeks kan oorweeg.

PS

Ander uit die K8s wenke en truuks-reeks:

Bron: will.com

Voeg 'n opmerking