Gå? Våldsamt slag! Möt skaloperatören (recension och videorapport från KubeCon EU'2020)

I år var den största europeiska Kubernetes-konferensen - KubeCon + CloudNativeCon Europe 2020 - virtuell. En sådan förändring av formatet hindrade oss dock inte från att leverera vår sedan länge planerade rapport ”Gå? Våldsamt slag! Möt Shell-operatören” dedikerad till vårt Open Source-projekt skal-operatör.

Den här artikeln, inspirerad av föredraget, presenterar ett tillvägagångssätt för att förenkla processen att skapa operatörer för Kubernetes och visar hur du kan göra din egen med minimal ansträngning med hjälp av en skal-operator.

Gå? Våldsamt slag! Möt skaloperatören (recension och videorapport från KubeCon EU'2020)

Introducerar video av rapporten (~23 minuter på engelska, märkbart mer informativ än artikeln) och huvudutdraget ur den i textform. Gå!

På Flant optimerar och automatiserar vi ständigt allt. Idag ska vi prata om ett annat spännande koncept. Träffa: molnbaserad skalskript!

Men låt oss börja med sammanhanget där allt detta händer: Kubernetes.

Kubernetes API och kontroller

API:et i Kubernetes kan representeras som en slags filserver med kataloger för varje typ av objekt. Objekt (resurser) på denna server representeras av YAML-filer. Dessutom har servern ett grundläggande API som låter dig göra tre saker:

  • motta resurs efter dess slag och namn;
  • förändra resurs (i det här fallet lagrar servern endast "korrekta" objekt - alla felaktigt utformade eller avsedda för andra kataloger kasseras);
  • Spår för resursen (i detta fall får användaren omedelbart sin nuvarande/uppdaterade version).

Sålunda fungerar Kubernetes som en slags filserver (för YAML-manifest) med tre grundläggande metoder (ja, det finns faktiskt andra, men vi kommer att utelämna dem tills vidare).

Gå? Våldsamt slag! Möt skaloperatören (recension och videorapport från KubeCon EU'2020)

Problemet är att servern bara kan lagra information. För att få det att fungera behöver du styrenhet - det näst viktigaste och grundläggande konceptet i Kubernetes-världen.

Det finns två huvudtyper av styrenheter. Den första tar information från Kubernetes, bearbetar den enligt kapslad logik och returnerar den till K8s. Den andra tar information från Kubernetes, men, till skillnad från den första typen, ändrar tillståndet för vissa externa resurser.

Låt oss ta en närmare titt på processen att skapa en distribution i Kubernetes:

  • Implementeringskontroller (ingår i kube-controller-manager) tar emot information om distribution och skapar en replikuppsättning.
  • ReplicaSet skapar två repliker (två pods) baserat på denna information, men dessa pods är inte schemalagda än.
  • Schemaläggaren schemalägger poddar och lägger till nodinformation till deras YAML.
  • Kubelets gör ändringar i en extern resurs (säg Docker).

Sedan upprepas hela denna sekvens i omvänd ordning: kubelet kontrollerar behållarna, beräknar poddens status och skickar tillbaka den. ReplicaSet-styrenheten tar emot statusen och uppdaterar replikuppsättningens tillstånd. Samma sak händer med Deployment Controller och användaren får äntligen den uppdaterade (aktuella) statusen.

Gå? Våldsamt slag! Möt skaloperatören (recension och videorapport från KubeCon EU'2020)

Shell-operatör

Det visar sig att Kubernetes bygger på olika kontrollanters gemensamma arbete (Kubernetes-operatörer är också kontrollanter). Frågan uppstår, hur skapar man en egen operatör med minimal ansträngning? Och här kommer den vi utvecklade till undsättning skal-operatör. Det låter systemadministratörer skapa sina egna uttalanden med bekanta metoder.

Gå? Våldsamt slag! Möt skaloperatören (recension och videorapport från KubeCon EU'2020)

Enkelt exempel: kopiera hemligheter

Låt oss titta på ett enkelt exempel.

Låt oss säga att vi har ett Kubernetes-kluster. Den har ett namnutrymme default med någon hemlighet mysecret. Dessutom finns det andra namnutrymmen i klustret. Vissa av dem har en specifik etikett fäst vid dem. Vårt mål är att kopiera Secret till namnutrymmen med en etikett.

Uppgiften kompliceras av det faktum att nya namnutrymmen kan dyka upp i klustret, och vissa av dem kan ha denna etikett. Å andra sidan, när etiketten raderas, bör Secret också tas bort. Utöver detta kan själva hemligheten också ändras: i det här fallet måste den nya hemligheten kopieras till alla namnutrymmen med etiketter. Om Secret av misstag raderas i något namnområde, bör vår operatör återställa det omedelbart.

Nu när uppgiften har formulerats är det dags att börja implementera den med hjälp av skaloperatorn. Men först är det värt att säga några ord om själva skaloperatören.

Hur shell-operator fungerar

Liksom andra arbetsbelastningar i Kubernetes körs shell-operator i sin egen pod. I denna pod i katalogen /hooks körbara filer lagras. Dessa kan vara skript i Bash, Python, Ruby, etc. Vi kallar sådana körbara filer hooks (krokar).

Gå? Våldsamt slag! Möt skaloperatören (recension och videorapport från KubeCon EU'2020)

Shell-operator prenumererar på Kubernetes-evenemang och kör dessa hooks som svar på de händelser som vi behöver.

Gå? Våldsamt slag! Möt skaloperatören (recension och videorapport från KubeCon EU'2020)

Hur vet skaloperatören vilken krok som ska köras och när? Poängen är att varje krok har två steg. Under uppstart kör skal-operatören alla krokar med ett argument --config Detta är konfigurationsstadiet. Och efter det lanseras krokar på normalt sätt - som svar på de händelser som de är knutna till. I det senare fallet får kroken det bindande sammanhanget (bindande sammanhang) - data i JSON-format, som vi kommer att prata om mer i detalj nedan.

Att göra en operatör i Bash

Nu är vi redo för implementering. För att göra detta måste vi skriva två funktioner (vi rekommenderar förresten biblioteket shell_lib, vilket avsevärt förenklar skrivkrokar i Bash):

  • den första behövs för konfigurationssteget - den visar det bindande sammanhanget;
  • den andra innehåller krokens huvudlogik.

#!/bin/bash

source /shell_lib.sh

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

function __main__() {
  # THE LOGIC
}

hook::run "$@"

Nästa steg är att bestämma vilka föremål vi behöver. I vårt fall måste vi spåra:

  • källhemlighet för ändringar;
  • alla namnutrymmen i klustret, så att du vet vilka som har en etikett kopplad till dem;
  • målhemligheter för att säkerställa att de alla är synkroniserade med källhemligheten.

Prenumerera på den hemliga källan

Bindande konfiguration för det är ganska enkel. Vi anger att vi är intresserade av Secret med namnet mysecret i namnutrymmet default:

Gå? Våldsamt slag! Möt skaloperatören (recension och videorapport från 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 ett resultat kommer kroken att triggas när källhemligheten ändras (src_secret) och få följande bindande sammanhang:

Gå? Våldsamt slag! Möt skaloperatören (recension och videorapport från KubeCon EU'2020)

Som du kan se innehåller den namnet och hela objektet.

Hålla reda på namnutrymmen

Nu måste du prenumerera på namnutrymmen. För att göra detta anger vi följande bindningskonfiguration:

- 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 ett nytt fält dykt upp i konfigurationen med namnet jqFilter. Som namnet antyder, jqFilter filtrerar bort all onödig information och skapar ett nytt JSON-objekt med de fält som är av intresse för oss. En krok med en liknande konfiguration kommer att få följande bindande sammanhang:

Gå? Våldsamt slag! Möt skaloperatören (recension och videorapport från KubeCon EU'2020)

Den innehåller en array filterResults för varje namnområde i klustret. Boolesk variabel hasLabel anger om en etikett är kopplad till ett givet namnområde. Väljare keepFullObjectsInMemory: false indikerar att det inte finns något behov av att behålla kompletta objekt i minnet.

Spåra målhemligheter

Vi prenumererar på alla hemligheter som har en anteckning specificerad managed-secret: "yes" (detta är vårt 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 det här fallet jqFilter filtrerar bort all information utom namnområdet och parametern resourceVersion. Den sista parametern skickades till anteckningen när hemligheten skapades: den låter dig jämföra versioner av hemligheter och hålla dem uppdaterade.

En hook konfigurerad på detta sätt kommer, när den exekveras, att ta emot de tre bindningskontexterna som beskrivs ovan. De kan ses som ett slags ögonblicksbild (ögonblicksbild) kluster.

Gå? Våldsamt slag! Möt skaloperatören (recension och videorapport från KubeCon EU'2020)

Baserat på all denna information kan en grundläggande algoritm utvecklas. Det itererar över alla namnrymder och:

  • om hasLabel frågor true för det aktuella namnområdet:
    • jämför den globala hemligheten med den lokala:
      • om de är lika, gör det ingenting;
      • om de skiljer sig - exekverar kubectl replace eller create;
  • om hasLabel frågor false för det aktuella namnområdet:
    • ser till att Secret inte finns i det angivna namnutrymmet:
      • om den lokala hemligheten finns, radera den med hjälp av kubectl delete;
      • om den lokala hemligheten inte upptäcks gör den ingenting.

Gå? Våldsamt slag! Möt skaloperatören (recension och videorapport från KubeCon EU'2020)

Implementering av algoritmen i Bash kan du ladda ner i vår arkiv med exempel.

Det var så vi kunde skapa en enkel Kubernetes-kontroller med 35 rader YAML-konfiguration och ungefär samma mängd Bash-kod! Skaloperatörens uppgift är att länka ihop dem.

Att kopiera hemligheter är dock inte det enda användningsområdet för verktyget. Här är några fler exempel för att visa vad han kan.

Exempel 1: Göra ändringar i ConfigMap

Låt oss titta på en distribution som består av tre kapslar. Pods använder ConfigMap för att lagra viss konfiguration. När poddarna lanserades var ConfigMap i ett visst tillstånd (låt oss kalla det v.1). Följaktligen använder alla poddar just den här versionen av ConfigMap.

Låt oss nu anta att ConfigMap har ändrats (v.2). Poddarna kommer dock att använda den tidigare versionen av ConfigMap (v.1):

Gå? Våldsamt slag! Möt skaloperatören (recension och videorapport från KubeCon EU'2020)

Hur kan jag få dem att byta till nya ConfigMap (v.2)? Svaret är enkelt: använd en mall. Låt oss lägga till en kontrollsummannotering till avsnittet template Implementeringskonfigurationer:

Gå? Våldsamt slag! Möt skaloperatören (recension och videorapport från KubeCon EU'2020)

Som ett resultat kommer denna kontrollsumma att registreras i alla pods, och den kommer att vara densamma som för Deployment. Nu behöver du bara uppdatera anteckningen när ConfigMap ändras. Och skal-operatören kommer väl till pass i det här fallet. Allt du behöver göra är att programmera en hook som kommer att prenumerera på ConfigMap och uppdatera kontrollsumman.

Om användaren gör ändringar i ConfigMap kommer skal-operatören att lägga märke till dem och räkna om kontrollsumman. Därefter kommer Kubernetes magi att träda i kraft: orkestratorn kommer att döda poden, skapa en ny, vänta på att den blir Ready, och går vidare till nästa. Som ett resultat kommer Deployment att synkronisera och byta till den nya versionen av ConfigMap.

Gå? Våldsamt slag! Möt skaloperatören (recension och videorapport från KubeCon EU'2020)

Exempel 2: Arbeta med anpassade resursdefinitioner

Som du vet låter Kubernetes dig skapa anpassade typer av objekt. Till exempel kan du skapa slag MysqlDatabase. Låt oss säga att den här typen har två metadataparametrar: name и namespace.

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

Vi har ett Kubernetes-kluster med olika namnutrymmen där vi kan skapa MySQL-databaser. I det här fallet kan skal-operatör användas för att spåra resurser MysqlDatabase, ansluta dem till MySQL-servern och synkronisera de önskade och observerade tillstånden i klustret.

Gå? Våldsamt slag! Möt skaloperatören (recension och videorapport från KubeCon EU'2020)

Exempel 3: Klusternätverksövervakning

Som du vet är ping det enklaste sättet att övervaka ett nätverk. I det här exemplet kommer vi att visa hur man implementerar sådan övervakning med shell-operator.

Först och främst måste du prenumerera på noder. Skaloperatören behöver namnet och IP-adressen för varje nod. Med deras hjälp kommer han att pinga dessa 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: [] förhindrar att kroken körs som svar på någon händelse (det vill säga som svar på att ändra, lägga till, ta bort noder). Men han kommer att köras (och uppdatera listan med noder) Schemalagt - varje minut, som föreskrivs av fältet schedule.

Nu uppstår frågan, hur exakt vet vi om problem som paketförlust? Låt 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 itererar genom listan med noder, får deras namn och IP-adresser, pingar dem och skickar resultaten till Prometheus. Shell-operatör kan exportera mätvärden till Prometheus, sparar dem i en fil som ligger enligt sökvägen som anges i miljövariabeln $METRICS_PATH.

Här så du kan skapa en operatör för enkel nätverksövervakning i ett kluster.

Kömekanism

Denna artikel skulle vara ofullständig utan att beskriva en annan viktig mekanism inbyggd i skal-operatören. Föreställ dig att den utför någon form av hook som svar på en händelse i klustret.

  • Vad händer om det samtidigt händer något i klustret? en till händelse?
  • Kommer skal-operatören att köra en annan instans av kroken?
  • Tänk om, säg, fem händelser händer i klustret samtidigt?
  • Kommer skaloperatören att behandla dem parallellt?
  • Hur är det med förbrukade resurser som minne och CPU?

Lyckligtvis har skal-operatören en inbyggd kömekanism. Alla händelser köas och bearbetas sekventiellt.

Låt oss illustrera detta med exempel. Låt oss säga att vi har två krokar. Den första händelsen går till den första kroken. När bearbetningen är klar flyttas kön framåt. De nästa tre händelserna omdirigeras till den andra kroken - de tas bort från kön och läggs in i den i ett "paket". Det är hook tar emot en rad händelser — eller, mer exakt, en rad bindande sammanhang.

Även dessa evenemang kan kombineras till en stor. Parametern är ansvarig för detta group i den bindande konfigurationen.

Gå? Våldsamt slag! Möt skaloperatören (recension och videorapport från KubeCon EU'2020)

Du kan skapa valfritt antal köer/hooks och deras olika kombinationer. Till exempel kan en kö fungera med två krokar, eller vice versa.

Gå? Våldsamt slag! Möt skaloperatören (recension och videorapport från KubeCon EU'2020)

Allt du behöver göra är att konfigurera fältet därefter queue i den bindande konfigurationen. Om ett könamn inte anges körs kroken på standardkön (default). Denna kömekanism låter dig helt lösa alla resurshanteringsproblem när du arbetar med krokar.

Slutsats

Vi förklarade vad en skal-operatör är, visade hur den kan användas för att snabbt och enkelt skapa Kubernetes-operatörer och gav flera exempel på dess användning.

Detaljerad information om skal-operatören, samt en snabb handledning om hur man använder den, finns i motsvarande repositories på GitHub. Tveka inte att kontakta oss med frågor: du kan diskutera dem i en speciell Telegram grupp (på ryska) eller på detta forum (på engelska).

Och om du gillade det är vi alltid glada att se nya nummer/PR/stjärnor på GitHub, där du förresten kan hitta andra intressanta projekt. Bland dem är det värt att lyfta fram addon-operatör, som är storebror till shell-operator. Det här verktyget använder Helm-diagram för att installera tillägg, kan leverera uppdateringar och övervaka olika diagramparametrar/värden, kontrollerar installationsprocessen för diagram och kan även modifiera dem som svar på händelser i klustret.

Gå? Våldsamt slag! Möt skaloperatören (recension och videorapport från KubeCon EU'2020)

Videor och bilder

Video från föreställningen (~23 minuter):


Presentation av rapporten:

PS

Läs även på vår blogg:

Källa: will.com

Lägg en kommentar