Nio Kubernetes prestandatips

Nio Kubernetes prestandatips

Hej alla! Jag heter Oleg Sidorenkov, jag arbetar på DomClick som chef för infrastrukturteamet. Vi har använt Kubik i produktionen i mer än tre år, och under den här tiden har vi upplevt många olika intressanta ögonblick med den. Idag ska jag berätta hur du, med rätt tillvägagångssätt, kan pressa ut ännu mer prestanda ur vanilj Kubernetes för ditt kluster. Klara färdiga gå!

Ni vet alla mycket väl att Kubernetes är ett skalbart open source -system för containerorkestrering; Tja, eller 5 binärer som arbetar magi genom att hantera livscykeln för dina mikroservices i en servermiljö. Dessutom är det ett ganska flexibelt verktyg som kan monteras som LEGO för maximal anpassning för olika uppgifter.

Och allt verkar vara bra: kasta servrar i klustret som ved i en eldstad, och du kommer inte att känna någon sorg. Men om du är för miljön kommer du att tänka: "Hur kan jag hålla elden brinnande och skona skogen?" Med andra ord, hur man hittar sätt att förbättra infrastrukturen och minska kostnaderna.

1. Övervaka team- och applikationsresurser

Nio Kubernetes prestandatips

En av de vanligaste men effektiva metoderna är införandet av förfrågningar/begränsningar. Dela upp applikationer efter namnutrymmen och namnområden efter utvecklingsteam. Innan distribution, ställ in applikationsvärdena för förbrukning av processortid, minne och tillfällig lagring.

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

Genom erfarenhet kom vi till slutsatsen: du bör inte blåsa upp förfrågningar från gränserna med mer än två gånger. Klustrets volym beräknas utifrån förfrågningar, och om du ger applikationer en skillnad i resurser, till exempel 5-10 gånger, föreställ dig då vad som kommer att hända med din nod när den fylls med pods och plötsligt får belastning. Inget bra. Som ett minimum, strypande och högst, kommer du att säga adjö till arbetaren och få en cyklisk belastning på de återstående noderna efter att baljorna börjar röra sig.

Dessutom med hjälp limitranges I början kan du ställa in resursvärden för behållaren - minimum, maximum och standard:

➜  ~ 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

Glöm inte att begränsa namnutrymmesresurser så att ett team inte kan ta över alla resurser i klustret:

➜  ~ 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

Som framgår av beskrivningen resourcequotas, om ops-teamet vill distribuera poddar som kommer att förbruka ytterligare 10 cpu, kommer schemaläggaren inte att tillåta detta och kommer att ge ett felmeddelande:

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

För att lösa ett sådant problem kan du skriva ett verktyg, till exempel, som detta, kunna lagra och bestämma tillståndet för kommandoresurser.

2. Välj optimal fillagring

Nio Kubernetes prestandatips

Här skulle jag vilja beröra ämnet beständiga volymer och diskundersystemet för Kubernetes-arbetsnoder. Jag hoppas att ingen använder "Kuben" på en hårddisk i produktionen, men ibland räcker det inte längre med en vanlig SSD. Vi stötte på ett problem där loggarna dödade disken på grund av I/O-operationer, och det finns inte många lösningar:

  • Använd högpresterande SSD:er eller byt till NVMe (om du hanterar din egen hårdvara).

  • Minska loggningsnivån.

  • Gör "smart" balansering av baljor som våldtar disken (podAntiAffinity).

Skärmen ovan visar vad som händer under nginx-ingress-controller på disken när access_logs-loggning är aktiverad (~12 tusen loggar/sek). Detta tillstånd kan naturligtvis leda till försämring av alla applikationer på denna nod.

När det gäller PV, tyvärr, jag har inte provat allt typer Beständiga volymer. Använd det bästa alternativet som passar dig. Historiskt har det hänt i vårt land att en liten del av tjänsterna kräver RWX-volymer och för länge sedan började man använda NFS-lagring för denna uppgift. Billigt och... lagom. Naturligtvis åt han och jag skit - välsigne dig, men vi lärde oss att ställa in det, och mitt huvud gör inte ont längre. Och om möjligt, flytta till S3-objektlagring.

3. Samla optimerade bilder

Nio Kubernetes prestandatips

Det är bäst att använda behållaroptimerade bilder så att Kubernetes kan hämta dem snabbare och exekvera dem mer effektivt. 

Optimerad innebär att bilderna:

  • innehålla endast en applikation eller utföra endast en funktion;

  • liten i storlek, eftersom stora bilder överförs sämre över nätverket;

  • har hälso- och beredskapsändpunkter som gör det möjligt för Kubernetes att vidta åtgärder i händelse av driftstopp;

  • använd containervänliga operativsystem (som Alpine eller CoreOS), som är mer motståndskraftiga mot konfigurationsfel;

  • använd flerstegsbyggen så att du bara kan distribuera kompilerade applikationer och inte de medföljande källorna.

Det finns många verktyg och tjänster som gör att du kan kontrollera och optimera bilder i farten. Det är viktigt att alltid hålla dem uppdaterade och testade för säkerhets skull. Som ett resultat får du:

  1. Minskad nätverksbelastning på hela klustret.

  2. Minska behållarens starttid.

  3. Mindre storlek på hela ditt Docker-register.

4. Använd DNS-cache

Nio Kubernetes prestandatips

Om vi ​​pratar om höga belastningar är livet ganska uselt utan att justera klustrets DNS-system. En gång i tiden stödde Kubernetes-utvecklarna deras kube-dns-lösning. Den implementerades också här, men den här programvaran var inte speciellt inställd och gav inte den prestanda som krävdes, även om det verkade vara en enkel uppgift. Sedan dök det upp coredns, som vi bytte till och inte hade någon sorg, det blev senare standard DNS-tjänsten i K8s. Vid något tillfälle växte vi till 40 tusen rps till DNS-systemet, och denna lösning blev också otillräcklig. Men av tur kom Nodelocaldns ut, aka node local cache, aka NodeLocal DNSCache.

Varför använder vi detta? Det finns en bugg i Linux-kärnan som, när flera anrop via conntrack NAT över UDP, leder till ett racevillkor för poster i conntrack-tabeller, och en del av trafiken genom NAT går förlorad (varje resa genom Tjänsten är NAT). Nodelocaldns löser detta problem genom att bli av med NAT och uppgradera anslutningen till TCP till uppströms DNS, samt lokalt cache uppströms DNS-frågor (inklusive en kort 5 sekunders negativ cache).

5. Skala kapslar horisontellt och vertikalt automatiskt

Nio Kubernetes prestandatips

Kan du med tillförsikt säga att alla dina mikrotjänster är redo för en två till trefaldig ökning av belastningen? Hur allokerar man resurser till sina applikationer korrekt? Att hålla ett par pods igång utöver arbetsbelastningen kan vara överflödigt, men att hålla dem rygg mot rygg riskerar att bli stillestånd från en plötslig ökning av trafiken till tjänsten. Tjänster som t.ex Horisontell Pod Autoscaler и Vertical Pod Autoscaler.

VPA låter dig automatiskt höja förfrågningar/gränser för dina behållare i podden beroende på faktisk användning. Hur kan det vara användbart? Om du har poddar som inte kan skalas horisontellt av någon anledning (vilket inte är helt tillförlitligt), så kan du försöka anförtro ändringar av dess resurser till VPA. Dess funktion är ett rekommendationssystem baserat på historiska och aktuella data från metrisk-servern, så om du inte vill ändra förfrågningar/gränser automatiskt kan du helt enkelt övervaka de rekommenderade resurserna för dina behållare och optimera inställningarna för att spara CPU och minne i klustret.

Nio Kubernetes prestandatipsBild hämtad från https://levelup.gitconnected.com/kubernetes-autoscaling-101 cluster-autoscalerer-horizontal-pod-autoscalerer-and-vertical-pod-2a441d9ad231

Schemaläggaren i Kubernetes är alltid baserad på förfrågningar. Oavsett vilket värde du lägger där, kommer schemaläggaren att söka efter en lämplig nod baserat på det. Gränsvärdena behövs för att kuben ska förstå när den ska gasa eller döda kapseln. Och eftersom den enda viktiga parametern är förfrågningsvärdet, kommer VPA att arbeta med det. Närhelst du skalar en applikation vertikalt definierar du vilka förfrågningar som ska vara. Vad händer med gränserna då? Denna parameter kommer också att skalas proportionellt.

Till exempel, här är de vanliga podinställningarna:

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

Rekommendationsmotorn avgör att din applikation kräver 300m CPU och 500Mi för att fungera korrekt. Du får följande inställningar:

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

Som nämnts ovan är detta proportionell skalning baserat på förfrågningar/gränser i manifestet:

  • CPU: 200m → 300m: förhållande 1:1.75;

  • Minne: 250Mi → 500Mi: förhållande 1:2.

med avseende på HPA, då är operationsmekanismen mer transparent. Mätvärden som CPU och minne är tröskelvärden, och om medelvärdet av alla repliker överskrider tröskeln, skalas applikationen med +1 sub tills värdet faller under tröskeln eller tills det maximala antalet repliker uppnås.

Nio Kubernetes prestandatipsBild hämtad från https://levelup.gitconnected.com/kubernetes-autoscaling-101 cluster-autoscalerer-horizontal-pod-autoscalerer-and-vertical-pod-2a441d9ad231

Förutom de vanliga mätvärdena som CPU och minne, kan du ställa in trösklar på dina anpassade mätvärden från Prometheus och arbeta med dem om du tror att det är den mest exakta indikationen på när du ska skala din applikation. När applikationen har stabiliserats under det angivna metriska tröskelvärdet, börjar HPA skala ner pods till det minsta antalet repliker eller tills belastningen når det specificerade tröskelvärdet.

6. Glöm inte Node Affinity och Pod Affinity

Nio Kubernetes prestandatips

Alla noder körs inte på samma hårdvara, och alla pods behöver inte köra datorintensiva applikationer. Kubernetes låter dig ställa in specialiseringen av noder och poddar med hjälp av Nodaffinitet и Pod Affinity.

Om du har noder som är lämpliga för beräkningsintensiva operationer, är det för maximal effektivitet bättre att knyta applikationer till motsvarande noder. För att göra detta använd nodeSelector med en nodetikett.

Låt oss säga att du har två noder: en med CPUType=HIGHFREQ och ett stort antal snabba kärnor, en annan med MemoryType=HIGHMEMORY mer minne och snabbare prestanda. Det enklaste sättet är att tilldela driftsättning till en nod HIGHFREQgenom att lägga till i avsnittet spec denna väljare:

…
nodeSelector:
	CPUType: HIGHFREQ

Ett dyrare och mer specifikt sätt att göra detta på är att använda nodeAffinity i fält affinity sektion spec. Det finns två alternativ:

  • requiredDuringSchedulingIgnoredDuringExecution: hård inställning (schemaläggaren kommer att distribuera pods endast på specifika noder (och ingen annanstans));

  • preferredDuringSchedulingIgnoredDuringExecution: mjuk inställning (schemaläggaren kommer att försöka distribuera till specifika noder, och om det misslyckas kommer den att försöka distribuera till nästa tillgängliga nod).

Du kan ange en specifik syntax för att hantera nodetiketter, till exempel In, NotIn, Exists, DoesNotExist, Gt eller Lt. Kom dock ihåg att komplexa metoder i långa listor med etiketter kommer att bromsa beslutsfattandet i kritiska situationer. Med andra ord, håll det enkelt.

Som nämnts ovan låter Kubernetes dig ställa in affiniteten för de aktuella poddarna. Det vill säga att du kan se till att vissa poddar fungerar tillsammans med andra poddar i samma tillgänglighetszon (relevant för moln) eller noder.

В podAffinity fält affinity sektion spec samma fält är tillgängliga som i fallet med nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution и preferredDuringSchedulingIgnoredDuringExecution. Den enda skillnaden är det matchExpressions kommer att binda podarna till en nod som redan kör en pod med den etiketten.

Kubernetes erbjuder också ett fält podAntiAffinity, som tvärtom inte binder podden till en nod med specifika pods.

Om uttryck nodeAffinity Samma råd kan ges: försök att hålla reglerna enkla och logiska, försök inte överbelasta podspecifikationen med en komplex uppsättning regler. Det är mycket lätt att skapa en regel som inte matchar villkoren för klustret, vilket skapar onödig belastning på schemaläggaren och minskar den totala prestandan.

7. Fläckar och toleranser

Det finns ett annat sätt att hantera schemaläggaren. Om du har ett stort kluster med hundratals noder och tusentals mikrotjänster, då är det väldigt svårt att inte tillåta vissa pods att finnas på vissa noder.

Mekanismen för fläckar – förbjudande regler – hjälper till med detta. Till exempel, i vissa scenarier kan du förbjuda vissa noder från att köra pods. För att applicera fläck på en specifik nod måste du använda alternativet taint i kubectl. Ange nyckeln och värdet och färga sedan som NoSchedule eller NoExecute:

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

Det är också värt att notera att fläckmekanismen stöder tre huvudeffekter: NoSchedule, NoExecute и PreferNoSchedule.

  • NoSchedule betyder att det för närvarande inte kommer att finnas någon motsvarande post i podspecifikationen tolerations, kommer den inte att kunna distribueras på noden (i det här exemplet node10).

  • PreferNoSchedule - förenklad version NoSchedule. I det här fallet kommer schemaläggaren att försöka att inte tilldela skidor som inte har en matchande post tolerations per nod, men detta är ingen svår begränsning. Om det inte finns några resurser i klustret kommer pods att börja distribueras på denna nod.

  • NoExecute - denna effekt utlöser omedelbar evakuering av baljor som inte har en motsvarande ingång tolerations.

Intressant nog kan detta beteende avbrytas med hjälp av tolerationsmekanismen. Detta är praktiskt när det finns en "förbjuden" nod och du bara behöver placera infrastrukturtjänster på den. Hur man gör det? Tillåt endast de baljor för vilka det finns en lämplig tolerans.

Så här skulle podspecifikationen se ut:

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

Detta betyder inte att nästa omdistribuering kommer att falla på just denna nod, detta är inte Node Affinity-mekanismen och nodeSelector. Men genom att kombinera flera funktioner kan du uppnå mycket flexibla schemaläggningsinställningar.

8. Ställ in Pod-distributionsprioritet

Bara för att du har poddar tilldelade noder betyder det inte att alla pods ska behandlas med samma prioritet. Du kanske till exempel vill distribuera vissa pods före andra.

Kubernetes erbjuder olika sätt att konfigurera Pod Priority och Preemption. Inställningen består av flera delar: objekt PriorityClass och fältbeskrivningar priorityClassName i podspecifikationen. Låt oss titta på ett exempel:

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"

Vi skapar PriorityClass, ge den ett namn, beskrivning och värde. Desto högre value, desto högre prioritet. Värdet kan vara vilket 32-bitars heltal som helst mindre än eller lika med 1 000 000 000. Högre värden är reserverade för verksamhetskritiska systempods som i allmänhet inte kan förebyggas. Förskjutning sker bara om en högprioriterad pod inte har någon plats att vända sig om, då kommer några av podarna från en viss nod att evakueras. Om denna mekanism är för stel för dig kan du lägga till alternativet preemptionPolicy: Never, och då kommer det inte att finnas någon preemption, podden kommer att stå först i kön och vänta på att schemaläggaren ska hitta lediga resurser för den.

Därefter skapar vi en pod där vi anger namnet 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
          

Du kan skapa hur många prioriterade klasser du vill, även om det rekommenderas att inte ryckas med detta (säg, begränsa dig till låg, medel och hög prioritet).

Således kan du vid behov öka effektiviteten i att distribuera kritiska tjänster som nginx-ingress-controller, coredns, etc.

9. Optimera ETCD-klustret

Nio Kubernetes prestandatips

ETCD kan kallas hjärnan i hela klustret. Det är mycket viktigt att upprätthålla driften av denna databas på en hög nivå, eftersom operationshastigheten i Cube beror på den. En ganska standard, och samtidigt bra lösning skulle vara att behålla ETCD-klustret på masternoderna för att få en minimal fördröjning till kube-apiservern. Om du inte kan göra detta, placera sedan ETCD så nära som möjligt, med bra bandbredd mellan deltagarna. Var också uppmärksam på hur många noder från ETCD som kan falla ut utan att skada klustret

Nio Kubernetes prestandatips

Tänk på att en överdriven ökning av antalet medlemmar i ett kluster kan öka feltoleransen på bekostnad av prestanda, allt ska vara med måtta.

Om vi ​​pratar om att ställa in tjänsten finns det några rekommendationer:

  1. Ha bra hårdvara, baserat på storleken på klustret (du kan läsa här).

  2. Justera några parametrar om du har spridit ett kluster mellan ett par DCs eller ditt nätverk och diskar lämnar mycket övrigt att önska (du kan läsa här).

Slutsats

Den här artikeln beskriver de punkter som vårt team försöker följa. Detta är inte en steg-för-steg-beskrivning av åtgärder, utan alternativ som kan vara användbara för att optimera klusteroverhead. Det är tydligt att varje kluster är unikt på sitt sätt, och konfigurationslösningarna kan variera mycket, så det skulle vara intressant att få din feedback om hur du övervakar ditt Kubernetes-kluster och hur du förbättrar dess prestanda. Dela din upplevelse i kommentarerna, det kommer att bli intressant att veta.

Källa: will.com