Vă prezentăm shell-operator: crearea operatorilor pentru Kubernetes tocmai a devenit mai ușoară

Au existat deja articole pe blogul nostru despre care se vorbește capabilitățile operatorului în Kubernetes si cum scrieți singur un operator simplu. De data aceasta am dori să vă prezentăm atenției soluția noastră Open Source, care duce crearea de operatori la un nivel super-ușor - verificați operator-shell!

De ce?

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:

Vă prezentăm shell-operator: crearea operatorilor pentru Kubernetes tocmai a devenit mai ușoară

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:

$ cat Dockerfile
FROM flant/shell-operator:v1.0.0-beta.1-alpine3.9
ADD namespace-hook.sh /hooks

$ docker build -t registry.example.com/my-operator:v1 . 
$ docker push registry.example.com/my-operator:v1

Alergă într-un cluster

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:

  1. se abonează la evenimente de creare a spațiului de nume;
  2. 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.

Descrierea finală în YAML va arăta cam așa:

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: monitor-namespaces-acc

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: monitor-namespaces
rules:
- apiGroups: [""]
  resources: ["namespaces"]
  verbs: ["get", "watch", "list"]
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "list", "create", "patch"]

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: monitor-namespaces
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: monitor-namespaces
subjects:
  - kind: ServiceAccount
    name: monitor-namespaces-acc
    namespace: example-monitor-namespaces

Puteți lansa imaginea asamblată ca o simplă implementare:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: my-operator
spec:
  template:
    spec:
      containers:
      - name: my-operator
        image: registry.example.com/my-operator:v1
      serviceAccountName: monitor-namespaces-acc

Pentru comoditate, este creat un spațiu de nume separat unde va fi lansat operatorul shell și vor fi aplicate manifestele create:

$ kubectl create ns example-monitor-namespaces
$ kubectl -n example-monitor-namespaces apply -f rbac.yaml
$ kubectl -n example-monitor-namespaces apply -f deployment.yaml

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.

Vă prezentăm shell-operator: crearea operatorilor pentru Kubernetes tocmai a devenit mai ușoară

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:

Vă prezentăm shell-operator: crearea operatorilor pentru Kubernetes tocmai a devenit mai ușoară

Există o altă ilustrație în această chestiune...Vă prezentăm shell-operator: crearea operatorilor pentru Kubernetes tocmai a devenit mai ușoară

Î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:

cat <<EOF
{
"onKubernetesEvent": [
{ "kind": "deployment",
  "event":["update"],
  "jqFilter": ".metadata.labels"
}
]}
EOF

Această expresie jqFilter transformă manifestul JSON lung al implementării în JSON scurt cu etichete:

Vă prezentăm shell-operator: crearea operatorilor pentru Kubernetes tocmai a devenit mai ușoară

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:

{"onKubernetesEvent":[
  {"name":"OnCreatePod",
  "kind": "pod",
  "event":["add"]
  },
  {"name":"OnModifiedNamespace",
  "kind": "namespace",
  "event":["update"],
  "jqFilter": ".metadata.labels"
  }
],
"schedule": [
{ "name":"every 10 min",
  "crontab":"* */10 * * * *"
}, {"name":"on Mondays at 12:10",
"crontab": "* 10 12 * * 1"
]}

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:

[{ "binding": "every 10 min"}]

... și luni va începe cu asta:

[{ "binding": "every 10 min"}, { "binding": "on Mondays at 12:10"}]

Pentru onKubernetesEvent Vor exista mai multe declanșatoare JSON, deoarece conține o descriere a obiectului:

[
 {
 "binding": "onCreatePod",
 "resourceEvent": "add",
 "resourceKind": "pod",
 "resourceName": "foo",
 "resourceNamespace": "bar"
 }
]

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ă.

Rămâneţi aproape!

PS

Citește și pe blogul nostru:

Sursa: www.habr.com

Adauga un comentariu