Gå? Bash! Møt shell-operatøren (gjennomgang og videorapport fra KubeCon EU'2020)

I år var den viktigste europeiske Kubernetes-konferansen - KubeCon + CloudNativeCon Europe 2020 - virtuell. En slik endring i format hindret oss imidlertid ikke i å levere vår lenge planlagte rapport «Go? Bash! Meet the Shell-operator» dedikert til vårt Open Source-prosjekt shell-operatør.

Denne artikkelen, inspirert av foredraget, presenterer en tilnærming til å forenkle prosessen med å lage operatører for Kubernetes og viser hvordan du kan lage din egen med minimal innsats ved å bruke en shell-operator.

Gå? Bash! Møt shell-operatøren (gjennomgang og videorapport fra KubeCon EU'2020)

Introduserer video av rapporten (~23 minutter på engelsk, merkbart mer informativ enn artikkelen) og hovedutdraget fra den i tekstform. Gå!

Hos Flant optimaliserer og automatiserer vi hele tiden alt. I dag skal vi snakke om et annet spennende konsept. Møte: skybasert skall-skripting!

La oss imidlertid starte med konteksten alt dette skjer i: Kubernetes.

Kubernetes API og kontrollere

API-en i Kubernetes kan representeres som en slags filserver med kataloger for hver type objekt. Objekter (ressurser) på denne serveren er representert av YAML-filer. I tillegg har serveren en grunnleggende API som lar deg gjøre tre ting:

  • motta ressurs etter type og navn;
  • endring ressurs (i dette tilfellet lagrer serveren bare "riktige" objekter - alle feil utformede eller beregnet på andre kataloger blir forkastet);
  • spor for ressursen (i dette tilfellet mottar brukeren umiddelbart sin gjeldende/oppdaterte versjon).

Dermed fungerer Kubernetes som en slags filserver (for YAML-manifester) med tre grunnleggende metoder (ja, faktisk er det andre, men vi vil utelate dem foreløpig).

Gå? Bash! Møt shell-operatøren (gjennomgang og videorapport fra KubeCon EU'2020)

Problemet er at serveren bare kan lagre informasjon. For å få det til å fungere trenger du controller - det nest viktigste og grunnleggende konseptet i Kubernetes-verdenen.

Det er to hovedtyper av kontrollere. Den første tar informasjon fra Kubernetes, behandler den i henhold til nestet logikk og returnerer den til K8s. Den andre henter informasjon fra Kubernetes, men, i motsetning til den første typen, endrer tilstanden til noen eksterne ressurser.

La oss se nærmere på prosessen med å lage en distribusjon i Kubernetes:

  • Implementeringskontroller (inkludert i kube-controller-manager) mottar informasjon om distribusjon og oppretter et replikasett.
  • ReplicaSet oppretter to replikaer (to pods) basert på denne informasjonen, men disse podene er ikke planlagt ennå.
  • Planleggeren planlegger pods og legger til nodeinformasjon til YAML-ene deres.
  • Kubelets gjør endringer i en ekstern ressurs (si Docker).

Deretter gjentas hele denne sekvensen i omvendt rekkefølge: kubelet sjekker beholderne, beregner podens status og sender den tilbake. ReplicaSet-kontrolleren mottar statusen og oppdaterer tilstanden til replikasettet. Det samme skjer med distribusjonskontrolleren og brukeren får til slutt den oppdaterte (gjeldende) statusen.

Gå? Bash! Møt shell-operatøren (gjennomgang og videorapport fra KubeCon EU'2020)

Shell-operatør

Det viser seg at Kubernetes er basert på felles arbeid fra ulike kontrollere (Kubernetes-operatører er også kontrollere). Spørsmålet oppstår, hvordan lage din egen operatør med minimal innsats? Og her kommer den vi utviklet til unnsetning shell-operatør. Det lar systemadministratorer lage sine egne uttalelser ved hjelp av kjente metoder.

Gå? Bash! Møt shell-operatøren (gjennomgang og videorapport fra KubeCon EU'2020)

Enkelt eksempel: kopiering av hemmeligheter

La oss se på et enkelt eksempel.

La oss si at vi har en Kubernetes-klynge. Den har et navneområde default med en hemmelighet mysecret. I tillegg er det andre navnerom i klyngen. Noen av dem har en spesifikk etikett knyttet til seg. Målet vårt er å kopiere Secret til navneområder med en etikett.

Oppgaven kompliseres av det faktum at nye navneområder kan dukke opp i klyngen, og noen av dem kan ha denne etiketten. På den annen side, når etiketten slettes, bør Secret også slettes. I tillegg til dette kan selve hemmeligheten også endres: i dette tilfellet må den nye hemmeligheten kopieres til alle navneområder med etiketter. Hvis Secret slettes ved et uhell i et navneområde, bør operatøren vår gjenopprette det umiddelbart.

Nå som oppgaven er formulert, er det på tide å begynne å implementere den ved å bruke shell-operatoren. Men først er det verdt å si noen ord om selve skalloperatøren.

Hvordan shell-operator fungerer

Som andre arbeidsmengder i Kubernetes, kjører shell-operator i sin egen pod. I denne poden i katalogen /hooks kjørbare filer lagres. Dette kan være skript i Bash, Python, Ruby, etc. Vi kaller slike kjørbare filer kroker (kroker).

Gå? Bash! Møt shell-operatøren (gjennomgang og videorapport fra KubeCon EU'2020)

Shell-operatør abonnerer på Kubernetes-arrangementer og kjører disse krokene som svar på de hendelsene vi trenger.

Gå? Bash! Møt shell-operatøren (gjennomgang og videorapport fra KubeCon EU'2020)

Hvordan vet skalloperatøren hvilken krok som skal kjøres og når? Poenget er at hver krok har to stadier. Under oppstart kjører shell-operatøren alle kroker med et argument --config Dette er konfigurasjonsstadiet. Og etter det lanseres kroker på vanlig måte - som svar på hendelsene de er knyttet til. I sistnevnte tilfelle mottar kroken den bindende konteksten (forpliktende kontekst) - data i JSON-format, som vi vil snakke om mer detaljert nedenfor.

Å lage en operatør i Bash

Nå er vi klare for implementering. For å gjøre dette må vi skrive to funksjoner (forresten, vi anbefaler biblioteket shell_lib, som i stor grad forenkler skrivekroker i Bash):

  • den første er nødvendig for konfigurasjonsfasen - den viser den bindende konteksten;
  • den andre inneholder hovedlogikken til kroken.

#!/bin/bash

source /shell_lib.sh

function __config__() {
  cat << EOF
    configVersion: v1
    # BINDING CONFIGURATION
EOF
}

function __main__() {
  # THE LOGIC
}

hook::run "$@"

Det neste trinnet er å bestemme hvilke gjenstander vi trenger. I vårt tilfelle må vi spore:

  • kildehemmelighet for endringer;
  • alle navneområder i klyngen, slik at du vet hvilke som har en etikett knyttet til dem;
  • målhemmeligheter for å sikre at de alle er synkronisert med kildehemmeligheten.

Abonner på den hemmelige kilden

Bindende konfigurasjon for det er ganske enkelt. Vi indikerer at vi er interessert i Secret med navnet mysecret i navneområdet default:

Gå? Bash! Møt shell-operatøren (gjennomgang og videorapport fra KubeCon EU'2020)

function __config__() {
  cat << EOF
    configVersion: v1
    kubernetes:
    - name: src_secret
      apiVersion: v1
      kind: Secret
      nameSelector:
        matchNames:
        - mysecret
      namespace:
        nameSelector:
          matchNames: ["default"]
      group: main
EOF

Som et resultat vil kroken utløses når kildehemmeligheten endres (src_secret) og motta følgende bindende kontekst:

Gå? Bash! Møt shell-operatøren (gjennomgang og videorapport fra KubeCon EU'2020)

Som du kan se, inneholder den navnet og hele objektet.

Holder styr på navneområder

Nå må du abonnere på navneområder. For å gjøre dette spesifiserer vi følgende bindingskonfigurasjon:

- name: namespaces
  group: main
  apiVersion: v1
  kind: Namespace
  jqFilter: |
    {
      namespace: .metadata.name,
      hasLabel: (
       .metadata.labels // {} |  
         contains({"secret": "yes"})
      )
    }
  group: main
  keepFullObjectsInMemory: false

Som du kan se, har et nytt felt dukket opp i konfigurasjonen med navnet jqFilter. Som navnet antyder, jqFilter filtrerer ut all unødvendig informasjon og lager et nytt JSON-objekt med feltene som er av interesse for oss. En krok med en lignende konfigurasjon vil motta følgende bindende kontekst:

Gå? Bash! Møt shell-operatøren (gjennomgang og videorapport fra KubeCon EU'2020)

Den inneholder en matrise filterResults for hvert navneområde i klyngen. Boolsk variabel hasLabel indikerer om en etikett er knyttet til et gitt navneområde. Velger keepFullObjectsInMemory: false indikerer at det ikke er behov for å beholde komplette objekter i minnet.

Sporing av målhemmeligheter

Vi abonnerer på alle hemmeligheter som har en anmerkning spesifisert managed-secret: "yes" (dette er målet vårt dst_secrets):

- name: dst_secrets
  apiVersion: v1
  kind: Secret
  labelSelector:
    matchLabels:
      managed-secret: "yes"
  jqFilter: |
    {
      "namespace":
        .metadata.namespace,
      "resourceVersion":
        .metadata.annotations.resourceVersion
    }
  group: main
  keepFullObjectsInMemory: false

I dette tilfellet jqFilter filtrerer ut all informasjon unntatt navneområdet og parameteren resourceVersion. Den siste parameteren ble sendt til merknaden når du opprettet hemmeligheten: den lar deg sammenligne versjoner av hemmeligheter og holde dem oppdatert.

En krok konfigurert på denne måten vil, når den utføres, motta de tre bindingskontekstene beskrevet ovenfor. De kan ses på som et slags øyeblikksbilde (snapshot) klynge.

Gå? Bash! Møt shell-operatøren (gjennomgang og videorapport fra KubeCon EU'2020)

Basert på all denne informasjonen kan en grunnleggende algoritme utvikles. Det itererer over alle navneområder og:

  • hvis hasLabel saker true for gjeldende navneområde:
    • sammenligner den globale hemmeligheten med den lokale:
      • hvis de er like, gjør det ingenting;
      • hvis de er forskjellige - utføres kubectl replace eller create;
  • hvis hasLabel saker false for gjeldende navneområde:
    • sørger for at Secret ikke er i det gitte navnerommet:
      • hvis den lokale hemmeligheten er til stede, slett den ved å bruke kubectl delete;
      • hvis den lokale hemmeligheten ikke oppdages, gjør den ingenting.

Gå? Bash! Møt shell-operatøren (gjennomgang og videorapport fra KubeCon EU'2020)

Implementering av algoritmen i Bash du kan laste ned i vår depoter med eksempler.

Det var slik vi var i stand til å lage en enkel Kubernetes-kontroller ved å bruke 35 linjer med YAML-konfigurasjon og omtrent samme mengde Bash-kode! Skalloperatørens jobb er å knytte dem sammen.

Kopiering av hemmeligheter er imidlertid ikke det eneste bruksområdet for verktøyet. Her er noen flere eksempler for å vise hva han er i stand til.

Eksempel 1: Gjør endringer i ConfigMap

La oss se på en distribusjon som består av tre pods. Pods bruker ConfigMap til å lagre noen konfigurasjoner. Da podene ble lansert, var ConfigMap i en viss tilstand (la oss kalle det v.1). Følgelig bruker alle pods denne spesielle versjonen av ConfigMap.

La oss nå anta at ConfigMap har endret seg (v.2). Podene vil imidlertid bruke den forrige versjonen av ConfigMap (v.1):

Gå? Bash! Møt shell-operatøren (gjennomgang og videorapport fra KubeCon EU'2020)

Hvordan kan jeg få dem til å bytte til den nye ConfigMap (v.2)? Svaret er enkelt: bruk en mal. La oss legge til en sjekksumkommentar til delen template Implementeringskonfigurasjoner:

Gå? Bash! Møt shell-operatøren (gjennomgang og videorapport fra KubeCon EU'2020)

Som et resultat vil denne sjekksummen bli registrert i alle pods, og den vil være den samme som for Deployment. Nå trenger du bare å oppdatere merknaden når ConfigMap endres. Og shell-operatoren kommer godt med i dette tilfellet. Alt du trenger å gjøre er å programmere en krok som vil abonnere på ConfigMap og oppdatere sjekksummen.

Hvis brukeren gjør endringer i ConfigMap, vil shell-operatøren legge merke til dem og beregne kontrollsummen på nytt. Deretter vil magien til Kubernetes spille inn: orkestratoren vil drepe poden, lage en ny, vente til den blir Ready, og går videre til neste. Som et resultat vil Deployment synkronisere og bytte til den nye versjonen av ConfigMap.

Gå? Bash! Møt shell-operatøren (gjennomgang og videorapport fra KubeCon EU'2020)

Eksempel 2: Arbeide med egendefinerte ressursdefinisjoner

Som du vet lar Kubernetes deg lage egendefinerte typer objekter. For eksempel kan du lage slag MysqlDatabase. La oss si at denne typen har to metadataparametere: name и namespace.

apiVersion: example.com/v1alpha1
kind: MysqlDatabase
metadata:
  name: foo
  namespace: bar

Vi har en Kubernetes-klynge med forskjellige navneområder der vi kan lage MySQL-databaser. I dette tilfellet kan shell-operator brukes til å spore ressurser MysqlDatabase, koble dem til MySQL-serveren og synkronisere de ønskede og observerte tilstandene til klyngen.

Gå? Bash! Møt shell-operatøren (gjennomgang og videorapport fra KubeCon EU'2020)

Eksempel 3: Klyngenettverksovervåking

Som du vet, er bruk av ping den enkleste måten å overvåke et nettverk på. I dette eksemplet vil vi vise hvordan man implementerer slik overvåking ved hjelp av shell-operator.

Først av alt må du abonnere på noder. Skaloperatøren trenger navnet og IP-adressen til hver node. Med deres hjelp vil han pinge disse nodene.

configVersion: v1
kubernetes:
- name: nodes
  apiVersion: v1
  kind: Node
  jqFilter: |
    {
      name: .metadata.name,
      ip: (
       .status.addresses[] |  
        select(.type == "InternalIP") |
        .address
      )
    }
  group: main
  keepFullObjectsInMemory: false
  executeHookOnEvent: []
schedule:
- name: every_minute
  group: main
  crontab: "* * * * *"

Parameter executeHookOnEvent: [] hindrer kroken i å kjøre som svar på enhver hendelse (det vil si som svar på endring, tilføying, sletting av noder). Imidlertid, han vil kjøre (og oppdater listen over noder) Planlagt - hvert minutt, som foreskrevet av feltet schedule.

Nå oppstår spørsmålet, hvordan vet vi om problemer som pakketap? La oss ta en titt på koden:

function __main__() {
  for i in $(seq 0 "$(context::jq -r '(.snapshots.nodes | length) - 1')"); do
    node_name="$(context::jq -r '.snapshots.nodes['"$i"'].filterResult.name')"
    node_ip="$(context::jq -r '.snapshots.nodes['"$i"'].filterResult.ip')"
    packets_lost=0
    if ! ping -c 1 "$node_ip" -t 1 ; then
      packets_lost=1
    fi
    cat >> "$METRICS_PATH" <<END
      {
        "name": "node_packets_lost",
        "add": $packets_lost,
        "labels": {
          "node": "$node_name"
        }
      }
END
  done
}

Vi itererer gjennom listen over noder, henter navn og IP-adresser, pinger dem og sender resultatene til Prometheus. Shell-operatør kan eksportere beregninger til Prometheus, og lagrer dem i en fil som ligger i henhold til banen spesifisert i miljøvariabelen $METRICS_PATH.

Her så du kan lage en operatør for enkel nettverksovervåking i en klynge.

Kømekanisme

Denne artikkelen ville være ufullstendig uten å beskrive en annen viktig mekanisme innebygd i skalloperatøren. Tenk deg at den utfører en slags krok som svar på en hendelse i klyngen.

  • Hva skjer hvis det samtidig skjer noe i klyngen? en til begivenhet?
  • Vil shell-operator kjøre en annen forekomst av kroken?
  • Hva om for eksempel fem hendelser skjer i klyngen samtidig?
  • Vil shell-operatøren behandle dem parallelt?
  • Hva med forbrukte ressurser som minne og CPU?

Heldigvis har shell-operator en innebygd kømekanisme. Alle hendelser settes i kø og behandles sekvensielt.

La oss illustrere dette med eksempler. La oss si at vi har to kroker. Den første begivenheten går til den første kroken. Når behandlingen er fullført, går køen fremover. De neste tre hendelsene blir omdirigert til den andre kroken - de fjernes fra køen og legges inn i den i en "bunt". Det er hook mottar en rekke hendelser — eller, mer presist, en rekke bindende kontekster.

Også disse arrangementer kan kombineres til en stor. Parameteren er ansvarlig for dette group i bindingskonfigurasjonen.

Gå? Bash! Møt shell-operatøren (gjennomgang og videorapport fra KubeCon EU'2020)

Du kan opprette et hvilket som helst antall køer/hooks og deres forskjellige kombinasjoner. For eksempel kan én kø fungere med to kroker, eller omvendt.

Gå? Bash! Møt shell-operatøren (gjennomgang og videorapport fra KubeCon EU'2020)

Alt du trenger å gjøre er å konfigurere feltet deretter queue i bindingskonfigurasjonen. Hvis et kønavn ikke er spesifisert, kjører kroken på standardkøen (default). Denne kømekanismen lar deg løse alle ressursstyringsproblemer når du arbeider med kroker.

Konklusjon

Vi forklarte hva en shell-operator er, viste hvordan den kan brukes til å raskt og enkelt lage Kubernetes-operatører, og ga flere eksempler på bruken.

Detaljert informasjon om skalloperatøren, samt en rask veiledning om hvordan du bruker den, er tilgjengelig i den tilsvarende repositories på GitHub. Ikke nøl med å kontakte oss med spørsmål: du kan diskutere dem i en spesiell Telegram gruppe (på russisk) eller på dette forumet (på engelsk).

Og hvis du likte det, er vi alltid glade for å se nye utgaver/PR/stjerner på GitHub, hvor du forresten kan finne andre interessante prosjekter. Blant dem er det verdt å fremheve addon-operatør, som er storebroren til shell-operator. Dette verktøyet bruker Helm-diagrammer for å installere tillegg, kan levere oppdateringer og overvåke ulike diagramparametere/verdier, kontrollerer installasjonsprosessen til diagrammer, og kan også endre dem som svar på hendelser i klyngen.

Gå? Bash! Møt shell-operatøren (gjennomgang og videorapport fra KubeCon EU'2020)

Videoer og lysbilder

Video fra forestillingen (~23 minutter):


Presentasjon av rapporten:

PS

Les også på bloggen vår:

Kilde: www.habr.com

Legg til en kommentar