Kubernetes tips & tricks: kenmerken van sierlijk afsluiten in NGINX en PHP-FPM

Een typische voorwaarde bij het implementeren van CI/CD in Kubernetes: de applicatie moet in staat zijn om geen nieuwe clientverzoeken te accepteren voordat deze volledig stopt, en het allerbelangrijkste: bestaande verzoeken met succes voltooien.

Kubernetes tips & tricks: kenmerken van sierlijk afsluiten in NGINX en PHP-FPM

Als u aan deze voorwaarde voldoet, kunt u tijdens de implementatie geen downtime realiseren. Maar zelfs als je zeer populaire bundels gebruikt (zoals NGINX en PHP-FPM), kun je problemen tegenkomen die bij elke implementatie tot een golf van fouten zullen leiden...

Theorie. Hoe pod leeft

We hebben al uitgebreid gepubliceerd over de levenscyclus van een peul dit artikel. In de context van het onderwerp dat we bespreken, zijn we geïnteresseerd in het volgende: op het moment dat de pod de staat binnenkomt Het beëindigen, worden er geen nieuwe verzoeken meer naar gestuurd (pod verwijderd uit de lijst met eindpunten voor de service). Om downtime tijdens de implementatie te voorkomen, volstaat het dat we het probleem van het correct stoppen van de applicatie oplossen.

Houd er ook rekening mee dat de standaard respijtperiode is 30 seconden: hierna wordt de pod beëindigd en moet de applicatie de tijd hebben om alle aanvragen vóór deze periode te verwerken. Noot: hoewel elk verzoek dat meer dan 5-10 seconden duurt al problematisch is, en een sierlijke afsluiting niet langer zal helpen...

Om beter te begrijpen wat er gebeurt als een pod eindigt, kunt u het volgende diagram bekijken:

Kubernetes tips & tricks: kenmerken van sierlijk afsluiten in NGINX en PHP-FPM

A1, B1 - Wijzigingen ontvangen over de staat van de haard
A2 - Vertrek SIGTERM
B2 - Een pod verwijderen van eindpunten
B3 - Wijzigingen ontvangen (de lijst met eindpunten is gewijzigd)
B4 - Update iptables-regels

Let op: het verwijderen van de eindpuntpod en het verzenden van SIGTERM gebeurt niet opeenvolgend, maar parallel. En vanwege het feit dat Ingress niet onmiddellijk de bijgewerkte lijst met eindpunten ontvangt, worden nieuwe verzoeken van clients naar de pod verzonden, wat een 500-fout zal veroorzaken tijdens het beëindigen van de pod (voor meer gedetailleerd materiaal over dit onderwerp verwijzen we naar vertaald). Dit probleem moet op de volgende manieren worden opgelost:

  • Verbinding verzenden: sluit de responsheaders af (als dit een HTTP-applicatie betreft).
  • Als het niet mogelijk is om wijzigingen in de code aan te brengen, beschrijft het volgende artikel een oplossing waarmee u verzoeken kunt verwerken tot het einde van de respijtperiode.

Theorie. Hoe NGINX en PHP-FPM hun processen beëindigen

NGINX

Laten we beginnen met NGINX, omdat alles er min of meer voor de hand liggend is. Als we in de theorie duiken, leren we dat NGINX één hoofdproces en verschillende ‘werkers’ heeft: dit zijn onderliggende processen die klantverzoeken verwerken. Er is een handige optie beschikbaar: gebruik de opdracht nginx -s <SIGNAL> beëindig processen in de snelle afsluit- of sierlijke afsluitmodus. Het is duidelijk dat deze laatste optie ons interesseert.

Dan is alles eenvoudig: je moet toevoegen preStop-haak een commando dat een sierlijk uitschakelsignaal verzendt. Dit kan gedaan worden in Deployment, in het containerblok:

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

Wanneer de pod nu wordt afgesloten, zien we het volgende in de NGINX-containerlogboeken:

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 betekent wat we nodig hebben: NGINX wacht tot de verzoeken zijn voltooid en beëindigt vervolgens het proces. Hieronder zullen we echter ook een veelvoorkomend probleem beschouwen, waardoor zelfs met de opdracht nginx -s quit het proces wordt ten onrechte beëindigd.

En in dit stadium zijn we klaar met NGINX: uit de logs kun je tenminste begrijpen dat alles werkt zoals het zou moeten.

Hoe zit het met PHP-FPM? Hoe gaat het om met een sierlijke afsluiting? Laten we het uitzoeken.

PHP-FPM

In het geval van PHP-FPM is er iets minder informatie. Als je je concentreert op officiële handleiding volgens PHP-FPM zal het zeggen dat de volgende POSIX-signalen worden geaccepteerd:

  1. SIGINT, SIGTERM — snelle uitschakeling;
  2. SIGQUIT - sierlijke afsluiting (wat we nodig hebben).

De resterende signalen zijn niet vereist voor deze taak, dus we zullen de analyse ervan achterwege laten. Om het proces correct te beëindigen, moet u de volgende preStop-hook schrijven:

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

Op het eerste gezicht is dit alles wat nodig is om beide containers netjes af te sluiten. De taak is echter moeilijker dan het lijkt. Hieronder staan ​​twee gevallen waarin een sierlijke afsluiting niet werkte en ervoor zorgde dat het project tijdens de implementatie op korte termijn niet beschikbaar was.

Oefening. Mogelijke problemen met sierlijke afsluiting

NGINX

Allereerst is het handig om te onthouden: naast het uitvoeren van de opdracht nginx -s quit Er is nog een fase die de moeite waard is om op te letten. We zijn een probleem tegengekomen waarbij NGINX nog steeds SIGTERM verzendt in plaats van het SIGQUIT-signaal, waardoor verzoeken niet correct worden voltooid. Soortgelijke gevallen zijn bijvoorbeeld te vinden hier. Helaas konden we de specifieke reden voor dit gedrag niet vaststellen: er was een vermoeden over de NGINX-versie, maar deze werd niet bevestigd. Het symptoom was dat er berichten werden waargenomen in de NGINX-containerlogboeken: "Open socket #10 links in aansluiting 5", waarna de pod stopte.

We kunnen een dergelijk probleem bijvoorbeeld waarnemen aan de hand van de antwoorden op de Ingress die we nodig hebben:

Kubernetes tips & tricks: kenmerken van sierlijk afsluiten in NGINX en PHP-FPM
Indicatoren van statuscodes op het moment van implementatie

In dit geval ontvangen we slechts een 503-foutcode van Ingress zelf: deze heeft geen toegang tot de NGINX-container, omdat deze niet langer toegankelijk is. Als je met NGINX naar de containerlogboeken kijkt, bevatten deze het volgende:

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

Na het veranderen van het stopsignaal begint de container correct te stoppen: dit wordt bevestigd door het feit dat de 503-fout niet langer wordt waargenomen.

Als u een soortgelijk probleem tegenkomt, is het zinvol om uit te zoeken welk stopsignaal er in de container wordt gebruikt en hoe de preStop-haak er precies uitziet. Het is heel goed mogelijk dat de reden juist hierin ligt.

PHP-FPM... en meer

Het probleem met PHP-FPM wordt op een triviale manier beschreven: het wacht niet op de voltooiing van onderliggende processen, maar beëindigt deze. Daarom treden er 502-fouten op tijdens de implementatie en andere bewerkingen. Er zijn sinds 2005 verschillende bugrapporten op bugs.php.net (bijv hier и hier), waarin dit probleem wordt beschreven. Maar hoogstwaarschijnlijk zult u niets in de logs zien: PHP-FPM zal de voltooiing van het proces aankondigen zonder fouten of meldingen van derden.

Het is de moeite waard om te verduidelijken dat het probleem zelf in meer of mindere mate afhankelijk kan zijn van de applicatie zelf en zich mogelijk niet manifesteert in bijvoorbeeld monitoring. Als je het toch tegenkomt, denk je eerst aan een eenvoudige oplossing: voeg een preStop hook toe met sleep(30). Hiermee kunt u alle eerdere verzoeken voltooien (en we accepteren geen nieuwe, aangezien pod reeds in staat tot Het beëindigen), en na 30 seconden zal de pod zelf eindigen met een signaal SIGTERM.

Het blijkt dat lifecycle want de container ziet er als volgt uit:

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

Echter, vanwege de 30 seconden sleep we сильно we zullen de implementatietijd verlengen, aangezien elke pod wordt beëindigd minimum 30 seconden, wat slecht is. Wat kan hieraan gedaan worden?

Laten we ons wenden tot de partij die verantwoordelijk is voor de directe uitvoering van de applicatie. In ons geval wel PHP-FPMDie houdt standaard geen toezicht op de uitvoering van de onderliggende processen: Het masterproces wordt onmiddellijk beëindigd. U kunt dit gedrag wijzigen met behulp van de richtlijn process_control_timeout, die de tijdslimieten specificeert waarbinnen onderliggende processen moeten wachten op signalen van de master. Als u de waarde instelt op 20 seconden, dekt dit de meeste query's die in de container worden uitgevoerd en wordt het hoofdproces gestopt zodra deze zijn voltooid.

Laten we met deze kennis terugkeren naar ons laatste probleem. Zoals gezegd is Kubernetes geen monolithisch platform: de communicatie tussen de verschillende componenten kost enige tijd. Dit geldt met name als we kijken naar de werking van Ingresses en andere gerelateerde componenten, omdat het vanwege een dergelijke vertraging op het moment van implementatie gemakkelijk is om een ​​golf van 500 fouten te krijgen. Er kan bijvoorbeeld een fout optreden in de fase van het verzenden van een verzoek naar een upstream, maar de "vertraging" van de interactie tussen componenten is vrij kort: minder dan een seconde.

daarom In totaal met de reeds genoemde richtlijn process_control_timeout Je kunt de volgende constructie gebruiken lifecycle:

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

In dit geval compenseren we de vertraging met het commando sleep en de implementatietijd niet enorm verlengen: is er een merkbaar verschil tussen 30 seconden en één? process_control_timeoutEn lifecycle alleen gebruikt als “vangnet” in geval van vertraging.

Algemeen gesproken het beschreven gedrag en de bijbehorende oplossing zijn niet alleen van toepassing op PHP-FPM. Een soortgelijke situatie kan zich op de een of andere manier voordoen bij het gebruik van andere talen/frameworks. Als u het correct afsluiten niet op andere manieren kunt oplossen, bijvoorbeeld door de code te herschrijven zodat de toepassing beëindigingssignalen correct verwerkt, kunt u de beschreven methode gebruiken. Het is misschien niet het mooiste, maar het werkt.

Oefening. Belastingstests om de werking van de pod te controleren

Belastingtesten zijn een van de manieren om te controleren hoe de container werkt, omdat deze procedure hem dichter bij echte gevechtsomstandigheden brengt wanneer gebruikers de locatie bezoeken. Om de bovenstaande aanbevelingen te testen, kunt u gebruiken Yandex. Tankom: Het dekt perfect al onze behoeften. Hieronder volgen tips en aanbevelingen voor het uitvoeren van tests met een duidelijk voorbeeld uit onze ervaring dankzij de grafieken van Grafana en Yandex.Tank zelf.

Het belangrijkste hier is controleer de wijzigingen stap voor stap. Nadat u een nieuwe oplossing hebt toegevoegd, voert u de test uit en kijkt u of de resultaten zijn veranderd ten opzichte van de laatste run. Anders zal het moeilijk zijn om ineffectieve oplossingen te identificeren, en op de lange termijn kan het alleen maar schade aanrichten (bijvoorbeeld de implementatietijd verlengen).

Een andere nuance is om tijdens de beëindiging naar de containerlogboeken te kijken. Wordt daar informatie over een sierlijke afsluiting vastgelegd? Zijn er fouten in de logboeken bij het benaderen van andere bronnen (bijvoorbeeld naar een aangrenzende PHP-FPM-container)? Fouten in de applicatie zelf (zoals in het geval met NGINX hierboven beschreven)? Ik hoop dat de inleidende informatie uit dit artikel u zal helpen beter te begrijpen wat er met de container gebeurt tijdens de beëindiging ervan.

De eerste testrit vond dus plaats zonder lifecycle en zonder aanvullende richtlijnen voor de applicatieserver (process_control_timeout in PHP-FPM). Het doel van deze test was om bij benadering het aantal fouten te identificeren (en of die er zijn). Uit aanvullende informatie moet u ook weten dat de gemiddelde implementatietijd voor elke pod ongeveer 5-10 seconden bedroeg totdat deze volledig gereed was. De resultaten zijn:

Kubernetes tips & tricks: kenmerken van sierlijk afsluiten in NGINX en PHP-FPM

Het Yandex.Tank-informatiepaneel toont een piek van 502 fouten, die plaatsvonden op het moment van implementatie en gemiddeld maximaal 5 seconden duurden. Vermoedelijk kwam dit doordat bestaande verzoeken aan de oude pod werden beëindigd toen deze werd beëindigd. Hierna verschenen er 503 fouten, die het gevolg waren van een gestopte NGINX-container, die ook verbindingen verbroken vanwege de backend (waardoor Ingress er geen verbinding mee kon maken).

Laten we kijken hoe process_control_timeout in PHP-FPM zal ons helpen wachten op de voltooiing van onderliggende processen, d.w.z. corrigeer dergelijke fouten. Implementeer opnieuw met behulp van deze richtlijn:

Kubernetes tips & tricks: kenmerken van sierlijk afsluiten in NGINX en PHP-FPM

Er zijn geen fouten meer tijdens de 500e implementatie! De implementatie is succesvol, een sierlijke afsluiting werkt.

Het is echter de moeite waard om het probleem met Ingress-containers te onthouden, een klein percentage fouten waarin we mogelijk te maken krijgen vanwege een vertraging. Om ze te vermijden, hoeft u alleen maar een structuur toe te voegen sleep en herhaal de implementatie. In ons specifieke geval waren er echter geen wijzigingen zichtbaar (opnieuw geen fouten).

Conclusie

Om het proces netjes te beëindigen, verwachten we het volgende gedrag van de applicatie:

  1. Wacht een paar seconden en stop dan met het accepteren van nieuwe verbindingen.
  2. Wacht tot alle verzoeken zijn voltooid en sluit alle keepalive-verbindingen die geen verzoeken uitvoeren.
  3. Beëindig uw proces.

Niet alle applicaties kunnen echter op deze manier werken. Eén oplossing voor het probleem in de Kubernetes-realiteit is:

  • het toevoegen van een pre-stophaak die een paar seconden wacht;
  • het configuratiebestand van onze backend bestuderen op de juiste parameters.

Het voorbeeld met NGINX maakt duidelijk dat zelfs een applicatie die in eerste instantie beëindigingssignalen correct zou moeten verwerken, dit mogelijk niet doet. Het is dus van cruciaal belang om te controleren op 500-fouten tijdens de implementatie van de applicatie. Hierdoor kun je ook breder naar het probleem kijken en niet focussen op een enkele pod of container, maar naar de gehele infrastructuur als geheel.

Als testtool kunt u Yandex.Tank gebruiken in combinatie met elk monitoringsysteem (in ons geval zijn voor de test gegevens uit Grafana gehaald met een Prometheus-backend). Problemen met sierlijk afsluiten zijn duidelijk zichtbaar onder zware belasting die de benchmark kan genereren, en monitoring helpt om de situatie tijdens of na de test gedetailleerder te analyseren.

Als reactie op feedback op het artikel: het is vermeldenswaard dat de problemen en oplossingen hier worden beschreven met betrekking tot NGINX Ingress. Voor andere gevallen zijn er andere oplossingen, die we kunnen overwegen in de volgende materialen uit de serie.

PS

Overige uit de K8s tips & tricks serie:

Bron: www.habr.com

Voeg een reactie