10 almindelige fejl ved brug af Kubernetes

Bemærk. overs.: Forfatterne til denne artikel er ingeniører fra et lille tjekkisk firma, pipetail. Det lykkedes dem at sammensætte en vidunderlig liste over [nogle gange banale, men stadig] meget presserende problemer og misforståelser relateret til driften af ​​Kubernetes-klynger.

10 almindelige fejl ved brug af Kubernetes

Gennem årene med brug af Kubernetes har vi arbejdet med et stort antal klynger (både administrerede og ikke-administrerede - på GCP, AWS og Azure). Med tiden begyndte vi at bemærke, at nogle fejl konstant blev gentaget. Det er der dog ingen skam i: Vi har gjort de fleste af dem selv!

Artiklen indeholder de mest almindelige fejl og nævner også, hvordan man retter dem.

1. Ressourcer: anmodninger og begrænsninger

Denne genstand fortjener absolut den tætteste opmærksomhed og førstepladsen på listen.

CPU-anmodning normalt enten slet ikke angivet eller har en meget lav værdi (for at placere så mange pods på hver node som muligt). Dermed bliver knudepunkterne overbelastede. I tider med høj belastning udnyttes nodens processorkraft fuldt ud, og en bestemt arbejdsbyrde modtager kun det, den "anmoder" af CPU drosling. Dette fører til øget applikationsforsinkelse, timeouts og andre ubehagelige konsekvenser. (Læs mere om dette i vores anden nylige oversættelse: "CPU-grænser og aggressiv drosling i Kubernetes"- ca. oversættelse)

Bedste forsøg (ekstremt nej anbefalede):

resources: {}

Ekstremt lav CPU-anmodning (ekstremt nej anbefalede):

   resources:
      Requests:
        cpu: "1m"

På den anden side kan tilstedeværelsen af ​​en CPU-grænse føre til urimelig springning af clock-cyklusser af pods, selvom nodeprocessoren ikke er fuldt indlæst. Igen kan dette føre til øgede forsinkelser. Kontroversen fortsætter omkring parameteren CPU CFS-kvote i Linux-kernen og CPU-begrænsning afhængigt af de indstillede grænser, samt deaktivering af CFS-kvoten... Desværre, CPU-grænser kan forårsage flere problemer, end de kan løse. Mere information om dette kan findes på nedenstående link.

For stort udvalg (overforpligtende) hukommelsesproblemer kan føre til større problemer. At nå CPU-grænsen indebærer at springe clock-cyklusser over, mens at nå hukommelsesgrænsen indebærer, at poden dræbes. Har du nogensinde observeret OOMkill? Ja, det er præcis det, vi taler om.

Vil du minimere sandsynligheden for, at dette sker? Over-alloker ikke hukommelse og brug Guaranteed QoS (Quality of Service) ved at indstille hukommelsesanmodningen til grænsen (som i eksemplet nedenfor). Læs mere om dette i Henning Jacobs oplæg (Lead Engineer Zalando).

Sprængbar (højere chance for at blive OOM-dræbt):

   resources:
      requests:
        memory: "128Mi"
        cpu: "500m"
      limits:
        memory: "256Mi"
        cpu: 2

Garanteret:

   resources:
      requests:
        memory: "128Mi"
        cpu: 2
      limits:
        memory: "128Mi"
        cpu: 2

Hvad vil potentielt hjælpe ved opsætning af ressourcer?

Med metrics-server du kan se det aktuelle CPU-ressourceforbrug og hukommelsesforbrug af pods (og beholdere inde i dem). Mest sandsynligt bruger du det allerede. Bare kør følgende kommandoer:

kubectl top pods
kubectl top pods --containers
kubectl top nodes

De viser dog kun det aktuelle forbrug. Det kan give dig en nogenlunde idé om størrelsesordenen, men i sidste ende får du brug for det historie om ændringer i målinger over tid (for at besvare spørgsmål som: "Hvad var den maksimale CPU-belastning?", "Hvad var belastningen i går morges?" osv.). Til dette kan du bruge Prometheus, DataDog og andre værktøjer. De henter simpelthen metrics fra metrics-server og gemmer dem, og brugeren kan forespørge på dem og plotte dem i overensstemmelse hermed.

VerticalPodAutoscaler Det gør det muligt automatisere denne proces. Det sporer CPU- og hukommelsesbrugshistorik og opsætter nye anmodninger og begrænsninger baseret på disse oplysninger.

Det er ikke en nem opgave at bruge computerkraft effektivt. Det er som at spille Tetris hele tiden. Hvis du betaler for meget for computerkraft med lavt gennemsnitsforbrug (f.eks. ~10%), anbefaler vi at se på produkter baseret på AWS Fargate eller Virtual Kubelet. De er bygget på en serverløs/pay-per-usage faktureringsmodel, som kan vise sig at være billigere under sådanne forhold.

2. Liveness og parathedsonder

Som standard er aktiverings- og parathedstjek ikke aktiveret i Kubernetes. Og nogle gange glemmer de at tænde dem...

Men hvordan kan du ellers starte en servicegenstart i tilfælde af en fatal fejl? Og hvordan ved load balancer, at en pod er klar til at acceptere trafik? Eller at den kan klare mere trafik?

Disse tests forveksles ofte med hinanden:

  • Livsstil — Kontrol af "overlevelsesevne", som genstarter poden, hvis den fejler;
  • Readiness — beredskabskontrol, hvis det mislykkes, afbryder den poden fra Kubernetes-tjenesten (dette kan kontrolleres ved hjælp af kubectl get endpoints), og trafik kommer ikke til den, før næste kontrol er gennemført.

Begge disse kontroller UDFØRES I HELE PODENS LIVSCYKLUS. Det er meget vigtigt.

En almindelig misforståelse er, at parathedsprober kun køres ved opstart, så balanceren kan vide, at poden er klar (Ready) og kan begynde at behandle trafik. Dette er dog kun en af ​​mulighederne for deres brug.

En anden er muligheden for at finde ud af, at trafikken på poden er for stor og overbelaster det (eller poden udfører ressourcekrævende beregninger). I dette tilfælde hjælper beredskabskontrollen reducere belastningen på poden og "afkøl" den. Succesfuld gennemførelse af et beredskabstjek i fremtiden tillader det øge belastningen på poden igen. I dette tilfælde (hvis parathedstesten mislykkes), ville svigt af liveness-testen være meget kontraproduktiv. Hvorfor genstarte en pod, der er sund og arbejder hårdt?

Derfor er ingen kontrol i nogle tilfælde bedre end at aktivere dem med forkert konfigurerede parametre. Som nævnt ovenfor, if livhedstjek kopierer parathedstjek, så er du i store problemer. Mulig mulighed er at konfigurere kun parathedstestOg farlig livlighed lade være.

Begge typer af kontroller bør ikke mislykkes, når almindelige afhængigheder fejler, ellers vil dette føre til en cascading (lavine-lignende) fejl i alle pods. Med andre ord, ikke skade dig selv.

3. LoadBalancer for hver HTTP-tjeneste

Mest sandsynligt har du HTTP-tjenester i din klynge, som du gerne vil videresende til omverdenen.

Hvis du åbner tjenesten som type: LoadBalancer, vil dens controller (afhængigt af tjenesteudbyderen) levere og forhandle en ekstern LoadBalancer (ikke nødvendigvis kørende på L7, men snarere selv på L4), og dette kan påvirke omkostningerne (ekstern statisk IPv4-adresse, computerkraft, fakturering pr. sekund ) på grund af behovet for at skabe et stort antal af sådanne ressourcer.

I dette tilfælde er det meget mere logisk at bruge en ekstern load balancer, åbne tjenester som type: NodePort. Eller endnu bedre, udvide noget lignende nginx-ingress-controller (eller traefik), som bliver den eneste Node Port endepunkt knyttet til den eksterne belastningsbalancer og vil dirigere trafik i klyngen vha indtrængen-Kubernetes ressourcer.

Andre intra-cluster (mikro)tjenester, der interagerer med hinanden, kan "kommunikere" ved hjælp af tjenester som f.eks ClusterIP og en indbygget tjenestegenkendelsesmekanisme via DNS. Bare lad være med at bruge deres offentlige DNS/IP, da dette kan påvirke ventetiden og øge omkostningerne ved skytjenester.

4. Autoskalering af en klynge uden at tage hensyn til dens funktioner

Når du tilføjer noder til og fjerner dem fra en klynge, bør du ikke stole på nogle grundlæggende metrics som CPU-brug på disse noder. Podplanlægning skal tage højde for mange restriktioner, såsom pod/node-affinitet, pletter og tolerationer, ressourceanmodninger, QoS osv. Brug af en ekstern autoscaler, der ikke tager højde for disse nuancer, kan føre til problemer.

Forestil dig, at en bestemt pod skal planlægges, men al tilgængelig CPU-strøm anmodes om/afmonteres, og poden sidder fast i en tilstand Pending. Ekstern autoscaler ser den gennemsnitlige aktuelle CPU-belastning (ikke den anmodede) og starter ikke udvidelse (udskalering) - tilføjer ikke en anden node. Som følge heraf vil denne pod ikke blive planlagt.

I dette tilfælde omvendt skalering (indskalering) — at fjerne en node fra en klynge er altid sværere at implementere. Forestil dig, at du har en stateful pod (med vedvarende lagring tilsluttet). Vedvarende mængder normalt hører til specifik tilgængelighedszone og er ikke replikeret i regionen. Således, hvis en ekstern autoscaler sletter en node med denne pod, så vil planlæggeren ikke være i stand til at planlægge denne pod på en anden node, da dette kun kan gøres i tilgængelighedszonen, hvor det vedvarende lager er placeret. Pod vil sidde fast i tilstanden Pending.

Meget populær i Kubernetes-fællesskabet cluster-autoscaler. Den kører på en klynge, understøtter API'er fra store cloud-udbydere, tager højde for alle begrænsninger og kan skaleres i ovenstående tilfælde. Det er også i stand til at skalere ind, samtidig med at alle fastsatte grænser opretholdes, og derved spare penge (som ellers ville blive brugt på uudnyttet kapacitet).

5. Forsømmelse af IAM/RBAC-kapaciteter

Pas på med at bruge IAM-brugere med vedvarende hemmeligheder til maskiner og applikationer. Organiser midlertidig adgang ved hjælp af roller og servicekonti (servicekonti).

Vi støder ofte på det faktum, at adgangsnøgler (og hemmeligheder) er hårdkodet i applikationskonfigurationen, samt forsømmer rotationen af ​​hemmeligheder på trods af, at vi har adgang til Cloud IAM. Brug IAM-roller og servicekonti i stedet for brugere, hvor det er relevant.

10 almindelige fejl ved brug af Kubernetes

Glem alt om kube2iam og gå direkte til IAM-roller for servicekonti (som beskrevet i seddel af samme navn Štěpán Vraný):

apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-app-role
  name: my-serviceaccount
  namespace: default

En anmærkning. Ikke så svært, vel?

Tildel heller ikke tjenestekonti og instansprofiler privilegier admin и cluster-adminhvis de ikke har brug for det. Dette er lidt sværere at implementere, især i RBAC K8'er, men absolut besværet værd.

6. Stol ikke på automatisk anti-affinitet for bælg

Forestil dig, at du har tre replikaer af en eller anden implementering på en node. Noden falder, og sammen med den alle replikaerne. Ubehagelig situation, ikke? Men hvorfor var alle replikaerne på den samme knude? Er det ikke meningen, at Kubernetes skal levere høj tilgængelighed (HA)?!

Desværre overholder Kubernetes-planlæggeren på eget initiativ ikke reglerne for separat eksistens (anti-affinitet) til bælg. De skal udtrykkeligt angives:

// опущено для краткости
      labels:
        app: zk
// опущено для краткости
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: "app"
                    operator: In
                    values:
                    - zk
              topologyKey: "kubernetes.io/hostname"

Det er alt. Nu vil pods blive planlagt på forskellige noder (denne tilstand kontrolleres kun under planlægning, men ikke under deres drift - derfor requiredDuringSchedulingIgnoredDuringExecution).

Her taler vi om podAntiAffinity på forskellige noder: topologyKey: "kubernetes.io/hostname", - og ikke om forskellige tilgængelighedszoner. For at implementere en fuldgyldig HA bliver du nødt til at grave dybere ned i dette emne.

7. Ignorer PodDisruptionBudgets

Forestil dig, at du har en produktionsbelastning på en Kubernetes-klynge. Med jævne mellemrum skal noder og selve klyngen opdateres (eller tages ud af drift). PodDisruptionBudget (PDB) er noget som en servicegarantiaftale mellem klyngeadministratorer og brugere.

PDB giver dig mulighed for at undgå tjenesteafbrydelser forårsaget af mangel på noder:

apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: zk-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: zookeeper

I dette eksempel siger du som bruger af klyngen til administratorerne: "Hej, jeg har en dyrepassertjeneste, og uanset hvad du gør, vil jeg gerne have mindst 2 replikaer af denne tjeneste altid tilgængelige."

Du kan læse mere om dette her.

8. Flere brugere eller miljøer i en fælles klynge

Kubernetes navnerum (navnerum) giver ikke stærk isolering.

En almindelig misforståelse er, at hvis du implementerer en non-prod load i et navneområde og en prod load i et andet, så vil ikke påvirke hinanden på nogen måde... Dog kan et vist niveau af isolation opnås ved at bruge ressourceanmodninger/-begrænsninger, indstilling af kvoter og indstilling af prioritetsklasser. En vis "fysisk" isolation i dataplanet er tilvejebragt af affiniteter, tolerationer, pletter (eller nodeselectors), men en sådan adskillelse er ganske svært gennemføre.

De, der har brug for at kombinere begge typer arbejdsbelastninger i den samme klynge, bliver nødt til at håndtere kompleksitet. Hvis der ikke er et sådant behov, og du har råd til at have en endnu en klynge (f.eks. i en offentlig sky), så er det bedre at gøre det. Dette vil opnå et meget højere niveau af isolering.

9. ekstern TrafficPolicy: Klynge

Meget ofte ser vi, at al trafik inde i klyngen kommer gennem en tjeneste som NodePort, for hvilken standardpolitikken er indstillet externalTrafficPolicy: Cluster... Det betyder at Node Port er åben på hver node i klyngen, og du kan bruge en hvilken som helst af dem til at interagere med den ønskede service (sæt af pods).

10 almindelige fejl ved brug af Kubernetes

Samtidig er rigtige pods forbundet med den ovennævnte NodePort-tjeneste normalt kun tilgængelige på en bestemt delmængde af disse noder. Med andre ord, hvis jeg opretter forbindelse til en node, der ikke har den nødvendige pod, vil den videresende trafik til en anden node, tilføje en humle og stigende latency (hvis noder er placeret i forskellige tilgængelighedszoner/datacentre, kan latensen være ret høj; desuden vil omkostningerne til udgående trafik stige).

På den anden side, hvis en bestemt Kubernetes-tjeneste har en politik externalTrafficPolicy: Local, så åbner NodePort kun på de noder, hvor de nødvendige pods rent faktisk kører. Ved brug af en ekstern belastningsbalancer, der kontrollerer tilstanden (sundhedstjek) endepunkter (hvordan gør det AWS ELB), Han vil kun sende trafik til de nødvendige knudepunkter, hvilket vil have en gavnlig effekt på forsinkelser, computerbehov, udgangsregninger (og sund fornuft dikterer det samme).

Der er en stor chance for, at du allerede bruger noget lignende traefik eller nginx-ingress-controller som et NodePort-slutpunkt (eller LoadBalancer, som også bruger NodePort) til at dirigere HTTP-indgående trafik, og indstilling af denne mulighed kan reducere forsinkelsen for sådanne anmodninger betydeligt.

В denne udgivelse Du kan lære mere om externalTrafficPolicy, dens fordele og ulemper.

10. Bliv ikke bundet til klynger og misbrug ikke kontrolplanet

Tidligere var det sædvanligt at kalde servere ved egennavne: Anton, HAL9000 og Colossus... I dag er de blevet erstattet af tilfældigt genererede identifikatorer. Vanen blev dog ved, og nu går egennavne til klynger.

En typisk historie (baseret på virkelige begivenheder): det hele startede med et proof of concept, så klyngen havde et stolt navn test… Der er gået år, og det bruges STADIG i produktionen, og alle er bange for at røre ved det.

Der er ikke noget sjovt ved at klynger bliver til kæledyr, så vi anbefaler at fjerne dem med jævne mellemrum, mens du øver dig katastrofe recovery (dette vil hjælpe kaosteknik - ca. oversættelse). Derudover ville det ikke skade at arbejde på kontrollaget (kontrolplan). At være bange for at røre ved ham er ikke et godt tegn. Osv død? Gutter, I er virkelig i problemer!

På den anden side bør du ikke lade dig rive med af at manipulere det. Med tiden kontrollaget kan blive langsomt. Dette skyldes højst sandsynligt, at et stort antal objekter er oprettet uden at de roterer (en almindelig situation, når man bruger Helm med standardindstillinger, hvorfor dets tilstand i configmaps/hemmeligheder ikke opdateres - som følge heraf akkumuleres tusindvis af objekter i kontrollaget) eller med konstant redigering af kube-api objekter (til automatisk skalering, til CI/CD, til overvågning, hændelseslogs, controllere osv.).

Derudover anbefaler vi at tjekke SLA/SLO-aftalerne med den administrerede Kubernetes-udbyder og være opmærksom på garantierne. Sælgeren kan garantere kontrollags tilgængelighed (eller dets underkomponenter), men ikke p99-forsinkelsen af ​​anmodninger, du sender til den. Du kan med andre ord komme ind kubectl get nodes, og få svar først efter 10 minutter, og dette vil ikke være en overtrædelse af vilkårene i serviceaftalen.

11. Bonus: ved at bruge det seneste tag

Men dette er allerede en klassiker. På det seneste er vi stødt på denne teknik sjældnere, da mange, efter at have lært af bitter erfaring, er holdt op med at bruge mærket :latest og begyndte at fastgøre versioner. Hurra!

ECR opretholder uforanderligheden af ​​billedtags; Vi anbefaler, at du gør dig bekendt med denne bemærkelsesværdige funktion.

Resumé

Forvent ikke, at alt virker fra den ene dag til den anden: Kubernetes er ikke et vidundermiddel. Dårlig app vil forblive på denne måde selv i Kubernetes (og det bliver nok værre). Skødesløshed vil føre til overdreven kompleksitet, langsomt og stressende arbejde af kontrollaget. Derudover risikerer du at blive efterladt uden en katastrofegendannelsesstrategi. Forvent ikke, at Kubernetes giver isolation og høj tilgængelighed lige ud af boksen. Brug lidt tid på at gøre din applikation virkelig cloud-native.

Du kan stifte bekendtskab med forskellige teams mislykkede oplevelser i denne samling af historier af Henning Jacobs.

De, der ønsker at tilføje til listen over fejl i denne artikel, kan kontakte os på Twitter (@MarekBartik, @MstrsObserver).

PS fra oversætteren

Læs også på vores blog:

Kilde: www.habr.com

Tilføj en kommentar