Gaan? Bas! Maak kennis met de shell-operator (review en videoverslag van KubeCon EU'2020)

Dit jaar was de belangrijkste Europese Kubernetes-conferentie – KubeCon + CloudNativeCon Europe 2020 – virtueel. Een dergelijke verandering in format weerhield ons er echter niet van om ons lang geplande rapport “Go? Bas! Maak kennis met de Shell-operator” gewijd aan ons Open Source-project shell-operator.

Dit artikel, geïnspireerd op de lezing, presenteert een aanpak om het proces van het maken van operators voor Kubernetes te vereenvoudigen en laat zien hoe u met minimale inspanning uw eigen operators kunt maken met behulp van een shell-operator.

Gaan? Bas! Maak kennis met de shell-operator (review en videoverslag van KubeCon EU'2020)

Even voorstellen video van het rapport (~23 minuten in het Engels, merkbaar informatiever dan het artikel) en het belangrijkste fragment daaruit in tekstvorm. Gaan!

Bij Flant optimaliseren en automatiseren we voortdurend alles. Vandaag zullen we het hebben over een ander spannend concept. Ontmoeten: cloud-native shell-scripting!

Laten we echter beginnen met de context waarin dit allemaal gebeurt: Kubernetes.

Kubernetes API en controllers

De API in Kubernetes kan worden weergegeven als een soort bestandsserver met mappen voor elk type object. Objecten (bronnen) op deze server worden weergegeven door YAML-bestanden. Daarnaast beschikt de server over een basis-API waarmee je drie dingen kunt doen:

  • ontvangen hulpbron op soort en naam;
  • verandering bron (in dit geval slaat de server alleen "juiste" objecten op - alle onjuist gevormde objecten of bedoeld voor andere mappen worden weggegooid);
  • volgen voor de bron (in dit geval ontvangt de gebruiker onmiddellijk de huidige/bijgewerkte versie).

Kubernetes fungeert dus als een soort bestandsserver (voor YAML-manifesten) met drie basismethoden (ja, er zijn er eigenlijk nog meer, maar die laten we voorlopig achterwege).

Gaan? Bas! Maak kennis met de shell-operator (review en videoverslag van KubeCon EU'2020)

Het probleem is dat de server alleen informatie kan opslaan. Om het te laten werken heb je nodig controleur - het op een na belangrijkste en fundamentele concept in de wereld van Kubernetes.

Er zijn twee hoofdtypen controllers. De eerste haalt informatie uit Kubernetes, verwerkt deze volgens geneste logica en stuurt deze terug naar K8s. De tweede haalt informatie uit Kubernetes, maar verandert, in tegenstelling tot het eerste type, de status van sommige externe bronnen.

Laten we het proces van het maken van een implementatie in Kubernetes eens nader bekijken:

  • Implementatiecontroller (inbegrepen in kube-controller-manager) ontvangt informatie over de implementatie en maakt een ReplicaSet.
  • ReplicaSet maakt op basis van deze informatie twee replica's (twee peulen), maar deze peulen zijn nog niet gepland.
  • De planner plant peulen en voegt knooppuntinformatie toe aan hun YAML's.
  • Kubelets brengen wijzigingen aan in een externe bron (bijvoorbeeld Docker).

Vervolgens wordt deze hele reeks in omgekeerde volgorde herhaald: de kubelet controleert de containers, berekent de status van de pod en stuurt deze terug. De ReplicaSet-controller ontvangt de status en werkt de status van de replicaset bij. Hetzelfde gebeurt met de Deployment Controller en de gebruiker krijgt eindelijk de bijgewerkte (huidige) status.

Gaan? Bas! Maak kennis met de shell-operator (review en videoverslag van KubeCon EU'2020)

Shell-operator

Het blijkt dat Kubernetes gebaseerd is op het gezamenlijke werk van verschillende controllers (Kubernetes-operators zijn ook controllers). De vraag rijst: hoe kunt u met minimale inspanning uw eigen operator creëren? En hier komt degene die we hebben ontwikkeld te hulp shell-operator. Hiermee kunnen systeembeheerders hun eigen verklaringen maken met behulp van vertrouwde methoden.

Gaan? Bas! Maak kennis met de shell-operator (review en videoverslag van KubeCon EU'2020)

Eenvoudig voorbeeld: geheimen kopiëren

Laten we naar een eenvoudig voorbeeld kijken.

Laten we zeggen dat we een Kubernetes-cluster hebben. Het heeft een naamruimte default met een geheim mysecret. Daarnaast zijn er nog andere naamruimten in het cluster. Aan sommige is een specifiek label bevestigd. Ons doel is om Secret naar naamruimten met een label te kopiëren.

De taak wordt gecompliceerd door het feit dat er nieuwe naamruimten in het cluster kunnen verschijnen, en sommige daarvan kunnen dit label hebben. Aan de andere kant, wanneer het label wordt verwijderd, moet Secret ook worden verwijderd. Daarnaast kan het geheim zelf ook veranderen: in dit geval moet het nieuwe geheim worden gekopieerd naar alle naamruimten met labels. Als Secret per ongeluk in een naamruimte wordt verwijderd, moet onze operator deze onmiddellijk herstellen.

Nu de taak is geformuleerd, is het tijd om deze te gaan implementeren met behulp van de shell-operator. Maar eerst is het de moeite waard om een ​​paar woorden te zeggen over de shell-operator zelf.

Hoe shell-operator werkt

Net als andere workloads in Kubernetes draait shell-operator in zijn eigen pod. In deze pod in de directory /hooks uitvoerbare bestanden worden opgeslagen. Dit kunnen scripts zijn in Bash, Python, Ruby, etc. Dergelijke uitvoerbare bestanden noemen we hooks (haken).

Gaan? Bas! Maak kennis met de shell-operator (review en videoverslag van KubeCon EU'2020)

Shell-operator abonneert zich op Kubernetes-evenementen en voert deze hooks uit als reactie op de gebeurtenissen die we nodig hebben.

Gaan? Bas! Maak kennis met de shell-operator (review en videoverslag van KubeCon EU'2020)

Hoe weet de shell-operator welke hook hij moet gebruiken en wanneer? Het punt is dat elke haak twee fasen heeft. Tijdens het opstarten voert de shell-operator alle hooks uit met een argument --config Dit is de configuratiefase. En daarna worden haken op de normale manier gelanceerd - als reactie op de gebeurtenissen waaraan ze verbonden zijn. In het laatste geval ontvangt de hook de bindende context (bindende context) - gegevens in JSON-indeling, waarover we hieronder meer in detail zullen praten.

Een operator maken in Bash

Nu zijn we klaar voor implementatie. Om dit te doen, moeten we twee functies schrijven (we raden trouwens aan bibliotheek shell_lib, wat het schrijven van hooks in Bash enorm vereenvoudigt):

  • de eerste is nodig voor de configuratiefase - deze geeft de bindende context weer;
  • de tweede bevat de hoofdlogica van de hook.

#!/bin/bash

source /shell_lib.sh

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

function __main__() {
  # THE LOGIC
}

hook::run "$@"

De volgende stap is om te beslissen welke objecten we nodig hebben. In ons geval moeten we het volgende bijhouden:

  • brongeheim voor wijzigingen;
  • alle naamruimten in het cluster, zodat u weet aan welke een label is gekoppeld;
  • doelgeheimen om ervoor te zorgen dat ze allemaal synchroon zijn met het brongeheim.

Abonneer u op de geheime bron

De bindingsconfiguratie hiervoor is vrij eenvoudig. Met de naam geven wij aan geïnteresseerd te zijn in Secret mysecret in naamruimte default:

Gaan? Bas! Maak kennis met de shell-operator (review en videoverslag van 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

Als gevolg hiervan wordt de hook geactiveerd wanneer het brongeheim verandert (src_secret) en ontvang de volgende bindende context:

Gaan? Bas! Maak kennis met de shell-operator (review en videoverslag van KubeCon EU'2020)

Zoals u kunt zien, bevat deze de naam en het gehele object.

Naamruimten bijhouden

Nu moet u zich abonneren op naamruimten. Om dit te doen, specificeren we de volgende bindingsconfiguratie:

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

Zoals u kunt zien, is er een nieuw veld in de configuratie verschenen met de naam jqFilter. Zoals de naam al doet vermoeden, jqFilter filtert alle onnodige informatie eruit en maakt een nieuw JSON-object aan met de velden die voor ons interessant zijn. Een hook met een vergelijkbare configuratie ontvangt de volgende bindingscontext:

Gaan? Bas! Maak kennis met de shell-operator (review en videoverslag van KubeCon EU'2020)

Het bevat een array filterResults voor elke naamruimte in het cluster. Booleaanse variabele hasLabel geeft aan of een label aan een bepaalde naamruimte is gekoppeld. Selector keepFullObjectsInMemory: false geeft aan dat het niet nodig is om volledige objecten in het geheugen te bewaren.

Doelgeheimen volgen

We abonneren ons op alle geheimen waarvoor een annotatie is opgegeven managed-secret: "yes" (Dit zijn onze doelen 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

In dit geval jqFilter filtert alle informatie eruit behalve de naamruimte en parameter resourceVersion. De laatste parameter is aan de annotatie doorgegeven bij het maken van het geheim: hiermee kunt u versies van geheimen vergelijken en actueel houden.

Een op deze manier geconfigureerde hook zal, wanneer hij wordt uitgevoerd, de drie hierboven beschreven bindingscontexten ontvangen. Ze kunnen worden gezien als een soort momentopname (momentopname) TROS.

Gaan? Bas! Maak kennis met de shell-operator (review en videoverslag van KubeCon EU'2020)

Op basis van al deze informatie kan een basisalgoritme worden ontwikkeld. Het herhaalt zich over alle naamruimten en:

  • indien hasLabel heeft de betekenis true voor de huidige naamruimte:
    • vergelijkt het mondiale geheim met het lokale geheim:
      • als ze hetzelfde zijn, doet het niets;
      • als ze verschillen - wordt uitgevoerd kubectl replace of create;
  • indien hasLabel heeft de betekenis false voor de huidige naamruimte:
    • zorgt ervoor dat Secret zich niet in de opgegeven naamruimte bevindt:
      • als het lokale geheim aanwezig is, verwijder het dan met kubectl delete;
      • als het lokale geheim niet wordt gedetecteerd, doet het niets.

Gaan? Bas! Maak kennis met de shell-operator (review en videoverslag van KubeCon EU'2020)

Implementatie van het algoritme in Bash kunt u downloaden in onze archieven met voorbeelden.

Zo konden we een eenvoudige Kubernetes-controller maken met 35 regels YAML-configuratie en ongeveer dezelfde hoeveelheid Bash-code! De taak van de shell-operator is om ze met elkaar te verbinden.

Het kopiëren van geheimen is echter niet het enige toepassingsgebied van het hulpprogramma. Hier zijn nog een paar voorbeelden om te laten zien waartoe hij in staat is.

Voorbeeld 1: Wijzigingen aanbrengen in ConfigMap

Laten we eens kijken naar een implementatie die uit drie pods bestaat. Pods gebruiken ConfigMap om bepaalde configuraties op te slaan. Toen de pods werden gelanceerd, bevond ConfigMap zich in een bepaalde staat (laten we het v.1 noemen). Dienovereenkomstig gebruiken alle pods deze specifieke versie van ConfigMap.

Laten we nu aannemen dat de ConfigMap is gewijzigd (v.2). De pods gebruiken echter de vorige versie van ConfigMap (v.1):

Gaan? Bas! Maak kennis met de shell-operator (review en videoverslag van KubeCon EU'2020)

Hoe kan ik ervoor zorgen dat ze overschakelen naar de nieuwe ConfigMap (v.2)? Het antwoord is simpel: gebruik een sjabloon. Laten we een checksum-annotatie aan de sectie toevoegen template Implementatieconfiguraties:

Gaan? Bas! Maak kennis met de shell-operator (review en videoverslag van KubeCon EU'2020)

Als gevolg hiervan wordt deze controlesom in alle pods geregistreerd en is deze dezelfde als die van Implementatie. Nu hoeft u alleen maar de annotatie bij te werken wanneer de ConfigMap verandert. En de shell-operator komt in dit geval goed van pas. Het enige dat u hoeft te doen, is programmeren een hook die zich abonneert op de ConfigMap en de checksum bijwerkt.

Als de gebruiker wijzigingen aanbrengt in de ConfigMap, zal de shell-operator deze opmerken en de controlesom opnieuw berekenen. Waarna de magie van Kubernetes in het spel zal komen: de orkestrator zal de pod doden, een nieuwe maken, wachten tot deze wordt Ready, en gaat door naar de volgende. Als gevolg hiervan wordt Deployment gesynchroniseerd en overgeschakeld naar de nieuwe versie van ConfigMap.

Gaan? Bas! Maak kennis met de shell-operator (review en videoverslag van KubeCon EU'2020)

Voorbeeld 2: Werken met aangepaste resourcedefinities

Zoals u weet, kunt u met Kubernetes aangepaste typen objecten maken. U kunt bijvoorbeeld soort creëren MysqlDatabase. Laten we zeggen dat dit type twee metadataparameters heeft: name и namespace.

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

We hebben een Kubernetes-cluster met verschillende naamruimten waarin we MySQL-databases kunnen maken. In dit geval kan shell-operator worden gebruikt om bronnen te volgen MysqlDatabase, verbinden ze met de MySQL-server en synchroniseren de gewenste en waargenomen statussen van het cluster.

Gaan? Bas! Maak kennis met de shell-operator (review en videoverslag van KubeCon EU'2020)

Voorbeeld 3: Clusternetwerkmonitoring

Zoals u weet is het gebruik van ping de eenvoudigste manier om een ​​netwerk te controleren. In dit voorbeeld laten we zien hoe u een dergelijke monitoring kunt implementeren met behulp van de shell-operator.

Allereerst moet u zich abonneren op knooppunten. De shell-operator heeft de naam en het IP-adres van elk knooppunt nodig. Met hun hulp zal hij deze knooppunten pingen.

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: [] voorkomt dat de hook wordt uitgevoerd als reactie op een gebeurtenis (dat wil zeggen, als reactie op het wijzigen, toevoegen of verwijderen van knooppunten). Echter, hij zal rennen (en update de lijst met knooppunten) Gepland - elke minuut, zoals voorgeschreven door het veld schedule.

Nu rijst de vraag: hoe weten we precies over problemen zoals pakketverlies? Laten we de code eens bekijken:

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
}

We doorlopen de lijst met knooppunten, halen hun namen en IP-adressen op, pingen ze en sturen de resultaten naar Prometheus. Shell-operator kan statistieken exporteren naar Prometheus, en slaat ze op in een bestand dat zich bevindt volgens het pad dat is opgegeven in de omgevingsvariabele $METRICS_PATH.

Hier dus u kunt een operator maken voor eenvoudige netwerkmonitoring in een cluster.

Wachtrijmechanisme

Dit artikel zou onvolledig zijn zonder een ander belangrijk mechanisme te beschrijven dat in de shell-operator is ingebouwd. Stel je voor dat het een soort hook uitvoert als reactie op een gebeurtenis in het cluster.

  • Wat gebeurt er als er tegelijkertijd iets gebeurt in het cluster? nog een evenement?
  • Zal de shell-operator nog een exemplaar van de hook uitvoeren?
  • Wat als er bijvoorbeeld vijf gebeurtenissen tegelijk in het cluster plaatsvinden?
  • Zal de shell-operator ze parallel verwerken?
  • Hoe zit het met de verbruikte bronnen zoals geheugen en CPU?

Gelukkig heeft shell-operator een ingebouwd wachtrijmechanisme. Alle gebeurtenissen worden in de wachtrij geplaatst en opeenvolgend verwerkt.

Laten we dit illustreren met voorbeelden. Laten we zeggen dat we twee haken hebben. De eerste gebeurtenis gaat naar de eerste hook. Zodra de verwerking is voltooid, gaat de wachtrij verder. De volgende drie gebeurtenissen worden doorgestuurd naar de tweede hook - ze worden uit de wachtrij verwijderd en in een "bundel" ingevoerd. Dat is hook ontvangt een reeks gebeurtenissen – of, preciezer gezegd, een reeks bindende contexten.

Deze ook evenementen kunnen worden gecombineerd tot één groot evenement. De parameter is hiervoor verantwoordelijk group in de bindingsconfiguratie.

Gaan? Bas! Maak kennis met de shell-operator (review en videoverslag van KubeCon EU'2020)

U kunt een willekeurig aantal wachtrijen/hooks en hun verschillende combinaties maken. Eén wachtrij kan bijvoorbeeld met twee hooks werken, of omgekeerd.

Gaan? Bas! Maak kennis met de shell-operator (review en videoverslag van KubeCon EU'2020)

Het enige dat u hoeft te doen, is het veld dienovereenkomstig configureren queue in de bindingsconfiguratie. Als er geen wachtrijnaam is opgegeven, wordt de hook uitgevoerd op de standaardwachtrij (default). Met dit wachtrijmechanisme kunt u alle problemen met resourcebeheer volledig oplossen bij het werken met hooks.

Conclusie

We legden uit wat een shell-operator is, lieten zien hoe je hiermee snel en moeiteloos Kubernetes-operators kunt maken en gaven verschillende voorbeelden van het gebruik ervan.

Gedetailleerde informatie over de shell-operator, evenals een korte handleiding over het gebruik ervan, zijn beschikbaar in de bijbehorende opslagplaatsen op GitHub. Aarzel niet om ons te contacteren met vragen: u kunt ze bespreken in een special Telegramgroep (in het Russisch) of in dit forum (in Engels).

En als je het leuk vond, zijn we altijd blij om nieuwe nummers/PR/sterren op GitHub te zien, waar je trouwens andere kunt vinden interessante projecten. Onder hen is het de moeite waard om te benadrukken add-on-operator, de grote broer van shell-operator. Dit hulpprogramma gebruikt Helm-diagrammen om add-ons te installeren, kan updates leveren en verschillende diagramparameters/-waarden bewaken, regelt het installatieproces van diagrammen en kan deze ook wijzigen als reactie op gebeurtenissen in het cluster.

Gaan? Bas! Maak kennis met de shell-operator (review en videoverslag van KubeCon EU'2020)

Video's en dia's

Video van de voorstelling (~23 minuten):


Presentatie van het rapport:

PS

Lees ook op onze blog:

Bron: www.habr.com

Voeg een reactie