Fem feil når den første applikasjonen implementeres på Kubernetes

Fem feil når den første applikasjonen implementeres på KubernetesFail av Aris-Dreamer

Mange tror at det er nok å migrere applikasjonen til Kubernetes (enten ved å bruke Helm eller manuelt), og de vil være fornøyde. Men det er ikke så enkelt.

Lag Mail.ru skyløsninger oversatte en artikkel av DevOps-ingeniør Julian Gindi. Han deler hvilke fallgruver selskapet hans møtte under migrasjonsprosessen, slik at du ikke tråkker på samme rake.

Trinn én: Sette opp pod-forespørsler og grenser

La oss starte med å sette opp et rent miljø der podene våre skal kjøre. Kubernetes gjør en god jobb med å planlegge pods og håndtere feiltilstander. Men det viste seg at planleggeren noen ganger ikke kan plassere en pod hvis det er vanskelig å anslå hvor mange ressurser den trenger for å fungere vellykket. Det er her forespørsler om ressurser og begrensninger kommer opp. Det er mye debatt om den beste tilnærmingen til å sette forespørsler og grenser. Noen ganger føles det virkelig som om det er mer kunst enn vitenskap. Her er vår tilnærming.

Pod-forespørsler - Dette er hovedverdien som brukes av planleggeren for å plassere poden optimalt.

Av Kubernetes dokumentasjon: Filtreringstrinnet bestemmer settet med noder hvor poden kan planlegges. For eksempel sjekker PodFitsResources-filteret om en node har nok ressurser til å tilfredsstille en pods spesifikke ressursforespørsler.

Vi bruker søknadsforespørsler slik at de kan brukes til å anslå hvor mange ressurser faktisk Applikasjonen trenger den for å fungere skikkelig. På denne måten kan planleggeren plassere noder realistisk. Vi ønsket i utgangspunktet å sette forespørsler med margin for å sikre at hver pod hadde et tilstrekkelig stort antall ressurser, men vi la merke til at planleggingstidene økte betraktelig og noen pods ble aldri fullt ut planlagt, som om ingen ressursforespørsler hadde blitt mottatt for dem.

I dette tilfellet ville planleggeren ofte presse ut pods og ikke være i stand til å omplanlegge dem fordi kontrollplanet ikke hadde noen anelse om hvor mange ressurser applikasjonen ville kreve, en nøkkelkomponent i planleggingsalgoritmen.

Pod-grenser – dette er en klarere grense for poden. Den representerer den maksimale mengden ressurser som klyngen vil allokere til beholderen.

Igjen, fra offisiell dokumentasjon: Hvis en beholder har en 4 GiB minnegrense satt, vil kubelet (og beholderens kjøretid) håndheve det. Kjøretiden tillater ikke at beholderen bruker mer enn den angitte ressursgrensen. For eksempel, når en prosess i en beholder prøver å bruke mer enn den tillatte mengden minne, avslutter systemkjernen prosessen med en "tomt minne"-feil (OOM).

En beholder kan alltid bruke flere ressurser enn angitt i ressursforespørselen, men kan aldri bruke mer enn det som er spesifisert i grensen. Denne verdien er vanskelig å stille inn riktig, men den er veldig viktig.

Ideelt sett vil vi at ressurskravene til en pod skal endres over livssyklusen til en prosess uten å forstyrre andre prosesser i systemet – det er målet med å sette grenser.

Dessverre kan jeg ikke gi spesifikke instruksjoner om hvilke verdier som skal settes, men vi overholder selv følgende regler:

  1. Ved å bruke et lasttestingsverktøy simulerer vi et basisnivå for trafikk og overvåker bruken av pod-ressurser (minne og prosessor).
  2. Vi setter pod-forespørslene til en vilkårlig lav verdi (med en ressursgrense på omtrent 5 ganger verdien av forespørslene) og observerer. Når forespørslene er for lave, kan ikke prosessen starte, noe som ofte forårsaker mystiske Go-kjøretidsfeil.

Merk at høyere ressursgrenser gjør planlegging vanskeligere fordi poden trenger en målnode med nok tilgjengelige ressurser.

Se for deg en situasjon der du har en lett nettserver med en veldig høy ressursgrense, for eksempel 4 GB minne. Denne prosessen må sannsynligvis skaleres horisontalt, og hver nye modul må planlegges på en node med minst 4 GB tilgjengelig minne. Hvis ingen slik node eksisterer, må klyngen introdusere en ny node for å behandle den poden, noe som kan ta litt tid. Det er viktig å holde forskjellen mellom ressursforespørsler og grenser på et minimum for å sikre rask og jevn skalering.

Trinn to: sette opp liveness- og beredskapstester

Dette er et annet subtilt emne som ofte diskuteres i Kubernetes-fellesskapet. Det er viktig å ha en god forståelse av Liveness- og Readiness-tester, da de gir en mekanisme for programvare for å kjøre jevnt og minimere nedetid. De kan imidlertid forårsake et alvorlig ytelsestreff på applikasjonen din hvis den ikke er riktig konfigurert. Nedenfor er en oppsummering av hvordan begge prøvene er.

Livsstil viser om beholderen kjører. Hvis det mislykkes, dreper kubelet beholderen og en omstartspolicy er aktivert for den. Hvis beholderen ikke er utstyrt med en Liveness-sonde, vil standardtilstanden være suksess - dette er det den står i Kubernetes dokumentasjon.

Liveness-prober skal være billige, noe som betyr at de ikke skal bruke mye ressurser, fordi de kjører ofte og må informere Kubernetes om at applikasjonen kjører.

Hvis du angir alternativet for å kjøre hvert sekund, vil dette legge til 1 forespørsel per sekund, så vær oppmerksom på at det vil være behov for ekstra ressurser for å håndtere denne trafikken.

Hos vårt selskap kontrollerer Liveness-tester kjernekomponentene i en applikasjon, selv om dataene (for eksempel fra en ekstern database eller cache) ikke er fullt tilgjengelig.

Vi har konfigurert appene med et "helse"-endepunkt som ganske enkelt returnerer en svarkode på 200. Dette er en indikasjon på at prosessen kjører og er i stand til å behandle forespørsler (men ikke trafikk ennå).

test Beredskap indikerer om beholderen er klar til å betjene forespørsler. Hvis beredskapssonden mislykkes, fjerner endepunktkontrolleren podens IP-adresse fra endepunktene til alle tjenester som tilsvarer poden. Dette står også i Kubernetes-dokumentasjonen.

Beredskapssonder bruker mer ressurser fordi de må sendes til backend på en måte som indikerer at applikasjonen er klar til å akseptere forespørsler.

Det er mye debatt i samfunnet om man skal få tilgang til databasen direkte. Gitt overheaden (kontroller utføres ofte, men kan justeres), bestemte vi at for noen applikasjoner telles beredskapen til å betjene trafikk kun etter å ha verifisert at poster returneres fra databasen. Godt utformede beredskapsforsøk sørget for høyere nivåer av tilgjengelighet og eliminerte nedetid under distribusjon.

Hvis du bestemmer deg for å spørre databasen for å teste programmets beredskap, sørg for at den er så billig som mulig. La oss ta denne forespørselen:

SELECT small_item FROM table LIMIT 1

Her er et eksempel på hvordan vi konfigurerer disse to verdiene i Kubernetes:

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

Du kan legge til noen ekstra konfigurasjonsalternativer:

  • initialDelaySeconds — hvor mange sekunder som går mellom lanseringen av beholderen og starten av prøvene.
  • periodSeconds — venteintervall mellom prøvekjøringer.
  • timeoutSeconds – antall sekunder som enheten regnes som en nødsituasjon. Vanlig timeout.
  • failureThreshold — antall testfeil før et omstartsignal sendes til poden.
  • successThreshold — antall vellykkede prober før poden går inn i klar tilstand (etter en feil, når poden starter eller gjenoppretter seg).

Trinn tre: sette opp standard nettverkspolicyer for poden

Kubernetes har en "flat" nettverkstopografi; som standard kommuniserer alle pods direkte med hverandre. I noen tilfeller er dette ikke ønskelig.

Et potensielt sikkerhetsproblem er at en angriper kan bruke en enkelt sårbar applikasjon for å sende trafikk til alle pods på nettverket. Som med mange sikkerhetsområder, gjelder prinsippet om minste privilegium her. Ideelt sett bør nettverkspolicyer eksplisitt spesifisere hvilke forbindelser mellom poder som er tillatt og hvilke som ikke er det.

Nedenfor er for eksempel en enkel policy som nekter all innkommende trafikk for et bestemt navneområde:

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

Visualisering av denne konfigurasjonen:

Fem feil når den første applikasjonen implementeres på Kubernetes
(https://miro.medium.com/max/875/1*-eiVw43azgzYzyN1th7cZg.gif)
Flere detaljer her.

Trinn fire: tilpasset oppførsel ved hjelp av kroker og init-beholdere

Et av hovedmålene våre var å tilby distribusjoner til Kubernetes uten nedetid for utviklere. Dette er vanskelig fordi det er mange alternativer for å stenge applikasjoner og frigjøre ressursene de brukte.

Spesielle vanskeligheter oppsto med Nginx. Vi la merke til at når disse podene ble distribuert sekvensielt, ble aktive tilkoblinger droppet før vellykket fullføring.

Etter omfattende undersøkelser på nettet, viser det seg at Kubernetes ikke venter på at Nginx-tilkoblinger skal utmattes før de avslutter poden. Ved å bruke en pre-stop krok implementerte vi følgende funksjonalitet og ble fullstendig kvitt nedetid:

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 annet ekstremt nyttig paradigme er bruken av init-beholdere for å håndtere oppstart av spesifikke applikasjoner. Dette er spesielt nyttig hvis du har en ressurskrevende databasemigreringsprosess som må kjøres før applikasjonen starter. Du kan også angi en høyere ressursgrense for denne prosessen uten å angi en slik grense for hovedapplikasjonen.

Et annet vanlig opplegg er å få tilgang til hemmeligheter i en init-beholder som gir disse legitimasjonene til hovedmodulen, som forhindrer uautorisert tilgang til hemmeligheter fra selve hovedapplikasjonsmodulen.

Som vanlig, sitat fra dokumentasjonen: Init-beholdere kjører trygt tilpasset kode eller verktøy som ellers ville redusere sikkerheten til applikasjonsbeholderbildet. Ved å holde unødvendige verktøy adskilt begrenser du angrepsoverflaten til applikasjonsbeholderbildet.

Trinn fem: Konfigurere kjernen

Til slutt, la oss snakke om en mer avansert teknikk.

Kubernetes er en ekstremt fleksibel plattform som lar deg kjøre arbeidsmengder slik du synes passer. Vi har en rekke applikasjoner med høy ytelse som er ekstremt ressurskrevende. Etter å ha utført omfattende belastningstesting, oppdaget vi at en applikasjon slet med å håndtere den forventede trafikkbelastningen da Kubernetes' standardinnstillinger var i kraft.

Kubernetes lar deg imidlertid kjøre en privilegert beholder som endrer kjerneparametere bare for en bestemt pod. Her er hva vi brukte for å endre maksimalt antall åpne tilkoblinger:

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

Dette er en mer avansert teknikk som ofte ikke er nødvendig. Men hvis applikasjonen din sliter med å takle tung belastning, kan du prøve å justere noen av disse innstillingene. Flere detaljer om denne prosessen og innstilling av forskjellige verdier - som alltid i den offisielle dokumentasjonen.

i konklusjonen

Selv om Kubernetes kan virke som en ferdig løsning rett ut av esken, er det noen viktige trinn du må ta for å holde applikasjonene i gang.

Gjennom Kubernetes-migreringen er det viktig å følge "lasttestingssyklusen": start applikasjonen, lasttest den, observer beregninger og skaleringsatferd, juster konfigurasjonen basert på disse dataene, og gjenta deretter syklusen igjen.

Vær realistisk om den forventede trafikken din, og prøv å presse utover den for å se hvilke komponenter som går i stykker først. Med denne iterative tilnærmingen kan bare noen få av de oppførte anbefalingene være nok til å oppnå suksess. Eller det kan kreve dypere tilpasning.

Still deg selv alltid disse spørsmålene:

  1. Hvor mange ressurser bruker applikasjoner og hvordan vil dette volumet endre seg?
  2. Hva er de reelle skaleringskravene? Hvor mye trafikk vil appen håndtere i gjennomsnitt? Hva med topptrafikk?
  3. Hvor ofte vil tjenesten trenge å skalere horisontalt? Hvor raskt må nye pods bringes online for å motta trafikk?
  4. Hvor riktig slår podene seg av? Er dette nødvendig i det hele tatt? Er det mulig å oppnå distribusjon uten nedetid?
  5. Hvordan kan du minimere sikkerhetsrisikoen og begrense skaden fra eventuelle kompromitterte pods? Har noen tjenester tillatelser eller tilgang som de ikke krever?

Kubernetes tilbyr en utrolig plattform som lar deg utnytte beste praksis for å distribuere tusenvis av tjenester i en klynge. Imidlertid er hver applikasjon forskjellig. Noen ganger krever implementering litt mer arbeid.

Heldigvis gir Kubernetes den nødvendige konfigurasjonen for å oppnå alle tekniske mål. Ved å bruke en kombinasjon av ressursforespørsler og grenser, Liveness- og Readiness-prober, init-beholdere, nettverkspolicyer og tilpasset kjerneinnstilling, kan du oppnå høy ytelse sammen med feiltoleranse og rask skalerbarhet.

Hva annet å lese:

  1. Beste praksis og beste praksis for å kjøre containere og Kubernetes i produksjonsmiljøer.
  2. 90+ nyttige verktøy for Kubernetes: distribusjon, administrasjon, overvåking, sikkerhet og mer.
  3. Vår kanal Around Kubernetes i Telegram.

Kilde: www.habr.com

Legg til en kommentar