Presentazione di shell-operator: creare operatori per Kubernetes è diventato più semplice

Ci sono già stati articoli sul nostro blog che ne parlano funzionalità dell'operatore in Kubernetes e come scrivi tu stesso un semplice operatore. Questa volta vorremmo presentare alla vostra attenzione la nostra soluzione Open Source, che porta la creazione di operatori a un livello semplicissimo: guardate operatore di shell!

Perché?

L'idea di un operatore di shell è abbastanza semplice: iscriversi agli eventi degli oggetti Kubernetes e, quando questi eventi vengono ricevuti, avviare un programma esterno, fornendogli informazioni sull'evento:

Presentazione di shell-operator: creare operatori per Kubernetes è diventato più semplice

La necessità è nata quando, durante il funzionamento dei cluster, hanno cominciato ad apparire piccoli compiti che volevamo davvero automatizzare nel modo giusto. Tutti questi piccoli compiti sono stati risolti utilizzando semplici script bash, anche se, come sai, è meglio scrivere gli operatori in Golang. Ovviamente, investire nello sviluppo su vasta scala di un operatore per ciascun compito così piccolo sarebbe inefficace.

Operatore tra 15 minuti

Diamo un'occhiata a un esempio di cosa può essere automatizzato in un cluster Kubernetes e di come l'operatore shell può essere d'aiuto. Un esempio potrebbe essere il seguente: replicare un segreto per accedere al registro della finestra mobile.

I pod che utilizzano immagini da un registro privato devono contenere nel loro manifest un collegamento a un segreto con i dati per l'accesso al registro. Questo segreto deve essere creato in ogni spazio dei nomi prima di creare i pod. Questo può essere fatto manualmente, ma se configuriamo ambienti dinamici, lo spazio dei nomi per un'applicazione diventerà molto. E se poi non ci sono 2-3 applicazioni... il numero di segreti diventa molto grande. E ancora una cosa sui segreti: vorrei cambiare di tanto in tanto la chiave per accedere al registro. Infine, operazioni manuali come soluzione completamente inefficace — dobbiamo automatizzare la creazione e l'aggiornamento dei segreti.

Automazione semplice

Scriviamo uno script di shell che viene eseguito una volta ogni N secondi e controlla gli spazi dei nomi per la presenza di un segreto e, se non è presente alcun segreto, viene creato. Il vantaggio di questa soluzione è che sembra uno script di shell in cron: un approccio classico e comprensibile a tutti. Lo svantaggio è che nell'intervallo tra i suoi lanci può essere creato un nuovo spazio dei nomi e per qualche tempo rimarrà senza segreto, il che porterà a errori nell'avvio dei pod.

Automazione con operatore shell

Affinché il nostro script funzioni correttamente, è necessario sostituire il classico lancio di cron con un lancio quando viene aggiunto un namespace: in questo caso è possibile creare un segreto prima di utilizzarlo. Vediamo come implementarlo utilizzando shell-operator.

Per prima cosa, diamo un'occhiata alla sceneggiatura. Gli script in termini di operatori shell sono chiamati hook. Ogni gancio quando viene eseguito con una bandiera --config informa l'operatore shell sui suoi collegamenti, cioè su quali eventi dovrebbe essere lanciato. Nel nostro caso useremo onKubernetesEvent:

#!/bin/bash
if [[ $1 == "--config" ]] ; then
cat <<EOF
{
"onKubernetesEvent": [
  { "kind": "namespace",
    "event":["add"]
  }
]}
EOF
fi

Qui viene descritto che siamo interessati ad aggiungere eventi (add) oggetti di tipo namespace.

Ora devi aggiungere il codice che verrà eseguito quando si verifica l'evento:

#!/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

Grande! Il risultato è stato una piccola, bellissima sceneggiatura. Per “ravvivarla” restano due passaggi: preparare l'immagine e lanciarla nel cluster.

Preparare un'immagine con un gancio

Se guardi lo script, puoi vedere che i comandi vengono utilizzati kubectl и jq. Ciò significa che l'immagine deve avere le seguenti cose: il nostro hook, un operatore di shell che monitorerà gli eventi ed eseguirà l'hook, e i comandi utilizzati dall'hook (kubectl e jq). Hub.docker.com ha già un'immagine già pronta in cui sono pacchettizzati shell-operator, kubectl e jq. Non resta che aggiungere un semplice gancio 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

In esecuzione in un cluster

Diamo nuovamente un'occhiata all'hook e questa volta scriviamo quali azioni e con quali oggetti esegue nel cluster:

  1. si iscrive agli eventi di creazione dello spazio dei nomi;
  2. crea un segreto in spazi dei nomi diversi da quello in cui viene avviato.

Si scopre che il pod in cui verrà avviata la nostra immagine deve disporre delle autorizzazioni per eseguire queste azioni. Questo può essere fatto creando il tuo ServiceAccount. L'autorizzazione deve essere rilasciata sotto forma di ClusterRole e ClusterRoleBinding, perché siamo interessati agli oggetti dell'intero cluster.

La descrizione finale in YAML sarà simile a questa:

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

Puoi avviare l'immagine assemblata come una semplice distribuzione:

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

Per comodità, viene creato uno spazio dei nomi separato in cui verrà avviato l'operatore shell e verranno applicati i manifest creati:

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

Questo è tutto: l'operatore shell verrà avviato, si iscriverà agli eventi di creazione dello spazio dei nomi ed eseguirà l'hook quando necessario.

Presentazione di shell-operator: creare operatori per Kubernetes è diventato più semplice

Così, la un semplice script di shell trasformato in un vero e proprio operatore per Kubernetes e funziona come parte di un cluster. E tutto questo senza il complesso processo di sviluppo degli operatori a Golang:

Presentazione di shell-operator: creare operatori per Kubernetes è diventato più semplice

C'è un altro esempio su questo argomento...Presentazione di shell-operator: creare operatori per Kubernetes è diventato più semplice

Ne riveleremo il significato in modo più dettagliato in una delle seguenti pubblicazioni.

filtraggio

Tracciare gli oggetti è utile, ma spesso è necessario reagire modificando alcune proprietà dell'oggetto, ad esempio, per modificare il numero di repliche in Distribuzione o per modificare le etichette degli oggetti.

Quando arriva un evento, l'operatore shell riceve il manifest JSON dell'oggetto. Possiamo selezionare le proprietà che ci interessano in questo JSON ed eseguire l'hook solo quando cambiano. C'è un campo per questo jqFilter, dove è necessario specificare l'espressione jq che verrà applicata al manifest JSON.

Ad esempio, per rispondere alle modifiche nelle etichette per gli oggetti di distribuzione, è necessario filtrare il campo labels fuori dal campo metadata. La configurazione sarà così:

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

Questa espressione jqFilter trasforma il lungo manifest JSON di Deployment in un breve JSON con etichette:

Presentazione di shell-operator: creare operatori per Kubernetes è diventato più semplice

shell-operator eseguirà l'hook solo quando questo breve JSON cambia e le modifiche ad altre proprietà verranno ignorate.

Hook contesto di lancio

La configurazione dell'hook ti consente di specificare diverse opzioni per gli eventi, ad esempio 2 opzioni per eventi da Kubernetes e 2 pianificazioni:

{"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"
]}

Una piccola digressione: sì, supporta l'operatore shell eseguendo script in stile crontab. Maggiori dettagli possono essere trovati in documentazione.

Per distinguere il motivo per cui è stato lanciato l'hook, l'operatore shell crea un file temporaneo e ne passa il percorso in una variabile all'hook BINDING_CONTEXT_TYPE. Il file contiene una descrizione JSON del motivo dell'esecuzione dell'hook. Ad esempio, ogni 10 minuti verrà eseguito l'hook con il seguente contenuto:

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

...e lunedì si inizierà così:

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

per onKubernetesEvent Ci saranno più trigger JSON, perché contiene una descrizione dell'oggetto:

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

Il contenuto dei campi può essere compreso dai loro nomi e si possono leggere maggiori dettagli documentazione. Un esempio di come ottenere un nome di risorsa da un campo resourceName l'uso di jq è già stato mostrato in un hook che replica i segreti:

jq -r '.[0].resourceName' $BINDING_CONTEXT_PATH

Puoi ottenere altri campi in modo simile.

Quali sono le prospettive?

Nel repository del progetto, in /directory di esempi, ci sono esempi di hook pronti per essere eseguiti su un cluster. Quando scrivi i tuoi hook, puoi usarli come base.

È disponibile il supporto per la raccolta di parametri utilizzando Prometheus: i parametri disponibili sono descritti nella sezione METRICA.

Come puoi immaginare, l'operatore shell è scritto in Go e distribuito con licenza Open Source (Apache 2.0). Saremo grati per qualsiasi assistenza allo sviluppo progetto su GitHub: e stelle, problemi e richieste pull.

Sollevando il velo di segretezza, vi informeremo anche che l'operatore shell è piccolo parte del nostro sistema che può mantenere aggiornati i componenti aggiuntivi installati nel cluster Kubernetes ed eseguire varie azioni automatiche. Ulteriori informazioni su questo sistema detto letteralmente lunedì all'HighLoad++ 2019 a San Pietroburgo: presto pubblicheremo il video e la trascrizione di questo rapporto.

Abbiamo un piano per rendere accessibile il resto di questo sistema: l'operatore aggiuntivo e la nostra collezione di hook e moduli. A proposito, addon-operator lo è già disponibile su github, ma la relativa documentazione è ancora in arrivo. L'uscita della raccolta di moduli è prevista per l'estate.

Rimanete sintonizzati!

PS

Leggi anche sul nostro blog:

Fonte: habr.com

Aggiungi un commento