Fem missar när den första applikationen implementeras på Kubernetes

Fem missar när den första applikationen implementeras på KubernetesFail av Aris-Dreamer

Många tror att det räcker med att migrera programmet till Kubernetes (antingen med hjälp av Helm eller manuellt) och de kommer att vara nöjda. Men det är inte så enkelt.

Team Mail.ru molnlösningar översatte en artikel av DevOps-ingenjören Julian Gindi. Han delar med sig av vilka fallgropar hans företag stötte på under migreringsprocessen så att du inte trampar på samma rake.

Steg ett: Ställa in pod-förfrågningar och gränser

Låt oss börja med att skapa en ren miljö där våra pods kommer att köras. Kubernetes gör ett bra jobb med att schemalägga pods och hantera feltillstånd. Men det visade sig att schemaläggaren ibland inte kan placera en pod om det är svårt att uppskatta hur många resurser den behöver för att fungera framgångsrikt. Det är här förfrågningar om resurser och begränsningar dyker upp. Det finns mycket debatt om det bästa sättet att sätta förfrågningar och gränser. Ibland känns det verkligen som att det är mer konst än vetenskap. Här är vårt tillvägagångssätt.

Pod-förfrågningar - Detta är huvudvärdet som används av schemaläggaren för att placera podden optimalt.

Av Kubernetes dokumentation: Filtreringssteget bestämmer uppsättningen av noder där podden kan schemaläggas. Till exempel kontrollerar PodFitsResources-filtret om en nod har tillräckligt med resurser för att tillfredsställa en pods specifika resursbegäranden.

Vi använder applikationsförfrågningar så att de kan användas för att uppskatta hur många resurser faktiskt Applikationen behöver den för att fungera korrekt. På så sätt kan schemaläggaren placera noder realistiskt. Vi ville först ställa in förfrågningar med marginal för att säkerställa att varje pod hade ett tillräckligt stort antal resurser, men vi märkte att schemaläggningstiderna ökade avsevärt och vissa poddar var aldrig helt schemalagda, som om inga resursbegäranden hade mottagits för dem.

I det här fallet skulle schemaläggaren ofta trycka ut pods och inte kunna schemalägga dem eftersom kontrollplanet inte hade någon aning om hur många resurser applikationen skulle kräva, en nyckelkomponent i schemaläggningsalgoritmen.

Pod-gränser – det här är en tydligare gräns för podden. Det representerar den maximala mängden resurser som klustret kommer att allokera till behållaren.

Återigen, från officiell dokumentation: Om en behållare har en 4 GiB minnesgräns inställd, kommer kubelet (och behållarens körtid) att upprätthålla det. Körtiden tillåter inte att behållaren använder mer än den angivna resursgränsen. Till exempel, när en process i en behållare försöker använda mer än den tillåtna mängden minne, avslutar systemkärnan processen med ett felmeddelande om att minnet är slut (OOM).

En container kan alltid använda fler resurser än vad som anges i resursbegäran, men kan aldrig använda mer än vad som anges i gränsen. Detta värde är svårt att ställa in korrekt, men det är mycket viktigt.

Helst vill vi att resurskraven för en pod ska förändras under en processs livscykel utan att störa andra processer i systemet – det är målet att sätta gränser.

Tyvärr kan jag inte ge specifika instruktioner om vilka värden som ska ställas in, men vi följer själva följande regler:

  1. Med hjälp av ett belastningstestverktyg simulerar vi en baslinjenivå för trafik och övervakar användningen av podresurser (minne och processor).
  2. Vi ställer in pod-förfrågningarna till ett godtyckligt lågt värde (med en resursgräns på cirka 5 gånger värdet av förfrågningarna) och observerar. När förfrågningarna är för låga kan processen inte starta, vilket ofta orsakar mystiska Go-runtime-fel.

Observera att högre resursgränser gör schemaläggning svårare eftersom podden behöver en målnod med tillräckligt med resurser tillgängliga.

Föreställ dig en situation där du har en lätt webbserver med en mycket hög resursgräns, säg 4 GB minne. Denna process kommer sannolikt att behöva skalas horisontellt, och varje ny modul måste schemaläggas på en nod med minst 4 GB tillgängligt minne. Om det inte finns någon sådan nod måste klustret introducera en ny nod för att bearbeta den podden, vilket kan ta lite tid. Det är viktigt att hålla skillnaden mellan resursbegäranden och gränser till ett minimum för att säkerställa snabb och smidig skalning.

Steg två: ställa in Liveness och Readiness tester

Detta är ett annat subtilt ämne som ofta diskuteras i Kubernetes-gemenskapen. Det är viktigt att ha en god förståelse för Liveness- och Readiness-tester eftersom de tillhandahåller en mekanism för mjukvara att köra smidigt och minimera stillestånd. De kan dock orsaka en allvarlig prestandapåverkan på din applikation om den inte konfigureras korrekt. Nedan är en sammanfattning av hur båda proverna är.

Livlighet visar om behållaren är igång. Om det misslyckas dödar kubelet behållaren och en omstartspolicy är aktiverad för den. Om behållaren inte är utrustad med en Liveness-sond kommer standardtillståndet att vara framgångsrikt - det är vad det står i Kubernetes dokumentation.

Liveness-sonder ska vara billiga, vilket innebär att de inte ska förbruka mycket resurser, eftersom de körs ofta och måste informera Kubernetes om att applikationen körs.

Om du ställer in alternativet att köra varje sekund kommer detta att lägga till 1 begäran per sekund, så tänk på att ytterligare resurser kommer att behövas för att hantera denna trafik.

Hos vårt företag kontrollerar Liveness-tester kärnkomponenterna i en applikation, även om data (till exempel från en fjärrdatabas eller cache) inte är helt tillgänglig.

Vi har konfigurerat apparna med en "hälsoslutpunkt" som helt enkelt returnerar en svarskod på 200. Detta är en indikation på att processen körs och kan behandla förfrågningar (men ännu inte trafik).

testet Beredskap indikerar om behållaren är redo att betjäna förfrågningar. Om beredskapssonden misslyckas tar slutpunktskontrollern bort poddens IP-adress från slutpunkterna för alla tjänster som motsvarar podden. Detta står också i Kubernetes dokumentation.

Beredskapssonder förbrukar mer resurser eftersom de måste skickas till backend på ett sätt som indikerar att applikationen är redo att acceptera förfrågningar.

Det finns en hel del debatt i samhället om huruvida man ska få tillgång till databasen direkt. Med tanke på omkostnaderna (kontroller utförs ofta, men kan justeras) beslutade vi att för vissa applikationer räknas beredskapen att betjäna trafik endast efter att ha verifierat att poster returneras från databasen. Väl utformade beredskapsförsök säkerställde högre nivåer av tillgänglighet och eliminerade stillestånd under driftsättning.

Om du bestämmer dig för att fråga databasen för att testa din ansökans beredskap, se till att den är så billig som möjligt. Låt oss ta denna begäran:

SELECT small_item FROM table LIMIT 1

Här är ett exempel på hur vi konfigurerar dessa två värden i Kubernetes:

livenessProbe: 
 httpGet:   
   path: /api/liveness    
   port: http 
readinessProbe:  
 httpGet:    
   path: /api/readiness    
   port: http  periodSeconds: 2

Du kan lägga till några ytterligare konfigurationsalternativ:

  • initialDelaySeconds — hur många sekunder som kommer att gå mellan lanseringen av behållaren och början av proverna.
  • periodSeconds — Vänteintervall mellan provkörningar.
  • timeoutSeconds — antalet sekunder efter vilket enheten anses vara en nödsituation. Regelbunden timeout.
  • failureThreshold — Antalet testfel innan en omstartssignal skickas till poden.
  • successThreshold — Antalet framgångsrika sönder innan kapseln går in i redoläge (efter ett fel, när podden startar eller återhämtar sig).

Steg tre: ställa in standardnätverkspolicyer för podden

Kubernetes har en "plat" nätverkstopografi; som standard kommunicerar alla poddar direkt med varandra. I vissa fall är detta inte önskvärt.

Ett potentiellt säkerhetsproblem är att en angripare kan använda en enda sårbar applikation för att skicka trafik till alla pods på nätverket. Som med många säkerhetsområden gäller principen om minsta privilegium här. Helst bör nätverkspolicyer explicit specificera vilka anslutningar mellan poddar som är tillåtna och vilka som inte är det.

Till exempel nedan är en enkel policy som nekar all inkommande trafik för ett specifikt namnområde:

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:  
 name: default-deny-ingress
spec:  
 podSelector: {}  
 policyTypes:  
   - Ingress

Visualisering av denna konfiguration:

Fem missar när den första applikationen implementeras på Kubernetes
(https://miro.medium.com/max/875/1*-eiVw43azgzYzyN1th7cZg.gif)
Mer detaljer här.

Steg fyra: anpassat beteende med hjälp av krokar och init-behållare

Ett av våra huvudmål var att tillhandahålla distributioner till Kubernetes utan driftstopp för utvecklare. Detta är svårt eftersom det finns många alternativ för att stänga av program och frigöra de resurser de använde.

Särskilda svårigheter uppstod med nginx. Vi märkte att när dessa poddar distribuerades sekventiellt avbröts aktiva anslutningar innan de slutfördes.

Efter omfattande efterforskningar på nätet visar det sig att Kubernetes inte väntar på att Nginx-anslutningar ska ta slut innan de avslutar podden. Med hjälp av en förstoppskrok implementerade vi följande funktionalitet och blev helt av med stillestånd:

lifecycle: 
 preStop:
   exec:
     command: ["/usr/local/bin/nginx-killer.sh"]

Men nginx-killer.sh:

#!/bin/bash
sleep 3
PID=$(cat /run/nginx.pid)
nginx -s quit
while [ -d /proc/$PID ]; do
   echo "Waiting while shutting down nginx..."
   sleep 10
done

Ett annat extremt användbart paradigm är användningen av init-behållare för att hantera uppstarten av specifika applikationer. Detta är särskilt användbart om du har en resurskrävande databasmigreringsprocess som måste köras innan programmet startar. Du kan också ange en högre resursgräns för den här processen utan att ställa in en sådan gräns för huvudapplikationen.

Ett annat vanligt schema är att komma åt hemligheter i en init-behållare som tillhandahåller dessa referenser till huvudmodulen, vilket förhindrar obehörig åtkomst till hemligheter från själva huvudapplikationsmodulen.

Som vanligt, citera från dokumentationen: Init-behållare kör på ett säkert sätt anpassad kod eller verktyg som annars skulle minska säkerheten för applikationsbehållareavbildningen. Genom att hålla onödiga verktyg åtskilda begränsar du attackytan för applikationsbehållarebilden.

Steg fem: Konfigurera kärnan

Låt oss slutligen tala om en mer avancerad teknik.

Kubernetes är en extremt flexibel plattform som låter dig köra arbetsbelastningar som du tycker passar. Vi har ett antal högpresterande applikationer som är extremt resurskrävande. Efter att ha utfört omfattande belastningstester upptäckte vi att en applikation kämpade för att hantera den förväntade trafikbelastningen när Kubernetes standardinställningar var i kraft.

Kubernetes låter dig dock köra en privilegierad behållare som endast ändrar kärnparametrar för en specifik pod. Här är vad vi använde för att ändra det maximala antalet öppna anslutningar:

initContainers:
  - name: sysctl
     image: alpine:3.10
     securityContext:
         privileged: true
      command: ['sh', '-c', "sysctl -w net.core.somaxconn=32768"]

Detta är en mer avancerad teknik som ofta inte behövs. Men om din applikation har svårt att klara av tung belastning kan du prova att justera några av dessa inställningar. Mer information om denna process och inställning av olika värden - som alltid i den officiella dokumentationen.

Sammanfattningsvis

Även om Kubernetes kan verka som en färdig lösning ur lådan, finns det några viktiga steg du måste vidta för att dina applikationer ska fungera smidigt.

Under hela din Kubernetes-migrering är det viktigt att följa "belastningstestcykeln": starta programmet, ladda testa det, observera mätvärden och skalningsbeteende, justera konfigurationen baserat på dessa data, och upprepa sedan cykeln igen.

Var realistisk om din förväntade trafik och försök gå bortom den för att se vilka komponenter som går sönder först. Med detta iterativa tillvägagångssätt kan endast ett fåtal av de listade rekommendationerna vara tillräckligt för att nå framgång. Eller det kan kräva djupare anpassning.

Ställ dig alltid dessa frågor:

  1. Hur många resurser förbrukar applikationer och hur kommer denna volym att förändras?
  2. Vilka är de verkliga skalningskraven? Hur mycket trafik hanterar appen i genomsnitt? Hur är det med högtrafik?
  3. Hur ofta behöver tjänsten skalas horisontellt? Hur snabbt måste nya poddar tas online för att ta emot trafik?
  4. Hur korrekt stängs kapslarna av? Är detta överhuvudtaget nödvändigt? Är det möjligt att uppnå driftsättning utan driftstopp?
  5. Hur kan du minimera säkerhetsriskerna och begränsa skadorna från eventuella komprometterade pods? Har några tjänster behörigheter eller åtkomst som de inte kräver?

Kubernetes tillhandahåller en otrolig plattform som låter dig utnyttja bästa praxis för att distribuera tusentals tjänster i ett kluster. Men varje applikation är annorlunda. Ibland kräver implementeringen lite mer arbete.

Lyckligtvis tillhandahåller Kubernetes den nödvändiga konfigurationen för att uppnå alla tekniska mål. Genom att använda en kombination av resursbegäranden och gränser, Liveness- och Readiness-prober, init-behållare, nätverkspolicyer och anpassad kärninställning, kan du uppnå hög prestanda tillsammans med feltolerans och snabb skalbarhet.

Vad mer att läsa:

  1. Bästa praxis och bästa praxis för att köra containrar och Kubernetes i produktionsmiljöer.
  2. Över 90 användbara verktyg för Kubernetes: distribution, hantering, övervakning, säkerhet och mer.
  3. Vår kanal Around Kubernetes i Telegram.

Källa: will.com

Lägg en kommentar