
Mnogi smatraju da je dovoljno preseliti aplikaciju na Kubernetes (bilo uz pomoć Helma ili ručno) – i sreća je zagarantovana. No, nije sve tako jednostavno.
Komanda prevela članak DevOps inženjera Juliana Gindija. On opisuje s kakvim su se zamkama njegova tvrtka susrela tijekom migracije, kako biste vi izbjegli iste greške.
Korak prvi: postavljanje zahtjeva pod-a i limita
Započnimo s postavljanjem čistog okruženja u kojem će raditi naši pod-ovi. Kubernetes izvrsno upravlja raspoređivanjem pod-ova i obradom stanja otkaza. No, pokazalo se da ponekad planer ne može smjestiti pod ako ne može ocijeniti koliko mu je resursa potrebno za uspješan rad. Tu se pojavljuju zahtjevi za resursima i limiti. Vodili su se mnogi rasprave o najboljem pristupu postavljanju zahtjeva i limita. Ponekad se čini da je to zaista više umjetnost nego znanost. Ovo je naš pristup.
Zahtjevi pod-a (pod requests) su osnovna vrijednost koju planer koristi za optimalno smještanje pod-a.
Od : u koraku filtracije određuje se skup čvorova gdje pod može biti raspoređen. Na primjer, filtriranje PodFitsResources provjerava ima li čvor dovoljno resursa za zadovoljavanje specifičnih zahtjeva pod-a za resursima.
Zahtjeve aplikacija koristimo tako da se njima može ocijeniti koliko resursa zapravo je potrebno aplikaciji za normalan rad. Tako će planer moći realno rasporediti čvorove. U početku smo htjeli postaviti zahtjeve s rezervom, kako bismo osigurali dovoljno resursa za svaki pod, ali primijetili smo da se vrijeme raspoređivanja znatno povećalo, a neki pod-ovi nikada nisu bili u potpunosti raspoređeni, kao da za njih nije došlo nikakve zahtjeva za resursima.
U ovom slučaju planer bi često "izgurao" pod-ove i nije mogao ponovno ih rasporediti zbog toga što upravljačka ravnina nije imala pojma koliko će resursa aplikaciji biti potrebno, a to je ključna komponenta algoritma raspoređivanja.
Limiti pod-a (pod limits) su jasnije ograničenje za pod. Oni predstavljaju maksimalnu količinu resursa koju klaster dodjeljuje kontejneru.
Ponovno, iz : ako je za kontejner postavljena ograničenje memorije od 4 GiB, kubelet (i okruženje izvršavanja kontejnera) će ga prisilno uvesti. Okruženje izvršavanja ne dopušta kontejneru da koristi više od zadanog ograničenja resursa. Na primjer, kada proces unutar kontejnera pokuša koristiti više memorije nego što je dozvoljeno, jezgra sustava završava taj proces s greškom 'nema više memorije' (OOM).
Kontejner uvijek može koristiti više resursa nego što je navedeno u zahtjevu za resursima, ali nikada ne može koristiti više od onoga što je navedeno u ograničenju. Teško je ispravno postaviti tu vrijednost, ali je izuzetno važna.
U idealnom slučaju želimo da zahtjevi za resursima poda variraju tijekom životnog ciklusa procesa, ne ometajući druge procese u sustavu — to je cilj postavljanja ograničenja.
Nažalost, ne mogu dati specifične upute o tome koje vrijednosti postaviti, ali mi se pridržavamo sljedećih pravila:
- Koristeći alat za testiranje opterećenja, modeliramo osnovnu razinu prometa i pratimo korištenje resursa poda (memorije i CPU-a).
- Postavljamo zahtjeve poda na proizvoljno nisku vrijednost (s ograničenjem resursa otprilike 5 puta većim od vrijednosti zahtjeva) i pratimo. Kada su zahtjevi na preniskoj razini, proces ne može započeti, što često uzrokuje zagonetne runtime greške u Go-u.
Želim naglasiti da viša ograničenja resursa otežavaju planiranje, budući da pod zahtijeva ciljani čvor s dovoljnom količinom dostupnih resursa.
Zamislite situaciju u kojoj imate lagani web poslužitelj s vrlo visokim ograničenjem resursa, primjerice 4 GB memorije. Vjerojatno će biti potrebno horizontalno skalirati taj proces, a svaki novi modul treba planirati na čvoru s dostupnim kapacitetom memorije ne manjim od 4 GB. Ako takvog čvora nema, klaster će morati dodati novi čvor za obradu ovog poda, što može potrajati. Važno je postići minimalnu razliku između zahtjeva za resursima i ograničenja kako bi se osiguralo brzo i glatko skaliranje.
Korak drugi: postavljanje Liveness i Readiness testova
Ovo je još jedna suptilna tema koja se često raspravlja u Kubernetes zajednici. Važno je dobro razumjeti testove žive sposobnosti (Liveness) i spremnosti (Readiness), budući da oni osiguravaju mehanizam stabilnog rada softvera i minimiziraju vrijeme zastoja. Međutim, oni mogu ozbiljno utjecati na performanse vaše aplikacije ako nisu pravilno postavljeni. U nastavku je sažetak što predstavljaju oba testa.
Živost pokazuje radi li kontejner. Ako ne uspije, kubelet ubija kontejner, a aktivira se politika ponovnog pokretanja. Ako kontejner nije opremljen testom žive sposobnosti, zadano stanje će biti uspjeh — tako se navodi u .
Testovi žive sposobnosti trebaju biti jeftini, tj. ne bi trebali trošiti mnogo resursa jer se pokreću često i trebaju informirati Kubernetes da je aplikacija pokrenuta.
Ako postavite parametar za pokretanje svake sekunde, dodaje se 1 zahtjev u sekundi, pa uzmite u obzir da će za obradu ovog prometa biti potrebni dodatni resursi.
U našoj firmi testovi žive sposobnosti provjeravaju glavne komponente aplikacije, čak i ako podaci (npr. iz udaljene baze podataka ili cachea) nisu potpuno dostupni.
Postavili smo u aplikacijama krajnju točku 'spremnosti', koja jednostavno vraća kod odgovora 200. To je pokazatelj da je proces pokrenut i sposoban za obradu zahtjeva (ali još ne i prometa).
Test Spremnost ukazuje je li kontejner spreman za obradu zahtjeva. Ako test spremnosti ne uspije, kontroler krajnjih točaka uklanja IP adresu poda iz krajnjih točaka svih usluga koje su povezane s podom. To se također navodi u dokumentaciji Kubernetes.
Testovi spremnosti troše više resursa jer moraju doći do backend-a na način koji pokazuje spremnost aplikacije za primanje zahtjeva.
U zajednici se vodi mnogo rasprava o tome treba li se obraćati izravno bazi podataka. Imajuci u vidu troškove (provjere se često izvode, ali ih je moguće regulirati), odlučili smo da se za neke aplikacije spremnost za opsluživanje prometa računa tek nakon provjere jesu li zapisi vraćeni iz baze podataka. Dobro osmišljeni probni testovi spremnosti osigurali su viši nivo dostupnosti i eliminirali zastoje tijekom implementacije.
Ako odlučite napraviti upit bazi podataka za provjeru spremnosti aplikacije, osigurajte da to košta što manje. Uzmimo sljedeći upit:
SELECT small_item FROM table LIMIT 1Evo primjera kako postavljamo ta dva parametra u Kubernetesu:
livenessProbe:
httpGet:
path: /api/liveness
port: http
readinessProbe:
httpGet:
path: /api/readiness
port: http periodSeconds: 2
Možete dodati neke dodatne parametre konfiguracije:
initialDelaySeconds— koliko sekundi prolazi između pokretanja kontejnera i početka izvođenja probe.periodSeconds— interval čekanja između izvođenja proba.timeoutSeconds— broj sekundi nakon kojih se pod smatra neuspjelo. Uobičajeni timeout.failureThreshold— broj neuspjeha testova prije nego što se podu pošalje signal za ponovno pokretanje.successThreshold— broj uspješnih proba prije nego što pod pređe u stanje spremnosti (nakon kvara, kada se pod pokreće ili obnavlja).
Korak tri: postavljanje zadane mrežne politike poda
U Kubernetesu postoji 'ravna' mrežna topologija, prema zadanim postavkama svi podovi izravno međusobno komuniciraju. U nekim slučajevima to nije poželjno.
Potencijalni sigurnosni problem leži u tome što napadač može iskoristiti jedinstvenu ranjivu aplikaciju za slanje prometa svim podovima u mreži. Kao i u mnogim područjima sigurnosti, ovdje vrijedi načelo najmanjih privilegija. Idealno bi mrežne politike trebale izričito navesti koje veze između podova su dopuštene, a koje nisu.
Na primjer, u nastavku je jednostavna politika koja zabranjuje sav dolazni promet za određenu namjenu:
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
Vizualizacija ove konfiguracije:

(https://miro.medium.com/max/875/1*-eiVw43azgzYzyN1th7cZg.gif)
Detaljnije .
Četvrti korak: neobično ponašanje uz pomoć hookova i init-kontejnera
Jedan od naših glavnih zadataka bio je osigurati deploye u Kubernetesu bez zastoja za programere. To je teže nego što se čini zbog brojnih mogućnosti zatvaranja aplikacija i oslobađanja resursa koje su koristile.
Posebne poteškoće su se pojavile s . Primijetili smo da su aktivne veze prekinute prije nego što su ovi podovi uspješno završili svoje izvršavanje.
Nakon opsežnog istraživanja na internetu saznali smo da Kubernetes ne čeka da se Nginx veze iscrpe prije nego što završi pod. Uz pomoć pre-stop hooka implementirali smo ovu funkcionalnost i potpuno se oslobodili zastoja:
lifecycle:
preStop:
exec:
command: ["/usr/local/bin/nginx-killer.sh"]
Evo 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
Još jedna iznimno korisna paradigma je korištenje init-kontejnera za obradu pokretanja specifičnih aplikacija. Ovo je posebno korisno ako imate resursno intenzivan proces migracije baze podataka koji treba pokrenuti prije pokretanja glavne aplikacije. Za ovaj proces možete također postaviti viši limit resursa, ne postavljajući takav limit za glavnu aplikaciju.
Druga uobičajena shema je pristupanje tajnama unutar init-kontejnera, koji pruža ove vjerodajnice glavnom modulu i time sprječava neautorizirani pristup tajnama iz same glavne aplikacije.
Kao i obično, citat iz dokumentacije: init-kontejneri sigurno pokreću korisnički kod ili alate koji bi inače smanjili sigurnost kontejnerske slike aplikacije. Čuvanjem nepotrebnih alata odvojeno, ograničavate površinu napada kontejnerske slike aplikacije.
Peti korak: podešavanje jezgre
Na kraju, govorimo o naprednijoj tehnici.
Kubernetes je iznimno fleksibilna platforma koja omogućuje pokretanje radnih opterećenja onako kako vi smatrate potrebnim. Imamo niz visoko učinkovitih aplikacija koje zahtijevaju izuzetno mnogo resursa. Provođenjem obsežnog opterećenja testiranja, otkrili smo da se jedna od aplikacija teško nosi s očekivanim prometom, kada su primjenjene zadanih Kubernetes postavki.
Međutim, Kubernetes omogućuje pokretanje privilegiranog kontejnera koji mijenja parametre jezgre samo za određeni pod. Ovo je ono što smo koristili za promjenu maksimalnog broja otvorenih veza:
initContainers:
- name: sysctl
image: alpine:3.10
securityContext:
privileged: true
command: ['sh', '-c', "sysctl -w net.core.somaxconn=32768"]
To je naprednija tehnika koja često nije potrebna. No, ako se vaša aplikacija jedva nosi s velikim opterećenjem, možete pokušati prilagoditi neke od ovih parametara. Detaljnije informacije o ovom postupku i podešavanju različitih vrijednosti – kao i uvijek .
Za kraj
Iako Kubernetes može izgledati kao spremno rješenje "iz kutije", za nesmetano funkcioniranje aplikacija potrebno je poduzeti nekoliko ključnih koraka.
Tijekom cijele migracije na Kubernetes važno je slijediti "ciklus testiranja opterećenja": pokrenuti aplikaciju, testirati je pod opterećenjem, pratiti metrike i ponašanje pri skaliranju, prilagoditi konfiguraciju na temelju tih podataka, a zatim ponovno ponoviti taj ciklus.
Realistično procijenite očekivani promet i pokušajte ga premašiti kako biste vidjeli koji će se komponenti prvi pokvariti. S takvim iterativnim pristupom, nekoliko od navedenih preporuka može biti dovoljno za postizanje uspjeha. Ipak, može biti potrebna dublja prilagodba.
Uvijek si postavite ovakva pitanja:
- Koliko resursa troše aplikacije i kako će se ta količina promijeniti?
- Koje su stvarne potrebe za skaliranjem? Koliko će prometa aplikacija prosječno obrađivati? A što je s vrhunskim prometom?
- Koliko često će usluga trebati horizontalno skaliranje? Koliko brzo je potrebno uvesti nove pode u rad kako bi prihvatili promet?
- Koliko ispravno završavaju rad podovi? Je li to uopće potrebno? Može li se postići raspored bez prekida rada?
- Kako minimizirati sigurnosne rizike i ograničiti štetu od svih kompromitiranih podova? Ima li koja usluga dozvole ili pristupe koji joj nisu potrebni?
Kubernetes nudi nevjerojatnu platformu koja omogućuje primjenu najboljih praksi za implementaciju tisuća servisa unutar klastera. Ipak, sve aplikacije su različite. Ponekad implementacija zahtijeva malo više truda.
Na sreću, Kubernetes pruža potrebne postavke za postizanje svih tehničkih ciljeva. Koristeći kombinaciju zahtjeva za resursima i ograničenja, probe Liveness i Readiness, init kontejnera, mrežnih politika i prilagodbe jezgre, možete postići visoke performanse zajedno s otpornosti na greške i brzim skaliranjem.
Što još pročitati:
- .
- .
- .
Izvor: habr.com
