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

I år var den vigtigste europæiske Kubernetes-konference - KubeCon + CloudNativeCon Europe 2020 - virtuel. En sådan ændring i format forhindrede os dog ikke i at levere vores længe planlagte rapport "Go? Bash! Mød Shell-operatøren" dedikeret til vores Open Source-projekt shell-operatør.

Denne artikel, inspireret af foredraget, præsenterer en tilgang til at forenkle processen med at oprette operatører til Kubernetes og viser, hvordan du kan lave din egen med minimal indsats ved hjælp af en shell-operator.

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

Introduktion video af rapporten (~23 minutter på engelsk, mærkbart mere informativ end artiklen) og hoveduddraget fra den i tekstform. Gå!

Hos Flant optimerer og automatiserer vi hele tiden alt. I dag vil vi tale om et andet spændende koncept. Møde: cloud-native shell-scripting!

Lad os dog starte med den kontekst, hvori alt dette sker: Kubernetes.

Kubernetes API og controllere

API'et i Kubernetes kan repræsenteres som en slags filserver med mapper for hver type objekt. Objekter (ressourcer) på denne server er repræsenteret af YAML-filer. Derudover har serveren en grundlæggende API, der giver dig mulighed for at gøre tre ting:

  • modtage ressource efter sin art og navn;
  • lave om ressource (i dette tilfælde gemmer serveren kun "korrekte" objekter - alle forkert formede eller beregnet til andre mapper kasseres);
  • spore for ressourcen (i dette tilfælde modtager brugeren straks sin aktuelle/opdaterede version).

Kubernetes fungerer således som en slags filserver (til YAML-manifester) med tre grundlæggende metoder (ja, faktisk er der andre, men vi udelader dem indtil videre).

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

Problemet er, at serveren kun kan gemme information. For at få det til at fungere har du brug for controller - det næstvigtigste og mest grundlæggende koncept i Kubernetes verden.

Der er to hovedtyper af controllere. Den første tager information fra Kubernetes, behandler den i henhold til indlejret logik og returnerer den til K8s. Den anden tager information fra Kubernetes, men ændrer, i modsætning til den første type, tilstanden for nogle eksterne ressourcer.

Lad os se nærmere på processen med at oprette en implementering i Kubernetes:

  • Implementeringscontroller (inkluderet i kube-controller-manager) modtager oplysninger om implementering og opretter et ReplicaSet.
  • ReplicaSet opretter to replikaer (to pods) baseret på disse oplysninger, men disse pods er ikke planlagt endnu.
  • Planlæggeren planlægger pods og tilføjer nodeoplysninger til deres YAML'er.
  • Kubelets foretager ændringer i en ekstern ressource (f.eks. Docker).

Så gentages hele denne sekvens i omvendt rækkefølge: kubelet tjekker beholderne, beregner pod'ens status og sender den tilbage. ReplicaSet-controlleren modtager status og opdaterer replikasættets tilstand. Det samme sker med Deployment Controller, og brugeren får endelig den opdaterede (aktuelle) status.

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

Shell-operatør

Det viser sig, at Kubernetes er baseret på forskellige controlleres fælles arbejde (Kubernetes-operatører er også controllere). Spørgsmålet opstår, hvordan man skaber sin egen operatør med minimal indsats? Og her kommer den, vi udviklede, til undsætning shell-operatør. Det giver systemadministratorer mulighed for at oprette deres egne erklæringer ved hjælp af velkendte metoder.

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

Simpelt eksempel: kopiering af hemmeligheder

Lad os se på et simpelt eksempel.

Lad os sige, at vi har en Kubernetes-klynge. Den har et navneområde default med en eller anden hemmelighed mysecret. Derudover er der andre navnerum i klyngen. Nogle af dem har en specifik etiket knyttet til dem. Vores mål er at kopiere Secret til navneområder med en etiket.

Opgaven kompliceres af, at der kan opstå nye navnerum i klyngen, og nogle af dem kan have denne etiket. På den anden side, når etiketten slettes, skal Secret også slettes. Ud over dette kan selve hemmeligheden også ændre sig: i dette tilfælde skal den nye hemmelighed kopieres til alle navnerum med etiketter. Hvis Secret ved et uheld slettes i et navneområde, bør vores operatør gendanne det med det samme.

Nu hvor opgaven er formuleret, er det tid til at begynde at implementere den ved hjælp af shell-operatoren. Men først er det værd at sige et par ord om selve shell-operatøren.

Hvordan shell-operator fungerer

Som andre arbejdsbelastninger i Kubernetes kører shell-operator i sin egen pod. I denne pod i mappen /hooks eksekverbare filer gemmes. Disse kan være scripts i Bash, Python, Ruby osv. Vi kalder sådanne eksekverbare filer hooks (kroge).

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

Shell-operatør abonnerer på Kubernetes-begivenheder og kører disse hooks som svar på de begivenheder, vi har brug for.

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

Hvordan ved shell-operatøren, hvilken krog han skal køre og hvornår? Pointen er, at hver krog har to trin. Under opstart kører shell-operatøren alle kroge med et argument --config Dette er konfigurationsstadiet. Og efter det lanceres kroge på normal vis - som svar på de begivenheder, som de er knyttet til. I sidstnævnte tilfælde modtager krogen den bindende kontekst (bindende sammenhæng) - data i JSON-format, som vi vil tale mere om nedenfor.

At lave en operatør i Bash

Nu er vi klar til implementering. For at gøre dette skal vi skrive to funktioner (i øvrigt anbefaler vi biblioteket shell_lib, hvilket i høj grad forenkler skrivekroge i Bash):

  • den første er nødvendig for konfigurationsfasen - den viser den bindende kontekst;
  • den anden indeholder krogens hovedlogik.

#!/bin/bash

source /shell_lib.sh

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

function __main__() {
  # THE LOGIC
}

hook::run "$@"

Det næste skridt er at beslutte, hvilke genstande vi har brug for. I vores tilfælde skal vi spore:

  • kildehemmelighed for ændringer;
  • alle navneområder i klyngen, så du ved, hvilke der har en etiket knyttet til dem;
  • målhemmeligheder for at sikre, at de alle er synkroniseret med kildehemmeligheden.

Abonner på den hemmelige kilde

Bindende konfiguration for det er ret simpelt. Vi angiver, at vi er interesserede i Secret med navnet mysecret i navnerummet default:

Gå? Bash! Mød shell-operatøren (gennemgang 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 krogen blive udløst, når kildehemmeligheden ændres (src_secret) og modtage følgende bindende kontekst:

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

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

Holder styr på navneområder

Nu skal du abonnere på navneområder. For at gøre dette specificerer vi følgende bindingskonfiguration:

- 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, er der dukket et nyt felt op i konfigurationen med navnet jqFilter. Som navnet antyder, jqFilter filtrerer al unødvendig information fra og opretter et nyt JSON-objekt med de felter, der er af interesse for os. En krog med en lignende konfiguration vil modtage følgende bindende kontekst:

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

Den indeholder et array filterResults for hvert navneområde i klyngen. Boolesk variabel hasLabel angiver, om en etiket er knyttet til et givet navneområde. Vælger keepFullObjectsInMemory: false angiver, at der ikke er behov for at opbevare komplette objekter i hukommelsen.

Sporing af målhemmeligheder

Vi abonnerer på alle hemmeligheder, der har en annotation specificeret managed-secret: "yes" (disse er vores mål 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 tilfælde jqFilter frafiltrerer alle oplysninger undtagen navneområdet og parameteren resourceVersion. Den sidste parameter blev overført til annoteringen, da hemmeligheden blev oprettet: den giver dig mulighed for at sammenligne versioner af hemmeligheder og holde dem opdateret.

En hook konfigureret på denne måde vil, når den udføres, modtage de tre bindingskontekster beskrevet ovenfor. De kan opfattes som en slags øjebliksbillede (snapshot) klynge.

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

Baseret på al denne information kan en grundlæggende algoritme udvikles. Det itererer over alle navneområder og:

  • hvis hasLabel sager true for det aktuelle navneområde:
    • sammenligner den globale hemmelighed med den lokale:
      • hvis de er ens, gør det ingenting;
      • hvis de er forskellige - udfører kubectl replace eller create;
  • hvis hasLabel sager false for det aktuelle navneområde:
    • sørger for, at Secret ikke er i det givne navneområde:
      • hvis den lokale hemmelighed er til stede, skal du slette den vha kubectl delete;
      • hvis den lokale hemmelighed ikke opdages, gør den intet.

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

Implementering af algoritmen i Bash du kan downloade i vores arkiver med eksempler.

Det var sådan, vi var i stand til at skabe en simpel Kubernetes-controller ved hjælp af 35 linjer med YAML-konfiguration og omtrent den samme mængde Bash-kode! Shell-operatørens opgave er at forbinde dem sammen.

Kopiering af hemmeligheder er dog ikke det eneste anvendelsesområde for værktøjet. Her er et par flere eksempler for at vise, hvad han er i stand til.

Eksempel 1: Ændringer i ConfigMap

Lad os se på en implementering bestående af tre pods. Pods bruger ConfigMap til at gemme nogle konfigurationer. Da pods blev lanceret, var ConfigMap i en bestemt tilstand (lad os kalde det v.1). Derfor bruger alle pods denne særlige version af ConfigMap.

Lad os nu antage, at ConfigMap er ændret (v.2). Pod'erne vil dog bruge den tidligere version af ConfigMap (v.1):

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

Hvordan kan jeg få dem til at skifte til det nye ConfigMap (v.2)? Svaret er enkelt: Brug en skabelon. Lad os tilføje en kontrolsum-anmærkning til sektionen template Implementeringskonfigurationer:

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

Som et resultat heraf vil denne kontrolsum blive registreret i alle pods, og den vil være den samme som for Deployment. Nu skal du bare opdatere annotationen, når ConfigMap ændres. Og shell-operatoren er praktisk i dette tilfælde. Alt du skal gøre er at programmere en hook, der vil abonnere på ConfigMap og opdatere kontrolsummen.

Hvis brugeren foretager ændringer i ConfigMap, vil shell-operatøren bemærke dem og genberegne kontrolsummen. Hvorefter Kubernetes magi kommer i spil: orkestratoren vil dræbe poden, skabe en ny, vente på, at den bliver Ready, og går videre til den næste. Som et resultat vil Deployment synkronisere og skifte til den nye version af ConfigMap.

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

Eksempel 2: Arbejde med brugerdefinerede ressourcedefinitioner

Som du ved, giver Kubernetes dig mulighed for at oprette brugerdefinerede typer objekter. For eksempel kan du skabe slags MysqlDatabase. Lad os sige, at denne type har to metadataparametre: name и namespace.

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

Vi har en Kubernetes-klynge med forskellige navnerum, hvor vi kan oprette MySQL-databaser. I dette tilfælde kan shell-operator bruges til at spore ressourcer MysqlDatabase, forbinder dem til MySQL-serveren og synkroniserer de ønskede og observerede tilstande i klyngen.

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

Eksempel 3: Klyngenetværksovervågning

Som du ved, er brug af ping den enkleste måde at overvåge et netværk på. I dette eksempel vil vi vise, hvordan man implementerer sådan overvågning ved hjælp af shell-operator.

Først og fremmest skal du abonnere på noder. Skaloperatøren har brug for navnet og IP-adressen på hver node. Med deres hjælp vil han pinge disse noder.

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: [] forhindrer krogen i at køre som reaktion på enhver hændelse (det vil sige som reaktion på ændring, tilføjelse eller sletning af noder). Dog han vil køre (og opdater listen over noder) Planlagt - hvert minut, som foreskrevet af feltet schedule.

Nu opstår spørgsmålet, hvordan ved vi præcist om problemer som pakketab? Lad os tage et kig 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 gennem listen over noder, får deres navne og IP-adresser, pinger dem og sender resultaterne til Prometheus. Shell-operatør kan eksportere metrics til Prometheus, gemmer dem i en fil, der er placeret i henhold til stien angivet i miljøvariablen $METRICS_PATH.

Her så du kan lave en operatør til simpel netværksovervågning i en klynge.

Kømekanisme

Denne artikel ville være ufuldstændig uden at beskrive en anden vigtig mekanisme indbygget i shell-operatoren. Forestil dig, at den udfører en slags hook som svar på en begivenhed i klyngen.

  • Hvad sker der, hvis der samtidig sker noget i klyngen? en til begivenhed?
  • Vil shell-operator køre en anden forekomst af krogen?
  • Hvad hvis f.eks. fem begivenheder sker i klyngen på én gang?
  • Vil shell-operatøren behandle dem parallelt?
  • Hvad med forbrugte ressourcer såsom hukommelse og CPU?

Heldigvis har shell-operator en indbygget kømekanisme. Alle hændelser sættes i kø og behandles sekventielt.

Lad os illustrere dette med eksempler. Lad os sige, at vi har to kroge. Den første begivenhed går til den første hook. Når behandlingen er færdig, bevæger køen sig fremad. De næste tre begivenheder omdirigeres til den anden hook - de fjernes fra køen og indtastes i den i en "bundt". Det er hook modtager en række begivenheder — eller mere præcist en række bindende sammenhænge.

Også disse begivenheder kan kombineres til én stor. Parameteren er ansvarlig for dette group i bindingskonfigurationen.

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

Du kan oprette et hvilket som helst antal køer/hooks og deres forskellige kombinationer. For eksempel kan én kø fungere med to kroge eller omvendt.

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

Alt du skal gøre er at konfigurere feltet i overensstemmelse hermed queue i bindingskonfigurationen. Hvis et kønavn ikke er angivet, kører krogen på standardkøen (default). Denne kømekanisme giver dig mulighed for fuldstændigt at løse alle ressourcestyringsproblemer, når du arbejder med kroge.

Konklusion

Vi forklarede, hvad en shell-operator er, viste, hvordan den kan bruges til hurtigt og ubesværet at oprette Kubernetes-operatører, og gav flere eksempler på dens brug.

Detaljeret information om shell-operatoren samt en hurtig tutorial om, hvordan man bruger den, er tilgængelig i den tilsvarende repositories på GitHub. Tøv ikke med at kontakte os med spørgsmål: du kan diskutere dem i en særlig Telegram gruppe (på russisk) eller på dette forum (på engelsk).

Og kunne du lide det, er vi altid glade for at se nye numre/PR/stjerner på GitHub, hvor du i øvrigt kan finde andre interessante projekter. Blandt dem er det værd at fremhæve addon-operatør, som er storebror til shell-operator. Dette værktøj bruger Helm-diagrammer til at installere tilføjelser, kan levere opdateringer og overvåge forskellige diagramparametre/værdier, styrer installationsprocessen af ​​diagrammer og kan også ændre dem som reaktion på hændelser i klyngen.

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

Videoer og dias

Video fra forestillingen (~23 minutter):


Præsentation af rapporten:

PS

Læs også på vores blog:

Kilde: www.habr.com

Tilføj en kommentar