Kubernetes savjeti i trikovi: karakteristike gracioznog isključivanja u NGINX-u i PHP-FPM-u

Tipičan uslov za implementaciju CI/CD-a u Kubernetes: aplikacija mora biti u stanju da ne prihvati nove klijentske zahteve pre nego što se potpuno zaustavi, i što je najvažnije, uspešno završi postojeće.

Kubernetes savjeti i trikovi: karakteristike gracioznog isključivanja u NGINX-u i PHP-FPM-u

Usklađenost sa ovim uslovom omogućava vam da postignete nultu zastoja tokom implementacije. Međutim, čak i kada koristite veoma popularne pakete (kao što su NGINX i PHP-FPM), možete naići na poteškoće koje će dovesti do talasa grešaka sa svakom implementacijom...

Teorija. Kako pod živi

O životnom ciklusu mahuna već smo detaljno objavili ovaj članak. U kontekstu teme koja se razmatra, zanima nas sljedeće: u trenutku kada mahuna ulazi u stanje Prekidanje, prestaju mu se slati novi zahtjevi (pod obrisano sa liste krajnjih tačaka za uslugu). Dakle, da bismo izbjegli zastoje tokom implementacije, dovoljno je da ispravno riješimo problem zaustavljanja aplikacije.

Takođe treba da zapamtite da je podrazumevani grejs period 30 sekundi: nakon ovoga, pod će biti prekinut i aplikacija mora imati vremena da obradi sve zahtjeve prije ovog perioda. primjedba: iako je svaki zahtjev koji traje više od 5-10 sekundi već problematičan, a graciozno gašenje mu više neće pomoći...

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

Kubernetes savjeti i trikovi: karakteristike gracioznog isključivanja u NGINX-u i PHP-FPM-u

A1, B1 - Prijem promjena o stanju ognjišta
A2 - Polazak SIGTERM
B2 - Uklanjanje modula sa krajnjih tačaka
B3 - Prijem promjena (lista krajnjih tačaka je promijenjena)
B4 - Ažurirajte iptables pravila

Imajte na umu: brisanje modula krajnje tačke i slanje SIGTERM-a se ne dešava sekvencijalno, već paralelno. A zbog činjenice da Ingress ne primi odmah ažuriranu listu krajnjih tačaka, novi zahtjevi klijenata će biti poslani u pod, što će uzrokovati grešku 500 tokom prekida pod (za detaljniji materijal o ovom pitanju, mi prevedeno). Ovaj problem je potrebno riješiti na sljedeće načine:

  • Pošalji vezu: zatvori u zaglavljima odgovora (ako se radi o HTTP aplikaciji).
  • Ako nije moguće izvršiti promjene u kodu, onda sljedeći članak opisuje rješenje koje će vam omogućiti da obrađujete zahtjeve do kraja grace period.

Teorija. Kako NGINX i PHP-FPM završavaju svoje procese

NGINX

Počnimo sa NGINX-om, pošto je sve manje-više očigledno sa njim. Uranjajuć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 pogodna opcija: korištenje naredbe nginx -s <SIGNAL> završite procese u brzom ili gracioznom načinu isključivanja. Očigledno nas zanima potonja opcija.

Tada je sve jednostavno: potrebno je dodati preStop-hook komanda koja će poslati graciozan signal za isključivanje. Ovo se može uraditi u Deploymentu, u bloku kontejnera:

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

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

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

I u ovoj fazi smo završili sa NGINX-om: barem iz evidencije možete shvatiti da sve radi kako treba.

Šta je sa PHP-FPM-om? Kako se nosi sa gracioznim isključivanjem? Hajde da to shvatimo.

PHP-FPM

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

  1. SIGINT, SIGTERM — brzo gašenje;
  2. SIGQUIT — graciozno gašenje (ono što nam treba).

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, ovo je sve što je potrebno da se izvrši graciozno gašenje u oba kontejnera. Međutim, zadatak je teži nego što se čini. Ispod su dva slučaja u kojima graciozno gašenje nije uspjelo i uzrokovalo je kratkoročnu nedostupnost projekta tokom implementacije.

Vježbajte. Mogući problemi sa gracioznim isključivanjem

NGINX

Prije svega, korisno je zapamtiti: pored izvršavanja naredbe nginx -s quit Postoji još jedna faza na koju vrijedi obratiti pažnju. Naišli smo na problem gdje bi NGINX i dalje slao SIGTERM umjesto SIGQUIT signala, uzrokujući da se zahtjevi ne dovršavaju ispravno. Slični slučajevi se mogu naći, npr. ovdje. Nažalost, nismo uspjeli utvrditi konkretan razlog ovakvog ponašanja: postojala je sumnja na verziju NGINX-a, ali nije potvrđena. Simptom je bio da su poruke uočene u dnevnikima NGINX kontejnera: "otvorena utičnica #10 lijevo u vezi 5", nakon čega je mahuna stala.

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

Kubernetes savjeti i trikovi: karakteristike gracioznog isključivanja u NGINX-u i PHP-FPM-u
Indikatori statusnih kodova u vrijeme implementacije

U ovom slučaju, od samog Ingress-a dobijamo samo kod greške 503: ne može pristupiti NGINX kontejneru, jer više nije dostupan. Ako pogledate dnevnike kontejnera sa 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 ispravno zaustavljati: to potvrđuje činjenica da se greška 503 više ne opaža.

Ako naiđete na sličan problem, ima smisla otkriti koji se signal zaustavljanja koristi u kontejneru i kako točno izgleda preStop kuka. Sasvim je moguće da razlog leži upravo u tome.

PHP-FPM... i više

Problem sa PHP-FPM-om je opisan na trivijalan način: on ne čeka završetak podređenih procesa, on ih prekida, zbog čega se javljaju greške 502 tokom implementacije i drugih operacija. Postoji nekoliko izvještaja o greškama na bugs.php.net od 2005. (npr ovdje и ovdje), koji opisuje ovaj problem. Ali najvjerovatnije nećete vidjeti ništa u evidenciji: PHP-FPM će objaviti završetak svog procesa bez ikakvih grešaka ili obavijesti trećih strana.

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

Ispostavilo se da je to lifecycle za kontejner će izgledati ovako:

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

Međutim, zbog 30-sekunde sleep mi smo snažno produžit ćemo vrijeme implementacije, budući da će svaki pod biti prekinut minimum 30 sekundi, što je loše. Šta se može učiniti povodom toga?

Okrenimo se strani koja je odgovorna za direktno izvršenje aplikacije. U našem slučaju jeste PHP-FPM, što je po defaultu ne nadgleda izvršavanje svojih podređenih procesa: Glavni proces se odmah prekida. Ovo ponašanje možete promijeniti pomoću direktive process_control_timeout, koji specificira vremenska ograničenja za podređene procese da čekaju signale od mastera. Ako postavite vrijednost na 20 sekundi, to će pokriti većinu upita koji se izvode u kontejneru i zaustavit će glavni proces kada se završe.

Uz ovo saznanje, vratimo se na naš posljednji problem. Kao što je spomenuto, Kubernetes nije monolitna platforma: komunikacija između njegovih različitih komponenti traje neko vrijeme. Ovo je posebno istinito kada uzmemo u obzir rad Ingresses-a i drugih srodnih komponenti, jer je zbog takvog kašnjenja u trenutku implementacije lako dobiti nalet od 500 grešaka. Na primjer, može doći do greške u fazi slanja zahtjeva uzvodnom, ali "vremenski kašnjenje" interakcije između komponenti je prilično kratko - manje od sekunde.

Zbog toga, Ukupno sa već pomenutom direktivom 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 ćemo nadoknaditi kašnjenje komandom sleep i nemojte mnogo povećavati vrijeme implementacije: postoji li primjetna razlika između 30 sekundi i jedne?.. U stvari, to je process_control_timeouti lifecycle koristi se samo kao „zaštitna mreža“ u slučaju kašnjenja.

Općenito opisano ponašanje i odgovarajuće rešenje ne primenjuju se samo na PHP-FPM. Slična situacija može na ovaj ili onaj način nastati kada se koriste drugi jezici/okviri. Ako ne možete popraviti graciozno gašenje na druge načine - na primjer, ponovnim pisanjem koda tako da aplikacija ispravno obrađuje signale završetka - možete koristiti opisanu metodu. Možda nije najljepša, ali funkcionira.

Vježbajte. Testiranje opterećenja za provjeru rada modula

Testiranje opterećenja je jedan od načina da provjerite kako kontejner radi, jer ga ovaj postupak približava stvarnim borbenim uvjetima kada korisnici posjete lokaciju. 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 grafovima Grafane i samog Yandex.Tank-a.

Najvažnija stvar je ovdje provjerite promjene korak po korak. Nakon dodavanja novog popravka, pokrenite test i pogledajte jesu li se rezultati promijenili u odnosu na prošlo pokretanje. U suprotnom će biti teško identificirati neefikasna rješenja, a dugoročno to može samo naštetiti (na primjer, povećati vrijeme implementacije).

Još jedna nijansa je da pogledate dnevnike kontejnera tokom njegovog završetka. Jesu li tamo zabilježene informacije o gracioznom isključivanju? Ima li grešaka u evidenciji prilikom pristupa drugim resursima (na primjer, susjednom PHP-FPM kontejneru)? Greške u samoj aplikaciji (kao u gore opisanom slučaju sa NGINX-om)? Nadam se da će vam uvodne informacije iz ovog članka pomoći da bolje shvatite šta se dešava sa kontejnerom tokom njegovog prekida.

Dakle, prva probna vožnja je obavljena bez lifecycle i bez dodatnih direktiva za poslužitelj aplikacija (process_control_timeout u PHP-FPM). Svrha ovog testa je bila da se utvrdi približan broj grešaka (i da li ih ima). Također, iz dodatnih informacija, trebali biste znati da je prosječno vrijeme postavljanja za svaki modul bilo oko 5-10 sekundi dok nije bio potpuno spreman. Rezultati su:

Kubernetes savjeti i trikovi: karakteristike gracioznog isključivanja u NGINX-u i PHP-FPM-u

Panel sa informacijama Yandex.Tank-a pokazuje porast od 502 greške, koje su se dogodile u vrijeme implementacije i koje su u prosjeku trajale do 5 sekundi. Vjerovatno je to bilo zato što su postojeći zahtjevi prema starom podu bili prekinuti kada je bio prekinut. Nakon ovoga pojavile su se 503 greške, što je rezultat zaustavljenog NGINX kontejnera, koji je također prekinuo konekcije zbog backenda (koji je spriječio Ingress da se poveže s njim).

Da vidimo kako process_control_timeout u PHP-FPM će nam pomoći da sačekamo završetak podređenih procesa, tj. ispraviti takve greške. Ponovno implementirajte koristeći ovu direktivu:

Kubernetes savjeti i trikovi: karakteristike gracioznog isključivanja u NGINX-u i PHP-FPM-u

Nema više grešaka tokom 500. raspoređivanja! Implementacija je uspješna, graciozno gašenje funkcionira.

Međutim, vrijedi zapamtiti problem s Ingress kontejnerima, mali postotak grešaka koje možemo dobiti zbog vremenskog kašnjenja. Da biste ih izbjegli, sve što ostaje je dodati strukturu s sleep i ponovite raspoređivanje. Međutim, u našem konkretnom slučaju nije bilo vidljivih promjena (opet, nema grešaka).

zaključak

Da bismo graciozno okončali proces, očekujemo sljedeće ponašanje od aplikacije:

  1. Pričekajte nekoliko sekundi, a zatim prestanite prihvaćati nove veze.
  2. Pričekajte da se svi zahtjevi završe i zatvorite sve 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 stvarnosti je:

  • dodavanje pre-stop kuke koja će čekati nekoliko sekundi;
  • proučavanje konfiguracijskog fajla našeg backenda za odgovarajuće parametre.

Primjer sa NGINX-om jasno pokazuje da čak i aplikacija koja bi u početku trebala ispravno obraditi signale završetka možda to neće učiniti, tako da je ključno provjeriti 500 grešaka tokom implementacije aplikacije. Ovo vam također omogućava da na problem sagledate šire i da se ne fokusirate na jedan pod ili kontejner, već na cijelu infrastrukturu u cjelini.

Kao alat za testiranje, možete koristiti Yandex.Tank u kombinaciji sa bilo kojim sistemom za praćenje (u našem slučaju podaci su preuzeti iz Grafane sa Prometheus backend-om za testiranje). Problemi sa gracioznim isključivanjem jasno su vidljivi pod velikim opterećenjima koja benčmark može da generiše, a praćenje pomaže da se situacija detaljnije analizira tokom ili nakon testa.

Kao odgovor na povratne informacije o članku: vrijedno je spomenuti da su ovdje opisani problemi i rješenja u vezi sa NGINX Ingress. Za ostale slučajeve postoje i druga rješenja koja možemo razmotriti u sljedećim materijalima serije.

PS

Ostali iz serije K8s tips & tricks:

izvor: www.habr.com

Dodajte komentar