Negen prestatietips voor Kubernetes

Negen prestatietips voor Kubernetes

Dag Allemaal! Mijn naam is Oleg Sidorenkov, ik werk bij DomClick als teamleider infrastructuur. We gebruiken de Cube al meer dan drie jaar voor de verkoop en in deze tijd hebben we er veel verschillende interessante momenten mee beleefd. Vandaag vertel ik je hoe je met de juiste aanpak nog meer performance uit vanilla Kubernetes kunt persen voor jouw cluster. Klaar, af!

U weet allemaal heel goed dat Kubernetes een schaalbaar open source-systeem is voor containerorkestratie; nou ja, of 5 binaire bestanden die magie doen door de levenscyclus van uw microservices in een serveromgeving te beheren. Bovendien is dit een redelijk flexibele tool die kan worden samengesteld als een Lego-constructor voor maximale aanpassing voor verschillende taken.

En alles lijkt in orde te zijn: gooi servers in het cluster, zoals brandhout in een vuurhaard, en weet geen verdriet. Maar als je voor het milieu bent, dan zul je denken: “Hoe kan ik het vuur in de kachel houden en spijt hebben van het bos?”. Met andere woorden, hoe u manieren kunt vinden om de infrastructuur te verbeteren en de kosten te verlagen.

1. Houd team- en toepassingsbronnen bij

Negen prestatietips voor Kubernetes

Een van de meest banale maar effectieve methodes is het invoeren van verzoeken/limieten. Scheid applicaties op naamruimten en naamruimten op ontwikkelteams. Stel de applicatie in voordat u waarden implementeert voor het verbruik van processortijd, geheugen, kortstondige opslag.

resources:
   requests:
     memory: 2Gi
     cpu: 250m
   limits:
     memory: 4Gi
     cpu: 500m

Uit ervaring kwamen we tot de conclusie: het is niet de moeite waard om verzoeken van limieten meer dan twee keer op te blazen. De clustergrootte wordt berekend op basis van verzoeken, en als u de toepassing instelt op een verschil in bronnen, bijvoorbeeld 5-10 keer, stel u dan voor wat er met uw knooppunt zal gebeuren als het gevuld is met pods en plotseling wordt belast. Niets goeds. Neem op zijn minst throttling en maximaal afscheid van de werker en zorg voor een cyclische belasting van de rest van de knooppunten nadat de pods beginnen te bewegen.

Bovendien met de hulp limitranges u kunt aan het begin resourcewaarden voor de container instellen - minimum, maximum en standaard:

➜  ~ kubectl describe limitranges --namespace ops
Name:       limit-range
Namespace:  ops
Type        Resource           Min   Max   Default Request  Default Limit  Max Limit/Request Ratio
----        --------           ---   ---   ---------------  -------------  -----------------------
Container   cpu                50m   10    100m             100m           2
Container   ephemeral-storage  12Mi  8Gi   128Mi            4Gi            -
Container   memory             64Mi  40Gi  128Mi            128Mi          2

Vergeet niet om de naamruimtebronnen te beperken, zodat één opdracht niet alle bronnen van het cluster kan gebruiken:

➜  ~ kubectl describe resourcequotas --namespace ops
Name:                   resource-quota
Namespace:              ops
Resource                Used          Hard
--------                ----          ----
limits.cpu              77250m        80
limits.memory           124814367488  150Gi
pods                    31            45
requests.cpu            53850m        80
requests.memory         75613234944   150Gi
services                26            50
services.loadbalancers  0             0
services.nodeports      0             0

Zoals je kunt zien aan de beschrijving resourcequotas, als de opdracht ops pods wil inzetten die nog eens 10 cpu verbruiken, dan staat de planner dit niet toe en geeft een foutmelding:

Error creating: pods "nginx-proxy-9967d8d78-nh4fs" is forbidden: exceeded quota: resource-quota, requested: limits.cpu=5,requests.cpu=5, used: limits.cpu=77250m,requests.cpu=53850m, limited: limits.cpu=10,requests.cpu=10

Om een ​​soortgelijk probleem op te lossen, kunt u bijvoorbeeld een tool schrijven als deze, die de status van opdrachtresources kan opslaan en vastleggen.

2. Kies de beste bestandsopslag

Negen prestatietips voor Kubernetes

Hier wil ik het hebben over persistente volumes en het schijfsubsysteem van Kubernetes-werkknooppunten. Ik hoop dat niemand de "Cube" op de HDD in productie gebruikt, maar soms is zelfs een gewone SSD al niet genoeg. We hadden zo'n probleem dat de logboeken de schijf doodden door I / O-bewerkingen, en er zijn hier niet veel oplossingen:

  • Gebruik krachtige SSD's of stap over op NVMe (als u uw eigen hardware beheert).

  • Verlaag het logboekniveau.

  • Doe "slim" balanceren van pods die de schijf verkrachten (podAntiAffinity).

De bovenstaande schermafbeelding laat zien wat er gebeurt onder nginx-ingress-controller met een schijf wanneer loggen van access_logs is ingeschakeld (~ 12k logs/sec). Een dergelijke toestand kan natuurlijk leiden tot degradatie van alle applicaties op dit knooppunt.

Wat betreft PV, helaas, ik heb niet alles geprobeerd. types Aanhoudende volumes. Gebruik de beste optie die bij u past. Historisch gezien is het in ons land gebeurd dat een klein deel van de services RWX-volumes nodig heeft, en lang geleden begonnen ze NFS-opslag voor deze taak te gebruiken. Goedkoop en ... genoeg. Natuurlijk aten we stront met hem - wees gezond, maar we hebben geleerd hoe we hem moeten afstemmen, en zijn hoofd doet geen pijn meer. En schakel indien mogelijk over naar S3-objectopslag.

3. Bouw geoptimaliseerde afbeeldingen

Negen prestatietips voor Kubernetes

Het is het beste om voor containers geoptimaliseerde afbeeldingen te gebruiken, zodat Kubernetes ze sneller kan ophalen en efficiënter kan uitvoeren. 

Optimalisatie betekent dat afbeeldingen:

  • slechts één applicatie bevatten of slechts één functie uitvoeren;

  • klein formaat, omdat grote afbeeldingen slechter over het netwerk worden verzonden;

  • eindpunten voor gezondheid en paraatheid hebben die Kubernetes kan gebruiken om actie te ondernemen in het geval van downtime;

  • gebruik containervriendelijke besturingssystemen (zoals Alpine of CoreOS) die beter bestand zijn tegen configuratiefouten;

  • gebruik multi-stage builds zodat u alleen gecompileerde applicaties kunt implementeren en niet de bijbehorende bronnen.

Er zijn veel tools en services waarmee u afbeeldingen direct kunt controleren en optimaliseren. Het is belangrijk om ze altijd up-to-date en veilig te houden. Als resultaat krijg je:

  1. Verminderde netwerkbelasting op het hele cluster.

  2. Verkorte opstarttijd van de container.

  3. Kleinere omvang van uw gehele Docker-register.

4. Gebruik een DNS-cache

Negen prestatietips voor Kubernetes

Als we het hebben over hoge belastingen, dan is het leven behoorlijk belabberd zonder het DNS-systeem van het cluster af te stemmen. Ooit ondersteunden de Kubernetes-ontwikkelaars hun kube-dns-oplossing. Het werd ook in ons land geïmplementeerd, maar deze software stemde niet bijzonder af en leverde niet de vereiste prestaties, hoewel de taak schijnbaar eenvoudig is. Toen verscheen coredns, waarop we overstapten en geen verdriet kenden, later werd het de standaard DNS-service in K8s. Op een gegeven moment groeiden we tot 40 rps naar het DNS-systeem, en ook deze oplossing was niet genoeg. Maar door een gelukkige kans kwam Nodelocaldns uit, oftewel node local cache, oftewel NodeLokale DNSCache.

Waarom gebruiken we het? Er zit een bug in de Linux-kernel die, wanneer meerdere toegangen via conntrack NAT via UDP, leidt tot een raceconditie voor het schrijven naar de conntrack-tabellen, en een deel van het verkeer via NAT gaat verloren (elke reis door de Service is NAT). Nodelocaldns lost dit probleem op door NAT te verwijderen en te upgraden naar TCP-connectiviteit naar upstream DNS, en door upstream DNS-query's lokaal te cachen (inclusief een korte negatieve cache van 5 seconden).

5. Schaal pods automatisch horizontaal en verticaal

Negen prestatietips voor Kubernetes

Kunt u met vertrouwen zeggen dat al uw microservices klaar zijn voor een twee- tot drievoudige toename van de belasting? Hoe wijs je resources op de juiste manier toe aan je applicaties? Het kan overbodig zijn om een ​​aantal pods boven de werklast te laten draaien, en als u ze rug aan rug houdt, bestaat het risico dat er downtime optreedt door een plotselinge toename van het verkeer naar de service. De gulden middenweg helpt om de betovering van vermenigvuldiging te bereiken, zoals diensten Horizontale pod-autoscaler и Verticale Pod Autoscaler.

VPA stelt u in staat om automatisch de verzoeken/limieten van uw containers in een pod te verhogen op basis van daadwerkelijk gebruik. Hoe kan het nuttig zijn? Als je pods hebt die om de een of andere reden niet horizontaal kunnen worden uitgeschaald (wat niet helemaal betrouwbaar is), dan kun je proberen VPA te vertrouwen om zijn bronnen te wijzigen. De functie is een aanbevelingssysteem gebaseerd op historische en actuele gegevens van de metrische server, dus als u verzoeken/limieten niet automatisch wilt wijzigen, kunt u eenvoudig de aanbevolen bronnen voor uw containers controleren en de instellingen optimaliseren om CPU en geheugen te besparen in het cluster.

Negen prestatietips voor KubernetesAfbeelding afkomstig van https://levelup.gitconnected.com/kubernetes-autoscaling-101-cluster-autoscaler-horizontal-pod-autoscaler-and-vertical-pod-2a441d9ad231

De planner in Kubernetes is altijd gebaseerd op verzoeken. Welke waarde u daar ook plaatst, de planner zal op basis daarvan op zoek gaan naar een geschikt knooppunt. De limietwaarde is nodig voor de kublet om te weten wanneer een pod moet worden gesmoord of gedood. En aangezien de enige belangrijke parameter de verzoekwaarde is, zal VPA ermee werken. Telkens wanneer u uw toepassing verticaal schaalt, definieert u wat verzoeken moeten zijn. En wat gebeurt er dan met limieten? Deze parameter wordt ook proportioneel geschaald.

Dit zijn bijvoorbeeld de typische pod-instellingen:

resources:
   requests:
     memory: 250Mi
     cpu: 200m
   limits:
     memory: 500Mi
     cpu: 350m

De aanbevelingsengine bepaalt dat uw toepassing 300 miljoen CPU en 500 Mi nodig heeft om correct te werken. Je krijgt deze instellingen:

resources:
   requests:
     memory: 500Mi
     cpu: 300m
   limits:
     memory: 1000Mi
     cpu: 525m

Zoals hierboven vermeld, is dit proportioneel schalen op basis van de verhouding verzoeken/limieten in het manifest:

  • CPU: 200m → 300m: verhouding 1:1.75;

  • Geheugen: 250Mi → 500Mi: verhouding 1:2.

ten opzichte van HPA, dan is het werkingsmechanisme transparanter. Er worden drempelwaarden ingesteld voor metrische gegevens zoals processor en geheugen, en als het gemiddelde van alle replica's de drempelwaarde overschrijdt, wordt de toepassing geschaald met +1 pod totdat de waarde onder de drempelwaarde valt of totdat het maximale aantal replica's is bereikt.

Negen prestatietips voor KubernetesAfbeelding afkomstig van https://levelup.gitconnected.com/kubernetes-autoscaling-101-cluster-autoscaler-horizontal-pod-autoscaler-and-vertical-pod-2a441d9ad231

Naast de gebruikelijke statistieken zoals CPU en geheugen, kunt u drempels instellen voor uw aangepaste Prometheus-statistieken en ermee werken als u denkt dat dit de meest nauwkeurige manier is om te bepalen wanneer uw toepassing moet worden geschaald. Zodra de toepassing zich onder de opgegeven metrische drempelwaarde heeft gestabiliseerd, begint HPA met het schalen van de pods tot het minimale aantal replica's of totdat de belasting de opgegeven drempelwaarde bereikt.

6. Vergeet Node Affinity en Pod Affinity niet

Negen prestatietips voor Kubernetes

Niet alle knooppunten draaien op dezelfde hardware en niet alle pods hoeven rekenintensieve toepassingen uit te voeren. Met Kubernetes kunt u de specialisatie van knooppunten en pods specificeren met behulp van Knoop Affiniteit и Pod-affiniteit.

Als u knooppunten hebt die geschikt zijn voor rekenintensieve bewerkingen, is het voor maximale efficiëntie beter om toepassingen aan de juiste knooppunten te binden. Gebruik hiervoor nodeSelector met knooppuntlabel.

Laten we zeggen dat je twee knooppunten hebt: een met CPUType=HIGHFREQ en een groot aantal snelle kernen, een andere met MemoryType=HIGHMEMORY meer geheugen en snellere prestaties. De eenvoudigste manier is om een ​​pod-implementatie toe te wijzen aan een knooppunt HIGHFREQdoor toe te voegen aan de sectie spec een selector als deze:

…
nodeSelector:
	CPUType: HIGHFREQ

Een duurdere en specifiekere manier om dit te doen, is door te gebruiken nodeAffinity in het veld affinity sectie spec. Er zijn twee opties:

  • requiredDuringSchedulingIgnoredDuringExecution: harde instelling (planner zal alleen pods inzetten op specifieke knooppunten (en nergens anders));

  • preferredDuringSchedulingIgnoredDuringExecution: zachte instelling (de planner probeert te implementeren op specifieke knooppunten en als dit mislukt, probeert het te implementeren op het volgende beschikbare knooppunt).

U kunt een specifieke syntaxis opgeven voor het beheren van knooppuntlabels, bijvoorbeeld In, NotIn, Exists, DoesNotExist, Gt of Lt. Onthoud echter dat complexe methoden in lange lijsten met labels de besluitvorming in kritieke situaties vertragen. Met andere woorden, maak het niet te ingewikkeld.

Zoals hierboven vermeld, kunt u met Kubernetes de binding van huidige pods instellen. Dat wil zeggen, u kunt ervoor zorgen dat bepaalde pods samenwerken met andere pods in dezelfde beschikbaarheidszone (relevant voor clouds) of knooppunten.

В podAffinity поля affinity sectie spec dezelfde velden zijn beschikbaar als in het geval van nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution и preferredDuringSchedulingIgnoredDuringExecution. Het enige verschil is dat matchExpressions zal pods binden aan een knooppunt waarop al een pod met dat label wordt uitgevoerd.

Meer Kubernetes biedt een veld podAntiAffinity, die daarentegen geen pod bindt aan een knooppunt met specifieke pods.

Over uitdrukkingen nodeAffinity Hetzelfde advies kan worden gegeven: probeer de regels eenvoudig en logisch te houden, probeer de pod-specificatie niet te overladen met een complexe set regels. Het is heel eenvoudig om een ​​regel te maken die niet overeenkomt met de voorwaarden van het cluster, waardoor de planner extra wordt belast en de algehele prestaties verslechteren.

7. Taints en toleranties

Er is een andere manier om de planner te beheren. Als u een groot cluster heeft met honderden knooppunten en duizenden microservices, is het erg moeilijk om te voorkomen dat bepaalde pods door bepaalde knooppunten worden gehost.

Het mechanisme van taints - regels verbieden - helpt daarbij. U kunt bijvoorbeeld voorkomen dat bepaalde knooppunten pods uitvoeren in bepaalde scenario's. Gebruik de optie om taint toe te passen op een specifiek knooppunt taint in kubectl. Geef sleutel en waarde op en bederf vervolgens NoSchedule of NoExecute:

$ kubectl taint nodes node10 node-role.kubernetes.io/ingress=true:NoSchedule

Het is ook vermeldenswaard dat het taint-mechanisme drie hoofdeffecten ondersteunt: NoSchedule, NoExecute и PreferNoSchedule.

  • NoSchedule betekent dat totdat er een corresponderende vermelding is in de podspecificatie tolerations, kan het niet worden geïmplementeerd op het knooppunt (in dit voorbeeld node10).

  • PreferNoSchedule - vereenvoudigde versie NoSchedule. In dit geval zal de planner proberen geen pods toe te wijzen die geen overeenkomende invoer hebben. tolerations per knooppunt, maar dit is geen harde limiet. Als er geen resources in het cluster zijn, worden de pods op dit knooppunt geïmplementeerd.

  • NoExecute - dit effect activeert een onmiddellijke evacuatie van pods die geen overeenkomende ingang hebben tolerations.

Vreemd genoeg kan dit gedrag ongedaan worden gemaakt met behulp van het tolerantiemechanisme. Dit is handig wanneer er een "verboden" knooppunt is en u er alleen infrastructuurdiensten op hoeft te plaatsen. Hoe je dat doet? Sta alleen die peulen toe waarvoor een geschikte tolerantie bestaat.

Dit is hoe de pod-specificatie eruit zou zien:

spec:
   tolerations:
     - key: "node-role.kubernetes.io/ingress"
        operator: "Equal"
        value: "true"
        effect: "NoSchedule"

Dit betekent niet dat tijdens de volgende herimplementatie de pod precies dit knooppunt zal raken, dit is niet het Node Affinity-mechanisme en nodeSelector. Maar door verschillende functies te combineren, kunt u een zeer flexibele planningsconfiguratie bereiken.

8. Stel de prioriteit van Pod-implementatie in

Het feit dat u pod-naar-node-bindingen hebt geconfigureerd, betekent niet dat alle pods met dezelfde prioriteit moeten worden behandeld. U wilt bijvoorbeeld sommige pods eerder implementeren dan andere.

Kubernetes biedt verschillende manieren om Pod Priority en Preemption in te stellen. De instelling bestaat uit verschillende onderdelen: object PriorityClass en veldbeschrijvingen priorityClassName in de podspecificatie. Overweeg een voorbeeld:

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 99999
globalDefault: false
description: "This priority class should be used for very important pods only"

We creëren PriorityClass, geef het een naam, beschrijving en waarde. Hoe hoger value, hoe hoger de prioriteit. De waarde kan elk 32-bits geheel getal zijn dat kleiner is dan of gelijk is aan 1. Hogere waarden zijn gereserveerd voor bedrijfskritische systeempods, die doorgaans niet kunnen worden verwijderd. De ontruiming vindt alleen plaats als de pod met hoge prioriteit nergens heen kan, waarna enkele pods van een bepaald knooppunt worden geëvacueerd. Als dit mechanisme te rigide voor u is, kunt u de optie toevoegen preemptionPolicy: Never, en dan is er geen voorrang, de pod zal de eerste in de wachtrij zijn en wachten tot de planner er vrije middelen voor vindt.

Vervolgens maken we een pod, waarin we de naam specificeren priorityClassName:

apiVersion: v1
kind: Pod
metadata:
  name: static-web
  labels:
    role: myrole
 spec:
  containers:
    - name: web
      image: nginx
      ports:
        - name: web
          containerPort: 80
          protocol: TCP
  priorityClassName: high-priority
          

Je kunt zoveel prioriteitsklassen maken als je wilt, hoewel het wordt aanbevolen om je hier niet mee te laten meeslepen (beperk jezelf bijvoorbeeld tot lage, gemiddelde en hoge prioriteit).

Zo kunt u, indien nodig, de efficiëntie van het inzetten van kritieke services verhogen, zoals nginx-ingress-controller, coredns, enz.

9. Optimaliseer uw ETCD-cluster

Negen prestatietips voor Kubernetes

ETCD kan het brein van het hele cluster worden genoemd. Het is erg belangrijk om de werking van deze database op een hoog niveau te houden, aangezien de snelheid van bewerkingen in de "Cube" ervan afhangt. Een redelijk standaard en tegelijkertijd goede oplossing zou zijn om een ​​ETCD-cluster op de hoofdknooppunten te houden om een ​​minimale vertraging naar kube-apiserver te hebben. Als dit niet mogelijk is, plaats de ETCD dan zo dicht mogelijk bij elkaar, met een goede bandbreedte tussen de deelnemers. Let ook op hoeveel knooppunten van ETCD eruit kunnen vallen zonder schade toe te brengen aan het cluster.

Negen prestatietips voor Kubernetes

Houd er rekening mee dat een buitensporige toename van het aantal deelnemers in het cluster de fouttolerantie kan verhogen ten koste van de prestaties, alles moet met mate gebeuren.

Als we het hebben over het opzetten van de service, dan zijn er enkele aanbevelingen:

  1. Zorg voor goede hardware, gebaseerd op de grootte van het cluster (u kunt lezen hier).

  2. Tweak een paar parameters als je een cluster hebt verspreid over een paar DC's of als je netwerk en schijven veel te wensen overlaten (je kunt lezen hier).

Conclusie

Dit artikel beschrijft de punten waar ons team zich aan probeert te houden. Dit is geen stapsgewijze beschrijving van acties, maar opties die handig kunnen zijn om de overhead van een cluster te optimaliseren. Het is duidelijk dat elk cluster uniek is op zijn eigen manier, en afstemmingsoplossingen kunnen sterk variëren, dus het zou interessant zijn om feedback van u te krijgen: hoe monitort u uw Kubernetes-cluster, hoe verbetert u de prestaties. Deel uw ervaring in de opmerkingen, het zal interessant zijn om het te weten.

Bron: www.habr.com