Konsiletoj kaj lertaĵoj de Kubernetes: trajtoj de gracia haltigo en NGINX kaj PHP-FPM

Tipa kondiĉo dum efektivigo de CI/KD en Kubernetes: la aplikaĵo devas povi ne akcepti novajn klientajn petojn antaŭ tute ĉesi, kaj plej grave, sukcese plenumi ekzistantajn.

Konsiletoj kaj lertaĵoj de Kubernetes: trajtoj de gracia haltigo en NGINX kaj PHP-FPM

Konformeco kun ĉi tiu kondiĉo permesas atingi nulan malfunkcion dum deplojo. Tamen, eĉ kiam vi uzas tre popularajn pakaĵojn (kiel NGINX kaj PHP-FPM), vi povas renkonti malfacilaĵojn, kiuj kondukos al pliiĝo de eraroj kun ĉiu deplojo...

Teorio. Kiel pod vivas

Ni jam publikigis detale pri la vivociklo de balgo ĉi tiu artikolo. En la kunteksto de la konsiderata temo, ni interesiĝas pri la sekvanta: en la momento, kiam la podo eniras la ŝtaton Finante, novaj petoj ĉesas esti senditaj al ĝi (pod forigita el la listo de finpunktoj por la servo). Tiel, por eviti malfunkcion dum deplojo, sufiĉas por ni solvi la problemon ĝuste haltigi la aplikaĵon.

Vi ankaŭ devas memori, ke la defaŭlta gracia periodo estas 30 sekundoj: post tio, la pod estos finita kaj la aplikaĵo devas havi tempon por prilabori ĉiujn petojn antaŭ ĉi tiu periodo. Примечание: kvankam ajna peto, kiu daŭras pli ol 5-10 sekundojn, jam estas problema, kaj gracia malŝalto ne plu helpos ĝin...

Por pli bone kompreni kio okazas kiam pod finiĝas, simple rigardu la sekvan diagramon:

Konsiletoj kaj lertaĵoj de Kubernetes: trajtoj de gracia haltigo en NGINX kaj PHP-FPM

A1, B1 - Ricevante ŝanĝojn pri la stato de la kameno
A2 - Foriro SIGTERM
B2 - Forigante pod el finpunktoj
B3 - Ricevante ŝanĝojn (la listo de finpunktoj ŝanĝiĝis)
B4 - Ĝisdatigu iptables-regulojn

Bonvolu noti: forigo de la finpunktopodo kaj sendi SIGTERM ne okazas sinsekve, sed paralele. Kaj pro la fakto, ke Ingress ne tuj ricevas la ĝisdatigitan liston de Endpoints, novaj petoj de klientoj estos senditaj al la pod, kio kaŭzos 500-eraron dum podfino. (por pli detala materialo pri ĉi tiu afero, ni tradukita). Ĉi tiu problemo devas esti solvita laŭ la sekvaj manieroj:

  • Sendu Konekton: fermu en respondaj kaplinioj (se tio koncernas HTTP-aplikaĵon).
  • Se ne eblas fari ŝanĝojn al la kodo, tiam la sekva artikolo priskribas solvon, kiu permesos al vi prilabori petojn ĝis la fino de la gracia periodo.

Teorio. Kiel NGINX kaj PHP-FPM finas siajn procezojn

NGINX

Ni komencu per NGINX, ĉar ĉio estas pli-malpli evidenta kun ĝi. Plonĝante en la teorion, ni lernas, ke NGINX havas unu majstran procezon kaj plurajn "laboristojn" - ĉi tiuj estas infanaj procezoj, kiuj procesas klientajn petojn. Estas disponigita oportuna opcio: uzi la komandon nginx -s <SIGNAL> ĉesigu procezojn aŭ en rapida haltigo aŭ gracia malŝalta reĝimo. Evidente, estas la lasta opcio kiu interesas nin.

Tiam ĉio estas simpla: vi devas aldoni al preStop-hoko komando kiu sendos gracian haltsignalon. Ĉi tio povas esti farita en Deplojo, en la ujo-bloko:

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

Nun, kiam la pod fermiĝas, ni vidos la jenon en la protokoloj de la ujo de NGINX:

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

Kaj ĉi tio signifos tion, kion ni bezonas: NGINX atendas ke petoj finiĝos, kaj poste mortigas la procezon. Tamen ĉi-sube ni ankaŭ konsideros oftan problemon pro kiu, eĉ kun la komando nginx -s quit la procezo finiĝas malĝuste.

Kaj en ĉi tiu etapo ni finis kun NGINX: almenaŭ el la protokoloj vi povas kompreni, ke ĉio funkcias kiel ĝi devus.

Kio estas la interkonsento kun PHP-FPM? Kiel ĝi traktas gracian ĉesigon? Ni eltrovu ĝin.

PHP-FPM

En la kazo de PHP-FPM, estas iom malpli da informoj. Se vi enfokusigas oficiala manlibro laŭ PHP-FPM, ĝi diros, ke la sekvaj POSIX-signaloj estas akceptitaj:

  1. SIGINT, SIGTERM — rapida haltigo;
  2. SIGQUIT — gracia haltigo (kion ni bezonas).

La ceteraj signaloj ne estas postulataj en ĉi tiu tasko, do ni preterlasos ilian analizon. Por fini la procezon ĝuste, vi devos skribi la sekvan preStop-hokon:

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

Unuavide, ĉi tio estas ĉio, kio necesas por plenumi gracian haltigon en ambaŭ ujoj. Tamen, la tasko estas pli malfacila ol ĝi ŝajnas. Malsupre estas du kazoj en kiuj gracia ĉesigo ne funkciis kaj kaŭzis mallongdaŭran nehaveblecon de la projekto dum deplojo.

Praktiko. Eblaj problemoj kun gracia haltigo

NGINX

Antaŭ ĉio, estas utile memori: krom ekzekuti la komandon nginx -s quit Estas unu plia etapo, pri kiu indas atenti. Ni renkontis problemon, kie NGINX ankoraŭ sendos SIGTERM anstataŭ la SIGQUIT-signalo, kaŭzante petojn neĝuste plenumi. Similaj kazoj troveblas, ekzemple, tie. Bedaŭrinde, ni ne povis determini la specifan kialon de ĉi tiu konduto: estis suspekto pri la versio de NGINX, sed ĝi ne estis konfirmita. La simptomo estis, ke mesaĝoj estis observitaj en la protokoloj de ujo de NGINX: "malferma ingo #10 maldekstre en konekto 5", post kio la balgo haltis.

Ni povas observi tian problemon, ekzemple, de la respondoj pri la Eniro, kiujn ni bezonas:

Konsiletoj kaj lertaĵoj de Kubernetes: trajtoj de gracia haltigo en NGINX kaj PHP-FPM
Indikiloj de statuskodoj en la momento de deplojo

En ĉi tiu kazo, ni ricevas nur 503-eraran kodon de Ingress mem: ĝi ne povas aliri la NGINX-ujon, ĉar ĝi ne plu estas alirebla. Se vi rigardas la ujajn protokolojn kun NGINX, ili enhavas la jenajn:

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

Ŝanĝinte la haltsignalon, la ujo komencas halti ĝuste: ĉi tio estas konfirmita de la fakto, ke la 503-eraro ne plu observas.

Se vi renkontas similan problemon, estas senco ekscii, kia haltsignalo estas uzata en la ujo kaj kia precize aspektas la preStop-hoko. Estas tute eble, ke la kialo kuŝas ĝuste en tio.

PHP-FPM... kaj pli

La problemo kun PHP-FPM estas priskribita en bagatela maniero: ĝi ne atendas la kompletigon de infanaj procezoj, ĝi ĉesigas ilin, tial 502-eraroj okazas dum disfaldo kaj aliaj operacioj. Estas pluraj cimraportoj ĉe bugs.php.net ekde 2005 (ekz tie и tie), kiu priskribas ĉi tiun problemon. Sed vi plej verŝajne ne vidos ion ajn en la protokoloj: PHP-FPM anoncos la finiĝon de sia procezo sen eraroj aŭ triaj sciigoj.

Indas klarigi, ke la problemo mem eble dependas en pli aŭ pli granda mezuro de la aplikaĵo mem kaj eble ne manifestiĝas, ekzemple, en monitorado. Se vi renkontas ĝin, unue venas en menso simpla solvo: aldonu preStop-hokon per sleep(30). Ĝi permesos al vi plenumi ĉiujn petojn kiuj estis antaŭe (kaj ni ne akceptas novajn, ekde pod jam kapabla de Finante), kaj post 30 sekundoj la balgo mem finiĝos per signalo SIGTERM.

Ĝi rezultas tion lifecycle ĉar la ujo aspektos jene:

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

Tamen, pro la 30-sekundo sleep мы multe ni pliigos la deplojtempon, ĉar ĉiu pod estos ĉesigita minimumo 30 sekundoj, kio estas malbona. Kion oni povas fari pri tio?

Ni turnu nin al la respondeculo de la rekta plenumo de la aplikaĵo. En nia kazo ĝi estas PHP-FPM, kiu defaŭlte ne kontrolas la ekzekuton de ĝiaj infanaj procezoj: La majstra procezo tuj finiĝas. Vi povas ŝanĝi ĉi tiun konduton per la direktivo process_control_timeout, kiu precizigas la tempolimojn por infanprocezoj por atendi signalojn de la majstro. Se vi fiksas la valoron al 20 sekundoj, ĉi tio kovros la plej multajn el la demandoj kurantaj en la ujo kaj haltigos la majstran procezon post kiam ili estos kompletigitaj.

Kun ĉi tiu scio, ni revenu al nia lasta problemo. Kiel menciite, Kubernetes ne estas monolita platformo: komunikado inter ĝiaj malsamaj komponantoj daŭras iom da tempo. Ĉi tio estas precipe vera se ni konsideras la funkciadon de Eniroj kaj aliaj rilataj komponantoj, ĉar pro tia malfruo en la momento de deplojo estas facile akiri pliiĝon de 500 eraroj. Ekzemple, eraro povas okazi en la stadio de sendado de peto al kontraŭfluo, sed la "tempomalfruo" de interago inter komponantoj estas sufiĉe mallonga - malpli ol sekundo.

Sekve, Entute kun la jam menciita direktivo process_control_timeout vi povas uzi la jenan konstruon por lifecycle:

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

En ĉi tiu kazo, ni kompensos la malfruon per la komando sleep kaj ni ne signife pliigas la deplojtempon: finfine, la diferenco inter 30 sekundoj kaj unu estas videbla?.. Fakte, ĝi estas la process_control_timeoutkaj lifecycle uzata nur kiel "sekureca reto" en kazo de malfruo.

Ĝenerale parolante, la priskribita konduto kaj la responda solvo validas ne nur por PHP-FPM. Simila situacio povas iel aŭ alia aperi kiam oni uzas aliajn lingvojn/kadrojn. Se vi ne povas ripari gracian malŝalton alimaniere - ekzemple, reverkante la kodon por ke la aplikaĵo ĝuste prilaboras finsignalojn - vi povas uzi la priskribitan metodon. Ĝi eble ne estas la plej bela, sed ĝi funkcias.

Praktiko. Ŝarĝo-testado por kontroli la funkciadon de la pod

Ŝarĝotestado estas unu el la manieroj kontroli kiel la ujo funkcias, ĉar ĉi tiu proceduro proksimigas ĝin al realaj batalkondiĉoj kiam uzantoj vizitas la retejon. Por testi la suprajn rekomendojn, vi povas uzi Yandex.Tankom: Ĝi kovras ĉiujn niajn bezonojn perfekte. La jenaj estas konsiletoj kaj rekomendoj por fari provojn kun klara ekzemplo de nia sperto danke al la grafikaĵoj de Grafana kaj Yandex.Tank mem.

La plej grava afero ĉi tie estas kontrolu ŝanĝojn paŝon post paŝo. Post aldoni novan solvon, rulu la teston kaj vidu ĉu la rezultoj ŝanĝiĝis kompare kun la lasta kuro. Alie, estos malfacile identigi neefikajn solvojn, kaj longtempe ĝi povas nur damaĝi (ekzemple, pliigi disfalda tempo).

Alia nuanco estas rigardi la ujajn protokolojn dum ĝia fino. Ĉu informoj pri gracia haltigo estas registrita tie? Ĉu estas eraroj en la protokoloj dum aliro al aliaj rimedoj (ekzemple al najbara PHP-FPM-ujo)? Eraroj en la aplikaĵo mem (kiel en la kazo de NGINX priskribita supre)? Mi esperas, ke la enkondukaj informoj de ĉi tiu artikolo helpos vin pli bone kompreni, kio okazas al la ujo dum ĝia fino.

Do, la unua provkuro okazis sen lifecycle kaj sen pliaj direktivoj por la aplika servilo (process_control_timeout en PHP-FPM). La celo de ĉi tiu testo estis identigi la proksimuman nombron da eraroj (kaj ĉu ekzistas iuj). Ankaŭ, laŭ pliaj informoj, vi devus scii, ke la averaĝa disfalda tempo por ĉiu pod estis ĉirkaŭ 5-10 sekundoj ĝis ĝi estis plene preta. La rezultoj estas:

Konsiletoj kaj lertaĵoj de Kubernetes: trajtoj de gracia haltigo en NGINX kaj PHP-FPM

La informpanelo Yandex.Tank montras pikon de 502-eraroj, kiuj okazis en la momento de deplojo kaj daŭris averaĝe ĝis 5 sekundoj. Supozeble tio estis ĉar ekzistantaj petoj al la malnova balgo estis ĉesigitaj kiam ĝi estis ĉesigita. Post ĉi tio, 503 eraroj aperis, kio estis la rezulto de haltigita NGINX-ujo, kiu ankaŭ faligis konektojn pro la backend (kiu malhelpis Ingress konektiĝi al ĝi).

Ni vidu kiel process_control_timeout en PHP-FPM helpos nin atendi la kompletigon de infanaj procezoj, t.e. korekti tiajn erarojn. Redeploji uzante ĉi tiun direktivon:

Konsiletoj kaj lertaĵoj de Kubernetes: trajtoj de gracia haltigo en NGINX kaj PHP-FPM

Ne plu estas eraroj dum la 500-a deplojo! La deplojo estas sukcesa, graciaj haltigo funkcias.

Tamen indas memori la aferon pri Ingress-ujoj, malgranda procento de eraroj, en kiuj ni povas ricevi pro tempomalfruo. Por eviti ilin, restas nur aldoni strukturon kun sleep kaj ripetu la deplojon. Tamen, en nia aparta kazo, neniuj ŝanĝoj estis videblaj (denove, neniuj eraroj).

konkludo

Por fini la procezon gracie, ni atendas la sekvan konduton de la aplikaĵo:

  1. Atendu kelkajn sekundojn kaj poste ĉesu akcepti novajn konektojn.
  2. Atendu, ke ĉiuj petoj kompletigos kaj fermu ĉiujn ligojn de konservado, kiuj ne plenumas petojn.
  3. Finu vian procezon.

Tamen, ne ĉiuj aplikoj povas funkcii tiel. Unu solvo al la problemo en Kubernetes realaĵoj estas:

  • aldonante antaŭ-haltan hokon, kiu atendos kelkajn sekundojn;
  • studante la agordan dosieron de nia backend por la taŭgaj parametroj.

La ekzemplo kun NGINX klarigas, ke eĉ aplikaĵo, kiu komence devus ĝuste prilabori finsignalojn, eble ne faras tion, do estas kritike kontroli 500-erarojn dum aplikaĵo deplojo. Ĉi tio ankaŭ ebligas al vi rigardi la problemon pli larĝe kaj ne koncentriĝi sur ununura pod aŭ ujo, sed rigardi la tutan infrastrukturon entute.

Kiel testa ilo, vi povas uzi Yandex.Tank kune kun ajna monitora sistemo (en nia kazo, datumoj estis prenitaj de Grafana kun Prometheus backend por la testo). Problemoj kun gracia haltigo estas klare videblaj sub pezaj ŝarĝoj, kiujn la komparnormo povas generi, kaj monitorado helpas analizi la situacion pli detale dum aŭ post la testo.

Responde al sugestoj pri la artikolo: menciindas, ke la problemoj kaj solvoj estas priskribitaj ĉi tie rilate al NGINX Ingress. Por aliaj kazoj, ekzistas aliaj solvoj, kiujn ni povas konsideri en la sekvaj materialoj de la serio.

PS

Aliaj el la serio de konsiletoj kaj lertaĵoj K8s:

fonto: www.habr.com

Aldoni komenton