CPU-limiidid ja agressiivne throttling Kubernetes'is

Märkus tõlke kohta.: see this instructive story of Omio — a European travel aggregator — guides readers from basic theory to fascinating practical nuances in Kubernetes configuration. Familiarity with such cases helps not only to broaden horizons but also to prevent non-trivial problems.

CPU-limiidid ja agressiivne throttling Kubernetes'is

Have you ever faced the issue where an application got 'stuck', stopped responding to health check requests, and you couldn’t understand the reason for its behavior? One possible explanation is related to CPU resource quota limits. That will be the topic of this article.

TL;DR:
We strongly recommend abandoning CPU limits in Kubernetes (or disabling CFS quotas in Kubelet) if using a Linux kernel version with a CFS quota bug. In the kernel on saadaval there is a serious and well-known bug that leads to excessive throttling and delays
.

At Omio, the entire infrastructure is managed by Kubernetes.All our stateful and stateless workloads operate exclusively on Kubernetes (we use Google Kubernetes Engine). Over the past six months, we have begun to observe random lags. Applications hang or stop responding to health checks, lose network connectivity, etc. Such behavior long left us puzzled, and finally, we decided to tackle the problem head-on.

Summary of the article:

  • A few words about containers and Kubernetes;
  • How CPU requests and limits are implemented;
  • How CPU limit works in multi-core environments;
  • How to monitor CPU throttling;
  • Solution to the problem and nuances.

A few words about containers and Kubernetes

Kubernetes, in essence, is the modern standard in the world of infrastructure. Its primary task is the orchestration of containers.

Konteinerid

In the past, we had to create artifacts like Java JARs/WARs, Python Eggs, or executables for subsequent launch on servers. However, to make them operate, extra work was needed: setting up a runtime environment (Java/Python), placing the necessary files in the right locations, ensuring compatibility with a specific version of the operating system, etc. In other words, great attention had to be paid to configuration management (which often caused friction between developers and system administrators).

Containers changed everything. Nüüd on artefaktiks konteineripilt. Seda võib kujutada kui laienenud käivitusfaili, mis sisaldab mitte ainult programmi, vaid ka täielikku käituskeskkonda (Java/Python/...); samuti vajalikke faile/pakette, mis on eelnevalt installitud ja käivitamiseks valmis. Konteinerit saab juurutada ja käivitada erinevatel serveritel ilma täiendavate toiminguteta.

Lisaks töötavad konteinerid omaette keskkonnas. Neil on oma virtuaalne võrgukaart, oma failisüsteem piiratud juurdepääsuga, oma protsessihierarhia, oma piirangud CPU ja mälu osas jne. Kõik see on ellu viidud tänu Linuxi tuuma erilise alamsüsteemi — nimealad (namespaces) — kaudu.

Kubernetes

Kuidas varem mainitud, on Kubernetes konteinerite orkestrator. See töötab järgmiselt: te annate sellele hulk masinaid ja ütlete: „Hei, Kubernetes, käivita kümme minu konteinerisikapti 2 protsessoriga ja 3 GB mäluga igaühele ning hoia neid töökorras!“. Kubernetes hoolitseb ülejäänud eest. See leiab vabad ressursid, käivitab konteinerid ja vajadusel taaskäivitab need, viib läbi uuendusi versioonide vahetumisel jne. Essentsiaalselt võimaldab Kubernetes abstraheerida riistvara komponendist ja muudab kõik erinevad süsteemid rakenduste juurutamiseks ja töötamiseks sobivaks.

CPU-limiidid ja agressiivne throttling Kubernetes'is
Kubernetes tavainimese vaatepunktist

Mis on request’id ja limit’id Kuberneteses

Okei, oleme aru saanud konteineritest ja Kubernetesest. Teame ka, et mitmed konteinerid võivad olla ühel masinal.

Seda saab võrrelda kommunaalkorteriga. Võetakse avar ruum (masinad/sõlmed) ja antakse mitmele üürnikule (konteineritele) üürile. Kubernetes mängib kinnisvaramaakleri rolli. Tõukub küsimus, kuidas hoiduda üürnike konfliktidest omavahel? Mis juhtub, kui üks neist soovib näiteks vannituba pooleks päevaks enda valdusesse võtta?

Siin astuvad mängu request’id ja limit’id. CPU Päring on vajalik ainult planeerimiseks. See on midagi nagu konteineri „soovide nimekiri“, mida kasutatakse sobivaima sõlme leidmiseks. Samas võib CPU Piirang võrrelda üürilepinguga — kohe kui oleme leidnud sõlme konteinerile, siis ei saa ületada kehtestatud piire. Ja siin tekib probleem...

Kuidas on Kubernetesis rakendatud request'e ja limit'e

Kubernetes kasutab CPU limit'ide rakendamiseks tuumikus sisseehitatud throttling'u mehhanismi. Kui rakendus ületab limiidi, aktiveeritakse throttling (ehk see saab vähem CPU tsükleid). Mälu request'id ja limit'id on korraldatud teisiti, seega on neid lihtsam tuvastada. Selleks piisab, kui kontrollida pod'i viimast taaskäivitamise olekut: ei ole see «OOMKilled». CPU throttlinguga ei ole kõik nii lihtne, kuna K8s teeb kätte ainult kasutamise metrikate, mitte cgroups'i põhjal.

CPU Request

CPU-limiidid ja agressiivne throttling Kubernetes'is
Kuidas on rakendatud CPU request

Kerguse huvides vaatame protsessi näitel, kus on 4-tuuma CPU.

K8s kasutab ressursside (mälu ja CPU) jaotamise juhtimiseks cgroups'i mehhanismi. Sellel on hierarhiline mudel: alamhari pärib vanema grupi limit'id. Jagamise üksikasjad salvestatakse virtuaalsesse failisüsteemi (/sys/fs/cgroup). Protsessori puhul on see /sys/fs/cgroup/cpu,cpuacct/*.

K8s kasutab faili cpu.share protsessori ressursside jaotamiseks. Meie juhus moraali järgi, juurt kontrollrühm saab 4096 ressursi CPU osa – 100% kätte saadava CPU võimsusest (1 südamik = 1024; see on fikseeritud väärtus). Juurt rühm jaotab ressursse proportsionaalselt vastavalt alamrühmade osadele, mis on määratud cpu.share, ja need omakorda käituvad oma järglastega sarnaselt jne. Tüüpilises Kubernetes'i sõlmes on juurrühmadel kolm järglast: system.slice, user.slice ja kubepods. Kaks esimest alamhulka kasutatakse ressursside jaotamiseks kriitiliste süsteemikoormuste ja kasutajaprogrammide vahel väljaspool K8s. Viimane — kubepods — luuakse Kubernetes'i poolt, et jaotada ressursse pod'ide vahel.

Ülaltoodud skeemilt on näha, et esimesed ja teised alamhulgad on saanud kumbki 1024 osa, samas kui kubepod'ile on määratud 4096 osasid. Kuidas on see võimalik: juurrühmale on ju kätte saadavad vaid 4096 osi, kuid selle järglaste osade summa ületab seda arvu märgatavalt (6144)? Asja on selles, et väärtus on loogiliselt mõtteline, seega kasutab Linux'i planeerija (CFS) seda CPU ressursside proportsionaalseks jaotamiseks. Meie juhul saavad esimesed kaks grupi kummagi 680 reaalset osa (16,6% 4096-st), samas kui kubepod saab ülejäänud 2736 osasid. Ooteseisus ei kasuta kaks esimest gruppi määratud ressursse.

Õnneks on planeerijas mehhanism, mis võimaldab vältida kasutamata CPU ressursside kaotamist. See suunab "seisvaid" võimsusi globaalsetesse fondidesse, kust neid jaotatakse gruppidele, mis vajavad täiendavaid protsessori võimsusi (edastus toimub partiidena, et vältida ümardamisest tingitud kaotusi). Sarnast meetodit rakendatakse ka kõikidele järeltulijatele.

See mehhanism tagab õiglaselt protsessori võimsuste jaotuse ja jälgib, et ükski protsess ei "varastaks" ressursse teistelt.

CPU Limiteerimine

Kuigi K8s-i limitide ja requestide konfiguratsioonid näevad välja sarnased, on nende rakendamine kardinaalselt erinev: see on kõige eksitavam ja kõige vähem dokumenteeritud osa.

K8s rakendab CFS kvota mehhanismi limiitide rakendamiseks. Nende seaded määratakse failidesse cfs_period_us ja cfs_quota_us cgroup kataloogis (seal asub ka fail cpu.share).

Erinevalt cpu.share, kvota põhineb aja perioodil, mitte saadaval oleva protsessori võimsusel. cfs_period_us määrab perioodi (ajastu) kestuse - see on alati 100000 mikrosekundit (100 ms). K8s-il on võimalus seda väärtust muuta, kuid see on hetkel saadaval vaid alfa-versioonis. Planeerija kasutab ajastu lõppemise aega kasutatud kvotade uuesti käivitamiseks. Teine fail, cfs_quota_us, määrab iga ajastu saadaval oleva aja (kvota). Pange tähele, et see on samuti määratud mikrosekundites. Kvota võib olla pikem kui ajastu kestus; teisisõnu, see võib ületada 100 ms.

Vaadakem kahte stsenaariumi 16- tuumaga masinatel (kõige levinum arvutitüüp meie Omios):

CPU-limiidid ja agressiivne throttling Kubernetes'is
Stsenaarium 1: 2 voogu ja 200 ms limiit. Ilma trottlemiseta

CPU-limiidid ja agressiivne throttling Kubernetes'is
Stsenaarium 2: 10 voogu ja 200 ms limiit. Trottlemine algab pärast 20 ms, juurdepääs protsessori ressurssidele taastub veel 80 ms pärast

Oletame, et olete seadistanud CPU limiidi 2 tuumadele; Kubernetes tõlgendab seda väärtust kui 200 ms. See tähendab, et konteiner võib kasutada maksimaalselt 200 ms protsessoriaega ilma trottlemiseta.

Ja siin hakkavad asjad huvitavaks minema. Nagu öeldud, on saadaval kvota 200 ms. Kui teil on samal ajal töötamas kümme voogu 12-tuumaga masinas (vt stsenaariumi 2 joonist), siis kui kõik muud pod'id seisavad, kahaneb kvota vaid 20 ms pärast (kuna 10 * 20 ms = 200 ms), ja kõik selle pod'i vood "peatatakse" (throttle) järgmise 80 ms. Olukorda halvendab juba mainitud planeerija viga, mille tõttu tekib liigset trottlingut ja konteiner ei suuda isegi olemasolevat kvooti kasutada.

Kuidas hinnata trottlingut pod'ides?

Lihtsalt siseneda pod'isse ja käivitada cat /sys/fs/cgroup/cpu/cpu.stat.

  • nr_periods — planeerija koguperioodide arv;
  • nr_throttled — trottlingperioodide arv, nr_periods;
  • throttled_time — kogutud trottlinguaeg nanosekundites.

CPU-limiidid ja agressiivne throttling Kubernetes'is

Mis tegelikult toimub?

Kokkuvõttes saame kõikides rakendustes kõrge trottlingu. Mõnikord on see ühe ja poole korra tugevam kui arvutatud!

See toob kaasa erinevaid vigu — valmisoleku kontrollide (readiness) tõrkeid, konteinerite seisakuid, võrguühenduste katkestusi, teenusekõnede aegumisi. Lõppkokkuvõttes kajastub see suurenenud viivituses ja vigade arvu tõusus.

Lahendus ja tagajärjed

Siin on kõik lihtne. Me loobusime CPU piirangutest ja tegelesime OS-i tuuma värskendamisega klastrites värskesse versiooni, kus viga oli parandatud. Vigade (HTTP 5xx) arv meie teenustes langes kohe oluliselt:

HTTP 5xx vead

CPU-limiidid ja agressiivne throttling Kubernetes'is
Ühe kriitilise teenuse HTTP 5xx vead

p95 vastamisaeg

CPU-limiidid ja agressiivne throttling Kubernetes'is
Kriitilise teenuse päringute viivitus, 95. protsentil

Ekspluatatsioonikulud

CPU-limiidid ja agressiivne throttling Kubernetes'is
Kulutatud eksemplarituurid

Mis on petukäik?

Nagu artikli alguses öeldi:

Seda saab võrrelda ühiselt kasutatava korteriga... Kubernetes mängib kinnisvaramaakleri rolli. Aga kuidas hoida üürnikud vältimast konflikte omavahel? Mis siis, kui keegi neist otsustab näiteks hõivata vannituba pooleks päevaks?

Siin peitubki trik. Üks lohakas konteiner võib kõrvaldada kõik saadaolevad protsessori ressursid masinas. Kui teil on korralik rakenduste virn (nt korralikult seadistatud JVM, Go, Node VM), siis pole see probleem: sellistes tingimustes saab töötada pikka aega. Kuid kui rakendused on halvasti optimeeritud või üldse mitte optimeeritud (FROM java:latest), võib situatsioon minna kontrolli alt välja. Omios on meil automatiseeritud põhised Dockerfile'id, millel on mõistlikud vaikeseaded peamiste keelte virna jaoks, seetõttu ei olnud sellist probleemi.

Soovitame jälgida mõõdikuid USE (kasutamine, küllastus ja vead), API viivituste ja vigade sageduse osas. Veenduge, et tulemused vastavad ootustele.

Viidatud lingid

See on meie lugu. Järgmised materjalid aitasid oluliselt mõista, mis toimub:

Kubernetes'i viga aruanded:

Kas olete kogenud sarnaseid probleeme või on teil kogemusi, mis on seotud konteineriseeritud tootmis keskkondade trottimisega? Jagage oma lugu kommentaarides!

P.S. tõlkijalt

Lugege ka meie blogist:

Allikas: habr.com

Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster