Vai? Bash! Incuntrà l'operatore di shell (revisione è video rapportu da KubeCon EU'2020)

Quist'annu, a principale cunferenza europea di Kubernetes - KubeCon + CloudNativeCon Europe 2020 - era virtuale. Tuttavia, un tali cambiamentu di furmatu ùn ci hà impeditu di purtà u nostru rapportu longu pianificatu "Vai? Bash! Meet the Shell-operator" dedicatu à u nostru prughjettu Open Source shell-operatore.

Questu articulu, ispiratu da a discussione, presenta un approcciu per simplificà u prucessu di creazione di l'operatori per Kubernetes è mostra cumu pudete fà u vostru propiu cù u minimu sforzu cù un operatore di shell.

Vai? Bash! Incuntrà l'operatore di shell (revisione è video rapportu da KubeCon EU'2020)

Introducendu video di u rapportu (~ 23 minuti in inglese, notevolmente più informattivu cà l'articulu) è l'estrattu principale da ellu in forma di testu. Vai !

À Flant ottimisimu constantemente è automatizemu tuttu. Oghje avemu da parlà di un altru cuncettu passiunanti. Incontru: scripting shell nativu di nuvola!

In ogni casu, cuminciamu cù u cuntestu in quale tuttu questu succede: Kubernetes.

API Kubernetes è controllers

L'API in Kubernetes pò esse rapprisintatu cum'è un tipu di servitore di file cù repertorii per ogni tipu d'ughjettu. L'uggetti (risorse) in stu servitore sò rapprisintati da i schedari YAML. Inoltre, u servitore hà una API basica chì vi permette di fà trè cose:

  • uttene risorsa per u so tipu è u nome;
  • cambià risorsa (in questu casu, u servitore guarda solu l'uggetti "corretti" - tutti i formati incorrectamente o destinati à altri cartulari sò scartati);
  • pista per a risorsa (in questu casu, l'utilizatore riceve immediatamente a so versione attuale / aghjurnata).

Cusì, Kubernetes agisce cum'è un tipu di servitore di file (per manifesti YAML) cù trè metudi basi (sì, in realtà ci sò altri, ma l'avemu omessi per avà).

Vai? Bash! Incuntrà l'operatore di shell (revisione è video rapportu da KubeCon EU'2020)

U prublema hè chì u servitore pò almacenà solu l'infurmazioni. Per fà u travagliu avete bisognu controller - u sicondu cuncettu più impurtante è fundamentale in u mondu di Kubernetes.

Ci sò dui tippi principali di cuntrolli. U primu piglia l'infurmazioni da Kubernetes, u processa secondu a logica nidificata, è torna à K8s. U sicondu pigghia infurmazione da Kubernetes, ma, à u cuntrariu di u primu tipu, cambia u statu di qualchi risorse esterni.

Fighjemu un ochju più vicinu à u prucessu di creazione di una implementazione in Kubernetes:

  • Controller di implementazione (cumpresu in kube-controller-manager) riceve infurmazione nantu à u Deployment è crea un ReplicaSet.
  • ReplicaSet crea duie rèpliche (dui pods) basatu annantu à questa infurmazione, ma sti pods ùn sò micca pianificati.
  • U pianificatore pianifica pods è aghjunghje infurmazioni di nodu à i so YAML.
  • Kubelets facenu cambiamenti à una risorsa esterna (dice Docker).

Allora sta sequenza sana hè ripetuta in l'ordine inversu: u kubelet cuntrolla i cuntenituri, calcula l'estatus di u pod è rinvia. U controller ReplicaSet riceve u statutu è aghjurnà u statu di u set di replica. A listessa cosa succede cù u Controller di Deployment è l'utilizatore finalmente riceve u statu aghjurnatu (attuali).

Vai? Bash! Incuntrà l'operatore di shell (revisione è video rapportu da KubeCon EU'2020)

Shell-operatore

Ci hè chì Kubernetes hè basatu annantu à u travagliu cumunu di diversi controllers (l'operatori di Kubernetes sò ancu cuntrolli). A quistione sorge, cumu creà u vostru propiu operatore cù u minimu sforzu? È quì quellu chì avemu sviluppatu vene in salvezza shell-operatore. Permette à l'amministratori di u sistema di creà e so propiu dichjarazioni cù metudi familiari.

Vai? Bash! Incuntrà l'operatore di shell (revisione è video rapportu da KubeCon EU'2020)

Esempiu simplice: cupià i sicreti

Fighjemu un esempiu simplice.

Diciamu chì avemu un cluster Kubernetes. Havi un spaziu di nomi default cù qualchi Secret mysecret. Inoltre, ci sò altri spazii di nomi in u cluster. Certi di elli anu una etichetta specifica attaccata à elli. U nostru scopu hè di copià Secret in namespaces cù una etichetta.

U compitu hè cumplicatu da u fattu chì i novi spazii di nomi ponu appare in u cluster, è alcuni di elli ponu avè sta etichetta. Per d 'altra banda, quandu l'etichetta hè sguassata, Secret deve esse ancu eliminata. In più di questu, u Secretu stessu pò ancu cambià: in questu casu, u novu Secret deve esse copiatu in tutti i spazii di nomi cù etichette. Se Secret hè sguassatu accidintali in ogni spaziu di nomi, u nostru operatore deve restaurà immediatamente.

Avà chì u compitu hè statu formulatu, hè ora di cumincià à implementà cù l'operatore di shell. Ma prima vale a pena dì uni pochi di parolle nantu à l'operatore di shell stessu.

Cumu funziona l'operatore di shell

Cum'è l'altri carichi di travagliu in Kubernetes, l'operatore di shell funziona in u so propiu pod. In questu pod in u cartulare /hooks i fugliali eseguibili sò almacenati. Questi ponu esse script in Bash, Python, Ruby, etc. Chjamemu tali file eseguibili ganci (anziani).

Vai? Bash! Incuntrà l'operatore di shell (revisione è video rapportu da KubeCon EU'2020)

L'operatore Shell si abbona à l'avvenimenti Kubernetes è eseguisce questi ganci in risposta à quelli avvenimenti chì avemu bisognu.

Vai? Bash! Incuntrà l'operatore di shell (revisione è video rapportu da KubeCon EU'2020)

Cumu l'operatore di shell sapi quale ganciu per eseguisce è quandu? U puntu hè chì ogni ganciu hà duie tappe. Durante l'iniziu, l'operatore di shell esegue tutti i ganci cù un argumentu --config Questu hè u stadiu di cunfigurazione. E dopu, i ganci sò lanciati in modu normale - in risposta à l'avvenimenti à quale sò attaccati. In l'ultimu casu, u ganciu riceve u cuntestu di ubligatoriu (cuntestu ubligatoriu) - dati in u formatu JSON, chì avemu da parlà in più detail sottu.

Fà un operatore in Bash

Avà simu pronti per l'implementazione. Per fà questu, avemu bisognu di scrive duie funzioni (per via, ricumandemu a biblioteca shell_lib, chì simplifica assai i ganci di scrittura in Bash):

  • u primu hè necessariu per a tappa di cunfigurazione - mostra u cuntestu di ubligatoriu;
  • u sicondu cuntene a logica principale di u ganciu.

#!/bin/bash

source /shell_lib.sh

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

function __main__() {
  # THE LOGIC
}

hook::run "$@"

U prossimu passu hè di decide chì l'uggetti avemu bisognu. In u nostru casu, avemu bisognu di seguità:

  • sicretu fonte per i cambiamenti;
  • tutti i namespaces in u cluster, cusì chì sapete quale anu una etichetta attaccata à elli;
  • sicreti di destinazione per assicurà chì sò tutti in sincronia cù u sicretu fonte.

Abbonate à a fonte secreta

A cunfigurazione di ubligatoriu per questu hè abbastanza simplice. Indichemu chì avemu interessatu in Secret cù u nome mysecret in namespace default:

Vai? Bash! Incuntrà l'operatore di shell (revisione è video rapportu da 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

In u risultatu, u ganciu serà attivatu quandu u sicretu di fonte cambia (src_secret) è riceve u seguente cuntestu vincolante:

Vai? Bash! Incuntrà l'operatore di shell (revisione è video rapportu da KubeCon EU'2020)

Comu pudete vede, cuntene u nome è l'ughjettu sanu.

Mantene a traccia di i spazii di nomi

Avà avete bisognu di abbonate à i namespaces. Per fà questu, avemu specificatu a seguente cunfigurazione di ubligatoriu:

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

Comu pudete vede, un novu campu hè apparsu in a cunfigurazione cù u nome jqFilter. Cum'è u so nome suggerisce, jqFilter filtra tutte l'infurmazioni innecessarii è crea un novu oggettu JSON cù i campi chì sò d'interessu per noi. Un ganciu cù una cunfigurazione simili riceverà u cuntestu di ubligatoriu seguente:

Vai? Bash! Incuntrà l'operatore di shell (revisione è video rapportu da KubeCon EU'2020)

Contene un array filterResults per ogni namespace in u cluster. Variabile booleana hasLabel indica se una etichetta hè attaccata à un spaziu di nome datu. Selettore keepFullObjectsInMemory: false indica chì ùn ci hè bisognu di mantene l'uggetti cumpleti in memoria.

Segui i secreti di destinazione

Avemu sottumessi à tutti i Secrets chì anu una annotazione specifica managed-secret: "yes" (questi sò u nostru scopu 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

In questu casu jqFilter filtra tutte l'infurmazioni eccettu u namespace è u paràmetru resourceVersion. L'ultimu paràmetru hè passatu à l'annotazione quandu crea u sicretu: permette di paragunà e versioni di sicreti è mantene a data.

Un ganciu cunfiguratu in questu modu riceverà, quandu eseguitu, i trè cuntesti di ubligatoriu descritti sopra. Puderanu esse pensati cum'è una spezia di snapshot (una snapshot) cluster.

Vai? Bash! Incuntrà l'operatore di shell (revisione è video rapportu da KubeCon EU'2020)

Basatu nantu à tutte queste informazioni, un algoritmu di basa pò esse sviluppatu. Itera in tutti i spazii di nomi è:

  • si hasLabel tràttanu true per u spaziu di nomi attuale:
    • compara u sicretu glubale cù u lucale:
      • s'elli sò listessi, ùn faci nunda;
      • si sò diffirenti - eseguisce kubectl replace o create;
  • si hasLabel tràttanu false per u spaziu di nomi attuale:
    • assicura chì Secret ùn hè micca in u namespace datu:
      • se u Sicretu lucale hè presente, sguassate u usu kubectl delete;
      • se u Sicretu lucale ùn hè micca rilevatu, ùn face nunda.

Vai? Bash! Incuntrà l'operatore di shell (revisione è video rapportu da KubeCon EU'2020)

Implementazione di l'algoritmu in Bash pudete scaricà in u nostru repository cù esempi.

Hè cusì chì pudemu creà un semplice controller Kubernetes usendu 35 linee di cunfigurazione YAML è circa a stessa quantità di codice Bash! U travagliu di l'operatore di shell hè di ligà inseme.

Tuttavia, a copia di i secreti ùn hè micca l'unicu spaziu di applicazione di l'utilità. Eccu uni pochi di più esempi per dimustrà ciò ch'ellu hè capaci di fà.

Esempiu 1: Fà cambiamenti à ConfigMap

Fighjemu un Implantamentu custituitu di trè pods. I pods utilizanu ConfigMap per almacenà una certa cunfigurazione. Quandu i pods sò stati lanciati, ConfigMap era in un certu statu (chjamemu v.1). Per quessa, tutti i pods usanu sta versione particulare di ConfigMap.

Avà assumemu chì u ConfigMap hà cambiatu (v.2). Tuttavia, i pods utilizanu a versione precedente di ConfigMap (v.1):

Vai? Bash! Incuntrà l'operatore di shell (revisione è video rapportu da KubeCon EU'2020)

Cumu possu fà passà à u novu ConfigMap (v.2) ? A risposta hè simplice: utilizate un mudellu. Aghjunghjemu una annotazione di checksum à a sezione template Configurazioni di implementazione:

Vai? Bash! Incuntrà l'operatore di shell (revisione è video rapportu da KubeCon EU'2020)

In u risultatu, questu checksum serà registratu in tutti i pods, è serà u listessu cum'è quellu di Deployment. Avà basta à aghjurnà l'annotazione quandu u ConfigMap cambia. È l'operatore di shell hè utile in questu casu. Tuttu ciò chì duvete fà hè u prugramma un ganciu chì abbonarà à u ConfigMap è aghjurnà u checksum.

Se l'utilizatore face cambiamenti à u ConfigMap, l'operatore di shell li noterà è ricalcolerà u checksum. Dopu chì a magia di Kubernetes entrerà in ghjocu: l'orchestratore ucciderà u pod, creà un novu, aspittà chì diventerà. Ready, è passa à u prossimu. In u risultatu, Deployment sincronizà è passà à a nova versione di ConfigMap.

Vai? Bash! Incuntrà l'operatore di shell (revisione è video rapportu da KubeCon EU'2020)

Esempiu 2: U travagliu cù Definizioni di Risorse Personalizzate

Comu sapete, Kubernetes permette di creà tipi d'oggetti persunalizati. Per esempiu, pudete creà tipu MysqlDatabase. Diciamu chì stu tipu hà dui parametri di metadata: name и namespace.

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

Avemu un cluster Kubernetes cù spazii di nomi diffirenti in quale pudemu creà basa di dati MySQL. In questu casu, l'operatore di shell pò esse usatu per seguità e risorse MysqlDatabase, cunnessendu à u servitore MySQL è sincronizà i stati desiderati è osservati di u cluster.

Vai? Bash! Incuntrà l'operatore di shell (revisione è video rapportu da KubeCon EU'2020)

Esempiu 3: Monitoring Network Cluster

Comu sapete, l'usu di ping hè u modu più simplice di monitorà una reta. In questu esempiu, mostreremu cumu implementà un tali monitoraghju cù l'operatore di shell.

Prima di tuttu, vi tuccherà à subscribe à nodes. L'operatore di shell hà bisognu di u nome è l'indirizzu IP di ogni node. Cù u so aiutu, farà ping sti nodi.

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

Parameter executeHookOnEvent: [] impedisce à u ganciu di eseguisce in risposta à qualsiasi avvenimentu (vale à dì, in risposta à cambià, aghjunghje, sguassà nodi). Tuttavia, ellu correrà (è aghjurnà a lista di i nodi) Pianificatu - ogni minutu, cum'è prescrittu da u campu schedule.

Avà hè a quistione, cumu esattamente sapemu di prublemi cum'è a perdita di pacchetti? Fighjemu un ochju à u codice:

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
}

Iteremu à traversu a lista di nodi, uttene i so nomi è l'indirizzi IP, ping è mandate i risultati à Prometheus. L'operatore Shell pò esportà metriche à Prometheus, salvendu in un schedariu situatu secondu u percorsu specificatu in a variabile d'ambiente $METRICS_PATH.

Eccu accussì pudete fà un operatore per un seguimentu simplice di a rete in un cluster.

Meccanisimu di fila

Questu articulu ùn saria micca cumpletu senza discrive un altru mecanismu impurtante integratu in l'operatore di shell. Imagine chì eseguisce un tipu di ganciu in risposta à un avvenimentu in u cluster.

  • Chì succede se, à u stessu tempu, qualcosa succede in u cluster? unu di più avvenimentu?
  • L'operatore di shell eseguirà un'altra istanza di u ganciu?
  • E se, per dì, cinque avvenimenti accadenu in u cluster à una volta?
  • L'operatore di shell li processerà in parallelu ?
  • E risorse cunsumate cum'è memoria è CPU?

Fortunatamente, l'operatore di shell hà un mecanismu di fila integratu. Tutti l'avvenimenti sò in fila è processati in sequenza.

Illustremu questu cù esempi. Diciamu chì avemu dui ganci. U primu avvenimentu va à u primu ganciu. Quandu u so prucessu hè cumpletu, a fila avanza. I prossimi trè avvenimenti sò ridiretti à u sicondu ganciu - sò sguassati da a fila è intruti in questu in un "bundle". Hè ganciu riceve una serie di avvenimenti - o, più precisamente, una serie di cuntesti vincolanti.

Ancu questi l'avvenimenti ponu esse cumminati in un grande. U paràmetru hè rispunsevule per questu group in a cunfigurazione di ubligatoriu.

Vai? Bash! Incuntrà l'operatore di shell (revisione è video rapportu da KubeCon EU'2020)

Pudete creà ogni quantità di fila / ganci è e so diverse cumminazzioni. Per esempiu, una fila pò travaglià cù dui ganci, o viceversa.

Vai? Bash! Incuntrà l'operatore di shell (revisione è video rapportu da KubeCon EU'2020)

Tuttu ciò chì duvete fà hè cunfigurà u campu in cunseguenza queue in a cunfigurazione di ubligatoriu. Se un nome di fila ùn hè micca specificatu, u ganciu corre nantu à a fila predeterminata (default). Stu mecanismu di fila permette di risolve cumplettamente tutti i prublemi di gestione di risorse quandu travagliate cù ganci.

cunchiusioni

Avemu spiegatu ciò chì hè un operatore di shell, hà dimustratu cumu pò esse usatu per creà rapidamente è senza sforzu operatori Kubernetes, è hà datu parechji esempi di u so usu.

L'infurmazioni detallate nantu à l'operatore di shell, è ancu un tutorialu rapidu nantu à cumu aduprà, hè dispunibule in u currispundente. repository nantu à GitHub. Ùn esitate à cuntattateci cù e dumande: pudete discutiri in un speciale Gruppu Telegram (in russo) o in stu foru (in inglese).

È s'ellu ci hè piaciutu, simu sempre felici di vede novi prublemi / PR / stelle nantu à GitHub, induve, per via, pudete truvà altri. prughjetti interessanti. À mezu à elli, vale a pena enfasi addun-operatore, chì hè u fratellu maiò di shell-operator. Questa utilità usa i grafici Helm per installà add-ons, pò furnisce l'aghjurnamenti è monitorà diversi parametri / valori di chart, cuntrolla u prucessu di stallazione di charts, è pò ancu mudificà in risposta à l'avvenimenti in u cluster.

Vai? Bash! Incuntrà l'operatore di shell (revisione è video rapportu da KubeCon EU'2020)

Videos è slides

Video da u spettaculu (~ 23 minuti):


Presentazione di u rapportu:

PS

Leghjite puru nant'à u nostru blog:

Source: www.habr.com

Add a comment