Comment ce sidecar est-il arrivé ici [à Kubernetes]?

Remarque perev. : Avec cet article, écrit par Scott Rahner, ingénieur chez Dow Jones, nous continuons la série de nombreux documents qui expliquent comment Kubernetes fonctionne, comment ses composants de base fonctionnent, sont interconnectés et utilisés. Cette fois, il s'agit d'une note pratique avec un exemple de code pour créer un hook dans Kubernetes, démontrée par l'auteur «sous prétexte» de créer automatiquement des conteneurs sidecar.


(Photo de Gordon A. Maxwell, trouvée sur Internet.)

Lorsque j'ai commencé à apprendre les conteneurs side-car et le maillage de service, je devais comprendre comment fonctionne le mécanisme clé - l'insertion automatique des conteneurs side-car -. En effet, dans le cas d'utilisation de systèmes comme Istio ou Consul, lorsque le conteneur avec l'application est déployé, le conteneur Envoy déjà configuré apparaît soudainement dans son pod (une situation similaire se produit avec Conduit, dont nous avons parlé au début de l'année - environ Transl.) . Quoi? Comment? Alors mes recherches ont commencé ...

Pour ceux qui ne le savent pas, un sidecar-container est un conteneur qui est déployé à côté des conteneurs de l'application afin «d'aider» cette application d'une manière ou d'une autre. Un exemple d'une telle utilisation est un proxy pour gérer le trafic et terminer les sessions TLS, un conteneur pour les journaux et les métriques en streaming, un conteneur pour analyser les problèmes de sécurité ... L'idée est d'isoler divers aspects de l'application entière de la logique métier en utilisant des conteneurs séparés pour chacun fonctions.

Avant de poursuivre, je décrirai mes attentes. Le but de cet article n'est pas d'expliquer les subtilités et les scénarios d'utilisation de Docker, Kubernetes, service mesh, etc., mais de démontrer une approche puissante pour étendre les capacités de ces technologies. L'article est destiné à ceux qui connaissent déjà l'utilisation de ces technologies ou, au moins, ont beaucoup lu à leur sujet. Pour essayer la partie pratique en action, vous aurez besoin d'une machine avec Docker et Kubernetes déjà configurés. La manière la plus simple de procéder est https://docs.docker.com/docker-for-windows/kubernetes/ (un manuel Windows qui fonctionne avec Docker pour Mac). (Remarque perev .: Comme alternative aux utilisateurs de Linux et * nix-systèmes, nous pouvons offrir Minikube .)

Image globale


Pour commencer, jetons un coup d'œil à Kubernetes:


Kube Arch sous licence CC BY 4.0

Lorsque vous avez l'intention de déployer quelque chose sur Kubernetes, vous devez envoyer l'objet à kube-apiserver. Cela se fait le plus souvent en passant des arguments ou un fichier YAML à kubectl. Dans ce cas, le serveur API passe par plusieurs étapes avant de placer directement les données dans etcd et de planifier les tâches correspondantes:



Cette séquence est importante pour comprendre le fonctionnement de l'insertion d'un conteneur side-car. En particulier, vous devez faire attention au contrôle d'admission , dans lequel Kubernetes valide et, si nécessaire, modifie les objets avant de les stocker (pour plus de détails sur cette étape, consultez le chapitre "Contrôle d'accès" dans cet article - environ Transl.) . Kubernetes vous permet également d'enregistrer des webhooks pouvant effectuer des validations et mutations définies par l'utilisateur.

Cependant, le processus de création et d'enregistrement de vos hooks n'est pas si simple et bien documenté. J'ai dû passer plusieurs jours à lire et relire la documentation, ainsi qu'à analyser le code Istio et Consul. Et en ce qui concerne le code de certaines des réponses de l'API, j'ai passé au moins une demi-journée à faire des essais et des erreurs aléatoires.

Une fois le résultat obtenu, je pense qu'il sera injuste de ne pas le partager avec vous tous. C'est simple et en même temps efficace.

Code


Le nom webhook parle de lui-même - il s'agit d'un point de terminaison HTTP qui implémente l'API définie dans Kubernetes. Vous créez un serveur API que Kubernetes peut appeler avant de traiter les déploiements. J'ai dû faire face à des difficultés ici, car seuls quelques exemples sont disponibles, dont certains ne sont que des tests unitaires de Kubernetes, d'autres sont cachés au milieu d'une énorme base de code ... et tous sont écrits en Go. Mais j'ai choisi une option plus abordable - Node.js:

const app = express(); app.use(bodyParser.json()); app.post('/mutate', (req, res) => { console.log(req.body) console.log(req.body.request.object) let adminResp = {response:{ allowed: true, patch: Buffer.from("[{ \"op\": \"add\", \"path\": \"/metadata/labels/foo\", \"value\": \"bar\" }]").toString('base64'), patchType: "JSONPatch", }} console.log(adminResp) res.send(adminResp) }) const server = https.createServer(options, app); 

( index.js )

Le chemin vers l'API - dans ce cas /mutate - peut être arbitraire (il ne devrait correspondre qu'à la YAML passée à Kubernetes à l'avenir). Il est important pour lui de voir et de comprendre le JSON reçu du serveur API. Dans ce cas, nous ne retirons rien de JSON, mais cela pourrait être utile dans d'autres scénarios. Dans le code ci-dessus, nous mettons à jour JSON. Deux choses sont nécessaires pour cela:

  1. Apprenez et comprenez le correctif JSON .
  2. Convertissez correctement une expression de patch JSON en un tableau d'octets codés en base64.

Une fois cela fait, il vous suffit de transmettre la réponse au serveur API avec un objet très simple. Dans ce cas, nous ajoutons l'étiquette foo=bar tout pod qui nous parvient.

Déploiement


Eh bien, nous avons du code qui accepte les demandes du serveur API Kubernetes et y répond, mais comment le déployer? Et comment faire en sorte que Kubernetes nous redirige ces demandes? Vous pouvez déployer un tel point de terminaison partout où vous pouvez atteindre le serveur API Kubernetes. Le moyen le plus simple consiste à déployer le code sur le cluster Kubernetes lui-même, ce que nous ferons dans cet exemple. J'ai essayé de rendre l'exemple aussi simple que possible, donc pour toutes les actions, j'utilise uniquement Docker et kubectl. Commençons par créer un conteneur dans lequel le code s'exécutera:

 FROM node:8 USER node WORKDIR /home/node COPY index.js . COPY package.json . RUN npm install #       TLS CMD node index.js 

( Dockerfile )

Apparemment, tout est très simple ici. Prenez l'image de la communauté du nœud et déposez-y le code. Vous pouvez maintenant effectuer un assemblage simple:

 docker build . -t localserver 

L'étape suivante consiste à créer un déploiement dans Kubernetes:

 apiVersion: apps/v1 kind: Deployment metadata: name: webhook-server spec: replicas: 1 selector: matchLabels: component: webhook-server template: metadata: labels: component: webhook-server spec: containers: - name: webhook-server imagePullPolicy: Never image: localserver 

( deployment.yaml )

Remarquez comment nous avons fait allusion à l'image que nous venons de créer? Cela pourrait tout aussi bien être un pod, et quelque chose d'autre auquel nous pouvons connecter un service dans Kubernetes. Définissez maintenant ce service:

 apiVersion: v1 kind: Service metadata: name: webhook-service spec: ports: - port: 443 targetPort: 8443 selector: component: webhook-server 

Ainsi, dans Kubernetes, un point de terminaison apparaît avec un nom interne qui pointe vers notre conteneur. La dernière étape consiste à dire à Kubernetes que nous voulons que le serveur API appelle ce service lorsqu'il est prêt à effectuer des mutations :

 apiVersion: admissionregistration.k8s.io/v1beta1 kind: MutatingWebhookConfiguration metadata: name: webhook webhooks: - name: webhook-service.default.svc failurePolicy: Fail clientConfig: service: name: webhook-service namespace: default path: "/mutate" #    base64-  rootCA.crt #    `cat rootCA.crt | base64 | tr -d '\n'` #    .  caBundle: "==" rules: - operations: [ "CREATE" ] apiGroups: [""] apiVersions: ["v1"] resources: ["pods"] 
( hook.yaml )

Le nom et le chemin ici peuvent être quelconques, mais j'ai essayé de les rendre aussi significatifs que possible. Changer le chemin signifiera la nécessité de modifier le code correspondant en JavaScript. Échec du failurePolicy La failurePolicy est également failurePolicy - elle détermine si l'objet doit être enregistré si le hook renvoie une erreur ou échoue. Dans ce cas, nous demandons à Kubernetes de ne pas poursuivre le traitement. Enfin, les règles: elles changeront en fonction des appels d'API auxquels vous attendez des actions de Kubernetes. Dans ce cas, puisque nous essayons d'émuler l'insertion d'un conteneur de sidecar, nous devons intercepter les demandes pour créer un pod.

C'est tout! Si simple ... mais qu'en est-il de la sécurité? RBAC est un aspect qui n'est pas couvert dans l'article. Je suppose que vous exécutez l'exemple dans Minikube ou dans Kubernetes, fourni avec Docker pour Windows / Mac. Cependant, je vais vous parler d'un autre élément nécessaire. Le serveur Kubernetes API accède uniquement aux points de terminaison avec HTTPS, donc l'application nécessite des certificats SSL. Vous devrez également indiquer à Kubernetes qui est l'autorité de certification du certificat racine.

TLS


À des fins de démonstration uniquement (!!!), j'ai ajouté du code au Dockerfile pour créer une autorité de certification racine et l'utiliser pour signer le certificat:

 RUN openssl genrsa -out rootCA.key 4096 RUN openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.crt \ -subj "/C=US/ST=New Jersey/L=Princeton /O=Dow Jones/OU=PIB/CN=*.default.svc/emailAddress=scott.rahner@dowjones.com" RUN openssl genrsa -out webhook.key 4096 RUN openssl req -new -key webhook.key -out webhook.csr \ -subj "/C=US/ST=New Jersey/L=Princeton /O=Dow Jones/OU=PIB/CN=webhook-service.default.svc/emailAddress=scott.rahner@dowjones.com" RUN openssl x509 -req -in webhook.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out webhook.crt -days 1024 -sha256 RUN cat rootCA.crt | base64 | tr -d '\n' 

( Dockerfile )

Remarque: la dernière étape consiste à afficher une seule ligne avec l'autorité de certification racine codée en base64. C'est exactement ce qui est requis pour la configuration du hook, donc lors de vos tests ultérieurs, assurez-vous de copier cette ligne dans le champ caBundle fichier caBundle . Dockerfile jette des certificats directement dans WORKDIR , donc JavaScript les prend simplement à partir de là et les utilise pour le serveur:

 const privateKey = fs.readFileSync('webhook.key').toString(); const certificate = fs.readFileSync('webhook.crt').toString(); //… const options = {key: privateKey, cert: certificate}; const server = https.createServer(options, app); 

Maintenant, le code prend en charge le lancement HTTPS et a également indiqué à Kubernetes où nous trouver et quel centre de confiance faire confiance. Il ne reste plus qu'à tout intégrer dans un cluster:

 kubectl create -f deployment.yaml kubectl create -f service.yaml kubectl create -f hook.yaml 

Résumer


  • Deployment.yaml lance un conteneur qui sert l'API de raccordement via HTTPS et renvoie un correctif JSON pour modifier l'objet.
  • Service.yaml fournit un point de terminaison pour le conteneur - webhook-service.default.svc .
  • Hook.yaml indique au serveur API où nous trouver: https://webhook-service.default.svc/mutate .

Essayons en affaires!


Tout est déployé dans un cluster - il est temps d'essayer le code en action, ce que nous ferons en ajoutant un nouveau pod / Déploiement. Si tout fonctionne correctement, le crochet devra ajouter une étiquette supplémentaire foo :

 apiVersion: apps/v1 kind: Deployment metadata: name: test spec: replicas: 1 selector: matchLabels: component: test template: metadata: labels: component: test spec: containers: - name: test image: node:8 command: [ "/bin/sh", "-c", "--" ] args: [ "while true; do sleep 30; done;" ] 

( test.yaml )

 kubectl create -f test.yaml 

Ok, nous avons vu le deployment.apps test created ... mais cela a-t-il fonctionné?

 kubectl describe pods test Name: test-6f79f9f8bd-r7tbd Namespace: default Node: docker-for-desktop/192.168.65.3 Start Time: Sat, 10 Nov 2018 16:08:47 -0500 Labels: component=test foo=bar 

Super! Bien que test.yaml ait reçu une seule étiquette ( component ), le pod résultant en a reçu deux: component et foo .

Devoirs


Mais attends! Allons-nous utiliser ce code pour créer un conteneur sidecar? J'ai averti que je montrerais comment ajouter un side-car ... Et maintenant, avec les connaissances et le code: https://github.com/dowjones/k8s-webhook - expérimentez audacieusement et découvrez comment faire votre side-car inséré automatiquement. C'est assez simple: il vous suffit de préparer le correctif JSON correct, qui ajoutera un conteneur supplémentaire dans le déploiement de test. Bonne orchestration!

PS du traducteur


Lisez aussi dans notre blog:

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


All Articles