Ideea unui operator shell este destul de simplă: abonați-vă la evenimente din obiectele Kubernetes și, atunci când aceste evenimente sunt primite, lansați un program extern, oferindu-i informații despre eveniment:
Necesitatea ei a apărut atunci când, în timpul funcționării clusterelor, au început să apară sarcini mici pe care ne doream cu adevărat să le automatizăm în mod corect. Toate aceste sarcini mici au fost rezolvate folosind scripturi bash simple, deși, după cum știți, este mai bine să scrieți operatori în Golang. Evident, investiția în dezvoltarea la scară largă a unui operator pentru fiecare astfel de sarcină mică ar fi ineficientă.
Operator în 15 minute
Să ne uităm la un exemplu de ceea ce poate fi automatizat într-un cluster Kubernetes și cum poate ajuta operatorul shell. Un exemplu ar fi următorul: replicarea unui secret pentru a accesa registrul docker.
Pod-urile care folosesc imagini dintr-un registru privat trebuie să conțină în manifest o legătură către un secret cu date pentru accesarea registrului. Acest secret trebuie creat în fiecare spațiu de nume înainte de a crea pod-uri. Acest lucru se poate face manual, dar dacă setăm medii dinamice, atunci spațiul de nume pentru o aplicație va deveni mult. Si daca nici nu sunt 2-3 aplicatii... numarul secretelor devine foarte mare. Și încă un lucru despre secrete: aș dori să schimb din când în când cheia pentru a accesa registrul. În cele din urmă, operatii manuale ca solutie complet ineficient — trebuie să automatizăm crearea și actualizarea secretelor.
Automatizare simplă
Să scriem un script shell care rulează o dată la N secunde și verifică spațiile de nume pentru prezența unui secret, iar dacă nu există niciun secret, atunci acesta este creat. Avantajul acestei soluții este că arată ca un script shell în cron - o abordare clasică și de înțeles pentru toată lumea. Dezavantajul este că în intervalul dintre lansările sale se poate crea un nou spațiu de nume și o perioadă de timp va rămâne fără un secret, ceea ce va duce la erori la lansarea pod-urilor.
Automatizare cu shell-operator
Pentru ca scriptul nostru să funcționeze corect, lansarea cron clasică trebuie înlocuită cu o lansare atunci când este adăugat un spațiu de nume: în acest caz, puteți crea un secret înainte de a-l folosi. Să vedem cum să implementăm acest lucru folosind shell-operator.
Mai întâi, să ne uităm la scenariu. Scripturile în termeni de operator shell se numesc cârlige. Fiecare cârlig atunci când rulează cu un steag --config informează operatorul shell despre legăturile sale, adică pe ce evenimente ar trebui lansat. În cazul nostru vom folosi onKubernetesEvent:
#!/bin/bash
if [[ $1 == "--config" ]] ; then
cat <<EOF
{
"onKubernetesEvent": [
{ "kind": "namespace",
"event":["add"]
}
]}
EOF
fi
Este descris aici că suntem interesați să adăugăm evenimente (add) obiecte de tip namespace.
Acum trebuie să adăugați codul care va fi executat atunci când are loc evenimentul:
#!/bin/bash
if [[ $1 == "--config" ]] ; then
# конфигурация
cat <<EOF
{
"onKubernetesEvent": [
{ "kind": "namespace",
"event":["add"]
}
]}
EOF
else
# реакция:
# узнать, какой namespace появился
createdNamespace=$(jq -r '.[0].resourceName' $BINDING_CONTEXT_PATH)
# создать в нём нужный секрет
kubectl create -n ${createdNamespace} -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
...
data:
...
EOF
fi
Grozav! Rezultatul a fost un scenariu mic, frumos. Pentru a o „reanima”, mai sunt doi pași: pregătiți imaginea și lansați-o în cluster.
Pregătirea unei imagini cu un cârlig
Dacă te uiți la script, poți vedea că comenzile sunt folosite kubectl и jq. Aceasta înseamnă că imaginea trebuie să aibă următoarele lucruri: hook-ul nostru, un operator shell care va monitoriza evenimentele și va rula hook-ul și comenzile folosite de hook (kubectl și jq). Hub.docker.com are deja o imagine gata făcută în care sunt ambalate shell-operator, kubectl și jq. Tot ce rămâne este să adăugați un cârlig simplu Dockerfile:
Să ne uităm din nou la cârlig și de data aceasta să notăm ce acțiuni și cu ce obiecte efectuează în cluster:
se abonează la evenimente de creare a spațiului de nume;
creează un secret în alte spații de nume decât cel în care este lansat.
Se pare că podul în care va fi lansată imaginea noastră trebuie să aibă permisiuni pentru a face aceste acțiuni. Acest lucru se poate face prin crearea propriului ServiceAccount. Permisiunea trebuie făcută sub forma ClusterRole și ClusterRoleBinding, deoarece ne interesează obiectele din întregul cluster.
Asta e tot: operatorul shell va porni, se va abona la evenimentele de creare a spațiului de nume și va rula hook-ul atunci când este necesar.
Astfel, un simplu script shell transformat într-un operator real pentru Kubernetes și funcționează ca parte a unui cluster. Și toate acestea fără procesul complex de dezvoltare a operatorilor din Golang:
Există o altă ilustrație în această chestiune...
Îi vom dezvălui sensul mai detaliat într-una dintre publicațiile următoare.
filtrare
Urmărirea obiectelor este bună, dar deseori este nevoie să reacționați modificarea unor proprietăți ale obiectului, de exemplu, pentru a modifica numărul de replici în Deployment sau pentru a schimba etichetele obiectelor.
Când sosește un eveniment, operatorul shell primește manifestul JSON al obiectului. Putem selecta proprietățile care ne interesează în acest JSON și să rulăm hook-ul numai când se schimbă. Există un domeniu pentru asta jqFilter, unde trebuie să specificați expresia jq care va fi aplicată manifestului JSON.
De exemplu, pentru a răspunde la modificările etichetelor pentru obiectele de implementare, trebuie să filtrați câmpul labels în afara câmpului metadata. Configurația va fi așa:
Această expresie jqFilter transformă manifestul JSON lung al implementării în JSON scurt cu etichete:
shell-operator va rula hook-ul numai atunci când acest JSON scurt se schimbă, iar modificările altor proprietăți vor fi ignorate.
Contextul lansării cârligului
Configurarea cârligului vă permite să specificați mai multe opțiuni pentru evenimente - de exemplu, 2 opțiuni pentru evenimente din Kubernetes și 2 programe:
O mică digresiune: da, suportă operatorul shell rulează scripturi în stil crontab. Mai multe detalii pot fi găsite în documentație.
Pentru a distinge de ce a fost lansat cârligul, operatorul shell creează un fișier temporar și transmite calea către acesta într-o variabilă cârligului BINDING_CONTEXT_TYPE. Fișierul conține o descriere JSON a motivului rulării hook-ului. De exemplu, la fiecare 10 minute, cârligul va rula cu următorul conținut:
Conținutul câmpurilor poate fi înțeles din numele lor și mai multe detalii pot fi citite în documentație. Un exemplu de obținere a unui nume de resursă dintr-un câmp resourceName folosirea jq a fost deja afișată într-un cârlig care reproduce secretele:
jq -r '.[0].resourceName' $BINDING_CONTEXT_PATH
Puteți obține alte câmpuri într-un mod similar.
Ce urmeaza?
În depozitul de proiecte, în directoarele /examples, există exemple de cârlige care sunt gata să ruleze pe un cluster. Când scrieți propriile cârlige, le puteți folosi ca bază.
Există suport pentru colectarea de valori folosind Prometheus - valorile disponibile sunt descrise în secțiune METRICA.
După cum ați putea ghici, operatorul shell este scris în Go și distribuit sub o licență Open Source (Apache 2.0). Vom fi recunoscători pentru orice ajutor pentru dezvoltare proiect pe GitHub: și stele, și probleme și solicitări de tragere.
Ridicând vălul secretului, vă vom informa, de asemenea, că shell-operator este mic parte a sistemului nostru care poate menține actualizate suplimentele instalate în clusterul Kubernetes și poate efectua diverse acțiuni automate. Mai multe despre acest sistem noi a spus literalmente luni la HighLoad++ 2019 din Sankt Petersburg - vom publica în curând videoclipul și transcrierea acestui raport.
Avem un plan pentru a deschide restul acestui sistem: operatorul de supliment și colecția noastră de cârlige și module. Apropo, addon-operator este deja disponibil pe github, dar documentația pentru aceasta este încă pe drum. Lansarea colecției de module este planificată pentru vară.