Cinci rateuri la implementarea primei aplicații pe Kubernetes

Cinci rateuri la implementarea primei aplicații pe KubernetesFail de Aris-Dreamer

Mulți oameni cred că este suficient să migreze aplicația pe Kubernetes (fie folosind Helm, fie manual) și vor fi fericiți. Dar nu este atât de simplu.

Echipă Mail.ru Cloud Solutions a tradus un articol al inginerului DevOps Julian Gindi. El împărtășește ce capcane le-a întâlnit compania lui în timpul procesului de migrare, astfel încât să nu călcați pe același rake.

Pasul unu: Configurarea solicitărilor și limitelor podului

Să începem prin a crea un mediu curat în care să ruleze podurile noastre. Kubernetes face o treabă excelentă în planificarea podurilor și gestionarea condițiilor de eșec. Dar s-a dovedit că uneori planificatorul nu poate plasa un pod dacă este dificil de estimat de câte resurse are nevoie pentru a funcționa cu succes. Aici apar cererile de resurse și limite. Există multe dezbateri cu privire la cea mai bună abordare pentru stabilirea cererilor și a limitelor. Uneori se simte cu adevărat că este mai mult artă decât știință. Iată abordarea noastră.

Solicitări de pod - Aceasta este valoarea principală folosită de planificator pentru a plasa în mod optim podul.

De Documentația Kubernetes: Pasul de filtrare determină setul de noduri în care podul poate fi programat. De exemplu, filtrul PodFitsResources verifică dacă un nod are suficiente resurse pentru a satisface cererile specifice de resurse ale unui pod.

Folosim cereri de aplicații pentru a putea fi folosite pentru a estima câte resurse de fapt Aplicația are nevoie de ea pentru a funcționa corect. În acest fel, planificatorul poate plasa nodurile în mod realist. Am vrut inițial să setăm cereri cu marjă pentru a ne asigura că fiecare pod avea un număr suficient de mare de resurse, dar am observat că timpii de programare au crescut semnificativ și unele pod-uri nu au fost niciodată pe deplin programate, ca și cum nu s-ar fi primit nicio solicitare de resurse pentru ele.

În acest caz, planificatorul deseori scotea pod-uri și nu le putea reprograma, deoarece planul de control nu avea idee de câte resurse ar necesita aplicația, o componentă cheie a algoritmului de programare.

Limitele podului - aceasta este o limită mai clară pentru pod. Reprezintă cantitatea maximă de resurse pe care clusterul o va aloca containerului.

Din nou, de la documentație oficială: Dacă un container are setată o limită de memorie de 4 GiB, atunci kubelet (și durata de rulare a containerului) o vor aplica. Timpul de execuție nu permite containerului să utilizeze mai mult decât limita de resurse specificată. De exemplu, atunci când un proces dintr-un container încearcă să utilizeze mai mult decât cantitatea permisă de memorie, nucleul sistemului încheie procesul cu o eroare „memorie lipsită” (OOM).

Un container poate folosi întotdeauna mai multe resurse decât cele specificate în cererea de resurse, dar nu poate folosi niciodată mai mult decât specificat în limită. Această valoare este dificil de setat corect, dar este foarte importantă.

În mod ideal, dorim ca cerințele de resurse ale unui pod să se schimbe pe parcursul ciclului de viață al unui proces fără a interfera cu alte procese din sistem - acesta este scopul de a stabili limite.

Din păcate, nu pot da instrucțiuni specifice cu privire la ce valori să setăm, dar noi înșine respectăm următoarele reguli:

  1. Folosind un instrument de testare a încărcării, simulăm un nivel de referință al traficului și monitorizăm utilizarea resurselor pod (memorie și procesor).
  2. Setăm cererile pod la o valoare arbitrar scăzută (cu o limită de resurse de aproximativ 5 ori mai mare decât valoarea solicitărilor) și observăm. Când cererile sunt prea scăzute, procesul nu poate începe, provocând adesea erori misterioase de rulare Go.

Rețineți că limitele mai mari de resurse fac programarea mai dificilă, deoarece podul are nevoie de un nod țintă cu suficiente resurse disponibile.

Imaginați-vă o situație în care aveți un server web ușor, cu o limită de resurse foarte mare, să zicem 4 GB de memorie. Acest proces va trebui probabil să se scaleze pe orizontală, iar fiecare modul nou va trebui să fie programat pe un nod cu cel puțin 4 GB de memorie disponibilă. Dacă nu există un astfel de nod, clusterul trebuie să introducă un nou nod pentru a procesa acel pod, ceea ce poate dura ceva timp. Este important să păstrați diferența dintre cererile de resurse și limite la minim pentru a asigura o scalare rapidă și lină.

Pasul doi: configurarea testelor de Liveness și Readiness

Acesta este un alt subiect subtil care este adesea discutat în comunitatea Kubernetes. Este important să înțelegeți bine testele Liveness și Readiness, deoarece acestea oferă un mecanism pentru ca software-ul să funcționeze fără probleme și să minimizeze timpul de nefuncționare. Cu toate acestea, ele pot cauza o afectare serioasă a performanței aplicației dvs. dacă nu sunt configurate corect. Mai jos este un rezumat al modului în care sunt ambele mostre.

Viața arată dacă containerul rulează. Dacă nu reușește, kubelet-ul oprește containerul și o politică de repornire este activată pentru acesta. Dacă containerul nu este echipat cu o sondă Liveness, atunci starea implicită va fi de succes - acesta este ceea ce scrie în Documentația Kubernetes.

Sondele de liveness ar trebui să fie ieftine, adică nu ar trebui să consume multe resurse, deoarece rulează frecvent și trebuie să informeze Kubernetes că aplicația rulează.

Dacă setați opțiunea să ruleze la fiecare secundă, aceasta va adăuga 1 solicitare pe secundă, așa că rețineți că vor fi necesare resurse suplimentare pentru a gestiona acest trafic.

La compania noastră, testele Liveness verifică componentele de bază ale unei aplicații, chiar dacă datele (de exemplu, dintr-o bază de date la distanță sau dintr-o cache) nu sunt complet accesibile.

Am configurat aplicațiile cu un punct final „sănătate” care returnează pur și simplu un cod de răspuns de 200. Acesta este un indiciu că procesul rulează și că este capabil să proceseze cereri (dar nu încă trafic).

test promptitudine indică dacă containerul este pregătit pentru a servi cererile. Dacă sonda de pregătire eșuează, controlerul punctului final elimină adresa IP a pod-ului de la punctele finale ale tuturor serviciilor corespunzătoare pod-ului. Acest lucru este menționat și în documentația Kubernetes.

Sondele de pregătire consumă mai multe resurse, deoarece trebuie trimise către backend într-un mod care să indice că aplicația este pregătită să accepte cereri.

Există multe dezbateri în comunitate cu privire la accesul direct la baza de date. Având în vedere suprasolicitarea (verificările sunt efectuate frecvent, dar pot fi ajustate), am decis că, pentru unele aplicații, disponibilitatea de a servi traficul este socotită doar după verificarea faptului că înregistrările sunt returnate din baza de date. Testele de pregătire bine concepute au asigurat niveluri mai ridicate de disponibilitate și au eliminat timpul de nefuncționare în timpul implementării.

Dacă decideți să interogați baza de date pentru a testa gradul de pregătire a aplicației dvs., asigurați-vă că este cât mai ieftină posibil. Să luăm această cerere:

SELECT small_item FROM table LIMIT 1

Iată un exemplu despre cum configurăm aceste două valori în Kubernetes:

livenessProbe: 
 httpGet:   
   path: /api/liveness    
   port: http 
readinessProbe:  
 httpGet:    
   path: /api/readiness    
   port: http  periodSeconds: 2

Puteți adăuga câteva opțiuni de configurare suplimentare:

  • initialDelaySeconds — câte secunde vor trece între lansarea containerului și începerea probelor.
  • periodSeconds — intervalul de așteptare dintre probele.
  • timeoutSeconds — numărul de secunde după care unitatea este considerată o urgență. Timeout regulat.
  • failureThreshold — numărul de eșecuri ale testului înainte ca un semnal de repornire să fie trimis către pod.
  • successThreshold — numărul de sonde reușite înainte ca podul să intre în starea gata (după o defecțiune, când podul pornește sau se recuperează).

Pasul trei: configurarea politicilor de rețea implicite pentru pod

Kubernetes are o topografie de rețea „plată”; în mod implicit, toate podurile comunică direct între ele. În unele cazuri, acest lucru nu este de dorit.

O posibilă problemă de securitate este că un atacator ar putea folosi o singură aplicație vulnerabilă pentru a trimite trafic către toate podurile din rețea. Ca și în multe domenii de securitate, aici se aplică principiul cel mai mic privilegiu. În mod ideal, politicile de rețea ar trebui să specifice în mod explicit ce conexiuni între poduri sunt permise și care nu.

De exemplu, mai jos este o politică simplă care interzice tot traficul de intrare pentru un anumit spațiu de nume:

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:  
 name: default-deny-ingress
spec:  
 podSelector: {}  
 policyTypes:  
   - Ingress

Vizualizarea acestei configurații:

Cinci rateuri la implementarea primei aplicații pe Kubernetes
(https://miro.medium.com/max/875/1*-eiVw43azgzYzyN1th7cZg.gif)
Mai multe detalii aici.

Pasul patru: comportament personalizat folosind cârlige și containere init

Unul dintre obiectivele noastre principale a fost să oferim implementări pentru Kubernetes fără timpi de nefuncționare pentru dezvoltatori. Acest lucru este dificil, deoarece există multe opțiuni pentru a închide aplicațiile și a elibera resursele pe care le-au folosit.

Au apărut dificultăți deosebite cu nginx. Am observat că atunci când aceste poduri au fost implementate secvenţial, conexiunile active au fost abandonate înainte de finalizarea cu succes.

După cercetări ample online, se dovedește că Kubernetes nu așteaptă ca conexiunile Nginx să se epuizeze înainte de a termina podul. Folosind un cârlig de pre-stop, am implementat următoarea funcționalitate și am scăpat complet de timpul de nefuncționare:

lifecycle: 
 preStop:
   exec:
     command: ["/usr/local/bin/nginx-killer.sh"]

si aici nginx-killer.sh:

#!/bin/bash
sleep 3
PID=$(cat /run/nginx.pid)
nginx -s quit
while [ -d /proc/$PID ]; do
   echo "Waiting while shutting down nginx..."
   sleep 10
done

O altă paradigmă extrem de utilă este utilizarea containerelor init pentru a gestiona pornirea unor aplicații specifice. Acest lucru este util în special dacă aveți un proces de migrare a bazei de date care necesită mult resurse, care trebuie să ruleze înainte ca aplicația să pornească. De asemenea, puteți specifica o limită mai mare de resurse pentru acest proces fără a stabili o astfel de limită pentru aplicația principală.

O altă schemă comună este accesarea secretelor într-un container init care furnizează acele acreditări modulului principal, ceea ce împiedică accesul neautorizat la secrete din modulul principal al aplicației în sine.

Ca de obicei, citați din documentație: containerele Init rulează în siguranță cod personalizat sau utilități care altfel ar reduce securitatea imaginii containerului aplicației. Păstrând instrumentele inutile separate, limitați suprafața de atac a imaginii containerului aplicației.

Pasul cinci: Configurarea kernelului

În sfârșit, să vorbim despre o tehnică mai avansată.

Kubernetes este o platformă extrem de flexibilă care vă permite să rulați sarcinile de lucru așa cum credeți de cuviință. Avem o serie de aplicații de înaltă performanță, care consumă extrem de mult resurse. După ce am efectuat teste ample de încărcare, am descoperit că o aplicație se lupta să facă față sarcinii de trafic așteptate atunci când setările implicite ale Kubernetes erau în vigoare.

Cu toate acestea, Kubernetes vă permite să rulați un container privilegiat care modifică parametrii kernelului numai pentru un anumit pod. Iată ce am folosit pentru a modifica numărul maxim de conexiuni deschise:

initContainers:
  - name: sysctl
     image: alpine:3.10
     securityContext:
         privileged: true
      command: ['sh', '-c', "sysctl -w net.core.somaxconn=32768"]

Aceasta este o tehnică mai avansată, care adesea nu este necesară. Dar dacă aplicația dvs. se luptă să facă față unei sarcini grele, puteți încerca să modificați unele dintre aceste setări. Mai multe detalii despre acest proces și setarea unor valori diferite - ca întotdeauna în documentația oficială.

în concluzie

Deși Kubernetes poate părea o soluție gata făcută din cutie, există câțiva pași cheie pe care trebuie să-i faceți pentru ca aplicațiile să funcționeze fără probleme.

Pe parcursul migrării Kubernetes, este important să urmați „ciclul de testare a încărcării”: lansați aplicația, testați-o, observați valorile și comportamentul de scalare, ajustați configurația pe baza acestor date, apoi repetați ciclul din nou.

Fiți realiști în ceea ce privește traficul așteptat și încercați să depășiți acesta pentru a vedea ce componente se rup mai întâi. Cu această abordare iterativă, doar câteva dintre recomandările enumerate pot fi suficiente pentru a obține succesul. Sau poate necesita personalizare mai profundă.

Pune-ți întotdeauna aceste întrebări:

  1. Câte resurse consumă aplicațiile și cum se va schimba acest volum?
  2. Care sunt cerințele reale de scalare? Cât trafic va gestiona aplicația în medie? Dar traficul de vârf?
  3. Cât de des va trebui serviciul să se scaleze pe orizontală? Cât de repede trebuie introduse online pod-uri noi pentru a primi trafic?
  4. Cât de corect se închid podurile? Este deloc necesar? Este posibil să se realizeze implementarea fără timp de nefuncționare?
  5. Cum puteți minimiza riscurile de securitate și cum puteți limita daunele cauzate de orice pod compromise? Au vreun serviciu permisiuni sau acces pe care nu le necesită?

Kubernetes oferă o platformă incredibilă care vă permite să folosiți cele mai bune practici pentru implementarea a mii de servicii într-un cluster. Cu toate acestea, fiecare aplicație este diferită. Uneori, implementarea necesită puțin mai multă muncă.

Din fericire, Kubernetes oferă configurația necesară pentru a atinge toate obiectivele tehnice. Folosind o combinație de solicitări și limite de resurse, sonde Liveness și Readiness, containere de inițializare, politici de rețea și reglare personalizată a nucleului, puteți obține performanțe ridicate, împreună cu toleranța la erori și scalabilitate rapidă.

Ce altceva de citit:

  1. Cele mai bune practici și cele mai bune practici pentru rularea containerelor și Kubernetes în mediile de producție.
  2. Peste 90 de instrumente utile pentru Kubernetes: implementare, management, monitorizare, securitate și multe altele.
  3. Canalul nostru Around Kubernetes în Telegram.

Sursa: www.habr.com

Adauga un comentariu