Merge? Bash! Faceți cunoștință cu operatorul shell (recenzie și raport video de la KubeCon EU'2020)

Anul acesta, principala conferință europeană Kubernetes - KubeCon + CloudNativeCon Europe 2020 - a fost virtuală. Cu toate acestea, o astfel de schimbare a formatului nu ne-a împiedicat să livrăm raportul nostru de mult planificat „Mergi? Bash! Meet the Shell-operator” dedicat proiectului nostru Open Source operator-shell.

Acest articol, inspirat de discuție, prezintă o abordare a simplificării procesului de creare a operatorilor pentru Kubernetes și arată cum vă puteți face singur cu efort minim folosind un operator shell.

Merge? Bash! Faceți cunoștință cu operatorul shell (recenzie și raport video de la KubeCon EU'2020)

Introducand video cu raportul (~23 de minute în engleză, vizibil mai informativ decât articolul) și extrasul principal din acesta sub formă de text. Merge!

La Flant optimizăm și automatizăm în mod constant totul. Astăzi vom vorbi despre un alt concept interesant. Întâlni: scripting shell nativ în cloud!

Totuși, să începem cu contextul în care se întâmplă toate acestea: Kubernetes.

API și controlere Kubernetes

API-ul din Kubernetes poate fi reprezentat ca un fel de server de fișiere cu directoare pentru fiecare tip de obiect. Obiectele (resursele) de pe acest server sunt reprezentate de fișiere YAML. În plus, serverul are un API de bază care vă permite să faceți trei lucruri:

  • obține resursă după tipul și numele ei;
  • Schimbare resursă (în acest caz, serverul stochează doar obiecte „corecte” - toate cele formate incorect sau destinate altor directoare sunt aruncate);
  • urma pentru resursă (în acest caz, utilizatorul primește imediat versiunea curentă/actualizată).

Astfel, Kubernetes acționează ca un fel de server de fișiere (pentru manifestele YAML) cu trei metode de bază (da, de fapt există și altele, dar le vom omite deocamdată).

Merge? Bash! Faceți cunoștință cu operatorul shell (recenzie și raport video de la KubeCon EU'2020)

Problema este că serverul poate stoca doar informații. Ca să funcționeze aveți nevoie controlor - al doilea concept ca important și fundamental din lumea Kubernetes.

Există două tipuri principale de controlere. Primul preia informații din Kubernetes, le procesează conform logicii imbricate și le returnează la K8s. Al doilea preia informații de la Kubernetes, dar, spre deosebire de primul tip, schimbă starea unor resurse externe.

Să aruncăm o privire mai atentă asupra procesului de creare a unei implementări în Kubernetes:

  • Controller de implementare (inclus în kube-controller-manager) primește informații despre Deployment și creează un ReplicaSet.
  • ReplicaSet creează două replici (două poduri) pe baza acestor informații, dar aceste poduri nu sunt încă programate.
  • Programatorul programează poduri și adaugă informații despre noduri la YAML-urile lor.
  • Kubelets efectuează modificări unei resurse externe (să spunem Docker).

Apoi toată această secvență se repetă în ordine inversă: kubeletul verifică containerele, calculează starea podului și îl trimite înapoi. Controlerul ReplicaSet primește starea și actualizează starea setului de replici. Același lucru se întâmplă cu controlerul de implementare și utilizatorul primește în sfârșit starea actualizată (actuală).

Merge? Bash! Faceți cunoștință cu operatorul shell (recenzie și raport video de la KubeCon EU'2020)

Operator Shell

Se pare că Kubernetes se bazează pe munca comună a diverșilor controlori (operatorii Kubernetes sunt și controlori). Apare întrebarea, cum să vă creați propriul operator cu efort minim? Și iată că cel dezvoltat de noi vine în ajutor operator-shell. Permite administratorilor de sistem să-și creeze propriile declarații folosind metode familiare.

Merge? Bash! Faceți cunoștință cu operatorul shell (recenzie și raport video de la KubeCon EU'2020)

Exemplu simplu: copierea secretelor

Să ne uităm la un exemplu simplu.

Să presupunem că avem un cluster Kubernetes. Are un spațiu de nume default cu ceva Secret mysecret. În plus, există și alte spații de nume în cluster. Unele dintre ele au atașată o etichetă specifică. Scopul nostru este să copiem Secret în spații de nume cu o etichetă.

Sarcina este complicată de faptul că noi spații de nume pot apărea în cluster, iar unele dintre ele pot avea această etichetă. Pe de altă parte, atunci când eticheta este ștearsă, Secret ar trebui, de asemenea, să fie șters. În plus, Secretul în sine se poate schimba și el: în acest caz, noul Secret trebuie copiat în toate spațiile de nume cu etichete. Dacă Secret este șters accidental din orice spațiu de nume, operatorul nostru ar trebui să îl restabilească imediat.

Acum că sarcina a fost formulată, este timpul să începeți să o implementați folosind operatorul shell. Dar mai întâi merită să spuneți câteva cuvinte despre operatorul shell în sine.

Cum funcționează shell-operatorul

Ca și alte sarcini de lucru din Kubernetes, shell-operator rulează în propriul său pod. În acest pod din director /hooks fișierele executabile sunt stocate. Acestea pot fi scripturi în Bash, Python, Ruby etc. Numim astfel de fișiere executabile cârlige (cârlige).

Merge? Bash! Faceți cunoștință cu operatorul shell (recenzie și raport video de la KubeCon EU'2020)

Operatorul Shell se abonează la evenimente Kubernetes și rulează aceste cârlige ca răspuns la acele evenimente de care avem nevoie.

Merge? Bash! Faceți cunoștință cu operatorul shell (recenzie și raport video de la KubeCon EU'2020)

Cum știe operatorul shell ce cârlig să ruleze și când? Ideea este că fiecare cârlig are două etape. În timpul pornirii, operatorul shell rulează toate cârligele cu un argument --config Aceasta este etapa de configurare. Și după aceasta, cârligele sunt lansate în mod normal - ca răspuns la evenimentele la care sunt atașate. În acest din urmă caz, cârligul primește contextul de legare (context obligatoriu) - date în format JSON, despre care vom vorbi mai detaliat mai jos.

Crearea unui operator în Bash

Acum suntem gata de implementare. Pentru a face acest lucru, trebuie să scriem două funcții (apropo, vă recomandăm biblioteca shell_lib, care simplifică foarte mult cârligele de scriere în Bash):

  • primul este necesar pentru etapa de configurare - afișează contextul de legare;
  • al doilea conține logica principală a cârligului.

#!/bin/bash

source /shell_lib.sh

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

function __main__() {
  # THE LOGIC
}

hook::run "$@"

Următorul pas este să decidem ce obiecte avem nevoie. În cazul nostru, trebuie să urmărim:

  • sursa secretă pentru modificări;
  • toate spațiile de nume din cluster, astfel încât să știți care dintre ele au o etichetă atașată;
  • țintă secrete pentru a se asigura că toate sunt sincronizate cu secretul sursă.

Abonați-vă la sursa secretă

Configurația de legare pentru aceasta este destul de simplă. Indicăm că suntem interesați de Secret cu numele mysecret în spațiul de nume default:

Merge? Bash! Faceți cunoștință cu operatorul shell (recenzie și raport video de la 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

Ca rezultat, cârligul va fi declanșat atunci când secretul sursei se schimbă (src_secret) și primesc următorul context obligatoriu:

Merge? Bash! Faceți cunoștință cu operatorul shell (recenzie și raport video de la KubeCon EU'2020)

După cum puteți vedea, conține numele și întregul obiect.

Urmărirea spațiilor de nume

Acum trebuie să vă abonați la spațiile de nume. Pentru a face acest lucru, specificăm următoarea configurație de legare:

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

După cum puteți vedea, în configurație a apărut un câmp nou cu numele jqFilter. După cum sugerează și numele, jqFilter filtrează toate informațiile inutile și creează un nou obiect JSON cu câmpurile care ne interesează. Un cârlig cu o configurație similară va primi următorul context de legare:

Merge? Bash! Faceți cunoștință cu operatorul shell (recenzie și raport video de la KubeCon EU'2020)

Conține o matrice filterResults pentru fiecare spațiu de nume din cluster. Variabilă booleană hasLabel indică dacă o etichetă este atașată unui spațiu de nume dat. Selector keepFullObjectsInMemory: false indică faptul că nu este nevoie să păstrați obiecte complete în memorie.

Urmărirea secretelor țintei

Ne abonam la toate Secretele care au o adnotare specificată managed-secret: "yes" (acestea sunt ținta noastră 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

În acest caz jqFilter filtrează toate informațiile, cu excepția spațiului de nume și a parametrului resourceVersion. Ultimul parametru a fost trecut la adnotare la crearea secretului: vă permite să comparați versiuni de secrete și să le păstrați la zi.

Un cârlig configurat în acest fel va primi, atunci când este executat, cele trei contexte de legare descrise mai sus. Ele pot fi considerate ca un fel de instantaneu (instantaneu) cluster.

Merge? Bash! Faceți cunoștință cu operatorul shell (recenzie și raport video de la KubeCon EU'2020)

Pe baza tuturor acestor informații, se poate dezvolta un algoritm de bază. Iterează peste toate spațiile de nume și:

  • dacă hasLabel chestiuni true pentru spațiul de nume actual:
    • compară secretul global cu cel local:
      • dacă sunt la fel, nu face nimic;
      • dacă diferă – execută kubectl replace sau create;
  • dacă hasLabel chestiuni false pentru spațiul de nume actual:
    • se asigură că Secret nu este în spațiul de nume dat:
      • dacă Secretul local este prezent, ștergeți-l folosind kubectl delete;
      • dacă Secretul local nu este detectat, nu face nimic.

Merge? Bash! Faceți cunoștință cu operatorul shell (recenzie și raport video de la KubeCon EU'2020)

Implementarea algoritmului în Bash puteți descărca în nostru depozite cu exemple.

Așa am reușit să creăm un controler Kubernetes simplu folosind 35 de linii de configurare YAML și aproximativ aceeași cantitate de cod Bash! Sarcina operatorului shell este să le lege între ele.

Cu toate acestea, copierea secretelor nu este singurul domeniu de aplicare al utilitarului. Iată încă câteva exemple pentru a arăta de ce este capabil.

Exemplul 1: Efectuarea modificărilor la ConfigMap

Să ne uităm la o implementare constând din trei poduri. Pod-urile folosesc ConfigMap pentru a stoca anumite configurații. Când pod-urile au fost lansate, ConfigMap era într-o anumită stare (să-i spunem v.1). În consecință, toate podurile folosesc această versiune specială a ConfigMap.

Acum să presupunem că ConfigMap s-a schimbat (v.2). Cu toate acestea, pod-urile vor folosi versiunea anterioară a ConfigMap (v.1):

Merge? Bash! Faceți cunoștință cu operatorul shell (recenzie și raport video de la KubeCon EU'2020)

Cum îi pot face să treacă la noua ConfigMap (v.2)? Răspunsul este simplu: folosește un șablon. Să adăugăm o adnotare a sumei de control la secțiune template Configurații de implementare:

Merge? Bash! Faceți cunoștință cu operatorul shell (recenzie și raport video de la KubeCon EU'2020)

Ca rezultat, această sumă de control va fi înregistrată în toate podurile și va fi aceeași cu cea a Deployment. Acum trebuie doar să actualizați adnotarea atunci când ConfigMap se schimbă. Și operatorul shell este util în acest caz. Tot ce trebuie să faci este să programezi un cârlig care se va abona la ConfigMap și va actualiza suma de control.

Dacă utilizatorul face modificări la ConfigMap, operatorul shell le va observa și va recalcula suma de control. După care magia Kubernetes va intra în joc: orchestratorul va ucide podul, va crea unul nou, așteaptă ca acesta să devină Ready, și trece la următorul. Ca rezultat, Deployment se va sincroniza și va trece la noua versiune de ConfigMap.

Merge? Bash! Faceți cunoștință cu operatorul shell (recenzie și raport video de la KubeCon EU'2020)

Exemplul 2: Lucrul cu definiții personalizate de resurse

După cum știți, Kubernetes vă permite să creați tipuri personalizate de obiecte. De exemplu, puteți crea kind MysqlDatabase. Să presupunem că acest tip are doi parametri de metadate: name и namespace.

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

Avem un cluster Kubernetes cu diferite spații de nume în care putem crea baze de date MySQL. În acest caz, shell-operator poate fi folosit pentru a urmări resursele MysqlDatabase, conectându-le la serverul MySQL și sincronizând stările dorite și observate ale clusterului.

Merge? Bash! Faceți cunoștință cu operatorul shell (recenzie și raport video de la KubeCon EU'2020)

Exemplul 3: Monitorizarea rețelei cluster

După cum știți, utilizarea ping este cea mai simplă modalitate de a monitoriza o rețea. În acest exemplu vom arăta cum să implementăm o astfel de monitorizare folosind shell-operator.

În primul rând, va trebui să vă abonați la noduri. Operatorul shell are nevoie de numele și adresa IP ale fiecărui nod. Cu ajutorul lor, el va ping aceste noduri.

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

Parametru executeHookOnEvent: [] împiedică rularea cârligului ca răspuns la orice eveniment (adică ca răspuns la schimbarea, adăugarea, ștergerea nodurilor). Cu toate acestea, el va alerga (și actualizați lista de noduri) Programat - în fiecare minut, conform prevederilor din domeniu schedule.

Acum apare întrebarea, cum știm exact despre probleme precum pierderea pachetelor? Să aruncăm o privire la cod:

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
}

Repetăm ​​lista de noduri, obținem numele și adresele IP ale acestora, le trimitem ping și trimitem rezultatele către Prometheus. Operatorul Shell poate exporta valorile către Prometheus, salvându-le într-un fișier localizat conform căii specificate în variabila de mediu $METRICS_PATH.

Aici puteți face un operator pentru monitorizarea simplă a rețelei într-un cluster.

Mecanism de așteptare

Acest articol ar fi incomplet fără a descrie un alt mecanism important încorporat în operatorul shell. Imaginați-vă că execută un fel de cârlig ca răspuns la un eveniment din cluster.

  • Ce se întâmplă dacă, în același timp, se întâmplă ceva în cluster? încă una eveniment?
  • Operatorul shell va rula o altă instanță a cârligului?
  • Ce se întâmplă dacă, să zicem, cinci evenimente au loc în cluster deodată?
  • Le va procesa operatorul shell în paralel?
  • Dar resursele consumate, cum ar fi memoria și procesorul?

Din fericire, shell-operator are încorporat un mecanism de așteptare. Toate evenimentele sunt puse în coadă și procesate secvenţial.

Să ilustrăm acest lucru cu exemple. Să presupunem că avem două cârlige. Primul eveniment merge la primul cârlig. Odată ce procesarea sa este completă, coada se deplasează înainte. Următoarele trei evenimente sunt redirecționate către al doilea cârlig - sunt scoase din coadă și introduse într-un „pachet”. Acesta este hook primește o serie de evenimente — sau, mai precis, o serie de contexte obligatorii.

De asemenea, acestea evenimentele pot fi combinate într-unul mare. Parametrul este responsabil pentru acest lucru group în configurația de legare.

Merge? Bash! Faceți cunoștință cu operatorul shell (recenzie și raport video de la KubeCon EU'2020)

Puteți crea orice număr de cozi/cârlige și diferitele lor combinații. De exemplu, o singură coadă poate funcționa cu două cârlige sau invers.

Merge? Bash! Faceți cunoștință cu operatorul shell (recenzie și raport video de la KubeCon EU'2020)

Tot ce trebuie să faceți este să configurați câmpul în consecință queue în configurația de legare. Dacă nu este specificat un nume de coadă, cârligul rulează pe coada implicită (default). Acest mecanism de așteptare vă permite să rezolvați complet toate problemele de gestionare a resurselor atunci când lucrați cu cârlige.

Concluzie

Am explicat ce este un operator shell, am arătat cum poate fi folosit pentru a crea rapid și fără efort operatori Kubernetes și am dat câteva exemple de utilizare.

Informații detaliate despre operatorul shell, precum și un tutorial rapid despre cum să îl utilizați, sunt disponibile în secțiunea corespunzătoare. depozite pe GitHub. Nu ezitați să ne contactați cu întrebări: le puteți discuta într-un mod special Grupul Telegram (în rusă) sau în acest forum (în limba engleză).

Și dacă ți-a plăcut, suntem mereu bucuroși să vedem noi probleme/PR/stele pe GitHub, unde, apropo, poți găsi altele proiecte interesante. Printre acestea merită evidențiat addon-operator, care este fratele mai mare al lui shell-operator. Acest utilitar folosește diagramele Helm pentru a instala suplimente, poate furniza actualizări și monitoriza diferiți parametri/valori ale diagramelor, controlează procesul de instalare a diagramelor și, de asemenea, le poate modifica ca răspuns la evenimentele din cluster.

Merge? Bash! Faceți cunoștință cu operatorul shell (recenzie și raport video de la KubeCon EU'2020)

Videoclipuri și diapozitive

Videoclip de la spectacol (~23 de minute):


Prezentarea raportului:

PS

Citește și pe blogul nostru:

Sursa: www.habr.com

Adauga un comentariu