Présentation de l'opérateur shell: faciliter encore plus les opérateurs pour Kubernetes

Notre blog contient déjà des articles sur les capacités des opérateurs de Kubernetes et comment écrire vous-même un opérateur simple . Cette fois, nous voulons attirer votre attention sur notre solution Open Source, qui élève la création d'opérateurs à un niveau super facile - familiarisez-vous avec l' opérateur shell !

Pourquoi?


L'idée d'un opérateur shell est assez simple: abonnez-vous aux événements des objets Kubernetes, et lors de la réception de ces événements, démarrez un programme externe, en lui fournissant des informations sur l'événement:



Le besoin s'est fait sentir chez nous lorsque, lors du fonctionnement des clusters, de petites tâches ont commencé à apparaître que nous voulions vraiment automatiser de la bonne manière. Toutes ces tâches mineures ont été résolues à l'aide de scripts bash simples, bien que, comme vous le savez, les opérateurs soient mieux écrits en Golang. De toute évidence, investir dans un développement d'opérateur à grande échelle pour chacune de ces petites tâches serait inefficace.

Opérateur en 15 minutes


Examinons un exemple de ce qui peut être automatisé dans un cluster Kubernetes et comment l'aide-opérateur va aider. Un exemple serait le suivant: duplication d'un secret pour accéder au registre docker.

Les pods qui utilisent des images du registre privé doivent contenir dans leur manifeste un lien vers un secret contenant des données pour accéder au registre. Ce secret doit être créé dans chaque espace de noms avant de créer des pods. Il est tout à fait possible de le faire manuellement, mais si nous configurons des environnements dynamiques, il y aura beaucoup d'espace de noms pour une application. Et si les applications ne sont pas non plus 2-3, le nombre de secrets devient très important. Et encore une chose au sujet des secrets: je voudrais changer la clé pour accéder au registre de temps en temps. En conséquence, les opérations manuelles en tant que solution sont totalement inefficaces - vous devez automatiser la création et la mise à jour des secrets.

Automatisation simple


Nous allons écrire un script shell qui s'exécute une fois toutes les N secondes et vérifie l'espace de noms pour un secret, et s'il n'y a pas de secret, il est créé. L'avantage de cette solution est qu'elle ressemble à un script shell dans cron - une approche classique et compréhensible. L'inconvénient est que dans l'intervalle entre ses lancements, un nouvel espace de noms peut être créé et pendant un certain temps, il restera sans secret, ce qui entraînera des erreurs lors du lancement des pods.

Automatisation avec shell-operator


Pour que notre script fonctionne correctement, le lancement classique de cron doit être remplacé par le lancement à l'espace de noms d'événement est ajouté: dans ce cas, vous pouvez réussir à créer un secret avant de l'utiliser. Voyons comment implémenter cela en utilisant l'opérateur shell.

Tout d'abord, analysons le script. Les scripts en termes d'opérateur shell sont appelés hooks. Chaque hook au démarrage avec l'indicateur --config informe l'opérateur du shell de ses liaisons, c'est-à-dire par quels événements il doit être lancé. Dans notre cas, nous utiliserons onKubernetesEvent :

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

Il est décrit ici que nous nous intéressons aux événements pour ajouter ( add ) des objets de type namespace .

Maintenant, vous devez ajouter le code qui sera exécuté lorsque l'événement se produira:

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

Super! Le résultat est un petit et beau script. Pour la «faire revivre», il reste deux étapes: préparer l'image et l'exécuter dans le cluster.

Préparer une image avec un crochet


Si vous regardez le script, vous pouvez voir que les jq kubectl et jq . Cela signifie que l'image doit avoir les éléments suivants: notre hook, un opérateur shell qui surveillera les événements et lancera le hook, ainsi que les commandes utilisées par le hook (kubectl et jq). Hub.docker.com a déjà une image toute prête dans laquelle shell-operator, kubectl et jq sont emballés. Il reste à ajouter le hook avec un simple 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 

Lancement du cluster


Encore une fois, regardons le crochet et écrivons cette fois quelles actions et avec quels objets il effectue dans le cluster:

  1. S'abonner aux événements d'espace de noms
  2. crée un secret dans des espaces de noms autres que celui où il s'exécute.

Il s'avère que le pod dans lequel notre image sera lancée doit avoir des autorisations pour ces actions. Cela peut être fait en créant votre propre ServiceAccount. L'autorisation doit être effectuée sous la forme de ClusterRole et ClusterRoleBinding, car nous nous intéressons aux objets de partout dans le cluster.

La description finale dans YAML ressemble à ceci:

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

Vous pouvez exécuter l'image assemblée sous la forme d'un déploiement simple:

 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 


Pour plus de commodité, un espace de noms séparé est créé où l'opérateur shell sera lancé et les manifestes créés seront appliqués:

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


C'est tout: l'opérateur shell va démarrer, s'abonner aux événements de création d'espace de noms et démarrer le hook si nécessaire.



Ainsi, un simple script shell est devenu un véritable opérateur pour Kubernetes et fonctionne comme une partie intégrante du cluster. Et tout cela - sans le processus compliqué de développement d'opérateurs sur Golang:



Il y a une autre illustration à ce sujet ...


Nous révélerons sa signification plus en détail dans l'une des publications suivantes. MISE À JOUR (1er mai 2019): voir « Extension et expansion de Kubernetes (revue et rapport vidéo) ».

Filtrage


Le suivi des objets est bon, mais il est souvent nécessaire de répondre aux modifications de certaines propriétés d'un objet , par exemple à une modification du nombre de répliques dans un déploiement ou à une modification des étiquettes d'un objet.

Lorsqu'un événement arrive, l'opérateur shell reçoit le manifeste JSON de l'objet. Vous pouvez sélectionner les propriétés qui nous intéressent dans ce JSON et exécuter le hook uniquement lorsqu'elles changent. Pour ce faire, le champ jqFilter est jqFilter , où vous devez spécifier l'expression jq qui sera appliquée au manifeste JSON.

Par exemple, pour répondre aux modifications d'étiquette sur les objets de déploiement, vous devez filtrer le champ d' labels partir du champ de metadata . La configuration sera comme ceci:

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

Cette expression dans jqFilter transforme le manifeste JSON long du déploiement en JSON court avec des étiquettes:



L'opérateur shell ne déclenchera un hook que lorsque ce court JSON change et que les modifications apportées à d'autres propriétés sont ignorées.

Contexte de lancement du crochet


La configuration du hook vous permet de spécifier plusieurs options pour les événements - par exemple, 2 options pour les événements de Kubernetes et 2 planifications:

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

Une petite digression: oui, shell-operator prend en charge l' exécution de scripts de style crontab . Vous pouvez en lire plus dans la documentation .

Pour distinguer la raison du lancement du hook, l'opérateur shell crée un fichier temporaire et lui transmet le chemin d'accès au BINDING_CONTEXT_TYPE dans la variable BINDING_CONTEXT_TYPE . Le fichier contient une description JSON de la raison du démarrage du hook. Par exemple, toutes les 10 minutes, un hook démarre avec le contenu suivant:

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

... et lundi, cela commencera par ceci:

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

Pour onKubernetesEvent aura plus de tir JSON depuis il contient une description de l'objet:

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

Le contenu des champs peut être compris à partir de leurs noms, et plus en détail - lire dans la documentation . Un exemple d'obtention du nom de ressource à partir du champ resourceName aide de jq a déjà été montré dans un hook qui réplique des secrets:

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

De même, vous pouvez obtenir les champs restants.

Et ensuite?


Dans le référentiel de projet, dans le répertoire / examples , il existe des exemples de hooks prêts à être exécutés dans le cluster. Lorsque vous écrivez vos crochets, vous pouvez les prendre comme base.

La collecte des métriques à l'aide de Prometheus est prise en charge - les métriques disponibles sont écrites dans la section METRICS .

Comme vous pouvez le deviner, l'opérateur shell est écrit en Go et distribué sous la licence Open Source (Apache 2.0). Nous serons reconnaissants pour toute aide sur le développement du projet sur GitHub : astérisques, problèmes et demandes de tirage.

Ouvrant le voile du secret, nous informons également que l'opérateur shell est une petite partie de notre système, qui peut maintenir à jour les modules complémentaires installés dans le cluster Kubernetes et effectuer diverses actions automatiques. Nous avons parlé de ce système plus en détail lundi à HighLoad ++ 2019 à Saint-Pétersbourg - la vidéo et la transcription de ce rapport seront publiées sous peu.

Nous avons un plan pour ouvrir le reste de ce système: addon-operator et notre collection de crochets et de modules. Soit dit en passant, addon-operator est déjà disponible sur GitHub , mais sa documentation est toujours en cours. La sortie de la collection de modules est prévue en été.

Restez à l'écoute!

PS


Lisez aussi dans notre blog:

Source: https://habr.com/ru/post/fr447442/


All Articles