CPU-grenser og aggressiv struping i Kubernetes

Merk. overs.: Denne øyeåpnende historien til Omio – en europeisk reiseaggregator – tar leserne fra grunnleggende teori til de fascinerende praktiske forviklingene ved Kubernetes-konfigurasjonen. Kjennskap til slike tilfeller hjelper ikke bare med å utvide horisonten din, men forhindrer også ikke-trivielle problemer.

CPU-grenser og aggressiv struping i Kubernetes

Har du noen gang hatt en søknad som sitter fast, slutter å svare på helsesjekker og ikke har klart å finne ut hvorfor? En mulig forklaring er relatert til CPU-ressurskvotegrenser. Dette er hva vi vil snakke om i denne artikkelen.

TL; DR:
Vi anbefaler på det sterkeste å deaktivere CPU-grenser i Kubernetes (eller deaktivere CFS-kvoter i Kubelet) hvis du bruker en versjon av Linux-kjernen med en CFS-kvotefeil. I kjernen det er alvorlig og Velkjente en feil som fører til overdreven struping og forsinkelser
.

I Omio hele infrastrukturen administreres av Kubernetes. Alle våre tilstandsfulle og statsløse arbeidsbelastninger kjører utelukkende på Kubernetes (vi bruker Google Kubernetes Engine). I løpet av de siste seks månedene begynte vi å observere tilfeldige nedganger. Apper fryser eller slutter å svare på helsesjekker, mister forbindelsen til nettverket osv. Denne oppførselen forundret oss i lang tid, og til slutt bestemte vi oss for å ta problemet på alvor.

Sammendrag av artikkelen:

  • Noen få ord om containere og Kubernetes;
  • Hvordan CPU-forespørsler og grenser implementeres;
  • Hvordan CPU-grense fungerer i flerkjernemiljøer;
  • Hvordan spore CPU-struping;
  • Problemløsning og nyanser.

Noen få ord om containere og Kubernetes

Kubernetes er i hovedsak den moderne standarden i infrastrukturverdenen. Hovedoppgaven er containerorkestrering.

containere

Tidligere måtte vi lage artefakter som Java JARs/WARs, Python Eggs eller kjørbare filer for å kjøre på servere. Men for å få dem til å fungere, måtte det gjøres ekstra arbeid: installere kjøretidsmiljøet (Java/Python), plassere de nødvendige filene på de riktige stedene, sikre kompatibilitet med en spesifikk versjon av operativsystemet, etc. Med andre ord, det måtte vies nøye oppmerksomhet til konfigurasjonsadministrasjon (som ofte var en kilde til strid mellom utviklere og systemadministratorer).

Containere endret alt. Nå er artefakten et beholderbilde. Den kan representeres som en slags utvidet kjørbar fil som inneholder ikke bare programmet, men også et fullverdig utførelsesmiljø (Java/Python/...), samt nødvendige filer/pakker, forhåndsinstallert og klar til å løpe. Beholdere kan distribueres og kjøres på forskjellige servere uten ekstra trinn.

I tillegg opererer containere i sitt eget sandkassemiljø. De har sin egen virtuelle nettverksadapter, sitt eget filsystem med begrenset tilgang, sitt eget hierarki av prosesser, sine egne begrensninger på CPU og minne osv. Alt dette er implementert takket være et spesielt undersystem av Linux-kjernen – navneområder.

Kubernetes

Som nevnt tidligere, er Kubernetes en containerorkestrator. Det fungerer slik: du gir den en pool av maskiner, og sier deretter: "Hei, Kubernetes, la oss lansere ti forekomster av beholderen min med 2 prosessorer og 3 GB minne hver, og holde dem i gang!" Kubernetes tar seg av resten. Den vil finne ledig kapasitet, starte beholdere og starte dem på nytt om nødvendig, rulle ut en oppdatering ved endring av versjoner, etc. I hovedsak lar Kubernetes deg abstrahere bort maskinvarekomponenten og gjør et bredt utvalg av systemer egnet for å distribuere og kjøre applikasjoner.

CPU-grenser og aggressiv struping i Kubernetes
Kubernetes fra lekmannens synspunkt

Hva er forespørsler og begrensninger i Kubernetes

Ok, vi har dekket containere og Kubernetes. Vi vet også at flere containere kan ligge på samme maskin.

En analogi kan trekkes med en felles leilighet. Et romslig lokale (maskiner/enheter) tas og leies ut til flere leietakere (containere). Kubernetes fungerer som eiendomsmegler. Spørsmålet oppstår, hvordan holde leietakere fra konflikter med hverandre? Hva om en av dem for eksempel bestemmer seg for å låne badet halve dagen?

Det er her forespørsler og grenser spiller inn. prosessor Be nødvendig kun for planleggingsformål. Dette er noe sånt som en "ønskeliste" av beholderen, og den brukes til å velge den mest passende noden. Samtidig med CPU Grense kan sammenlignes med en leieavtale - så snart vi velger en enhet for containeren, den kan ikke gå utover etablerte grenser. Og det er her problemet oppstår...

Hvordan forespørsler og begrensninger implementeres i Kubernetes

Kubernetes bruker en strupemekanisme (hoppe over klokkesykluser) innebygd i kjernen for å implementere CPU-grenser. Hvis en applikasjon overskrider grensen, er struping aktivert (dvs. den mottar færre CPU-sykluser). Forespørsler og begrensninger for minne er organisert annerledes, slik at de er lettere å oppdage. For å gjøre dette, sjekk den siste omstartstatusen til poden: om den er "OOMKilled". CPU-struping er ikke så enkelt, siden K8s bare gjør beregninger tilgjengelig etter bruk, ikke av cgroups.

CPU-forespørsel

CPU-grenser og aggressiv struping i Kubernetes
Hvordan CPU-forespørsel implementeres

For enkelhets skyld, la oss se på prosessen ved å bruke en maskin med en 4-kjerne CPU som et eksempel.

K8s bruker en kontrollgruppemekanisme (cgroups) for å kontrollere allokeringen av ressurser (minne og prosessor). En hierarkisk modell er tilgjengelig for det: barnet arver grensene til foreldregruppen. Distribusjonsdetaljene lagres i et virtuelt filsystem (/sys/fs/cgroup). I tilfelle av en prosessor er dette /sys/fs/cgroup/cpu,cpuacct/*.

K8s bruker fil cpu.share å tildele prosessorressurser. I vårt tilfelle får rot-cgroup 4096 andeler av CPU-ressurser - 100 % av tilgjengelig prosessorkraft (1 kjerne = 1024; dette er en fast verdi). Rotgruppen fordeler ressurser proporsjonalt avhengig av andelene til etterkommere registrert i cpu.share, og de på sin side gjør det samme med sine etterkommere osv. På en typisk Kubernetes-node har rot-c-gruppen tre barn: system.slice, user.slice и kubepods. De to første undergruppene brukes til å fordele ressurser mellom kritiske systembelastninger og brukerprogrammer utenfor K8-er. Den siste - kubepods – opprettet av Kubernetes for å distribuere ressurser mellom pods.

Diagrammet ovenfor viser at den første og andre undergruppen mottok hver 1024 aksjer, med kuberpod-undergruppen tildelt 4096 aksjer Hvordan er dette mulig: tross alt har rotgruppen bare tilgang til 4096 aksjer, og summen av aksjene til hennes etterkommere overstiger dette antallet betydelig (6144)? Poenget er at verdien gir logisk mening, så Linux-planleggeren (CFS) bruker den til å proporsjonalt allokere CPU-ressurser. I vårt tilfelle mottar de to første gruppene 680 reelle aksjer (16,6% av 4096), og kubepod mottar de resterende 2736 aksjer Ved nedetid vil ikke de to første gruppene bruke de tildelte ressursene.

Heldigvis har planleggeren en mekanisme for å unngå å kaste bort ubrukte CPU-ressurser. Den overfører "tomgangskapasitet" til en global pool, hvorfra den distribueres til grupper som trenger ekstra prosessorkraft (overføringen skjer i grupper for å unngå avrundingstap). En lignende metode brukes på alle etterkommere av etterkommere.

Denne mekanismen sikrer en rettferdig fordeling av prosessorkraft og sikrer at ingen prosesser "stjeler" ressurser fra andre.

CPU-grense

Til tross for at konfigurasjonene av grenser og forespørsler i K8s ser like ut, er implementeringen radikalt forskjellig: dette mest misvisende og den minst dokumenterte delen.

K8s engasjerer seg CFS kvotemekanisme å implementere grenser. Innstillingene deres er spesifisert i filer cfs_period_us и cfs_quota_us i cgroup-katalogen (filen ligger også der cpu.share).

I motsetning til cpu.share, er kvoten basert på periode, og ikke på tilgjengelig prosessorkraft. cfs_period_us spesifiserer varigheten av perioden (epoken) - den er alltid 100000 100 μs (8 ms). Det er et alternativ for å endre denne verdien i KXNUMXs, men den er kun tilgjengelig i alfa foreløpig. Planleggeren bruker epoken til å starte brukte kvoter på nytt. Andre fil cfs_quota_us, spesifiserer tilgjengelig tid (kvote) i hver epoke. Merk at det også er spesifisert i mikrosekunder. Kvoten kan overstige epokelengden; med andre ord, den kan være større enn 100 ms.

La oss se på to scenarier på 16-kjernemaskiner (den vanligste typen datamaskin vi har hos Omio):

CPU-grenser og aggressiv struping i Kubernetes
Scenario 1: 2 tråder og en grense på 200 ms. Ingen struping

CPU-grenser og aggressiv struping i Kubernetes
Scenario 2: 10 tråder og 200 ms grense. Throttling begynner etter 20 ms, tilgang til prosessorressurser gjenopptas etter ytterligere 80 ms

La oss si at du setter CPU-grensen til 2 kjerner; Kubernetes vil oversette denne verdien til 200 ms. Dette betyr at beholderen kan bruke maksimalt 200ms CPU-tid uten struping.

Og det er her moroa begynner. Som nevnt ovenfor er den tilgjengelige kvoten 200 ms. Hvis du jobber parallelt ti tråder på en 12-kjerners maskin (se illustrasjon for scenario 2), mens alle andre pods er inaktive, vil kvoten være oppbrukt på bare 20 ms (siden 10 * 20 ms = 200 ms), og alle trådene i denne poden vil henge » (Gasspedal) for de neste 80 ms. De allerede nevnte planleggingsfeil, på grunn av dette oppstår overdreven struping og containeren ikke engang kan oppfylle den eksisterende kvoten.

Hvordan evaluere struping i pods?

Bare logg inn på poden og utfør cat /sys/fs/cgroup/cpu/cpu.stat.

  • nr_periods — det totale antallet planleggerperioder;
  • nr_throttled — antall strupede perioder i komposisjonen nr_periods;
  • throttled_time — kumulativ strupet tid i nanosekunder.

CPU-grenser og aggressiv struping i Kubernetes

Hva skjer egentlig?

Som et resultat får vi høy struping i alle applikasjoner. Noen ganger er han med en og en halv gang sterkere enn beregnet!

Dette fører til ulike feil - feil i beredskapssjekk, fryser containere, brudd på nettverkstilkoblingen, tidsavbrudd i serviceanrop. Dette resulterer til slutt i økt ventetid og høyere feilfrekvens.

Beslutning og konsekvenser

Alt er enkelt her. Vi forlot CPU-grensene og begynte å oppdatere OS-kjernen i klynger til den nyeste versjonen, der feilen ble fikset. Antall feil (HTTP 5xx) i tjenestene våre falt umiddelbart betydelig:

HTTP 5xx-feil

CPU-grenser og aggressiv struping i Kubernetes
HTTP 5xx-feil for én kritisk tjeneste

Responstid s95

CPU-grenser og aggressiv struping i Kubernetes
Kritisk tjenesteforespørselsforsinkelse, 95. persentil

Driftskostnader

CPU-grenser og aggressiv struping i Kubernetes
Antall forekomsttimer brukt

Hva er fangsten?

Som nevnt i begynnelsen av artikkelen:

En analogi kan trekkes med en felles leilighet... Kubernetes fungerer som eiendomsmegler. Men hvordan holde leietakere fra konflikter med hverandre? Hva om en av dem for eksempel bestemmer seg for å låne badet halve dagen?

Her er fangsten. Én uforsiktig beholder kan spise opp alle tilgjengelige CPU-ressurser på en maskin. Hvis du har en smart applikasjonsstabel (for eksempel JVM, Go, Node VM er riktig konfigurert), så er ikke dette et problem: du kan jobbe under slike forhold i lang tid. Men hvis applikasjoner er dårlig optimalisert eller ikke optimalisert i det hele tatt (FROM java:latest), kan situasjonen komme ut av kontroll. Hos Omio har vi automatiserte base Dockerfiler med tilstrekkelige standardinnstillinger for hovedspråkstakken, så dette problemet eksisterte ikke.

Vi anbefaler å overvåke beregningene BRUK (bruk, metning og feil), API-forsinkelser og feilrater. Sørg for at resultatene svarer til forventningene.

referanser

Dette er vår historie. Følgende materialer hjalp i stor grad til å forstå hva som skjedde:

Kubernetes feilrapporter:

Har du støtt på lignende problemer i praksisen din eller har du erfaring knyttet til struping i containeriserte produksjonsmiljøer? Del historien din i kommentarfeltet!

PS fra oversetter

Les også på bloggen vår:

Kilde: www.habr.com

Legg til en kommentar