Limiti di CPU è throttling aggressivu in Kubernetes
Nota. transl.: Questa storia d'ochju di Omio-un aggregatore di viaghju europeu-porta i lettori da a teoria basica à l'affascinanti intricacies pratiche di a cunfigurazione di Kubernetes. A familiarità cù tali casi aiuta micca solu à allargà i vostri orizonti, ma ancu impediscenu prublemi micca triviali.
Avete mai avutu una applicazione bloccata in u locu, smette di risponde à i cuntrolli di salute, è ùn pò micca capisce perchè? Una spiegazione pussibule hè ligata à i limiti di quota di risorse CPU. Questu hè ciò chì avemu da parlà in questu articulu.
TL; DR:
Hè ricumandemu fermamente di disattivà i limiti di CPU in Kubernetes (o disattivà e quote CFS in Kubelet) se utilizate una versione di u kernel Linux cun un bug di quota CFS. In u core ci hè seriu è ben cunnisciutu un bug chì porta à throttling eccessivu è ritardi.
In Omio tutta l'infrastruttura hè gestita da Kubernetes. Tutti i nostri carichi di travagliu stateless è senza stati funzionanu esclusivamente in Kubernetes (utilicemu Google Kubernetes Engine). In l'ultimi sei mesi, avemu cuminciatu à osservà rallentamenti casuali. L'applicazioni si congelanu o cessanu di risponde à i cuntrolli di salute, perde a cunnessione à a reta, etc. Stu cumpurtamentu ci hà perplessu per un bellu pezzu, è infine avemu decisu di piglià u prublema in seriu.
Sommariu di l'articulu:
Uni pochi parolle nantu à i cuntenituri è Kubernetes;
Cumu e dumande è i limiti di CPU sò implementati;
Cumu u limitu di CPU funziona in ambienti multi-core;
Cumu seguità u throttling CPU;
Soluzione di prublema è sfumature.
Uni pochi parolle nantu à i cuntenituri è Kubernetes
Kubernetes hè essenzialmente u standard mudernu in u mondu di l'infrastruttura. U so compitu principale hè l'orchestrazione di u containeru.
Contenidors
In u passatu, avemu avutu à creà artefatti cum'è Java JAR / WARs, Python Eggs, o eseguibili per eseguisce nantu à i servitori. In ogni casu, per fà funziunà, un travagliu supplementu deve esse fattu: installà l'ambiente di runtime (Java / Python), mette i schedarii necessarii in i posti ghjusti, assicurendu a cumpatibilità cù una versione specifica di u sistema operatore, etc. In altri palori, l'attenzione attenta deve esse pagata à a gestione di cunfigurazione (chì era spessu una fonte di cuntinzione trà sviluppatori è amministratori di sistema).
I cuntenituri anu cambiatu tuttu. Avà l'artefattu hè una maghjina di cuntainer. Pò esse rapprisintatu cum'è una spezia di file eseguibile estensatu chì cuntene micca solu u prugramma, ma ancu un ambiente di esecutivu cumpletu (Java/Python/...), è ancu i schedarii / pacchetti necessarii, preinstallati è pronti à esse. corre. I cuntenituri ponu esse implementati è eseguiti in diversi servitori senza alcunu passu supplementu.
Inoltre, i cuntenituri operanu in u so propiu ambiente sandbox. Hanu u so propiu adattatore di rete virtuale, u so propiu sistema di schedariu cù accessu limitatu, a so ghjerarchia di prucessi, e so limitazioni in CPU è memoria, etc. Tuttu questu hè implementatu grazia à un subsistema speciale di u kernel Linux - namespaces.
Kubernetes
Cum'è dichjaratu prima, Kubernetes hè un orchestratore di container. Funziona cusì: dà un pool di macchine, è poi dite: "Ehi, Kubernetes, lanciamu dece istanze di u mo containeru cù 2 processori è 3 GB di memoria ognunu, è mantenemu in esecuzione!" Kubernetes hà da piglià a cura di u restu. Truvarà a capacità libera, lanciarà cuntenituri è riavvia se ne necessariu, stende l'aghjurnamenti quandu cambia versione, etc. Essenzialmente, Kubernetes permette di astrazione di u cumpunente hardware è rende una larga varietà di sistemi adattati per implementà è eseguisce applicazioni.
Kubernetes da u puntu di vista di u laicu
Chì sò e dumande è i limiti in Kubernetes
Va bè, avemu cupertu cuntenituri è Kubernetes. Sapemu ancu chì parechji cuntenituri ponu reside nantu à a stessa macchina.
Una analogia pò esse tracciata cù un appartamentu cumunale. Un locu spaziosu (macchine / unità) hè pigliatu è affittatu à parechji inquilini (contenitori). Kubernetes agisce cum'è un agente immubiliare. A quistione sorge, cumu mantene l'inquilini da cunflitti cù l'altri? E se unu di elli, dì, decide di piglià in prestu u bagnu per a mità di u ghjornu?
Hè quì chì e dumande è i limiti entranu in ghjocu. CPU Demande necessariu solu per scopi di pianificazione. Questu hè qualcosa cum'è una "lista di desideri" di u cuntinuu, è hè utilizatu per selezziunà u node più adattatu. À u listessu tempu u CPU limitazione pò esse paragunatu à un accordu di affittu - appena avemu sceltu una unità per u cuntinuu, u ùn pò micca va oltre i limiti stabiliti. È hè quì chì u prublema nasce ...
Cumu e dumande è i limiti sò implementati in Kubernetes
Kubernetes usa un mecanismu di throttling (saltà i cicli di clock) integratu in u kernel per implementà i limiti di CPU. Se una applicazione supera u limitu, u throttling hè attivatu (vale à dì riceve menu cicli di CPU). E dumande è i limiti per a memoria sò organizati in modu diversu, cusì sò più faciuli di detectà. Per fà questu, basta à verificà l'ultimu status di riavvia di u pod: s'ellu hè "OOMKilled". U throttling di CPU ùn hè micca cusì simplice, postu chì K8s rende solu metriche dispunibuli per usu, micca per cgroups.
Richiesta CPU
Cumu a dumanda di CPU hè implementata
Per simplicità, fighjemu u prucessu cù una macchina cù un CPU 4-core cum'è un esempiu.
K8s usa un mecanismu di gruppu di cuntrollu (cgroups) per cuntrullà l'assignazione di risorse (memoria è processore). Un mudellu gerarchicu hè dispunibule per questu: u zitellu eredita i limiti di u gruppu parenti. I dettagli di distribuzione sò guardati in un sistema di file virtuale (/sys/fs/cgroup). In u casu di un processatore questu hè /sys/fs/cgroup/cpu,cpuacct/*.
K8s usa u schedariu cpu.share per assignà e risorse di u processatore. In u nostru casu, a radicali cgroup riceve 4096 parte di risorse di CPU - 100% di a putenza di processore dispunibule (1 core = 1024; questu hè un valore fissu). U gruppu radicali distribuisce risorse proporzionalmente secondu e parte di i discendenti registrati in cpu.share, è elli, à u turnu, facenu u listessu cù i so discendenti, etc. In un node Kubernetes tipicu, u cgroup root hà trè figlioli: system.slice, user.slice и kubepods. I primi dui sottogruppi sò usati per distribuisce risorse trà i carichi critichi di u sistema è i prugrammi d'utilizatori fora di K8s. L'ultimu - kubepods - creatu da Kubernetes per distribuisce risorse trà pods.
U diagramma sopra mostra chì u primu è u sicondu sottugruppu anu ricevutu ognunu 1024 azzioni, cù u subgruppu kuberpod attribuitu 4096 azzioni Cumu hè pussibule: dopu à tuttu, u gruppu radicali hà accessu solu 4096 azzioni, è a summa di l'azzioni di i so discendenti supera significativamente stu numeru (6144)? U puntu hè chì u valore hè sensu logicu, cusì u pianificatore Linux (CFS) l'utiliza per assignà proporzionalmente risorse CPU. In u nostru casu, i primi dui gruppi ricevenu 680 azioni reali (16,6% di 4096), è kubepod riceve u restu 2736 azzioni In casu di downtime, i primi dui gruppi ùn anu micca aduprà e risorse attribuite.
Fortunatamente, u pianificatore hà un mecanismu per evità di perdite risorse CPU inutilizate. Trasferisce a capacità "idle" à una piscina glubale, da quale hè distribuitu à i gruppi chì anu bisognu di putenza di processore supplementu (u trasferimentu si faci in batch per evità perditi arrotondamenti). Un metudu simili hè applicatu à tutti i discendenti di i discendenti.
Stu mekanismu assicura una distribuzione ghjusta di u putere di u processatore è assicura chì nimu prucessa "robba" risorse da l'altri.
Limitu CPU
Malgradu u fattu chì e cunfigurazioni di i limiti è e dumande in K8 parenu simili, a so implementazione hè radicalmente diversa: questu più ingannevoli è a parte menu documentata.
K8s s'impegna Mécanisme des quotas du CFS per implementà i limiti. I so paràmetri sò specificati in i schedari cfs_period_us и cfs_quota_us in u cartulare cgroup (u schedariu hè ancu situatu quì cpu.share).
Ô cuntrariu cpu.share, a quota hè basatu nantu periodu di tempu, è micca nantu à a putenza di processore dispunibule. cfs_period_us specifica a durata di u periodu (epica) - hè sempre 100000 μs (100 ms). Ci hè una opzione per cambià stu valore in K8s, ma hè solu dispunibule in alfa per avà. U pianificatore usa l'epica per riavviare e quote usate. Second file cfs_quota_us, specifica u tempu dispunibule (quota) in ogni epoca. Nota chì hè ancu specificatu in microsecondi. A quota pò esse più di a durata di l'epica; in altri palori, pò esse più grande di 100 ms.
Fighjemu dui scenarii nantu à e macchine 16 core (u tipu di computer più cumuni chì avemu in Omio):
Scenariu 1: 2 fili è un limitu di 200 ms. Nisun throttling
Scenariu 2: 10 fili è limitu di 200 ms. U throttling principia dopu à 20 ms, l'accessu à e risorse di u processatore hè ripresu dopu à un altru 80 ms
Diciamu chì avete stabilitu u limitu di CPU 2 kernels; Kubernetes traduce stu valore in 200 ms. Questu significa chì u cuntinuu pò utilizà un massimu di 200 ms di tempu CPU senza throttling.
È questu hè induve u divertimentu principia. Cumu l'hà dettu sopra, a quota dispunibule hè 200 ms. Sè vo avete travagliatu in parallelu dieci i fili in una macchina di 12 core (vede l'illustrazione per u scenariu 2), mentre chì tutti l'altri pods sò inattivi, a quota serà esaurita in solu 20 ms (dapoi 10 * 20 ms = 200 ms), è tutti i fili di stu pod si penderanu. » (acceleratore) per i prossimi 80 ms. U digià citatu bug di pianificazione, per via di quale si trova un throttling eccessivu è u cuntinuu ùn pò ancu cumpiendu a quota esistente.
Cumu valutà u throttling in pods?
Basta à accede à u pod è eseguite cat /sys/fs/cgroup/cpu/cpu.stat.
nr_periods - u numeru tutale di periodi di pianificazione;
nr_throttled - numeru di periodi throttled in a cumpusizioni nr_periods;
throttled_time - tempu throttled cumulativu in nanosecondi.
Chì succede veramente ?
In u risultatu, avemu un altu throttling in tutte l'applicazioni. Calchì volta hè in una volta è mezu più forte chè calculatu !
Questu porta à diversi errori - fallimenti di verificazione di prontezza, congelazione di container, rotture di cunnessione di rete, timeout in e chjama di serviziu. Questu ultimamente si traduce in una latenza aumentata è tassi d'errore più alti.
Decisione è cunsequenze
Tuttu hè simplice quì. Abandunamu i limiti di CPU è cuminciamu à aghjurnà u kernel OS in clusters à l'ultima versione, in quale u bug hè stata corretta. U numaru d'errori (HTTP 5xx) in i nostri servizii hà subitu diminuitu significativamente:
Errori HTTP 5xx
Errori HTTP 5xx per un serviziu criticu
Tempu di risposta p95
Latenza di dumanda di serviziu criticu, 95 percentile
Costi operativi
Numero di ore di istanza spese
Chì ghjè a pesca?
Cum'è dettu à u principiu di l'articulu:
Una analogia pò esse disegnata cù un appartamentu cumunale ... Kubernetes agisce cum'è un realtor. Ma cumu mantene l'inquilini da cunflitti cù l'altri? E se unu di elli, dì, decide di piglià in prestu u bagnu per a mità di u ghjornu?
Eccu a cattura. Un contenitore trascuratu pò manghjà tutte e risorse di CPU dispunibili nantu à una macchina. Se tenete una pila d'applicazioni intelligenti (per esempiu, JVM, Go, Node VM sò cunfigurati bè), allora questu ùn hè micca un prublema: pudete travaglià in tali cundizioni per un bellu pezzu. Ma s'è l'applicazioni sò pocu ottimizzati o micca ottimisati in tuttu (FROM java:latest), a situazione pò esse fora di cuntrollu. In Omio avemu i Dockerfiles di basa automatizati cù paràmetri predeterminati adatti per a pila di lingua maiò, cusì stu prublema ùn esiste micca.
Avemu cunsigliatu per monitorà e metriche USE (usu, saturazione è errori), ritardi di l'API è tassi d'errore. S'assurer que les résultats répondent aux attentes.
referenze
Questa hè a nostra storia. I materiali seguenti aiutanu assai à capisce ciò chì succede:
Avete scontru prublemi simili in a vostra pratica o avete una sperienza ligata à u throttling in ambienti di produzzione containerizzati? Condividi a vostra storia in i cumenti!