Het is een goed gebruik om je pods te voorzien van CPU en memory limieten. Dit beschermt je cluster tegen vervelende situaties. Ik beschrijf wat situaties en uitdagingen waarmee je te maken kan krijgen als je de boel gaat beperken.
Limieten
Allereerst even een korte uitleg over de waardes die je kan instellen. Je hebt requests en limits. Bij de requests kan je een minimale beschikbare resource waarde opgeven. De Kubernetes scheduler plaatst de pod dan op een node waar dat beschikbaar is. “Limits” daarentegen beschrijven het maximale dat door de pod gebruikt mag worden.
De eenheden waarin de resources memory en cpu te definiëren zijn, is in mili CPU. 100 mili CPU is gelijk aan 0,1 cpu. 1 CPU in Kubernetes termen is gelijk aan 1 vCPU in Amazon, 1 vCore in Azure of een hyperthread in de fysieke wereld. Het resourcegebruik kan je op verschillende manieren instellen:
Gegarandeerd ( request == limit )
Dit is het meest Er vindt geen “overcommit” plaats. Dit zorgt er het snelst voor dat nieuw gelanceerde pods “pending” blijven staan aangezien de kans bestaat dat CPU/Memory wel geclaimd moeten kunnen worden. In sommige situaties kan het aantal “pending” pods ook een trigger zijn om horizontaal Kubernetes nodes op te schalen. Het ligt helemaal aan je use case of het verstandig is om op deze manier zo hard resources te claimen. Denk er goed over na.
Burst / Pieken ( request < limit )
De pods worden geplaatst op nodes waar een bepaalde “requested resource” aan CPU/Memory aanwezig is. De pod heeft echter de mogelijkheid om meer te gebruiken (tot zijn limit is bereikt). Dit is natuurlijk een iets agressievere manier van werken. Doordat de pods meer mogen gebruiken dan wat er in het request-deel staat, bestaat de kans dat je problemen krijgt met “overcommitten”. Als je goed weet waar de piekmomenten zitten hoeft het uiteraard geen probleem te zijn.
Geen limieten
Dit kan natuurlijk ook nog. loop je natuurlijk het meeste risico. Als je weet wat er gebeurt in je software kan dat uiteraard nog steeds een goed overwogen keuze zijn.
Testen met een “forkbomb”
In theorie zou je met resource limieten helemaal niets moeten merken op de Kubernetes node dat het resource gebruik binnen containers uit de hand loopt. Het is tijd voor een test om te kijken wat daar van waar is. Doe deze test uiteraard niet in omgeving die productie draait! Voor deze test ga ik er vanuit dat je metrics verzamelt van de cluster nodes en dat je deze kan visualiseren in bijvoorbeeld Grafana.
Maak een pod op basis van de volgende yaml file:
Open vervolgens een shell in de container: “kubectl -n default exec -it ubuntu bash” . Het is nu tijd om in de container shell een “forkbomb” te starten. Als je hierover gaat googlen kom je al vrij snel op een hele compacte versie uit. Voer alleen het volgende commando uit: ” :(){ :|: & };: “
Bekijk het actuele resource gebruik van de container met “kubectl top pods”.

Al snel valt het op dat alle ingestelde limits ook daadwerkelijk gehaald worden zonder het gebruik hoger wordt. De overlast voor de node waarop de container draait lijkt dus beperkt. Als je vervolgens in Grafana kijkt naar de waardes van de grafieken zie je dat het op node niveau wel degelijk effect heeft:




Waar een “forkbomb” er zonder bescherming voor zorgt dat de hele node vast loopt zie je in dit geval dat je shell in de container niet meer zo soepel reageert en uiteindelijk ook vastloopt. Op node niveau zie je enorme pieken verschijnen in de grafieken. Binnen de gestelde limieten van de CPU ontstaat een wachtrij. De load grafiek schiet omhoog. In het specifieke geval was het wel degelijk te merken op node niveau dat er iets niet helemaal goed ging. Dankzij de limieten was het echter wel mogelijk om de situatie te herstellen zonder dat de specifieke node herstart/gereset moest worden.
Bijwerkingen van resource limieten
Als je gebruik gaat maken van resource limieten krijg je soms met bijwerkingen te maken die in eerste instantie lastig te verklaren en op te lossen lijken te zijn. In een specifieke case waar ik mee bezig ben geweest was dit het geval. De opstelling bestond uit Traefik als Ingress controller met daarachter een aantal microservices geschreven in .NET-core die weer request op data sources buiten Kubernetes deden om hun antwoord vervolgens weer aan de client terug te sturen. Zonder resource limieten ging het goed maar met resource limieten (hoe hoog ook) begonnnen de problemen. Doordat de hele keten informatie over het request doorstuurt naar Jaeger heb ik het mooi kunnen visualiseren.
Elke bolletje vertegenwoordigt een request dat op de applicatie is afgevuurd. De reactietijden zijn duidelijk zichtbaar en het is duidelijk zichtbaar dat er veel variatie in zit. Na wat onderzoek bleek dat de “Completely Fair Scheduler” ofwel CFS van de generic linux kernel te traag is met “context switching”. Dit zorgt voor latency (en dus de rare uitschieters in de grafiek. In eerste instantie probeerde ik of ik met sysctl configuratieopties hier wat winst mee kon behalen maar ik realiseerde me dat ik me hiermee op glad ijs begeef. Het was voor mij onduidelijk of het behaalde voordeel met die aanpassingen misschien ergens anders een groot nadeel zouden vormen.
nadat ik me inmiddels een tijd afvroeg hoe ik de latency issues in de CPU scheduler binnen de kernel zou kunnen oplossen kwam het plotseling in me op om eens te kijken naar de “low latency” kernel. Ik ging op zoek op internet maar audio/video waren zo ongeveer de enige specifieke use cases die ik tegen kwam. Kubernetes gerelateerde zaken stonden er niet tussen. Uiteindelijk heb ik toch de stoute schoenen aangetrokken en de “low latency” kernel geïnstalleerd. Het was immers een testcluster!
In dit specifieke geval waren de resultaten erg goed. Nadat alle cluster nodes opnieuw opgestart waren met de andere kernel heb ik de requests opnieuw laten uitvoeren:
Al snel is in Jaeger te zien dat de verticale as een hele ander bereik heeft. Daarnaast is de baseline in de grafiek een stuk lager en veel meer requests bevinden zich op die lijn. Dat is een significant verschil met de generic kernel!
tot slot
Resource limieten zijn verstandig maar het blijkt niet altijd even makkelijk om met zo’n toch stabiele en goede resultaten te behalen. Het is echter wel de moeite waard om toch die stap extra te zetten. Zoals je kan zien aan de resultaten in Jaeger, betaalt het zich ook uit. Daarnaast bescherm je je cluster. het is vervelend dat door een fout in je software je pod crasht maar tegelijkertijd is het fijn dat de invloed van zo’n pod op het cluster enigszins beperkt blijft.
Met betrekking tot de “low latency” kernel: De eerste resultaten zijn veelbelovend maar ga er niet van uit dat dit per definitie de oplossing is voor jouw probleem. In het specifieke geval waarin ik de test uitvoer gaat het om een multithreaded applicatie. Daarbij kan het handig zijn dat de kernel sneller van context kan switchen. Natuurlijk kan dezelfde kernel in een andere situatie een nadeel zijn. Meten is weten en alles valt en staat bij het goed testen van alle componenten samen.
Auteur: Geurt Hakfoort – Cloud Specialist Denit