Fem fejl ved implementering af den første applikation på Kubernetes

Fem fejl ved implementering af den første applikation på KubernetesFail af Aris Dreamer

Mange mennesker tror, ​​at det er nok at overføre applikationen til Kubernetes (enten ved hjælp af Helm eller manuelt) - og der vil være lykke. Men ikke alt er så simpelt.

Team Mail.ru Cloud-løsninger oversat en artikel af DevOps-ingeniør Julian Gindy. Han fortæller, hvilke faldgruber hans virksomhed stod over for under migreringsprocessen, så man ikke træder på samme rake.

Trin et: Opsæt pod-anmodninger og begrænsninger

Lad os starte med at oprette et rent miljø, hvor vores pods kan køre. Kubernetes er fantastisk til pod-planlægning og failover. Men det viste sig, at skemalæggeren nogle gange ikke kan placere en pod, hvis det er svært at vurdere, hvor mange ressourcer den skal bruge for at fungere med succes. Det er her anmodninger om ressourcer og begrænsninger dukker op. Der er megen debat om den bedste tilgang til at sætte anmodninger og grænser. Nogle gange ser det ud til, at dette i virkeligheden er mere en kunst end en videnskab. Her er vores tilgang.

Pod-anmodninger er den vigtigste værdi, der bruges af skemalæggeren til at placere poden optimalt.

Af Kubernetes dokumentation: Filtertrinnet definerer et sæt noder, hvor en Pod kan planlægges. For eksempel kontrollerer PodFitsResources-filteret for at se, om en node har nok ressourcer til at opfylde specifikke ressourceanmodninger fra en pod.

Vi bruger ansøgningsanmodninger på en sådan måde, at vi kan estimere, hvor mange ressourcer faktisk Applikationen har brug for, at den fungerer korrekt. På denne måde kan planlæggeren realistisk placere noderne. I starten ønskede vi at overplanlægge anmodninger for at sikre nok ressourcer til hver Pod, men vi bemærkede, at planlægningstiden steg markant, og nogle Pods var ikke fuldt planlagt, som om der ikke var nogen ressourceanmodninger til dem.

I dette tilfælde ville skemalæggeren ofte "presse" pods og ikke være i stand til at omplanlægge dem, fordi kontrolplanet ikke havde nogen idé om, hvor mange ressourcer applikationen ville have brug for, hvilket er en nøglekomponent i planlægningsalgoritmen.

Pod-grænser er en klarere grænse for en pod. Det repræsenterer den maksimale mængde ressourcer, som klyngen vil allokere til containeren.

Igen fra officiel dokumentation: Hvis en container har en hukommelsesgrænse på 4 GiB, så vil kubelet (og container-runtime) håndhæve det. Kørselstiden forhindrer containeren i at bruge mere end den angivne ressourcegrænse. For eksempel, når en proces i en beholder forsøger at bruge mere end den tilladte mængde hukommelse, afslutter systemkernen processen med en "out of memory" (OOM) fejl.

En container kan altid bruge flere ressourcer, end ressourceanmodningen angiver, men den kan aldrig bruge mere end grænsen. Denne værdi er svær at indstille korrekt, men den er meget vigtig.

Ideelt set ønsker vi, at ressourcekravene for en pod ændrer sig i løbet af en process livscyklus uden at forstyrre andre processer i systemet - det er formålet med at sætte grænser.

Desværre kan jeg ikke give specifikke instruktioner om, hvilke værdier der skal indstilles, men vi overholder selv følgende regler:

  1. Ved hjælp af et belastningstestværktøj simulerer vi et basisniveau af trafik og observerer brugen af ​​pod-ressourcer (hukommelse og processor).
  2. Indstil pod-anmodningerne til en vilkårligt lav værdi (med en ressourcegrænse på ca. 5 gange anmodningsværdien) og observer. Når anmodninger er på et for lavt niveau, kan processen ikke starte, hvilket ofte forårsager kryptiske Go runtime fejl.

Jeg bemærker, at højere ressourcegrænser gør planlægning vanskeligere, fordi poden har brug for en målknude med tilstrækkelige ressourcer til rådighed.

Forestil dig en situation, hvor du har en letvægts webserver med en meget høj ressourcegrænse, f.eks. 4 GB hukommelse. Denne proces skal sandsynligvis skaleres ud vandret, og hver ny pod skal planlægges på en node med mindst 4 GB tilgængelig hukommelse. Hvis der ikke findes en sådan node, skal klyngen indføre en ny node for at behandle denne pod, hvilket kan tage noget tid. Det er vigtigt at opnå en minimumsforskel mellem ressourceanmodninger og grænser for at sikre hurtig og jævn skalering.

Trin to: Opsæt liveness- og parathedstest

Dette er et andet subtilt emne, der ofte diskuteres i Kubernetes-fællesskabet. Det er vigtigt at have en god forståelse af Liveness- og Readiness-tests, da de giver en mekanisme til stabil drift af softwaren og minimerer nedetid. De kan dog alvorligt påvirke din applikations ydeevne, hvis de ikke er konfigureret korrekt. Nedenfor er en oversigt over, hvad begge prøver er.

Livsstil viser om containeren kører. Hvis det mislykkes, dræber kubelet containeren, og genstartspolitikken er aktiveret for den. Hvis containeren ikke er udstyret med en Liveness Probe, vil standardtilstanden være succesfuld - som angivet i Kubernetes dokumentation.

Liveness-prober skal være billige, dvs. ikke forbruge mange ressourcer, fordi de kører ofte og bør informere Kubernetes om, at applikationen kører.

Hvis du indstiller muligheden for at køre hvert sekund, vil dette tilføje 1 anmodning pr. sekund, så vær opmærksom på, at der kræves yderligere ressourcer for at behandle denne trafik.

Hos vores virksomhed tester Liveness-test kernekomponenterne i en applikation, selvom dataene (f.eks. fra en ekstern database eller cache) ikke er fuldt tilgængelige.

Vi har oprettet et "sundheds"-slutpunkt i applikationerne, der blot returnerer en svarkode på 200. Dette er en indikation af, at processen kører og er i stand til at håndtere anmodninger (men ikke trafik endnu).

prøve Readiness angiver, om containeren er klar til at betjene anmodninger. Hvis parathedssonden fejler, fjerner endepunktscontrolleren pod'ens IP-adresse fra endepunkterne for alle tjenester, der matcher poden. Dette fremgår også af Kubernetes-dokumentationen.

Readiness-prober bruger flere ressourcer, da de skal ramme backend på en sådan måde, at de viser, at applikationen er klar til at acceptere anmodninger.

Der er en del debat i samfundet om, hvorvidt man skal tilgå databasen direkte. I betragtning af overheaden (kontrollerne er hyppige, men de kan kontrolleres), besluttede vi, at for nogle applikationer tælles parathed til at betjene trafik kun efter at have kontrolleret, at poster returneres fra databasen. Veldesignede beredskabsforsøg sikrede højere niveauer af tilgængelighed og eliminerede nedetid under implementeringen.

Hvis du beslutter dig for at forespørge databasen for at teste, om din ansøgning er klar, så sørg for, at den er så billig som muligt. Lad os tage denne forespørgsel:

SELECT small_item FROM table LIMIT 1

Her er et eksempel på, hvordan vi konfigurerer disse to værdier i Kubernetes:

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

Du kan tilføje nogle yderligere konfigurationsmuligheder:

  • initialDelaySeconds - hvor mange sekunder der går mellem lanceringen af ​​containeren og starten af ​​lanceringen af ​​sonderne.
  • periodSeconds — venteinterval mellem prøvekørsler.
  • timeoutSeconds — antallet af sekunder, hvorefter poden anses for at være nødsituation. Normal timeout.
  • failureThreshold er antallet af testfejl, før der sendes et genstartssignal til poden.
  • successThreshold er antallet af vellykkede forsøg, før poden skifter til klar-tilstand (efter en fejl, når poden starter eller genopretter).

Trin tre: Indstilling af pod'ens standardnetværkspolitikker

Kubernetes har en "flad" netværkstopografi, som standard kommunikerer alle pods direkte med hinanden. I nogle tilfælde er dette ikke ønskeligt.

Et potentielt sikkerhedsproblem er, at en hacker kan bruge et enkelt sårbart program til at sende trafik til alle pods på netværket. Som på mange sikkerhedsområder gælder princippet om mindste privilegium her. Ideelt set bør netværkspolitikker udtrykkeligt angive, hvilke forbindelser mellem pods der er tilladt, og hvilke der ikke er.

For eksempel er følgende en simpel politik, der afviser al indgående trafik for et bestemt navneområde:

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

Visualisering af denne konfiguration:

Fem fejl ved implementering af den første applikation på Kubernetes
(https://miro.medium.com/max/875/1*-eiVw43azgzYzyN1th7cZg.gif)
Flere detaljer her.

Trin fire: Brugerdefineret adfærd med kroge og Init-beholdere

Et af vores hovedmål var at levere implementeringer i Kubernetes uden nedetid for udviklere. Dette er svært, fordi der er mange muligheder for at lukke applikationer ned og frigive deres brugte ressourcer.

Særlige vanskeligheder opstod med Nginx. Vi har bemærket, at når disse Pods blev implementeret i rækkefølge, blev aktive forbindelser afbrudt, før de blev gennemført.

Efter omfattende research på internettet viste det sig, at Kubernetes ikke venter på, at Nginx-forbindelser er udmattede, før de lukker poden. Ved hjælp af pre-stop krogen implementerede vi følgende funktionalitet og kom fuldstændig af med nedetiden:

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

Et andet ekstremt nyttigt paradigme er brugen af ​​init-beholdere til at håndtere lanceringen af ​​specifikke applikationer. Dette er især nyttigt, hvis du har en ressourcekrævende databasemigreringsproces, der skal køres, før applikationen starter. Du kan også angive en højere ressourcegrænse for denne proces uden at angive en sådan grænse for hovedapplikationen.

En anden almindelig ordning er at få adgang til hemmeligheder i init-beholderen, som giver disse legitimationsoplysninger til hovedmodulet, hvilket forhindrer uautoriseret adgang til hemmeligheder fra selve hovedapplikationsmodulet.

Som sædvanlig et citat fra dokumentationen: init-containere kører sikkert brugerkode eller hjælpeprogrammer, der ellers ville kompromittere sikkerheden af ​​applikationens containerbillede. Ved at holde unødvendige værktøjer adskilt begrænser du angrebsoverfladen af ​​applikationens containerbillede.

Trin fem: Kernelkonfiguration

Lad os endelig tale om en mere avanceret teknik.

Kubernetes er en ekstremt fleksibel platform, der giver dig mulighed for at køre arbejdsbelastninger, som du finder passende. Vi har en række meget effektive applikationer, som er ekstremt ressourcekrævende. Efter omfattende belastningstest fandt vi ud af, at en af ​​applikationerne havde svært ved at følge med den forventede trafikbelastning, da standard Kubernetes-indstillingerne var i kraft.

Kubernetes giver dig dog mulighed for at køre en privilegeret container, der kun ændrer kerneparametre for en specifik pod. Her er, hvad vi brugte til at ændre det maksimale antal åbne forbindelser:

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

Dette er en mere avanceret teknik, som ofte ikke er nødvendig. Men hvis din applikation kæmper for at klare en stor belastning, kan du prøve at justere nogle af disse indstillinger. Mere information om denne proces og indstilling af forskellige værdier - som altid i den officielle dokumentation.

Afslutningsvis

Selvom Kubernetes kan virke som en klar løsning, er der et par vigtige trin, der skal tages for at holde applikationerne kørende.

Under hele migreringen til Kubernetes er det vigtigt at følge "belastningstestcyklussen": kør applikationen, test den under belastning, observer metrik og skaleringsadfærd, juster konfigurationen baseret på disse data, og gentag derefter denne cyklus igen.

Vær realistisk med hensyn til forventet trafik og prøv at gå ud over det for at se, hvilke komponenter der går i stykker først. Med denne iterative tilgang kan kun nogle få af de anførte anbefalinger være nok til at opnå succes. Eller mere dybdegående tilpasning kan være påkrævet.

Stil altid dig selv disse spørgsmål:

  1. Hvor mange ressourcer bruger applikationer, og hvordan vil dette beløb ændre sig?
  2. Hvad er de reelle skaleringskrav? Hvor meget trafik vil appen i gennemsnit håndtere? Hvad med spidsbelastning?
  3. Hvor ofte skal tjenesten skalere ud? Hvor hurtigt skal nye pods være oppe at køre for at modtage trafik?
  4. Hvor yndefuldt lukker bælgerne ned? Er det overhovedet nødvendigt? Er det muligt at opnå implementering uden nedetid?
  5. Hvordan minimerer man sikkerhedsrisici og begrænser skader fra eventuelle kompromitterede pods? Har nogen tjenester tilladelser eller adgange, som de ikke har brug for?

Kubernetes tilbyder en utrolig platform, der giver dig mulighed for at bruge bedste praksis til at implementere tusindvis af tjenester i en klynge. Men alle applikationer er forskellige. Nogle gange kræver implementering lidt mere arbejde.

Heldigvis giver Kubernetes de nødvendige indstillinger for at nå alle tekniske mål. Ved at bruge en kombination af ressourceanmodninger og grænser, Liveness- og Readiness-prober, init-beholdere, netværkspolitikker og tilpasset kernetuning kan du opnå høj ydeevne sammen med fejltolerance og hurtig skalerbarhed.

Hvad skal man ellers læse:

  1. Best Practices og Best Practices for at køre containere og Kubernetes i produktionsmiljøer.
  2. 90+ nyttige værktøjer til Kubernetes: Implementering, administration, overvågning, sikkerhed og mere.
  3. Vores kanal Around Kubernetes i Telegram.

Kilde: www.habr.com

Tilføj en kommentar