Kubernetes savjeti i trikovi: značajke elegantnog isključivanja u NGINX i PHP-FPM

Tipičan uvjet pri implementaciji CI/CD-a u Kubernetes: aplikacija mora moći ne prihvatiti zahtjeve novih klijenata prije potpunog zaustavljanja, i što je najvažnije, uspješno dovršiti postojeće.

Kubernetes savjeti i trikovi: značajke elegantnog isključivanja u NGINX i PHP-FPM

Usklađenost s ovim uvjetom omogućuje vam postizanje nultog vremena zastoja tijekom implementacije. Međutim, čak i kada koristite vrlo popularne pakete (kao što su NGINX i PHP-FPM), možete naići na poteškoće koje će dovesti do valova pogrešaka sa svakom implementacijom...

Teorija. Kako živi mahuna

O životnom ciklusu mahune već smo detaljno objavili ovaj članak. U kontekstu teme koja se razmatra, zanima nas sljedeće: u trenutku kada mahuna uđe u stanje Prekidom, prestaju mu se slati novi zahtjevi (pod uklonjen s popisa krajnjih točaka za uslugu). Dakle, kako bismo izbjegli zastoje tijekom implementacije, dovoljno je da ispravno riješimo problem zaustavljanja aplikacije.

Također biste trebali zapamtiti da je zadano razdoblje odgode 30 sekundi: nakon toga, pod će biti prekinut i aplikacija mora imati vremena za obradu svih zahtjeva prije tog razdoblja. Primijetiti: iako je svaki zahtjev koji traje više od 5-10 sekundi već problematičan, a graciozno gašenje više mu neće pomoći...

Da biste bolje razumjeli što se događa kada mahuna završi, samo pogledajte sljedeći dijagram:

Kubernetes savjeti i trikovi: značajke elegantnog isključivanja u NGINX i PHP-FPM

A1, B1 - Primanje promjena o stanju ognjišta
A2 - Odlazak SIGTERM
B2 - Uklanjanje mahune s krajnjih točaka
B3 - Primanje promjena (promijenjen je popis krajnjih točaka)
B4 - Ažuriranje iptables pravila

Imajte na umu: brisanje modula krajnje točke i slanje SIGTERM-a ne događa se uzastopno, već paralelno. A zbog činjenice da Ingress ne prima odmah ažurirani popis krajnjih točaka, novi zahtjevi od klijenata bit će poslani u pod, što će uzrokovati pogrešku 500 tijekom prekida poda (za detaljniji materijal o ovom pitanju, mi prevedeno). Ovaj problem je potrebno riješiti na sljedeće načine:

  • Pošalji vezu: zatvori zaglavlja odgovora (ako se to odnosi na HTTP aplikaciju).
  • Ako nije moguće izvršiti izmjene koda, sljedeći članak opisuje rješenje koje će vam omogućiti obradu zahtjeva do kraja razdoblja odgode.

Teorija. Kako NGINX i PHP-FPM prekidaju svoje procese

Nginx

Krenimo od NGINX-a, jer je kod njega sve više-manje očito. Zaronivši u teoriju, saznajemo da NGINX ima jedan glavni proces i nekoliko "radnika" - to su podređeni procesi koji obrađuju zahtjeve klijenata. Omogućena je prikladna opcija: pomoću naredbe nginx -s <SIGNAL> prekinuti procese u brzom ili elegantnom načinu isključivanja. Očito nas zanima potonja opcija.

Tada je sve jednostavno: trebate dodati preStop-kuka naredba koja će poslati elegantan signal za isključivanje. To se može učiniti u Deploymentu, u bloku spremnika:

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

Sada, kada se pod isključi, vidjet ćemo sljedeće u zapisnicima NGINX spremnika:

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 će značiti ono što nam treba: NGINX čeka da se zahtjevi završe, a zatim ubija proces. Međutim, u nastavku ćemo razmotriti i čest problem zbog kojeg, čak i kod naredbe nginx -s quit proces se neispravno završava.

I u ovoj smo fazi gotovi s NGINX-om: barem iz zapisa možete shvatiti da sve radi kako treba.

Što je s PHP-FPM-om? Kako se nosi s elegantnim isključivanjem? Hajdemo shvatiti.

PHP-FPM

U slučaju PHP-FPM-a ima nešto manje informacija. Ako se usredotočite na službeni priručnik prema PHP-FPM-u, reći će da su sljedeći POSIX signali prihvaćeni:

  1. SIGINT, SIGTERM — brzo isključivanje;
  2. SIGQUIT — graciozno isključivanje (ono što trebamo).

Preostali signali nisu potrebni u ovom zadatku, pa ćemo njihovu analizu izostaviti. Da biste ispravno prekinuli proces, morat ćete napisati sljedeći preStop hook:

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

Na prvi pogled, to je sve što je potrebno za elegantno gašenje u oba spremnika. Međutim, zadatak je teži nego što se čini. Ispod su dva slučaja u kojima pažljivo isključivanje nije funkcioniralo i uzrokovalo je kratkotrajnu nedostupnost projekta tijekom implementacije.

Praksa. Mogući problemi s elegantnim isključivanjem

Nginx

Prije svega korisno je zapamtiti: osim izvršenja naredbe nginx -s quit Postoji još jedna faza na koju vrijedi obratiti pozornost. Naišli smo na problem u kojem bi NGINX i dalje slao SIGTERM umjesto signala SIGQUIT, uzrokujući da se zahtjevi ne dovrše ispravno. Slični slučajevi mogu se naći npr. здесь. Nažalost, nismo uspjeli utvrditi konkretan razlog ovakvog ponašanja: postojala je sumnja na verziju NGINX, ali nije potvrđena. Simptom je bio da su poruke uočene u zapisnicima NGINX spremnika: "otvorena utičnica #10 lijevo u vezi 5", nakon čega se mahuna zaustavila.

Takav problem možemo uočiti, na primjer, iz odgovora na Ingressu koji su nam potrebni:

Kubernetes savjeti i trikovi: značajke elegantnog isključivanja u NGINX i PHP-FPM
Indikatori statusnih kodova u trenutku postavljanja

U ovom slučaju, od samog Ingressa primamo samo kod pogreške 503: ne može pristupiti NGINX spremniku jer više nije dostupan. Ako pogledate zapise spremnika s NGINX-om, oni sadrže sljedeće:

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

Nakon promjene signala za zaustavljanje, kontejner se počinje pravilno zaustavljati: to potvrđuje činjenica da se pogreška 503 više ne uočava.

Ako naiđete na sličan problem, ima smisla shvatiti koji se signal za zaustavljanje koristi u spremniku i kako točno izgleda preStop kuka. Vrlo je moguće da razlog leži upravo u tome.

PHP-FPM... i više

Problem s PHP-FPM-om opisan je na trivijalan način: ne čeka završetak podređenih procesa, već ih prekida, zbog čega se tijekom postavljanja i drugih operacija pojavljuju pogreške 502. Postoji nekoliko izvješća o greškama na bugs.php.net od 2005. (npr здесь и здесь), koji opisuje ovaj problem. Ali najvjerojatnije nećete vidjeti ništa u zapisima: PHP-FPM će objaviti završetak svog procesa bez ikakvih pogrešaka ili obavijesti trećih strana.

Vrijedno je pojasniti da sam problem može ovisiti u manjoj ili većoj mjeri o samoj aplikaciji i možda se neće manifestirati, na primjer, u nadzoru. Ako naiđete na to, prvo vam pada na pamet jednostavno zaobilazno rješenje: dodajte preStop kuku sa sleep(30). Omogućit će vam da ispunite sve zahtjeve koji su bili prije (i ne prihvaćamo nove, jer pod već sposoban za Prekidom), a nakon 30 sekundi sama mahuna će završiti sa signalom SIGTERM.

Ispada da lifecycle jer će spremnik izgledati ovako:

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

Međutim, zbog 30-sek sleep mi snažno produžit ćemo vrijeme raspoređivanja jer će svaka mahuna biti prekinuta minimum 30 sekundi, što je loše. Što se može učiniti po tom pitanju?

Obratimo se strani odgovornoj za izravno izvršenje zahtjeva. U našem slučaju jest PHP-FPMKoji prema zadanim postavkama ne prati izvršavanje svojih podređenih procesa: Glavni proces se odmah prekida. Ovo ponašanje možete promijeniti pomoću direktive process_control_timeout, koji određuje vremenska ograničenja za podređene procese da čekaju signale od nadređenog. Ako postavite vrijednost na 20 sekundi, to će pokriti većinu upita koji se izvode u spremniku i zaustavit će glavni proces nakon što se dovrše.

S ovim saznanjem, vratimo se našem posljednjem problemu. Kao što je spomenuto, Kubernetes nije monolitna platforma: komunikacija između njegovih različitih komponenti traje neko vrijeme. To je osobito istinito kada uzmemo u obzir rad Ingressesa i drugih srodnih komponenti, budući da je zbog takvog kašnjenja u trenutku implementacije lako dobiti val od 500 pogrešaka. Na primjer, može se pojaviti pogreška u fazi slanja zahtjeva uzlaznom kanalu, ali je "vremenski odmak" interakcije između komponenti prilično kratak - manji od sekunde.

Prema tome, Ukupno uz već spomenutu direktivu process_control_timeout možete koristiti sljedeću konstrukciju za lifecycle:

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

U ovom slučaju kašnjenje ćemo nadoknaditi naredbom sleep i nemojte uvelike povećati vrijeme postavljanja: postoji li primjetna razlika između 30 sekundi i jedne?.. Zapravo, to je process_control_timeoutI lifecycle koristi se samo kao "sigurnosna mreža" u slučaju kašnjenja.

Općenito govoreći opisano ponašanje i odgovarajuće rješenje odnose se ne samo na PHP-FPM. Slična situacija može na ovaj ili onaj način nastati pri korištenju drugih jezika/okvira. Ako ne možete popraviti graciozno isključivanje na druge načine - na primjer, prepisivanjem koda tako da aplikacija ispravno obrađuje signale prekida - možete koristiti opisanu metodu. Možda nije najljepše, ali djeluje.

Praksa. Testiranje opterećenja za provjeru rada kapsule

Load testing je jedan od načina da se provjeri kako spremnik radi, jer se ovim postupkom približava stvarnim borbenim uvjetima kada korisnici posjete stranicu. Da biste testirali gore navedene preporuke, možete koristiti Yandex.Tankom: Savršeno pokriva sve naše potrebe. Slijede savjeti i preporuke za provođenje testiranja s jasnim primjerom iz našeg iskustva zahvaljujući grafikonima Grafane i samog Yandex.Tanka.

Ovdje je najvažnije provjerite promjene korak po korak. Nakon dodavanja novog popravka, pokrenite test i pogledajte jesu li se rezultati promijenili u usporedbi s zadnjim izvođenjem. Inače će biti teško identificirati neučinkovita rješenja, a dugoročno može samo naštetiti (na primjer, povećati vrijeme implementacije).

Još jedna nijansa je pogledati zapise kontejnera tijekom njegovog prekida. Bilježe li se tamo informacije o pažljivom isključivanju? Postoje li greške u zapisnicima prilikom pristupa drugim resursima (na primjer, susjednom PHP-FPM spremniku)? Greške u samoj aplikaciji (kao u gore opisanom slučaju s NGINX-om)? Nadam se da će vam uvodne informacije iz ovog članka pomoći da bolje razumijete što se događa sa spremnikom tijekom njegovog zatvaranja.

Dakle, prva probna vožnja održana je bez lifecycle i bez dodatnih direktiva za aplikacijski poslužitelj (process_control_timeout u PHP-FPM). Svrha ovog testa bila je utvrditi približan broj grešaka (i ima li ih). Također, od dodatnih informacija, trebali biste znati da je prosječno vrijeme postavljanja za svaku kapsulu bilo oko 5-10 sekundi dok nije bila potpuno spremna. Rezultati su:

Kubernetes savjeti i trikovi: značajke elegantnog isključivanja u NGINX i PHP-FPM

Informacijska ploča Yandex.Tank pokazuje skok od 502 pogreške, koje su se dogodile u trenutku postavljanja i trajale su u prosjeku do 5 sekundi. Vjerojatno je to bilo zato što su se postojeći zahtjevi prema staroj grupi prekidali kada se ona prekidala. Nakon toga su se pojavile 503 greške, što je rezultat zaustavljenog NGINX kontejnera, koji je također prekinuo veze zbog pozadine (koja je spriječila Ingress da se poveže na njega).

Da vidimo kako process_control_timeout u PHP-FPM pomoći će nam da pričekamo završetak podređenih procesa, tj. ispraviti takve pogreške. Ponovno implementiraj pomoću ove direktive:

Kubernetes savjeti i trikovi: značajke elegantnog isključivanja u NGINX i PHP-FPM

Nema više grešaka tijekom 500. postavljanja! Implementacija je uspješna, elegantno gašenje radi.

Međutim, vrijedi se sjetiti problema s Ingress spremnicima, mali postotak pogrešaka u kojima možemo primiti zbog vremenskog odmaka. Da biste ih izbjegli, sve što preostaje je dodati strukturu sa sleep i ponovite raspoređivanje. Međutim, u našem konkretnom slučaju nisu vidljive nikakve promjene (opet nema grešaka).

Zaključak

Kako bismo elegantno prekinuli proces, od aplikacije očekujemo sljedeće ponašanje:

  1. Pričekajte nekoliko sekundi i zatim prestanite prihvaćati nove veze.
  2. Pričekajte da se svi zahtjevi završe i zatvorite sve keepalive veze koje ne izvršavaju zahtjeve.
  3. Završite svoj proces.

Međutim, ne mogu sve aplikacije raditi na ovaj način. Jedno rješenje problema u Kubernetes stvarnostima je:

  • dodavanje kuke prije zaustavljanja koja će pričekati nekoliko sekundi;
  • proučavanje konfiguracijske datoteke naše pozadine za odgovarajuće parametre.

Primjer s NGINX-om jasno pokazuje da čak i aplikacija koja bi trebala inicijalno pravilno obraditi signale prekida to možda neće učiniti, stoga je ključno provjeriti 500 pogrešaka tijekom postavljanja aplikacije. To vam također omogućuje da problem sagledate šire i da se ne fokusirate na jednu jedinicu ili kontejner, već da gledate na cijelu infrastrukturu kao cjelinu.

Kao alat za testiranje, možete koristiti Yandex.Tank u kombinaciji s bilo kojim sustavom za nadzor (u našem slučaju, podaci su uzeti iz Grafane s Prometheus pozadinom za test). Problemi s gracioznim gašenjem jasno su vidljivi pod velikim opterećenjima koja benchmark može generirati, a praćenje pomaže u detaljnijoj analizi situacije tijekom ili nakon testa.

Kao odgovor na povratne informacije o članku: vrijedi spomenuti da su problemi i rješenja opisani ovdje u vezi s NGINX Ingressom. Za druge slučajeve postoje druga rješenja, koja možemo razmotriti u sljedećim materijalima serije.

PS

Ostalo iz serije savjeta i trikova K8s:

Izvor: www.habr.com

Dodajte komentar