Megy? Bash! Ismerje meg a shell-operátort (áttekintés és videóriport a KubeCon EU'2020-ról)

Ebben az évben a fő európai Kubernetes konferencia – a KubeCon + CloudNativeCon Europe 2020 – virtuális volt. A formátum ilyen változása azonban nem akadályozott meg bennünket abban, hogy elkészítsük régóta tervezett „Go? Bash! Ismerje meg a Shell-operátort” nyílt forráskódú projektünknek szentelt shell-operátor.

Ez a cikk, amelyet a beszélgetés ihletett, bemutat egy megközelítést a Kubernetes operátorok létrehozásának folyamatának egyszerűsítésére, és bemutatja, hogyan készítheti el saját magát minimális erőfeszítéssel shell-operátor használatával.

Megy? Bash! Ismerje meg a shell-operátort (áttekintés és videóriport a KubeCon EU'2020-ról)

Bemutatkozik videó a riportról (~23 perc angolul, érezhetően informatívabb, mint a cikk) és a fő kivonat belőle szöveges formában. Megy!

A Flantnál folyamatosan mindent optimalizálunk és automatizálunk. Ma egy másik izgalmas koncepcióról fogunk beszélni. Találkozik: felhő-natív shell scripting!

Kezdjük azonban azzal a kontextussal, amelyben mindez megtörténik: Kubernetes.

Kubernetes API és vezérlők

A Kubernetes API-ja egyfajta fájlszerverként ábrázolható, minden objektumtípushoz könyvtárral. Az objektumokat (erőforrásokat) ezen a kiszolgálón YAML-fájlok képviselik. Ezenkívül a szerver rendelkezik egy alap API-val, amely három dolgot tesz lehetővé:

  • fogadni erőforrás fajtája és neve szerint;
  • változás erőforrás (ebben az esetben a szerver csak a „helyes” objektumokat tárolja - minden helytelenül kialakított vagy más könyvtáraknak szánt objektumot eldobnak);
  • vágány az erőforráshoz (ebben az esetben a felhasználó azonnal megkapja annak aktuális/frissített verzióját).

Így a Kubernetes egyfajta fájlszerverként működik (YAML manifesztek esetén) három alapvető metódussal (igen, valójában vannak mások is, de ezeket most kihagyjuk).

Megy? Bash! Ismerje meg a shell-operátort (áttekintés és videóriport a KubeCon EU'2020-ról)

A probléma az, hogy a szerver csak információkat tud tárolni. Ahhoz, hogy működjön, szüksége van rá ellenőr - a második legfontosabb és legalapvetőbb koncepció a Kubernetes világában.

A vezérlőknek két fő típusa van. Az első információt vesz a Kubernetestől, feldolgozza a beágyazott logika szerint, és visszaküldi a K8s-nak. A második a Kubernetestől veszi át az információkat, de az első típussal ellentétben megváltoztatja néhány külső erőforrás állapotát.

Nézzük meg közelebbről a Kubernetes telepítésének folyamatát:

  • Telepítési vezérlő (tartozék kube-controller-manager) információkat kap a telepítésről, és létrehoz egy ReplicaSet-et.
  • A ReplicaSet ezen információk alapján két replikát (két sorba rendezést) hoz létre, de ezek még nincsenek ütemezve.
  • Az ütemező ütemezi a podokat, és csomópontinformációkat ad hozzá a YAML-ekhez.
  • A kubeletek módosítják a külső erőforrásokat (mondjuk a Dockert).

Ezután ez az egész sorozat fordított sorrendben megismétlődik: a kubelet ellenőrzi a konténereket, kiszámítja a pod állapotát és visszaküldi. A ReplicaSet vezérlő fogadja a replikakészlet állapotát és frissíti az állapotát. Ugyanez történik a Deployment Controllerrel is, és a felhasználó végre megkapja a frissített (aktuális) állapotot.

Megy? Bash! Ismerje meg a shell-operátort (áttekintés és videóriport a KubeCon EU'2020-ról)

Shell-kezelő

Kiderült, hogy a Kubernetes különböző vezérlők közös munkáján alapul (a Kubernetes operátorok is kontrollerek). Felmerül a kérdés, hogyan lehet minimális ráfordítással létrehozni saját operátort? És itt az általunk kifejlesztett segít shell-operátor. Lehetővé teszi a rendszergazdák számára, hogy saját utasításokat készítsenek ismert módszerekkel.

Megy? Bash! Ismerje meg a shell-operátort (áttekintés és videóriport a KubeCon EU'2020-ról)

Egyszerű példa: titkok másolása

Nézzünk egy egyszerű példát.

Tegyük fel, hogy van egy Kubernetes-fürtünk. Van egy névtér default valami Titokkal mysecret. Ezen kívül más névterek is vannak a fürtben. Néhányukra külön címke van ragasztva. Célunk, hogy a Titkot címkével ellátott névterekbe másoljuk.

A feladatot nehezíti, hogy új névterek jelenhetnek meg a fürtben, és ezek egy része ezt a címkét viselheti. Másrészt a címke törlésekor a Secretet is törölni kell. Ezen kívül maga a Secret is változhat: ebben az esetben az új Secretet minden címkés névtérbe át kell másolni. Ha a Secret véletlenül törlődik bármely névtérben, kezelőnknek azonnal vissza kell állítania azt.

Most, hogy a feladat megfogalmazódott, ideje elkezdeni a megvalósítást a shell-operator segítségével. De először érdemes néhány szót ejteni magáról a shell-operatorról.

Hogyan működik a shell-operátor

A Kubernetes többi munkaterheléséhez hasonlóan a shell-operator is saját podban fut. Ebben a podban a könyvtárban /hooks futtatható fájlok tárolódnak. Ezek lehetnek Bash, Python, Ruby stb. szkriptek. Az ilyen futtatható fájlokat hoooknak (horgok).

Megy? Bash! Ismerje meg a shell-operátort (áttekintés és videóriport a KubeCon EU'2020-ról)

A Shell-operator előfizet a Kubernetes-eseményekre, és futtatja ezeket a hook-okat válaszul azokra az eseményekre, amelyekre szükségünk van.

Megy? Bash! Ismerje meg a shell-operátort (áttekintés és videóriport a KubeCon EU'2020-ról)

Honnan tudja a shell-operátor, hogy melyik horgot futtassa és mikor? A lényeg az, hogy minden horognak két szakasza van. Indítás közben a shell-operátor minden hook-ot argumentummal futtat le --config Ez a konfigurációs szakasz. Ezután a horgok a szokásos módon indulnak el - válaszul azokra az eseményekre, amelyekhez kapcsolódnak. Az utóbbi esetben a horog megkapja a kötési környezetet (kötelező kontextus) - JSON formátumú adatok, amelyekről az alábbiakban részletesebben fogunk beszélni.

Operátor készítése Bashban

Most készen állunk a megvalósításra. Ehhez két függvényt kell írnunk (mellesleg javasoljuk könyvtár shell_lib, ami nagymértékben leegyszerűsíti a Bash-ban írt horgokat):

  • az első a konfigurációs szakaszhoz szükséges - megjeleníti a kötési kontextust;
  • a második a horog fő logikáját tartalmazza.

#!/bin/bash

source /shell_lib.sh

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

function __main__() {
  # THE LOGIC
}

hook::run "$@"

A következő lépés annak eldöntése, hogy milyen tárgyakra van szükségünk. Esetünkben követnünk kell:

  • forrás titka a változtatásokhoz;
  • minden névteret a fürtben, hogy tudja, melyikhez van címkézve;
  • cél titkokat, hogy azok mind szinkronban legyenek a forrástitkokkal.

Iratkozzon fel a titkos forrásra

A kötés beállítása meglehetősen egyszerű. A névvel jelezzük, hogy a Titok iránt érdeklődünk mysecret névtérben default:

Megy? Bash! Ismerje meg a shell-operátort (áttekintés és videóriport a KubeCon EU'2020-ról)

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

Ennek eredményeként a horog akkor aktiválódik, amikor a forrás titkossága megváltozik (src_secret), és megkapja a következő kötelező szövegkörnyezetet:

Megy? Bash! Ismerje meg a shell-operátort (áttekintés és videóriport a KubeCon EU'2020-ról)

Amint látja, tartalmazza a nevet és a teljes objektumot.

A névterek nyomon követése

Most elő kell iratkoznia a névterekre. Ehhez a következő kötési konfigurációt adjuk meg:

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

Mint látható, egy új mező jelent meg a konfigurációban a névvel jqFilter. Ahogy a neve is sugallja, jqFilter kiszűr minden felesleges információt, és létrehoz egy új JSON-objektumot a számunkra érdekes mezőkkel. Egy hasonló konfigurációjú horog a következő kötési környezetet kapja:

Megy? Bash! Ismerje meg a shell-operátort (áttekintés és videóriport a KubeCon EU'2020-ról)

Egy tömböt tartalmaz filterResults a fürt minden névteréhez. Logikai változó hasLabel jelzi, hogy egy adott névtérhez van-e csatolva címke. Választó keepFullObjectsInMemory: false azt jelzi, hogy nincs szükség a teljes objektumok tárolására a memóriában.

A célpontok nyomon követése

Feliratkozunk minden olyan titokra, amelyhez megjegyzés tartozik managed-secret: "yes" (ezek a célpontunk 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

Ebben az esetben jqFilter minden információt kiszűr, kivéve a névteret és a paramétert resourceVersion. Az utolsó paramétert a titkosítás létrehozásakor adtuk át a megjegyzésnek: ez lehetővé teszi a titkok változatainak összehasonlítását és naprakészen tartását.

Az így konfigurált hook végrehajtásakor megkapja a fent leírt három kötési környezetet. Felfoghatók egyfajta pillanatfelvételnek (snapshot) klaszter.

Megy? Bash! Ismerje meg a shell-operátort (áttekintés és videóriport a KubeCon EU'2020-ról)

Mindezen információk alapján kidolgozható egy alapvető algoritmus. Minden névtéren ismétlődik, és:

  • ha hasLabel ügyek true az aktuális névtérhez:
    • összehasonlítja a globális titkot a helyivel:
      • ha egyformák, az nem tesz semmit;
      • ha különböznek – végrehajtja kubectl replace vagy create;
  • ha hasLabel ügyek false az aktuális névtérhez:
    • gondoskodik arról, hogy a Secret ne legyen a megadott névtérben:
      • ha a helyi titok jelen van, törölje a használatával kubectl delete;
      • ha a helyi titkot nem észleli, akkor nem csinál semmit.

Megy? Bash! Ismerje meg a shell-operátort (áttekintés és videóriport a KubeCon EU'2020-ról)

Az algoritmus megvalósítása Bash-ban letöltheti nálunk adattárak példákkal.

Így tudtunk létrehozni egy egyszerű Kubernetes vezérlőt 35 soros YAML konfigurációval és nagyjából ugyanennyi Bash kóddal! A shell-operátor feladata, hogy összekapcsolja őket.

A titkok másolása azonban nem az egyetlen alkalmazási területe a segédprogramnak. Íme még néhány példa, hogy megmutassa, mire képes.

1. példa: A ConfigMap módosítása

Nézzünk egy három podból álló telepítést. A podok a ConfigMap alkalmazást használják bizonyos konfigurációk tárolására. A pod-ok elindításakor a ConfigMap egy bizonyos állapotban volt (nevezzük v.1-nek). Ennek megfelelően minden pod a ConfigMap adott verzióját használja.

Most tegyük fel, hogy a ConfigMap megváltozott (v.2). A pod-ok azonban a ConfigMap korábbi verzióját (1. verzió) fogják használni:

Megy? Bash! Ismerje meg a shell-operátort (áttekintés és videóriport a KubeCon EU'2020-ról)

Hogyan érhetem el őket, hogy váltsanak az új ConfigMap-re (v.2)? A válasz egyszerű: használjon sablont. Adjunk hozzá egy ellenőrző összeg megjegyzést a szakaszhoz template Telepítési konfigurációk:

Megy? Bash! Ismerje meg a shell-operátort (áttekintés és videóriport a KubeCon EU'2020-ról)

Ennek eredményeként ez az ellenőrzőösszeg minden podban regisztrálásra kerül, és ugyanaz lesz, mint a telepítésnél. Most már csak frissítenie kell a megjegyzést, amikor a ConfigMap megváltozik. A shell-operátor pedig ebben az esetben jól jön. Csak programozni kell egy horog, amely előfizet a ConfigMap-re és frissíti az ellenőrző összeget.

Ha a felhasználó módosítja a ConfigMap-et, a shell-operátor észreveszi ezeket, és újraszámolja az ellenőrző összeget. Ezután a Kubernetes varázsa lép működésbe: a hangszerelő megöli a tokot, újat hoz létre, megvárja, míg azzá válik. Ready, és továbblép a következőre. Ennek eredményeként a Deployment szinkronizálódik, és átvált a ConfigMap új verziójára.

Megy? Bash! Ismerje meg a shell-operátort (áttekintés és videóriport a KubeCon EU'2020-ról)

2. példa: Egyéni erőforrás-definíciók használata

Mint tudják, a Kubernetes lehetővé teszi egyedi típusú objektumok létrehozását. Például létrehozhat kedvességet MysqlDatabase. Tegyük fel, hogy ennek a típusnak két metaadat-paramétere van: name и namespace.

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

Van egy Kubernetes-fürtünk különböző névterekkel, amelyben MySQL adatbázisokat hozhatunk létre. Ebben az esetben a shell-operátor használható az erőforrások nyomon követésére MysqlDatabase, összekapcsolja őket a MySQL szerverrel és szinkronizálja a fürt kívánt és megfigyelt állapotait.

Megy? Bash! Ismerje meg a shell-operátort (áttekintés és videóriport a KubeCon EU'2020-ról)

3. példa: Cluster Network Monitoring

Mint tudják, a ping a legegyszerűbb módja a hálózat figyelésének. Ebben a példában bemutatjuk, hogyan lehet végrehajtani egy ilyen megfigyelést a shell-operator segítségével.

Először is elő kell fizetnie a csomópontokra. A shell operátornak szüksége van minden csomópont nevére és IP-címére. Segítségükkel pingelni fogja ezeket a csomópontokat.

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: "* * * * *"

Paraméter executeHookOnEvent: [] megakadályozza, hogy a hook bármilyen eseményre válaszul (azaz csomópontok módosítására, hozzáadására, törlésére) reagálva futhasson. Azonban ő futni fog (és frissítse a csomópontok listáját) Ütemezett - percenként, a mezőny előírásai szerint schedule.

Felmerül a kérdés, hogy honnan tudunk pontosan olyan problémákról, mint a csomagvesztés? Nézzük a kódot:

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
}

Megismételjük a csomópontok listáját, megkapjuk a nevüket és az IP-címüket, megpingáljuk őket, és elküldjük az eredményeket a Prometheusnak. A Shell-operátor metrikákat exportálhat a Prometheusba, elmentve azokat egy fájlba, amely a környezeti változóban megadott elérési út szerint található $METRICS_PATH.

Itt van operátort készíthet az egyszerű hálózatfelügyelethez egy fürtben.

Sorbaállási mechanizmus

Ez a cikk hiányos lenne, ha nem írna le egy másik fontos, a shell-operátorba épített mechanizmust. Képzelje el, hogy valamilyen hook-ot hajt végre a fürtben lévő eseményre válaszul.

  • Mi történik, ha ugyanakkor valami történik a klaszterben? még egy esemény?
  • A shell-operator lefuttatja a hook másik példányát?
  • Mi van, ha mondjuk öt esemény történik egyszerre a klaszterben?
  • A shell-operátor párhuzamosan dolgozza fel őket?
  • Mi a helyzet az elhasznált erőforrásokkal, például a memóriával és a CPU-val?

Szerencsére a shell-operator beépített sorban állási mechanizmussal rendelkezik. Az összes esemény sorba kerül, és egymás után kerül feldolgozásra.

Illusztráljuk ezt példákkal. Tegyük fel, hogy két horgunk van. Az első esemény az első horoghoz megy. A feldolgozás befejezése után a sor továbblép. A következő három eseményt átirányítják a második horoghoz - eltávolítják a sorból, és egy „kötegben” bekerülnek abba. Azaz hook események sorát fogadja – vagy pontosabban kötődő összefüggések tömbje.

Ezeket is eseményeket egy nagyba lehet összevonni. A paraméter felelős ezért group a kötési konfigurációban.

Megy? Bash! Ismerje meg a shell-operátort (áttekintés és videóriport a KubeCon EU'2020-ról)

Létrehozhat tetszőleges számú sort/hookot és ezek különféle kombinációit. Például egy sor működhet két horoggal, vagy fordítva.

Megy? Bash! Ismerje meg a shell-operátort (áttekintés és videóriport a KubeCon EU'2020-ról)

Mindössze annyit kell tennie, hogy ennek megfelelően konfigurálja a mezőt queue a kötési konfigurációban. Ha nincs megadva sornév, a hook az alapértelmezett sorban fut (default). Ez a sorban állási mechanizmus lehetővé teszi, hogy teljesen megoldja az összes erőforrás-kezelési problémát, amikor horgokkal dolgozik.

Következtetés

Elmagyaráztuk, mi is az a shell-operátor, bemutattuk, hogyan lehet vele gyorsan és könnyedén létrehozni Kubernetes operátorokat, és számos példát adtunk a használatára.

Részletes információk a shell-operátorról, valamint egy gyors útmutató a használatáról a megfelelő adattárak a GitHubon. Kérdéseivel forduljon hozzánk bizalommal: külön megbeszélheti őket Távirat csoport (oroszul) vagy in ez a fórum (angolul).

És ha tetszett, mindig örömmel látunk új számokat/PR/sztárokat a GitHubon, ahol egyébként másokat is találhatsz érdekes projektek. Közülük érdemes kiemelni addon-operátor, ami a shell-operator nagy testvére. Ez a segédprogram Helm diagramokat használ a kiegészítők telepítéséhez, frissítéseket szállíthat és figyelheti a különböző diagramparamétereket/értékeket, vezérli a diagramok telepítési folyamatát, és módosíthatja azokat a fürt eseményei alapján.

Megy? Bash! Ismerje meg a shell-operátort (áttekintés és videóriport a KubeCon EU'2020-ról)

Videók és diák

Videó az előadásról (~23 perc):


A jelentés bemutatása:

PS

Olvassa el blogunkon is:

Forrás: will.com

Hozzászólás