CPU-limieten en agressieve beperking in Kubernetes

Opmerking. vert.: Deze opzienbarende geschiedenis van Omio, een Europese reisaggregator, neemt lezers mee van de basistheorie naar de fascinerende praktische complexiteit van de Kubernetes-configuratie. Bekendheid met dergelijke gevallen helpt niet alleen uw horizon te verbreden, maar ook niet-triviale problemen te voorkomen.

CPU-limieten en agressieve beperking in Kubernetes

Heeft u ooit een applicatie vastgelopen, niet meer gereageerd op gezondheidscontroles en niet kunnen achterhalen waarom? Een mogelijke verklaring houdt verband met de quotumlimieten voor CPU-bronnen. Dit is waar we het in dit artikel over zullen hebben.

TL; DR:
We raden ten zeerste aan om CPU-limieten in Kubernetes uit te schakelen (of CFS-quota in Kubelet uit te schakelen) als je een versie van de Linux-kernel gebruikt met een CFS-quotumbug. In de kern is beschikbaar serieus en bekend een bug die leidt tot overmatige beperking en vertragingen
.

In Omio de gehele infrastructuur wordt beheerd door Kubernetes. Al onze stateful en stateless workloads draaien uitsluitend op Kubernetes (we gebruiken Google Kubernetes Engine). De afgelopen zes maanden begonnen we willekeurige vertragingen waar te nemen. Applicaties lopen vast of reageren niet meer op statuscontroles, verliezen de verbinding met het netwerk, enz. Dit gedrag heeft ons lange tijd in verwarring gebracht en uiteindelijk besloten we het probleem serieus te nemen.

Samenvatting van het artikel:

  • Een paar woorden over containers en Kubernetes;
  • Hoe CPU-verzoeken en -limieten worden geïmplementeerd;
  • Hoe CPU-limiet werkt in multi-core-omgevingen;
  • Hoe CPU-throttling te volgen;
  • Probleemoplossing en nuances.

Een paar woorden over containers en Kubernetes

Kubernetes is in essentie de moderne standaard in de infrastructuurwereld. De hoofdtaak is containerorkestratie.

containers

In het verleden moesten we artefacten zoals Java JAR's/WAR's, Python Eggs of uitvoerbare bestanden maken om op servers te draaien. Om ze te laten functioneren moest er echter extra werk worden verricht: het installeren van de runtime-omgeving (Java/Python), het plaatsen van de benodigde bestanden op de juiste plaatsen, het zorgen voor compatibiliteit met een specifieke versie van het besturingssysteem, enz. Met andere woorden: er moest zorgvuldige aandacht worden besteed aan configuratiebeheer (wat vaak een bron van onenigheid was tussen ontwikkelaars en systeembeheerders).

Containers hebben alles veranderd. Het artefact is nu een containerimage. Het kan worden weergegeven als een soort uitgebreid uitvoerbaar bestand dat niet alleen het programma bevat, maar ook een volwaardige uitvoeringsomgeving (Java/Python/...), evenals de benodigde bestanden/pakketten, vooraf geïnstalleerd en gereed voor gebruik. loop. Containers kunnen zonder extra stappen op verschillende servers worden geïmplementeerd en uitgevoerd.

Daarnaast opereren containers in een eigen sandbox-omgeving. Ze hebben hun eigen virtuele netwerkadapter, hun eigen bestandssysteem met beperkte toegang, hun eigen hiërarchie van processen, hun eigen beperkingen op CPU en geheugen, enz. Dit alles wordt geïmplementeerd dankzij een speciaal subsysteem van de Linux-kernel: naamruimten.

Kubernetes

Zoals eerder vermeld is Kubernetes een containerorkestrator. Het werkt als volgt: je geeft het een verzameling machines en zegt dan: "Hé, Kubernetes, laten we tien exemplaren van mijn container lanceren met elk 2 processors en 3 GB geheugen, en ze draaiende houden!" Kubernetes zorgt voor de rest. Het vindt vrije capaciteit, lanceert containers en start ze indien nodig opnieuw op, rolt een update uit bij het wisselen van versie, enz. In wezen stelt Kubernetes u in staat de hardwarecomponent weg te abstraheren en maakt u een grote verscheidenheid aan systemen geschikt voor het implementeren en uitvoeren van applicaties.

CPU-limieten en agressieve beperking in Kubernetes
Kubernetes vanuit het perspectief van de leek

Wat zijn verzoeken en limieten in Kubernetes

Oké, we hebben containers en Kubernetes besproken. We weten ook dat er meerdere containers op dezelfde machine kunnen staan.

Er kan een analogie worden getrokken met een gemeenschappelijk appartement. Er wordt een ruim pand (machines/units) afgenomen en verhuurd aan meerdere huurders (containers). Kubernetes treedt op als makelaar. De vraag rijst: hoe kunnen huurders voor conflicten met elkaar worden behoed? Wat als een van hen bijvoorbeeld besluit de badkamer voor een halve dag te lenen?

Dit is waar verzoeken en limieten een rol spelen. CPU Aanvraag uitsluitend nodig voor planningsdoeleinden. Dit is zoiets als een ‘verlanglijstje’ van de container en wordt gebruikt om het meest geschikte knooppunt te selecteren. Tegelijkertijd de CPU Begrenzing kan worden vergeleken met een huurovereenkomst - zodra we een eenheid voor de container selecteren, wordt de kan niet voorbij de vastgestelde grenzen gaan. En dit is waar het probleem zich voordoet...

Hoe verzoeken en limieten worden geïmplementeerd in Kubernetes

Kubernetes gebruikt een throttling-mechanisme (klokcycli overslaan) dat in de kernel is ingebouwd om CPU-limieten te implementeren. Als een applicatie de limiet overschrijdt, wordt throttling ingeschakeld (dat wil zeggen dat deze minder CPU-cycli ontvangt). Verzoeken en limieten voor het geheugen zijn anders georganiseerd, zodat ze gemakkelijker te detecteren zijn. Om dit te doen, hoeft u alleen maar de laatste herstartstatus van de pod te controleren: of deze “OOMKilled” is. CPU-throttling is niet zo eenvoudig, omdat K8s alleen statistieken beschikbaar stelt op basis van gebruik, en niet op basis van cgroups.

CPU-verzoek

CPU-limieten en agressieve beperking in Kubernetes
Hoe het CPU-verzoek wordt geïmplementeerd

Laten we voor de eenvoud het proces bekijken met als voorbeeld een machine met een 4-core CPU.

K8s gebruikt een controlegroepmechanisme (cgroups) om de toewijzing van bronnen (geheugen en processor) te controleren. Hiervoor is een hiërarchisch model beschikbaar: het kind erft de grenzen van de bovenliggende groep. De distributiegegevens worden opgeslagen in een virtueel bestandssysteem (/sys/fs/cgroup). In het geval van een processor is dit wel het geval /sys/fs/cgroup/cpu,cpuacct/*.

K8s gebruikt bestand cpu.share om processorbronnen toe te wijzen. In ons geval krijgt de root-cgroup 4096 aandelen CPU-bronnen - 100% van het beschikbare processorvermogen (1 core = 1024; dit is een vaste waarde). De rootgroep verdeelt de middelen proportioneel, afhankelijk van de aandelen van de nakomelingen die erin zijn geregistreerd cpu.share, en zij doen op hun beurt hetzelfde met hun nakomelingen, enz. Op een typisch Kubernetes-knooppunt heeft de root-cgroup drie kinderen: system.slice, user.slice и kubepods. De eerste twee subgroepen worden gebruikt om bronnen te verdelen tussen kritieke systeembelastingen en gebruikersprogramma's buiten K8s. De laatste - kubepods – gemaakt door Kubernetes om bronnen tussen pods te verdelen.

Het diagram hierboven laat zien dat de eerste en tweede subgroep elk ontvingen 1024 aandelen, waarbij de Kuberpod-subgroep is toegewezen 4096 aandelen Hoe is dit mogelijk: de rootgroep heeft immers alleen toegang tot 4096 aandelen, en de som van de aandelen van haar nakomelingen overschrijdt dit aantal aanzienlijk (6144)? Het punt is dat de waarde logisch is, dus gebruikt de Linux-planner (CFS) deze om proportioneel CPU-bronnen toe te wijzen. In ons geval ontvangen de eerste twee groepen 680 echte aandelen (16,6% van 4096), en kubepod ontvangt de rest 2736 aandelen In geval van downtime zullen de eerste twee groepen de toegewezen middelen niet gebruiken.

Gelukkig heeft de planner een mechanisme om te voorkomen dat ongebruikte CPU-bronnen worden verspild. Het draagt ​​‘inactieve’ capaciteit over aan een mondiale pool, van waaruit het wordt gedistribueerd naar groepen die extra processorkracht nodig hebben (de overdracht vindt plaats in batches om afrondingsverliezen te voorkomen). Een soortgelijke methode wordt toegepast op alle nakomelingen van nakomelingen.

Dit mechanisme zorgt voor een eerlijke verdeling van de processorkracht en zorgt ervoor dat geen enkel proces bronnen van anderen ‘steelt’.

CPU-limiet

Ondanks het feit dat de configuraties van limieten en verzoeken in K8s op elkaar lijken, is hun implementatie radicaal anders: dit meest misleidend en het minst gedocumenteerde deel.

K8s grijpt in CFS-quotummechanisme grenzen te implementeren. Hun instellingen worden gespecificeerd in bestanden cfs_period_us и cfs_quota_us in de map cgroup (het bestand bevindt zich daar ook cpu.share).

Anders cpu.share, het quotum is gebaseerd op tijdsperiode, en niet op de beschikbare processorkracht. cfs_period_us specificeert de duur van de periode (epoch) - deze is altijd 100000 μs (100 ms). Er is een optie om deze waarde te wijzigen in K8s, maar deze is voorlopig alleen beschikbaar in alpha. De planner gebruikt het tijdperk om gebruikte quota's opnieuw te starten. Tweede bestand cfs_quota_us, specificeert de beschikbare tijd (quota) in elk tijdperk. Merk op dat dit ook in microseconden wordt gespecificeerd. Het quotum kan de duur van het tijdperk overschrijden; met andere woorden, het kan groter zijn dan 100 ms.

Laten we eens kijken naar twee scenario's op 16-core machines (het meest voorkomende type computer dat we bij Omio hebben):

CPU-limieten en agressieve beperking in Kubernetes
Scenario 1: 2 threads en een limiet van 200 ms. Geen throttling

CPU-limieten en agressieve beperking in Kubernetes
Scenario 2: 10 threads en limiet van 200 ms. De beperking begint na 20 ms, de toegang tot processorbronnen wordt na nog eens 80 ms hervat

Stel dat u de CPU-limiet instelt op 2 korrels; Kubernetes vertaalt deze waarde naar 200 ms. Dit betekent dat de container maximaal 200 ms CPU-tijd kan gebruiken zonder beperking.

En dit is waar het plezier begint. Zoals hierboven vermeld, is het beschikbare quotum 200 ms. Als je parallel werkt tien threads op een 12-core machine (zie illustratie voor scenario 2), terwijl alle andere pods inactief zijn, zal het quotum binnen slechts 20 ms opgebruikt zijn (aangezien 10 * 20 ms = 200 ms), en zullen alle threads van deze pod vastlopen » (gas geven) voor de volgende 80 ms. De reeds genoemde planner-bug, waardoor overmatige throttling optreedt en de container niet eens aan het bestaande quotum kan voldoen.

Hoe kan ik de beperking in pods evalueren?

Log gewoon in op de pod en voer het uit cat /sys/fs/cgroup/cpu/cpu.stat.

  • nr_periods — het totale aantal planningsperioden;
  • nr_throttled — aantal gesmoorde perioden in de compositie nr_periods;
  • throttled_time — cumulatieve gesmoorde tijd in nanoseconden.

CPU-limieten en agressieve beperking in Kubernetes

Wat is er echt aan de hand?

Als gevolg hiervan krijgen we hoge throttling in alle toepassingen. Soms is hij binnen anderhalf keer sterker dan berekend!

Dit leidt tot verschillende fouten: fouten in de gereedheidscontrole, het vastlopen van containers, onderbrekingen van de netwerkverbinding, time-outs bij servicebezoeken. Dit resulteert uiteindelijk in een hogere latentie en hogere foutenpercentages.

Beslissing en gevolgen

Alles is hier eenvoudig. We hebben de CPU-limieten opgegeven en zijn begonnen met het updaten van de OS-kernel in clusters naar de nieuwste versie, waarin de bug is verholpen. Het aantal fouten (HTTP 5xx) in onze dienstverlening daalde meteen flink:

HTTP 5xx-fouten

CPU-limieten en agressieve beperking in Kubernetes
HTTP 5xx-fouten voor één kritieke service

Reactietijd p95

CPU-limieten en agressieve beperking in Kubernetes
Latentie van kritieke serviceaanvragen, 95e percentiel

Operatie kosten

CPU-limieten en agressieve beperking in Kubernetes
Aantal bestede instantie-uren

Wat is de vangst?

Zoals aan het begin van het artikel vermeld:

Er kan een analogie worden getrokken met een gemeenschappelijk appartement... Kubernetes treedt op als makelaar. Maar hoe voorkom je dat huurders met elkaar in conflict komen? Wat als een van hen bijvoorbeeld besluit de badkamer voor een halve dag te lenen?

Hier is de vangst. Eén onzorgvuldige container kan alle beschikbare CPU-bronnen op een machine opslokken. Als je een slimme applicatiestack hebt (JVM, Go, Node VM zijn bijvoorbeeld goed geconfigureerd), dan is dit geen probleem: je kunt lang in dergelijke omstandigheden werken. Maar als applicaties slecht of helemaal niet geoptimaliseerd zijn (FROM java:latest), kan de situatie uit de hand lopen. Bij Omio hebben we geautomatiseerde basis Dockerfiles met adequate standaardinstellingen voor de belangrijkste talenstapel, dus dit probleem bestond niet.

We raden u aan de statistieken te monitoren GEBRUIK (gebruik, verzadiging en fouten), API-vertragingen en foutpercentages. Zorg ervoor dat de resultaten aan de verwachtingen voldoen.

referenties

Dit is ons verhaal. De volgende materialen hebben enorm geholpen om te begrijpen wat er gebeurde:

Kubernetes-bugrapporten:

Bent u soortgelijke problemen tegengekomen in uw praktijk of heeft u ervaring met throttling in gecontaineriseerde productieomgevingen? Deel jouw verhaal in de reacties!

PS van vertaler

Lees ook op onze blog:

Bron: www.habr.com

Voeg een reactie