CPU-grænser og aggressiv drosling i Kubernetes

Bemærk. overs.: Denne øjenåbnende historie om Omio – en europæisk rejseaggregator – tager læsere fra grundlæggende teori til de fascinerende praktiske forviklinger i Kubernetes-konfigurationen. Kendskab til sådanne tilfælde hjælper ikke kun med at udvide din horisont, men forhindrer også ikke-trivielle problemer.

CPU-grænser og aggressiv drosling i Kubernetes

Har du nogensinde fået en ansøgning til at sidde fast, holde op med at svare på sundhedstjek og ikke være i stand til at finde ud af hvorfor? En mulig forklaring er relateret til CPU-ressourcekvotegrænser. Dette er, hvad vi vil tale om i denne artikel.

TL; DR:
Vi anbefaler kraftigt at deaktivere CPU-grænser i Kubernetes (eller deaktivere CFS-kvoter i Kubelet), hvis du bruger en version af Linux-kernen med en CFS-kvotefejl. I kernen er tilgængelig seriøs og Kendt en fejl, der fører til overdreven drosling og forsinkelser
.

I Omio hele infrastrukturen administreres af Kubernetes. Alle vores statslige og statsløse arbejdsbelastninger kører udelukkende på Kubernetes (vi bruger Google Kubernetes Engine). I de sidste seks måneder begyndte vi at observere tilfældige opbremsninger. Applikationer fryser eller holder op med at svare på sundhedstjek, mister forbindelsen til netværket osv. Denne adfærd undrede os i lang tid, og til sidst besluttede vi at tage problemet alvorligt.

Resumé af artiklen:

  • Et par ord om containere og Kubernetes;
  • Hvordan CPU-anmodninger og -grænser implementeres;
  • Hvordan CPU-begrænsning fungerer i multi-core miljøer;
  • Sådan spores CPU drosling;
  • Problemløsning og nuancer.

Et par ord om containere og Kubernetes

Kubernetes er i bund og grund den moderne standard i infrastrukturverdenen. Dens hovedopgave er containerorkestrering.

Containere

Tidligere var vi nødt til at skabe artefakter som Java JARs/WARs, Python Eggs eller eksekverbare filer for at køre på servere. Men for at få dem til at fungere, skulle der udføres yderligere arbejde: installation af runtime-miljøet (Java/Python), placering af de nødvendige filer de rigtige steder, sikring af kompatibilitet med en specifik version af operativsystemet osv. Med andre ord skulle der lægges stor vægt på konfigurationsstyring (som ofte var en kilde til strid mellem udviklere og systemadministratorer).

Containere ændrede alt. Nu er artefaktet et containerbillede. Det kan repræsenteres som en slags udvidet eksekverbar fil, der ikke kun indeholder programmet, men også et fuldgyldigt eksekveringsmiljø (Java/Python/...), samt de nødvendige filer/pakker, forudinstalleret og klar til at løb. Containere kan implementeres og køres på forskellige servere uden yderligere trin.

Derudover opererer containere i deres eget sandkassemiljø. De har deres egen virtuelle netværksadapter, deres eget filsystem med begrænset adgang, deres eget hierarki af processer, deres egne begrænsninger på CPU og hukommelse osv. Alt dette er implementeret takket være et særligt undersystem af Linux-kernen - navneområder.

Kubernetes

Som tidligere nævnt er Kubernetes en containerorkestrator. Det fungerer sådan her: du giver den en pulje af maskiner og siger så: "Hej, Kubernetes, lad os starte ti forekomster af min container med 2 processorer og 3 GB hukommelse hver, og holde dem kørende!" Kubernetes tager sig af resten. Den vil finde ledig kapacitet, starte containere og genstarte dem, hvis det er nødvendigt, udrulle opdateringer ved ændring af versioner osv. I det væsentlige giver Kubernetes dig mulighed for at abstrahere hardwarekomponenten og gør en bred vifte af systemer egnede til at implementere og køre applikationer.

CPU-grænser og aggressiv drosling i Kubernetes
Kubernetes fra lægmandens synspunkt

Hvad er anmodninger og begrænsninger i Kubernetes

Okay, vi har dækket containere og Kubernetes. Vi ved også, at flere containere kan ligge på samme maskine.

En analogi kan drages med en fælles lejlighed. En rummelig lokal (maskiner/enheder) tages og udlejes til flere lejere (containere). Kubernetes fungerer som ejendomsmægler. Spørgsmålet opstår, hvordan man holder lejere fra konflikter med hinanden? Hvad hvis en af ​​dem f.eks. beslutter sig for at låne badeværelset den halve dag?

Det er her, anmodninger og grænser kommer i spil. CPU Anmod om udelukkende nødvendige til planlægningsformål. Dette er noget i retning af en "ønskeliste" af beholderen, og den bruges til at vælge den bedst egnede node. Samtidig med CPU'en Begræns kan sammenlignes med en lejeaftale - så snart vi vælger en enhed til containeren, den kan ikke gå ud over de fastsatte grænser. Og det er her problemet opstår...

Hvordan anmodninger og begrænsninger implementeres i Kubernetes

Kubernetes bruger en drosselmekanisme (spring clock-cyklusser over) indbygget i kernen til at implementere CPU-grænser. Hvis en applikation overskrider grænsen, aktiveres drosling (dvs. den modtager færre CPU-cyklusser). Anmodninger og begrænsninger for hukommelse er organiseret forskelligt, så de er nemmere at opdage. For at gøre dette skal du bare kontrollere poden's sidste genstartsstatus: om den er "OOMKilled". CPU-throttling er ikke så simpel, da K8s kun gør metrikker tilgængelige efter brug, ikke af cgroups.

CPU-anmodning

CPU-grænser og aggressiv drosling i Kubernetes
Hvordan CPU-anmodning implementeres

For nemheds skyld, lad os se på processen med en maskine med en 4-core CPU som eksempel.

K8s bruger en kontrolgruppemekanisme (cgroups) til at kontrollere allokeringen af ​​ressourcer (hukommelse og processor). En hierarkisk model er tilgængelig for det: barnet arver grænserne for forældregruppen. Distributionsdetaljerne gemmes i et virtuelt filsystem (/sys/fs/cgroup). I tilfælde af en processor er dette /sys/fs/cgroup/cpu,cpuacct/*.

K8s bruger fil cpu.share at allokere processorressourcer. I vores tilfælde får root cgroup 4096 andele af CPU-ressourcer - 100% af den tilgængelige processorkraft (1 kerne = 1024; dette er en fast værdi). Rodgruppen fordeler ressourcer forholdsmæssigt afhængigt af andele af efterkommere, der er registreret i cpu.share, og de gør til gengæld det samme med deres efterkommere mv. På en typisk Kubernetes-node har rod-cgruppen tre børn: system.slice, user.slice и kubepods. De første to undergrupper bruges til at fordele ressourcer mellem kritiske systembelastninger og brugerprogrammer uden for K8'er. Sidste - kubepods — oprettet af Kubernetes for at fordele ressourcer mellem pods.

Diagrammet ovenfor viser, at den første og anden undergruppe modtog hver 1024 aktier, med kuberpod-undergruppen tildelt 4096 aktier Hvordan er dette muligt: ​​når alt kommer til alt, har rodgruppen kun adgang til 4096 aktier, og summen af ​​hendes efterkommeres aktier overstiger væsentligt dette antal (6144)? Pointen er, at værdien giver logisk mening, så Linux-planlæggeren (CFS) bruger den til proportionalt at allokere CPU-ressourcer. I vores tilfælde modtager de to første grupper 680 reelle aktier (16,6% af 4096), og kubepod modtager de resterende 2736 aktier I tilfælde af nedetid vil de to første grupper ikke bruge de tildelte ressourcer.

Heldigvis har skemalæggeren en mekanisme til at undgå spild af ubrugte CPU-ressourcer. Den overfører "tomgangskapacitet" til en global pulje, hvorfra den distribueres til grupper, der har brug for yderligere processorkraft (overførslen sker i batches for at undgå afrunding af tab). En lignende metode anvendes på alle efterkommere af efterkommere.

Denne mekanisme sikrer en retfærdig fordeling af processorkraft og sikrer, at ingen processer "stjæler" ressourcer fra andre.

CPU-grænse

På trods af at konfigurationerne af grænser og anmodninger i K8'er ligner hinanden, er deres implementering radikalt anderledes: dette mest vildledende og den mindst dokumenterede del.

K8s engagerer sig CFS kvotemekanisme at implementere grænser. Deres indstillinger er angivet i filer cfs_period_us и cfs_quota_us i cgroup-mappen (filen er også placeret der cpu.share).

I modsætning til cpu.share, er kontingentet baseret på tidsperiode, og ikke på den tilgængelige processorkraft. cfs_period_us angiver varigheden af ​​perioden (epoken) - den er altid 100000 μs (100 ms). Der er en mulighed for at ændre denne værdi i K8s, men den er kun tilgængelig i alfa indtil videre. Planlæggeren bruger epoken til at genstarte brugte kvoter. Anden fil cfs_quota_us, angiver den tilgængelige tid (kvote) i hver epoke. Bemærk, at det også er angivet i mikrosekunder. Kvoten kan overstige epokelængden; med andre ord, den kan være større end 100 ms.

Lad os se på to scenarier på 16-kernemaskiner (den mest almindelige type computer, vi har hos Omio):

CPU-grænser og aggressiv drosling i Kubernetes
Scenarie 1: 2 tråde og en grænse på 200 ms. Ingen drosling

CPU-grænser og aggressiv drosling i Kubernetes
Scenarie 2: 10 tråde og 200 ms grænse. Throttling begynder efter 20 ms, adgang til processorressourcer genoptages efter yderligere 80 ms

Lad os sige, at du indstiller CPU-grænsen til 2 kerner; Kubernetes vil oversætte denne værdi til 200 ms. Det betyder, at containeren maksimalt kan bruge 200ms CPU-tid uden drosling.

Og det er her det sjove begynder. Som nævnt ovenfor er den tilgængelige kvote 200 ms. Hvis du arbejder parallelt ti tråde på en 12-kernet maskine (se illustration for scenario 2), mens alle andre pods er inaktive, vil kvoten være opbrugt på kun 20 ms (da 10 * 20 ms = 200 ms), og alle tråde i denne pod vil hænge » (gashåndtag) i de næste 80 ms. Det allerede nævnte skemalægger-fejl, hvorved der sker overdreven drosling, og containeren kan ikke engang opfylde den eksisterende kvote.

Hvordan evaluerer man drosling i bælg?

Bare log ind på poden og kør cat /sys/fs/cgroup/cpu/cpu.stat.

  • nr_periods — det samlede antal planlægningsperioder;
  • nr_throttled — antal strygede perioder i sammensætningen nr_periods;
  • throttled_time — kumulativ droslet tid i nanosekunder.

CPU-grænser og aggressiv drosling i Kubernetes

Hvad sker der egentlig?

Som et resultat får vi høj drosling i alle applikationer. Nogle gange er han med halvanden gang stærkere end beregnet!

Dette fører til forskellige fejl - beredskabskontrolfejl, containerfrysning, netværksforbindelsesbrud, timeouts inden for servicekald. Dette resulterer i sidste ende i øget latenstid og højere fejlfrekvenser.

Beslutning og konsekvenser

Alt er enkelt her. Vi opgav CPU-grænser og begyndte at opdatere OS-kernen i klynger til den seneste version, hvor fejlen blev rettet. Antallet af fejl (HTTP 5xx) i vores tjenester faldt straks markant:

HTTP 5xx fejl

CPU-grænser og aggressiv drosling i Kubernetes
HTTP 5xx-fejl for én kritisk tjeneste

Svartid p95

CPU-grænser og aggressiv drosling i Kubernetes
Kritisk tjenesteanmodningsforsinkelse, 95. percentil

Driftsomkostninger

CPU-grænser og aggressiv drosling i Kubernetes
Antal forekomsttimer brugt

Hvad er fangsten?

Som der står i begyndelsen af ​​artiklen:

En analogi kan drages med en fælleslejlighed... Kubernetes fungerer som ejendomsmægler. Men hvordan holder man lejere fra konflikter med hinanden? Hvad hvis en af ​​dem f.eks. beslutter sig for at låne badeværelset den halve dag?

Her er fangsten. En skødesløs beholder kan spise alle de tilgængelige CPU-ressourcer på en maskine. Hvis du har en smart applikationsstak (for eksempel JVM, Go, Node VM er korrekt konfigureret), så er dette ikke et problem: du kan arbejde under sådanne forhold i lang tid. Men hvis applikationer er dårligt optimeret eller slet ikke er optimeret (FROM java:latest), kan situationen komme ud af kontrol. Hos Omio har vi automatiseret base Dockerfiler med passende standardindstillinger for den store sprogstak, så dette problem eksisterede ikke.

Vi anbefaler at overvåge metrics BRUG (brug, mætning og fejl), API-forsinkelser og fejlfrekvenser. Sørg for, at resultater lever op til forventningerne.

RЎSЃS <P "RєRё

Dette er vores historie. Følgende materialer hjalp i høj grad til at forstå, hvad der skete:

Kubernetes fejlrapporter:

Har du stødt på lignende problemer i din praksis eller har du erfaring med drosling i containeriserede produktionsmiljøer? Del din historie i kommentarerne!

PS fra oversætteren

Læs også på vores blog:

Kilde: www.habr.com

Tilføj en kommentar