Kubernetes tips og tricks: funktioner i yndefuld nedlukning i NGINX og PHP-FPM

En typisk tilstand ved implementering af CI/CD i Kubernetes: applikationen skal være i stand til ikke at acceptere nye klientanmodninger, før den stopper fuldstændigt, og vigtigst af alt, fuldføre eksisterende.

Kubernetes tips og tricks: funktioner i yndefuld nedlukning i NGINX og PHP-FPM

Overholdelse af denne betingelse giver dig mulighed for at opnå nul nedetid under implementeringen. Men selv når du bruger meget populære bundter (som NGINX og PHP-FPM), kan du støde på vanskeligheder, der vil føre til en bølge af fejl med hver implementering...

Teori. Hvordan pod lever

Vi har allerede offentliggjort i detaljer om en pods livscyklus denne artikel. I forbindelse med det behandlede emne er vi interesserede i følgende: i det øjeblik, hvor poden kommer ind i staten afslutning, bliver nye anmodninger ikke længere sendt til den (pod slettet fra listen over endepunkter for tjenesten). For at undgå nedetid under implementeringen er det således nok for os at løse problemet med at stoppe applikationen korrekt.

Du skal også huske, at standardudsættelsesperioden er 30 sekunder: herefter vil poden blive afsluttet, og ansøgningen skal have tid til at behandle alle anmodninger inden denne periode. Bemærk: selvom enhver anmodning, der tager mere end 5-10 sekunder, allerede er problematisk, og yndefuld nedlukning vil ikke længere hjælpe det...

For bedre at forstå, hvad der sker, når en pod afsluttes, skal du blot se på følgende diagram:

Kubernetes tips og tricks: funktioner i yndefuld nedlukning i NGINX og PHP-FPM

A1, B1 - Modtagelse af ændringer om ildstedets tilstand
A2 - Afgang SIGTERM
B2 - Fjernelse af en pod fra endepunkter
B3 - Modtagelse af ændringer (listen over endepunkter er ændret)
B4 - Opdater iptables regler

Bemærk venligst: sletning af endpoint pod og afsendelse af SIGTERM sker ikke sekventielt, men parallelt. Og på grund af det faktum, at Ingress ikke umiddelbart modtager den opdaterede liste over Endpoints, vil nye anmodninger fra klienter blive sendt til poden, hvilket vil forårsage en 500-fejl under pod-terminering (for mere detaljeret materiale om dette spørgsmål, vi oversat). Dette problem skal løses på følgende måder:

  • Send forbindelse: luk svaroverskrifter (hvis dette vedrører en HTTP-applikation).
  • Hvis det ikke er muligt at foretage ændringer i koden, beskriver følgende artikel en løsning, der giver dig mulighed for at behandle anmodninger indtil udgangen af ​​den yndefulde periode.

Teori. Hvordan NGINX og PHP-FPM afslutter deres processer

Nginx

Lad os starte med NGINX, da alt er mere eller mindre indlysende med det. Når vi dykker ned i teorien, lærer vi, at NGINX har én hovedproces og flere "arbejdere" - det er underordnede processer, der behandler klientanmodninger. Der er en praktisk mulighed: ved hjælp af kommandoen nginx -s <SIGNAL> afslutte processer enten i hurtig nedlukning eller yndefuld nedlukningstilstand. Det er naturligvis sidstnævnte mulighed, der interesserer os.

Så er alt enkelt: du skal tilføje til preStop-krog en kommando, der sender et yndefuldt nedlukningssignal. Dette kan gøres i Deployment, i containerblokken:

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

Nu, når poden lukker ned, vil vi se følgende i NGINX-beholderlogfilerne:

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

Og dette vil betyde, hvad vi har brug for: NGINX venter på, at anmodninger er fuldført, og dræber derefter processen. Men nedenfor vil vi også overveje et almindeligt problem på grund af hvilket, selv med kommandoen nginx -s quit processen afsluttes forkert.

Og på dette stadie er vi færdige med NGINX: i det mindste ud fra loggene kan du forstå, at alt fungerer, som det skal.

Hvad er der med PHP-FPM? Hvordan håndterer den en yndefuld nedlukning? Lad os finde ud af det.

PHP-FPM

I tilfældet med PHP-FPM er der lidt mindre information. Hvis du fokuserer på officiel manual ifølge PHP-FPM vil den sige, at følgende POSIX-signaler accepteres:

  1. SIGINT, SIGTERM — hurtig nedlukning;
  2. SIGQUIT — yndefuld nedlukning (hvad vi har brug for).

De resterende signaler er ikke nødvendige i denne opgave, så vi udelader deres analyse. For at afslutte processen korrekt, skal du skrive følgende preStop-hook:

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

Ved første øjekast er det alt, der kræves for at udføre en yndefuld nedlukning i begge containere. Opgaven er dog sværere, end den ser ud til. Nedenfor er to tilfælde, hvor en yndefuld nedlukning ikke virkede og forårsagede kortvarig utilgængelighed af projektet under implementeringen.

Øve sig. Mulige problemer med yndefuld nedlukning

Nginx

Først og fremmest er det nyttigt at huske: ud over at udføre kommandoen nginx -s quit Der er endnu en fase, som er værd at være opmærksom på. Vi stødte på et problem, hvor NGINX stadig ville sende SIGTERM i stedet for SIGQUIT-signalet, hvilket forårsagede, at anmodninger ikke blev fuldført korrekt. Lignende sager findes f.eks. her. Desværre var vi ikke i stand til at fastslå den specifikke årsag til denne adfærd: der var en mistanke om NGINX-versionen, men den blev ikke bekræftet. Symptomet var, at meddelelser blev observeret i NGINX-beholderlogfilerne: "åben stik #10 tilbage i forbindelse 5", hvorefter poden stoppede.

Vi kan observere et sådant problem, for eksempel fra de svar på Ingress, vi har brug for:

Kubernetes tips og tricks: funktioner i yndefuld nedlukning i NGINX og PHP-FPM
Indikatorer for statuskoder på tidspunktet for implementering

I dette tilfælde modtager vi kun en 503 fejlkode fra Ingress selv: den kan ikke få adgang til NGINX-containeren, da den ikke længere er tilgængelig. Hvis du ser på containerlogfilerne med NGINX, indeholder de følgende:

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

Efter ændring af stopsignalet begynder beholderen at stoppe korrekt: dette bekræftes af, at 503-fejlen ikke længere observeres.

Hvis du støder på et lignende problem, giver det mening at finde ud af, hvilket stopsignal, der bruges i beholderen, og hvordan preStop-krogen præcist ser ud. Det er meget muligt, at årsagen netop ligger i dette.

PHP-FPM... og mere

Problemet med PHP-FPM er beskrevet på en triviel måde: det venter ikke på færdiggørelsen af ​​underordnede processer, det afslutter dem, hvilket er grunden til, at 502-fejl opstår under implementering og andre operationer. Der er flere fejlrapporter på bugs.php.net siden 2005 (f.eks her и her), som beskriver dette problem. Men du vil højst sandsynligt ikke se noget i logfilerne: PHP-FPM vil annoncere færdiggørelsen af ​​sin proces uden fejl eller tredjepartsmeddelelser.

Det er værd at præcisere, at selve problemet i mindre eller større grad kan afhænge af selve applikationen og måske ikke kommer til udtryk, for eksempel ved overvågning. Hvis du støder på det, kommer først en simpel løsning til at tænke på: Tilføj en preStop-krog med sleep(30). Det giver dig mulighed for at fuldføre alle anmodninger, der var før (og vi accepterer ikke nye, da pod allerede i stand til afslutning), og efter 30 sekunder slutter selve poden med et signal SIGTERM.

Det viser sig, at lifecycle for beholderen vil se sådan ud:

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

Men på grund af de 30 sekunder sleep vi er kraftigt vi vil øge implementeringstiden, da hver pod vil blive afsluttet mindste 30 sekunder, hvilket er dårligt. Hvad kan man gøre ved dette?

Lad os henvende os til den part, der er ansvarlig for den direkte udførelse af ansøgningen. I vores tilfælde er det PHP-FPMHvilket overvåger som standard ikke udførelsen af ​​sine underordnede processer: Masterprocessen afsluttes øjeblikkeligt. Du kan ændre denne adfærd ved hjælp af direktivet process_control_timeout, som specificerer tidsgrænserne for underordnede processer til at vente på signaler fra masteren. Hvis du indstiller værdien til 20 sekunder, vil dette dække de fleste af de forespørgsler, der kører i containeren, og vil stoppe masterprocessen, når de er afsluttet.

Med denne viden, lad os vende tilbage til vores sidste problem. Som nævnt er Kubernetes ikke en monolitisk platform: kommunikation mellem dens forskellige komponenter tager noget tid. Dette gælder især, når vi betragter driften af ​​Ingresses og andre relaterede komponenter, da det på grund af en sådan forsinkelse på tidspunktet for implementering er let at få en stigning på 500 fejl. For eksempel kan der opstå en fejl på tidspunktet for at sende en anmodning til en upstream, men "tidsforsinkelsen" for interaktion mellem komponenter er ret kort - mindre end et sekund.

Derfor I alt med det allerede nævnte direktiv process_control_timeout du kan bruge følgende konstruktion til lifecycle:

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

I dette tilfælde vil vi kompensere for forsinkelsen med kommandoen sleep og forøg ikke implementeringstiden markant: er der en mærkbar forskel mellem 30 sekunder og én?.. Faktisk er det process_control_timeoutOg lifecycle bruges kun som et "sikkerhedsnet" i tilfælde af forsinkelse.

Generelt set den beskrevne adfærd og den tilsvarende løsning gælder ikke kun for PHP-FPM. En lignende situation kan på den ene eller anden måde opstå ved brug af andre sprog/rammer. Hvis du ikke kan rette en yndefuld nedlukning på andre måder - for eksempel ved at omskrive koden, så applikationen behandler termineringssignaler korrekt - kan du bruge den beskrevne metode. Det er måske ikke det smukkeste, men det virker.

Øve sig. Belastningstest for at kontrollere funktionen af ​​poden

Belastningstest er en af ​​måderne til at kontrollere, hvordan containeren fungerer, da denne procedure bringer den tættere på virkelige kampforhold, når brugere besøger stedet. For at teste ovenstående anbefalinger kan du bruge Yandex.Tankom: Den dækker alle vores behov perfekt. Følgende er tips og anbefalinger til udførelse af test med et klart eksempel fra vores erfaring takket være graferne fra Grafana og Yandex.Tank selv.

Det vigtigste her er kontrollere ændringer trin for trin. Når du har tilføjet en ny rettelse, skal du køre testen og se, om resultaterne er ændret i forhold til den sidste kørsel. Ellers vil det være svært at identificere ineffektive løsninger, og i det lange løb kan det kun gøre skade (for eksempel øge implementeringstiden).

En anden nuance er at se på containerlogfilerne under dens afslutning. Er der registreret oplysninger om yndefuld nedlukning? Er der nogen fejl i logfilerne, når du får adgang til andre ressourcer (f.eks. til en tilstødende PHP-FPM-container)? Fejl i selve applikationen (som i tilfældet med NGINX beskrevet ovenfor)? Jeg håber, at de indledende oplysninger fra denne artikel vil hjælpe dig med bedre at forstå, hvad der sker med containeren under dens opsigelse.

Så den første testkørsel fandt sted uden lifecycle og uden yderligere direktiver for applikationsserveren (process_control_timeout i PHP-FPM). Formålet med denne test var at identificere det omtrentlige antal fejl (og om der er nogen). Ud fra yderligere oplysninger bør du også vide, at den gennemsnitlige implementeringstid for hver pod var omkring 5-10 sekunder, indtil den var helt klar. Resultaterne er:

Kubernetes tips og tricks: funktioner i yndefuld nedlukning i NGINX og PHP-FPM

Yandex.Tank-informationspanelet viser en stigning på 502 fejl, som opstod på tidspunktet for implementeringen og varede i gennemsnit op til 5 sekunder. Det var formodentlig fordi eksisterende anmodninger til den gamle pod blev afsluttet, da den blev afsluttet. Herefter dukkede 503 fejl op, hvilket var resultatet af en stoppet NGINX-container, som også droppede forbindelser på grund af backend (hvilket forhindrede Ingress i at oprette forbindelse til den).

Lad os se hvordan process_control_timeout i PHP-FPM vil hjælpe os med at vente på færdiggørelsen af ​​underordnede processer, dvs. rette sådanne fejl. Geninstaller ved hjælp af dette direktiv:

Kubernetes tips og tricks: funktioner i yndefuld nedlukning i NGINX og PHP-FPM

Der er ikke flere fejl under den 500. udrulning! Implementeringen er vellykket, yndefuld nedlukning fungerer.

Det er dog værd at huske problemet med Ingress-containere, en lille procentdel af fejl, som vi kan modtage på grund af en tidsforsinkelse. For at undgå dem er der kun tilbage at tilføje en struktur med sleep og gentag implementeringen. Men i vores særlige tilfælde var der ingen ændringer synlige (igen ingen fejl).

Konklusion

For at afslutte processen elegant forventer vi følgende adfærd fra applikationen:

  1. Vent et par sekunder, og stop derefter med at acceptere nye forbindelser.
  2. Vent på, at alle anmodninger er afsluttet, og luk alle keepalive-forbindelser, der ikke udfører anmodninger.
  3. Afslut din proces.

Det er dog ikke alle applikationer, der kan fungere på denne måde. En løsning på problemet i Kubernetes realiteter er:

  • tilføjelse af en pre-stop krog, der vil vente et par sekunder;
  • studerer konfigurationsfilen for vores backend for de relevante parametre.

Eksemplet med NGINX gør det klart, at selv en applikation, der oprindeligt skulle behandle afslutningssignaler korrekt, muligvis ikke gør det, så det er vigtigt at tjekke for 500 fejl under applikationsimplementeringen. Dette giver dig også mulighed for at se mere bredt på problemet og ikke fokusere på en enkelt pod eller container, men se på hele infrastrukturen som en helhed.

Som et testværktøj kan du bruge Yandex.Tank i forbindelse med ethvert overvågningssystem (i vores tilfælde blev data taget fra Grafana med en Prometheus-backend til testen). Problemer med yndefuld nedlukning er tydeligt synlige under tunge belastninger, som benchmark kan generere, og overvågning hjælper med at analysere situationen mere detaljeret under eller efter testen.

Som svar på feedback på artiklen: det er værd at nævne, at problemerne og løsningerne er beskrevet her i forhold til NGINX Ingress. For andre tilfælde er der andre løsninger, som vi kan overveje i de følgende materialer i serien.

PS

Andet fra K8s tips & tricks-serien:

Kilde: www.habr.com

Tilføj en kommentar