Consells i trucs de Kubernetes: funcions d'apagada elegant a NGINX i PHP-FPM

Una condició típica a l'hora d'implementar CI/CD a Kubernetes: l'aplicació ha de poder no acceptar noves sol·licituds de client abans d'aturar-se completament i, el més important, completar amb èxit les existents.

Consells i trucs de Kubernetes: funcions d'apagada elegant a NGINX i PHP-FPM

El compliment d'aquesta condició us permet aconseguir zero temps d'inactivitat durant el desplegament. Tanmateix, fins i tot quan utilitzeu paquets molt populars (com NGINX i PHP-FPM), podeu trobar dificultats que provocaran un augment d'errors amb cada desplegament...

Teoria. Com viu el pod

Ja hem publicat amb detall sobre el cicle de vida d'una beina aquest article. En el context del tema considerat, ens interessa el següent: en el moment en què la beina entra a l'estat S'està finalitzant, deixen d'enviar-s'hi sol·licituds noves (pod esborrat de la llista de punts finals del servei). Així, per evitar temps d'inactivitat durant el desplegament, n'hi ha prou amb resoldre correctament el problema d'aturar l'aplicació.

També heu de recordar que el període de gràcia predeterminat és 30 segons: després d'això, el pod es cancel·larà i l'aplicació ha de tenir temps per processar totes les sol·licituds abans d'aquest període. Nota: tot i que qualsevol sol·licitud que triga més de 5-10 segons ja és problemàtica, i l'apagada elegant ja no l'ajudarà...

Per entendre millor què passa quan s'acaba un pod, només cal que mireu el diagrama següent:

Consells i trucs de Kubernetes: funcions d'apagada elegant a NGINX i PHP-FPM

A1, B1 - Recepció de canvis sobre l'estat de la llar
A2 - Sortida SIGTERM
B2 - Eliminació d'un pod dels punts finals
B3 - Recepció de canvis (la llista de punts finals ha canviat)
B4 - Actualitzar les regles d'iptables

Tingueu en compte: l'eliminació del pod del punt final i l'enviament de SIGTERM no es fan de manera seqüencial, sinó en paral·lel. I a causa del fet que Ingress no rep immediatament la llista actualitzada d'Endpoints, s'enviaran noves sol·licituds dels clients al pod, cosa que provocarà un error 500 durant la terminació del pod. (per a material més detallat sobre aquest tema, nosaltres traduït). Aquest problema s'ha de resoldre de les maneres següents:

  • Connexió d'enviament: tanqueu les capçaleres de resposta (si es tracta d'una aplicació HTTP).
  • Si no és possible fer canvis al codi, l'article següent descriu una solució que us permetrà processar les sol·licituds fins al final del període de gràcia.

Teoria. Com NGINX i PHP-FPM acaben els seus processos

NGINX

Comencem per NGINX, ja que tot és més o menys evident amb ell. Endinsant-nos en la teoria, aprenem que NGINX té un procés mestre i diversos "treballadors": aquests són processos secundaris que processen les sol·licituds dels clients. Es proporciona una opció convenient: utilitzar l'ordre nginx -s <SIGNAL> finalitza els processos en mode d'apagada ràpida o d'apagada elegant. Evidentment, és aquesta última opció la que ens interessa.

Aleshores tot és senzill: cal afegir-hi preStop-hook una ordre que enviarà un senyal d'apagada elegant. Això es pot fer a Deployment, al bloc contenidor:

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

Ara, quan el pod s'apaga, veurem el següent als registres del contenidor 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

I això significarà el que necessitem: NGINX espera que es completin les sol·licituds i després mata el procés. Tanmateix, a continuació també considerarem un problema comú pel qual, fins i tot amb l'ordre nginx -s quit el procés finalitza incorrectament.

I en aquesta etapa hem acabat amb NGINX: almenys des dels registres es pot entendre que tot funciona com cal.

Què passa amb PHP-FPM? Com gestiona l'aturada elegant? Anem a esbrinar-ho.

PHP-FPM

En el cas de PHP-FPM, hi ha una mica menys d'informació. Si et concentres manual oficial segons PHP-FPM, dirà que s'accepten els següents senyals POSIX:

  1. SIGINT, SIGTERM - apagada ràpida;
  2. SIGQUIT — apagada graciosa (el que necessitem).

Els senyals restants no són necessaris en aquesta tasca, per la qual cosa ometrem la seva anàlisi. Per finalitzar el procés correctament, haureu d'escriure el següent ganxo preStop:

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

A primer cop d'ull, això és tot el que es requereix per realitzar un apagat elegant en ambdós contenidors. Tanmateix, la tasca és més difícil del que sembla. A continuació es mostren dos casos en què l'aturada elegant no va funcionar i va provocar una indisponibilitat a curt termini del projecte durant el desplegament.

Pràctica. Possibles problemes amb l'apagada elegant

NGINX

En primer lloc, és útil recordar: a més d'executar l'ordre nginx -s quit Hi ha una etapa més a la qual val la pena parar atenció. Ens hem trobat amb un problema en què NGINX encara enviava SIGTERM en comptes del senyal SIGQUIT, fet que va provocar que les sol·licituds no es completessin correctament. Es poden trobar casos similars, per exemple, aquí. Malauradament, no hem pogut determinar el motiu específic d'aquest comportament: hi havia una sospita sobre la versió NGINX, però no es va confirmar. El símptoma va ser que es van observar missatges als registres del contenidor NGINX: "obert el sòcol núm. 10 esquerre a la connexió 5", després del qual la beina es va aturar.

Podem observar aquest problema, per exemple, a partir de les respostes a l'Ingress que necessitem:

Consells i trucs de Kubernetes: funcions d'apagada elegant a NGINX i PHP-FPM
Indicadors dels codis d'estat en el moment del desplegament

En aquest cas, només rebem un codi d'error 503 del mateix Ingress: no pot accedir al contenidor NGINX, ja que ja no és accessible. Si mireu els registres del contenidor amb NGINX, contenen el següent:

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

Després de canviar el senyal d'aturada, el contenidor comença a aturar-se correctament: això es confirma pel fet que l'error 503 ja no s'observa.

Si trobeu un problema similar, té sentit esbrinar quin senyal d'aturada s'utilitza al contenidor i com és exactament el ganxo preStop. És molt possible que la raó rau precisament en això.

PHP-FPM... i molt més

El problema amb PHP-FPM es descriu d'una manera trivial: no espera a la finalització dels processos fills, els finalitza, per això es produeixen errors 502 durant el desplegament i altres operacions. Hi ha diversos informes d'errors a bugs.php.net des del 2005 (p. ex aquí и aquí), que descriu aquest problema. Però el més probable és que no vegeu res als registres: PHP-FPM anunciarà la finalització del seu procés sense cap error ni notificació de tercers.

Val la pena aclarir que el problema en si pot dependre en menor o major mesura de la pròpia aplicació i pot no manifestar-se, per exemple, en el seguiment. Si el trobeu, primer us ve al cap una solució senzilla: afegiu un ganxo preStop amb sleep(30). Et permetrà completar totes les sol·licituds que hi havia abans (i no n'acceptem de noves, ja que pod ja capaç de S'està finalitzant), i al cap de 30 segons la beina en si acabarà amb un senyal SIGTERM.

Resulta que lifecycle perquè el contenidor tindrà aquest aspecte:

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

Tanmateix, a causa dels 30 segons sleep som fortament augmentarem el temps de desplegament, ja que cada pod s'acabarà mínim 30 segons, que és dolent. Què es pot fer al respecte?

Anem a dirigir-nos al responsable de l'execució directa de la sol·licitud. En el nostre cas ho és PHP-FPMQue per defecte no supervisa l'execució dels seus processos fills: el procés mestre s'acaba immediatament. Podeu canviar aquest comportament mitjançant la directiva process_control_timeout, que especifica els límits de temps perquè els processos fills esperen els senyals del mestre. Si establiu el valor en 20 segons, això cobrirà la majoria de les consultes que s'executen al contenidor i aturarà el procés mestre un cop s'hagin completat.

Amb aquest coneixement, tornem al nostre darrer problema. Com s'ha esmentat, Kubernetes no és una plataforma monolítica: la comunicació entre els seus diferents components triga un temps. Això és especialment cert si tenim en compte el funcionament d'Ingresses i altres components relacionats, ja que a causa d'aquest retard en el moment del desplegament és fàcil obtenir un augment de 500 errors. Per exemple, es pot produir un error en l'etapa d'enviament d'una sol·licitud a un aigües amunt, però el "retard de temps" d'interacció entre components és força curt, menys d'un segon.

Per tant, En total amb la directiva ja esmentada process_control_timeout podeu utilitzar la següent construcció per lifecycle:

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

En aquest cas, compensarem el retard amb l'ordre sleep i no augmentar significativament el temps de desplegament: al cap i a la fi, la diferència entre 30 segons i un es nota?... De fet, és el process_control_timeoutI lifecycle s'utilitza només com a "xarxa de seguretat" en cas de retard.

En general el comportament descrit i la solució alternativa s'apliquen no només a PHP-FPM. Una situació similar pot sorgir d'una manera o altra quan s'utilitzen altres idiomes/marcs. Si no podeu solucionar l'apagada elegant d'altres maneres, per exemple, reescrivint el codi perquè l'aplicació processi correctament els senyals de terminació, podeu utilitzar el mètode descrit. Potser no és el més bonic, però funciona.

Pràctica. Prova de càrrega per comprovar el funcionament del pod

Les proves de càrrega són una de les maneres de comprovar el funcionament del contenidor, ja que aquest procediment l'apropa a les condicions reals de combat quan els usuaris visiten el lloc. Per provar les recomanacions anteriors, podeu utilitzar Yandex.Tankom: Cobreix perfectament totes les nostres necessitats. A continuació es mostren consells i recomanacions per fer proves amb un exemple clar de la nostra experiència gràcies als gràfics de Grafana i Yandex.Tank.

El més important aquí és comproveu els canvis pas a pas. Després d'afegir una nova correcció, executeu la prova i comproveu si els resultats han canviat en comparació amb l'última execució. En cas contrari, serà difícil identificar solucions ineficaces i, a la llarga, només pot causar danys (per exemple, augmentar el temps de desplegament).

Un altre matís és mirar els registres del contenidor durant la seva terminació. S'hi registra informació sobre l'aturada elegant? Hi ha errors als registres en accedir a altres recursos (per exemple, un contenidor PHP-FPM veí)? Errors a la pròpia aplicació (com en el cas de NGINX descrit anteriorment)? Espero que la informació introductòria d'aquest article us ajudi a entendre millor què passa amb el contenidor durant la seva terminació.

Per tant, la primera prova va tenir lloc sense lifecycle i sense directives addicionals per al servidor d'aplicacions (process_control_timeout en PHP-FPM). L'objectiu d'aquesta prova era identificar el nombre aproximat d'errors (i si n'hi ha). A més, a partir d'informació addicional, hauríeu de saber que el temps mitjà de desplegament per a cada pod era d'uns 5-10 segons fins que estava completament a punt. Els resultats són:

Consells i trucs de Kubernetes: funcions d'apagada elegant a NGINX i PHP-FPM

El tauler d'informació de Yandex.Tank mostra un pic d'errors 502, que es van produir en el moment del desplegament i van durar de mitjana fins a 5 segons. Presumiblement, això va ser perquè les sol·licituds existents a l'antic pod s'estaven cancel·lant quan s'acabava. Després d'això, van aparèixer errors 503, que va ser el resultat d'un contenidor NGINX aturat, que també va deixar caure les connexions a causa del backend (que va impedir que Ingress es connectés a ell).

A veure com process_control_timeout en PHP-FPM ens ajudarà a esperar la finalització dels processos fills, és a dir. corregir aquests errors. Torneu a desplegar amb aquesta directiva:

Consells i trucs de Kubernetes: funcions d'apagada elegant a NGINX i PHP-FPM

No hi ha més errors durant el desplegament número 500! El desplegament s'ha realitzat amb èxit, l'aturada funciona correctament.

No obstant això, val la pena recordar el problema amb els contenidors Ingress, un petit percentatge d'errors en els quals podem rebre per un retard de temps. Per evitar-los, només queda afegir una estructura amb sleep i repetir el desplegament. Tanmateix, en el nostre cas particular, no hi havia canvis visibles (de nou, cap error).

Conclusió

Per finalitzar el procés amb gràcia, esperem el següent comportament de l'aplicació:

  1. Espereu uns segons i després deixeu d'acceptar connexions noves.
  2. Espereu que es completin totes les sol·licituds i tanqueu totes les connexions keepalive que no estan executant sol·licituds.
  3. Finalitza el teu procés.

Tanmateix, no totes les aplicacions poden funcionar d'aquesta manera. Una solució al problema a les realitats de Kubernetes és:

  • afegint un ganxo pre-stop que esperarà uns segons;
  • estudiant el fitxer de configuració del nostre backend per als paràmetres adequats.

L'exemple amb NGINX deixa clar que fins i tot una aplicació que hauria de processar inicialment els senyals de terminació correctament pot no fer-ho, per la qual cosa és fonamental comprovar si hi ha 500 errors durant el desplegament de l'aplicació. Això també us permet mirar el problema de manera més àmplia i no centrar-vos en una sola beina o contenidor, sinó mirar tota la infraestructura en conjunt.

Com a eina de prova, podeu utilitzar Yandex.Tank juntament amb qualsevol sistema de monitorització (en el nostre cas, les dades es van extreure de Grafana amb un backend de Prometheus per a la prova). Els problemes amb l'apagada elegant són clarament visibles sota les càrregues pesades que pot generar el punt de referència, i el seguiment ajuda a analitzar la situació amb més detall durant o després de la prova.

En resposta als comentaris sobre l'article: val la pena esmentar que els problemes i les solucions es descriuen aquí en relació amb NGINX Ingress. Per a altres casos, hi ha altres solucions, que podem considerar en els següents materials de la sèrie.

PS

Altres de la sèrie de trucs i consells K8s:

Font: www.habr.com

Afegeix comentari