ProHoster > BLOG > administrare > Merge? Bash! Faceți cunoștință cu operatorul shell (recenzie și raport video de la KubeCon EU'2020)
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.
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ă).
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ă).
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.
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).
Operatorul Shell se abonează la evenimente Kubernetes și rulează aceste cârlige ca răspuns la acele evenimente de care avem nevoie.
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:
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:
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):
Î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.
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.
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):
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:
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.
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.
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.
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.
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.
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.