Nasveti in zvijače Kubernetes: funkcije elegantne zaustavitve v NGINX in PHP-FPM

Tipičen pogoj pri implementaciji CI/CD v Kubernetes: aplikacija mora imeti možnost, da ne sprejme novih zahtev odjemalcev, preden se popolnoma ustavi, in kar je najpomembneje, uspešno zaključi obstoječe.

Nasveti in zvijače Kubernetes: funkcije elegantne zaustavitve v NGINX in PHP-FPM

Skladnost s tem pogojem vam omogoča, da med uvajanjem dosežete nič izpadov. Toda tudi pri uporabi zelo priljubljenih svežnjev (kot sta NGINX in PHP-FPM) lahko naletite na težave, ki bodo povzročile val napak pri vsaki uvedbi ...

Teorija. Kako živi pod

Podrobneje o življenjskem ciklu stroka smo že objavili ta članek. V okviru obravnavane tematike nas zanima naslednje: v trenutku, ko pod preide v stanje Končanje, mu prenehajo pošiljati nove zahteve (pod odstranjeno s seznama končnih točk za storitev). Da bi se izognili izpadom med uvajanjem, je dovolj, da rešimo težavo s pravilno zaustavitvijo aplikacije.

Ne pozabite tudi, da je privzeto obdobje odloga 30 sekund: po tem bo sklop prekinjen in aplikacija mora imeti čas za obdelavo vseh zahtev pred tem obdobjem. Obvestilo: čeprav je vsaka zahteva, ki traja več kot 5-10 sekund, že problematična in ji eleganten izklop ne bo več pomagal ...

Če želite bolje razumeti, kaj se zgodi, ko se sklop konča, si oglejte naslednji diagram:

Nasveti in zvijače Kubernetes: funkcije elegantne zaustavitve v NGINX in PHP-FPM

A1, B1 - Prejemanje sprememb o stanju ognjišča
A2 - SIGTERM za odhod
B2 – Odstranjevanje stroka s končnih točk
B3 - Prejemanje sprememb (seznam končnih točk je spremenjen)
B4 - Posodobite pravila iptables

Prosimo, upoštevajte: brisanje sklopa končne točke in pošiljanje SIGTERM se ne zgodi zaporedno, ampak vzporedno. In zaradi dejstva, da Ingress ne prejme posodobljenega seznama končnih točk takoj, bodo nove zahteve odjemalcev poslane v sklop, kar bo povzročilo napako 500 med prekinitvijo sklopa (za podrobnejše gradivo o tem vprašanju smo prevedeno). To težavo je treba rešiti na naslednje načine:

  • Pošlji povezavo: zaprite glave odgovorov (če se to nanaša na aplikacijo HTTP).
  • Če kode ni mogoče spremeniti, je v naslednjem članku opisana rešitev, ki vam bo omogočila obdelavo zahtevkov do konca prehodnega obdobja.

Teorija. Kako NGINX in PHP-FPM zaključita svoje procese

nginx

Začnimo z NGINX, saj je z njim vse bolj ali manj očitno. Ko se potopimo v teorijo, izvemo, da ima NGINX en glavni proces in več "delavcev" - to so podrejeni procesi, ki obdelujejo zahteve strank. Na voljo je priročna možnost: uporaba ukaza nginx -s <SIGNAL> zaključite procese bodisi v načinu hitre zaustavitve ali elegantne zaustavitve. Očitno nas zanima zadnja možnost.

Potem je vse preprosto: morate dodati preStop-kavelj ukaz, ki bo poslal eleganten signal za zaustavitev. To lahko storite v Razmestitvi, v bloku vsebnika:

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

Zdaj, ko se pod izklopi, bomo v dnevnikih vsebnika NGINX videli naslednje:

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

In to bo pomenilo, kar potrebujemo: NGINX počaka, da se zahteve dokončajo, in nato ubije proces. Vendar pa bomo spodaj obravnavali tudi pogost problem, zaradi katerega tudi z ukazom nginx -s quit postopek se nepravilno zaključi.

In na tej stopnji smo končali z NGINX: vsaj iz dnevnikov lahko razberete, da vse deluje, kot bi moralo.

Kaj je s PHP-FPM? Kako se obnese elegantno zaustavitev? Ugotovimo.

PHP-FPM

V primeru PHP-FPM je informacij nekoliko manj. Če se osredotočite na uradni priročnik glede na PHP-FPM bo pisalo, da so sprejeti naslednji signali POSIX:

  1. SIGINT, SIGTERM — hiter izklop;
  2. SIGQUIT — eleganten izklop (kar potrebujemo).

Preostali signali v tej nalogi niso potrebni, zato bomo njihovo analizo izpustili. Za pravilno prekinitev postopka boste morali napisati naslednji kavelj preStop:

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

Na prvi pogled je to vse, kar je potrebno za eleganten izklop v obeh vsebnikih. Vendar je naloga težja, kot se zdi. Spodaj sta dva primera, v katerih elegantna zaustavitev ni delovala in je povzročila kratkotrajno nerazpoložljivost projekta med uvajanjem.

Vadite. Možne težave z elegantnim izklopom

nginx

Najprej si je koristno zapomniti: poleg izvajanja ukaza nginx -s quit Obstaja še ena faza, na katero je vredno biti pozoren. Naleteli smo na težavo, pri kateri je NGINX še vedno pošiljal SIGTERM namesto signala SIGQUIT, kar je povzročilo, da se zahteve ne dokončajo pravilno. Podobne primere najdemo npr. tukaj. Na žalost nismo mogli ugotoviti posebnega razloga za to vedenje: obstajal je sum o različici NGINX, vendar ni bil potrjen. Simptom je bil, da so bila v dnevnikih vsebnika NGINX opažena sporočila: "odprta vtičnica #10 levo v povezavi 5", nakar se je strok ustavil.

Takšen problem lahko opazimo na primer iz odgovorov na Ingressu, ki jih potrebujemo:

Nasveti in zvijače Kubernetes: funkcije elegantne zaustavitve v NGINX in PHP-FPM
Indikatorji statusnih kod v času uvajanja

V tem primeru od samega Ingressa prejmemo samo kodo napake 503: ne more dostopati do vsebnika NGINX, ker ni več dostopen. Če pogledate dnevnike vsebnika z NGINX, vsebujejo naslednje:

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

Po spremembi stop signala se kontejner začne pravilno ustavljati: to potrjuje dejstvo, da napaka 503 ni več opažena.

Če naletite na podobno težavo, je smiselno ugotoviti, kateri stop signal se uporablja v vsebniku in kako točno izgleda preStop kavelj. Čisto možno je, da se razlog skriva prav v tem.

PHP-FPM ... in več

Težava s PHP-FPM je opisana na trivialen način: ne čaka na dokončanje podrejenih procesov, jih prekine, zato se med uvajanjem in drugimi operacijami pojavijo napake 502. Na bugs.php.net je od leta 2005 več poročil o napakah (npr tukaj и tukaj), ki opisuje to težavo. Toda v dnevnikih najverjetneje ne boste videli ničesar: PHP-FPM bo objavil zaključek svojega postopka brez napak ali obvestil tretjih oseb.

Vredno je pojasniti, da je sama težava lahko v manjši ali večji meri odvisna od same aplikacije in se morda ne kaže, na primer pri spremljanju. Če naletite na to, vam najprej pride na misel preprosta rešitev: dodajte kavelj preStop z sleep(30). Omogočil vam bo, da izpolnite vse zahteve, ki so bile prej (in ne sprejemamo novih, saj pod že v stanju Končanje), po 30 sekundah pa se bo sama enota končala s signalom SIGTERM.

Izkazalo se je, da lifecycle ker bo posoda izgledala takole:

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

Vendar zaradi 30-sek sleep smo močno podaljšali bomo čas uvajanja, saj bo vsak sklop prekinjen najnižja 30 sekund, kar je slabo. Kaj se lahko glede tega naredi?

Obrnimo se na stranko, odgovorno za neposredno izvedbo vloge. V našem primeru je PHP-FPMKateri privzeto ne spremlja izvajanja svojih podrejenih procesov: Glavni proces se takoj zaključi. To vedenje lahko spremenite z uporabo direktive process_control_timeout, ki določa časovne omejitve za podrejene procese, da čakajo na signale glavnega. Če nastavite vrednost na 20 sekund, bo to pokrilo večino poizvedb, ki se izvajajo v vsebniku, in zaustavilo glavni proces, ko bodo dokončane.

S tem znanjem se vrnimo k naši zadnji težavi. Kot že omenjeno, Kubernetes ni monolitna platforma: komunikacija med njenimi različnimi komponentami traja nekaj časa. To še posebej velja, če upoštevamo delovanje Ingresses in drugih sorodnih komponent, saj je zaradi takšne zamude v času uvajanja enostavno dobiti 500 napak. Napaka se lahko na primer pojavi na stopnji pošiljanja zahteve navzgor, vendar je "časovni zamik" interakcije med komponentami precej kratek - manj kot sekunda.

Zato, Skupaj z že omenjeno direktivo process_control_timeout lahko uporabite naslednjo konstrukcijo lifecycle:

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

V tem primeru bomo zakasnitev kompenzirali z ukazom sleep in ne povečajte močno časa uvajanja: ali obstaja opazna razlika med 30 sekundami in eno?.. Pravzaprav je process_control_timeoutIn lifecycle uporablja le kot "varnostno mrežo" v primeru zamika.

Na splošno opisano vedenje in ustrezna rešitev ne veljata le za PHP-FPM. Podobna situacija se lahko tako ali drugače pojavi pri uporabi drugih jezikov/ogrodij. Če elegantne zaustavitve ne morete popraviti na druge načine - na primer s prepisovanjem kode, tako da aplikacija pravilno obdeluje zaključne signale - lahko uporabite opisano metodo. Morda ni najlepši, a deluje.

Vadite. Obremenitveno testiranje za preverjanje delovanja stroka

Obremenitveno testiranje je eden od načinov preverjanja delovanja vsebnika, saj ga s tem postopkom približamo realnim bojnim razmeram, ko uporabniki obiščejo stran. Če želite preizkusiti zgornja priporočila, lahko uporabite Yandex.Tankom: Popolnoma pokriva vse naše potrebe. Sledijo nasveti in priporočila za izvajanje testiranja z jasnim primerom iz naših izkušenj zahvaljujoč grafom Grafana in samega Yandex.Tank.

Najpomembnejša stvar tukaj je preverite spremembe korak za korakom. Po dodajanju novega popravka zaženite test in preverite, ali so se rezultati spremenili v primerjavi z zadnjim zagonom. V nasprotnem primeru bo težko prepoznati neučinkovite rešitve, dolgoročno pa lahko le škoduje (na primer podaljša čas uvajanja).

Drug odtenek je, da si ogledate dnevnike vsebnika med njegovim zaključkom. Ali so tam zabeležene informacije o pravilni zaustavitvi? Ali so v dnevnikih pri dostopu do drugih virov (na primer do sosednjega vsebnika PHP-FPM) kakšne napake? Napake v sami aplikaciji (kot v zgoraj opisanem primeru NGINX)? Upam, da vam bodo uvodne informacije iz tega članka pomagale bolje razumeti, kaj se zgodi s vsebnikom med njegovim prenehanjem.

Tako je prva testna vožnja potekala brez lifecycle in brez dodatnih direktiv za aplikacijski strežnik (process_control_timeout v PHP-FPM). Namen tega testa je bil ugotoviti približno število napak (in ali sploh obstajajo). Iz dodatnih informacij morate vedeti tudi, da je bil povprečni čas uvajanja za vsako enoto približno 5–10 sekund, dokler ni bila popolnoma pripravljena. Rezultati so:

Nasveti in zvijače Kubernetes: funkcije elegantne zaustavitve v NGINX in PHP-FPM

Informacijska plošča Yandex.Tank prikazuje skok 502 napak, ki so se pojavile v času uvajanja in so v povprečju trajale do 5 sekund. Verjetno je bilo to zato, ker so bile obstoječe zahteve za stari sklop prekinjene, ko so ga ukinjali. Po tem so se pojavile 503 napake, kar je bila posledica ustavljenega vsebnika NGINX, ki je prav tako prekinil povezave zaradi backend-a (ki je Ingressu preprečil povezavo z njim).

Poglejmo, kako process_control_timeout v PHP-FPM nam bo pomagal počakati na dokončanje podrejenih procesov, tj. popraviti take napake. Ponovno razporedite s to direktivo:

Nasveti in zvijače Kubernetes: funkcije elegantne zaustavitve v NGINX in PHP-FPM

Med 500. uvedbo ni več napak! Namestitev je uspešna, elegantna zaustavitev deluje.

Vendar se velja spomniti trenutka z vsebniki Ingress, majhen odstotek napak, v katerih lahko prejmemo zaradi časovnega zamika. Da bi se jim izognili, ostane le še, da dodate strukturo z sleep in ponovite razporeditev. Vendar v našem konkretnem primeru ni bilo vidnih nobenih sprememb (spet nobenih napak).

Zaključek

Za elegantno prekinitev postopka od aplikacije pričakujemo naslednje vedenje:

  1. Počakajte nekaj sekund in nato prenehajte sprejemati nove povezave.
  2. Počakajte, da se vse zahteve dokončajo, in zaprite vse vzdrževalne povezave, ki ne izvajajo zahtev.
  3. Končajte svoj postopek.

Vse aplikacije pa ne morejo delovati na ta način. Ena od rešitev težave v realnostih Kubernetes je:

  • dodajanje kljuke pred zaustavitvijo, ki bo počakala nekaj sekund;
  • preučevanje konfiguracijske datoteke našega zaledja za ustrezne parametre.

Primer z NGINX pojasnjuje, da celo aplikacija, ki bi morala na začetku pravilno obdelati zaključne signale, tega morda ne bo storila, zato je ključnega pomena, da med uvajanjem aplikacije preverite 500 napak. To vam tudi omogoča, da na problem pogledate širše in se ne osredotočate na posamezen pod ali vsebnik, ampak pogledate celotno infrastrukturo kot celoto.

Kot orodje za testiranje lahko uporabite Yandex.Tank v povezavi s katerim koli nadzornim sistemom (v našem primeru so bili podatki za test vzeti iz Grafana z zaledjem Prometheus). Težave z elegantnim izklopom so jasno vidne pri velikih obremenitvah, ki jih lahko ustvari merilo uspešnosti, spremljanje pa pomaga podrobneje analizirati situacijo med preskusom ali po njem.

Kot odgovor na povratne informacije o članku: treba je omeniti, da so težave in rešitve opisane tukaj v zvezi z NGINX Ingress. Za druge primere obstajajo druge rešitve, ki jih lahko upoštevamo v naslednjih gradivih serije.

PS

Drugo iz serije nasvetov in zvijač K8s:

Vir: www.habr.com

Dodaj komentar