Skip to main content

Déployer des applications natives Kubernetes en toute confiance

Écrit par
Headshot of Amir Moualem

Amir Moualem

kubernetes tumb

14 novembre 2019

0 minutes de lecture

Il y a quelques mois, notre équipe a commencé à développer un nouveau produit.

Ce nouveau produit présente plusieurs caractéristiques qui le distinguent des autres projets logiciels que nous avons développés jusqu’ici :

  1. Il est natif Kubernetes, c’est-à-dire étroitement lié à l’API Kubernetes et dépendant de cette API pour fonctionner (ou même pour être testé… nous y reviendrons plus tard).

  2. Certaines configurations doivent être appliquées pour qu’il fonctionne correctement. Nous devons donc fournir l’image avec les fichiers de configuration Kubernetes .yaml afin que nos utilisateurs puissent l’installer facilement. Notre volonté de simplifier au maximum la prise en main du produit complique encore les choses : nous devons prendre en charge l’installation avec les charts Helm et les fichiers .yaml « vanilla », qui doivent tous deux être testés et publiés.

  3. Il s’agit d’une application côté client : dès sa publication, nous en perdons le contrôle, sans possibilité de revenir en arrière. Nous pouvons publier des correctifs pour nos microservices en quelques minutes, mais demander régulièrement à nos utilisateurs de mettre leur installation à niveau ne ferait que compliquer leur prise en main.

Nous avons donc décidé de consacrer un peu plus de temps à la planification d’un pipeline CI/CD qui nous permettrait de livrer ce produit en toute confiance, sans trop nous ralentir.

Le pipeline CI/CD existant de Snyk pour les microservices repose sur les interactions de nos développeurs avec GitHub :

  1. L’ouverture d’une pull request installe, exécute et teste le service dans un environnement de test.

  2. La fusion d’une pull request commence de la même manière, puis déclenche une version sémantique du service, la création d’une image Docker, son exécution et son déploiement dans le cluster Kubernetes de notre environnement de production.

Nous voulions des interactions similaires afin que le processus paraisse aussi naturel que possible.

Alors, comment avons-nous abordé ce projet ?

Tests d’intégration Kubernetes avec Kind

Pour nos esprits peu expérimentés, la première grande question était de savoir comment tester un logiciel qui dépend autant de l’API Kubernetes. Simuler tous les flux liés à l’API Kubernetes serait extrêmement difficile à écrire et à maintenir, et risquerait de passer à côté de certains flux essentiels à tester. Une approche naïve consisterait à utiliser un véritable cluster Kubernetes, soit en en créant un pour chaque phase de test, soit en disposant d’un environnement permanent. Cette approche fonctionnerait sans aucun doute, mais elle entraînerait bien trop de complications à ce stade précoce du développement de notre logiciel :

  • Créer un nouvel environnement Kubernetes à chaque fois prendrait du temps à mettre en place et allongerait considérablement la configuration des tests.

  • Disposer d’un environnement permanent implique de gérer le nettoyage et risque de rendre les tests beaucoup moins fiables.

Heureusement, on nous a orientés vers leprojet Kind. Kind est un outil basé sur Docker qui permet d’exécuter des clusters Kubernetes locaux conformes à l’API Kubernetes. Il répondait parfaitement à nos besoins : il combine les avantages d’un environnement propre pour chaque test et d’une configuration très rapide.

Désormais, notre phase de test, dans notre environnement CI ou même en local, se contente de créer un cluster Kind, d’y charger l’image que nous venons de créer et de vérifier que notre conteneur effectue bien le travail qui lui est demandé.

Testez ce que vous livrez ; livrez ce que vous testez

Le défi suivant consistait à nous assurer que nous ne testions pas seulement notre code, mais aussi le produit complet que nous livrons.

Le code (et le service) est installé dans une image Docker. Cette image peut être déployée dans des clusters Kubernetes avec des charts Helm ou des fichiers .yaml « vanilla », et ces clusters peuvent prendre en charge différentes versions des API Kubernetes.

Nous cherchons à tester et à prendre en charge ce large éventail de scénarios.

Dès que nous créons une image, nous lui attribuons un tag afin de pouvoir l’identifier tout au long des étapes de test. Nous installons cette image avec les charts Helm et les fichiers .yaml « vanilla », sur des clusters Kubernetes prenant en charge différentes versions d’API, avant de lui attribuer le statut « approuvé ».

C’est également à cette étape que nous utilisons semantic-release pour versionner nos images. Au lieu de suivre nos produits au moyen de leurs SHA Git ou de versions arbitraires attribuées manuellement, nous avons choisi le versionnage sémantique. Nous pouvons ainsi fournir des versions simples et compréhensibles, avec des notes de version générées automatiquement et publiées sur GitHub. Au final, l’image porte un tag de version, par exemple 1.2.3.

À ce stade, il ne nous reste plus qu’à indiquer cette nouvelle version dans nos fichiers de déploiement .yaml…

Publier facilement avec les charts Helm et GitHub Pages

Helm est le gestionnaire de paquets de Kubernetes. Une brève recherche a révélé deux principales approches pour publier des charts Helm :

  1. Héberger le chart Helm dans ledépôt public de charts Helm sélectionnés.

  2. Les héberger nous-mêmes avecGithub Pages dans notre dépôt.

La deuxième approche nous semblait plus judicieuse, du moins au départ, car elle est autonome et ne crée aucune dépendance envers un tiers. À mesure que notre produit et notre pipeline CI/CD gagneront en maturité, nous pourrons aussi décider de contribuer notre chart au dépôt public.

Pour tout orchestrer avec GitHub

Il semble que nous ayons résolu la plupart de nos problèmes : nous exécutons des tests d’intégration avec Kind pour simuler de véritables environnements Kubernetes dans différents scénarios et utilisons le versionnage sémantique pour attribuer des tags à nos produits, que nous publions simplement sur GitHub Pages.

Comment tout cela s’intègre-t-il à notre processus de développement ?

Nous avons convenu d’utiliser deux branches principales dans notre dépôt Git :

  1. staging, notrebranche par défaut, est la branche de départ et de fusion de toutes les modifications apportées au produit (fonctionnalités, corrections de bugs, tâches courantes, etc.).

  2. master est la branche qui sert à décider de la publication de notre produit testé.

Le pipeline CI/CD comporte quatre étapes au total, entre ces deux branches et les deux principales interactions de nos développeurs avec les branches GitHub (ouvrir une pull request et la fusionner). Chacune vise à renforcer notre confiance dans le produit que nous avons créé et à nous rapprocher de sa livraison à nos utilisateurs.

Précisons que l’orchestration est assurée par Travis CI (la migration vers CircleCI est en cours), mais qu’à toutes fins utiles, un court script Python développé en interne aurait également pu faire l’affaire.

Le tableau suivant récapitule ces quatre étapes et ce que chacune nous permet d’accomplir.

Déclencheur

Action

Objectif

Ouvrir une pull request d’une branche de fonctionnalité vers staging.

Lint, compilation, tests unitaires.
Créer une image Docker (jetable) et l’utiliser pour les tests d’intégration avec Kind.

Tester dans un environnement propre.

Fusionner une pull request d’une branche de fonctionnalité vers staging.

Créer une image Docker. Lui attribuer le tag candidate.La soumettre à nos tests d’intégration.Utiliser Semantic Release pour attribuer à l’image testée le tag 1.2.3-approved.

Vérifier que les branches fusionnées ne sont pas en conflit. Tester et créer dans un environnement propre. Créer une seule fois le produit que nous prévoyons de publier.

Ouvrir une pull request de staging vers master.

Aucune.

C’est le moment de suspendre le processus pour effectuer les étapes manuelles souhaitées avant la publication du produit, comme ledogfooding.

Dernière occasion de recueillir des commentaires.

Rappel : inspecter manuellement certains environnements de test, si nécessaire.

Fusionner une pull request de staging vers master.

Remplacer le tag de l’image par son tag public, 1.2.3. Modifier la référence au tag dans nos fichiers .yaml et nos charts Helm pour qu’ils pointent vers la nouvelle version. Publier une nouvelle branchegh-pages contenant toutes les modifications afin que le produit puisse être utilisé.

Publier le produit qui a déjà été créé et testé : nous savons exactement ce que nous publions.

Pas de récapitulatif ennuyeux et banal ici

Je plaisante, c’est complètement ennuyeux. Mais je vais faire court.

Nous avons créé un excellent pipeline CI/CD et beaucoup appris en chemin. Nous trouvons le juste équilibre entre l’automatisation et quelques manipulations manuelles qui nous semblent naturelles. Chaque étape renforce notre confiance dans ce que nous livrons, et la dernière rend effectivement notre livraison publique.

Comme dans tout projet logiciel, il y a encore matière à amélioration. Nous pourrions commencer à publier dans un dépôt public de charts Helm. Nous pourrions étendre notre infrastructure de test pour y inclure davantage d’environnements de longue durée afin de tester les mises à niveau. Lorsque nous aurons suffisamment confiance dans nos tests, nous pourrons peut-être même supprimer certaines étapes manuelles avant la publication… qui sait ?

La sécurité des conteneurs, pensée pour les développeurs

Snyk détecte et corrige automatiquement les vulnérabilités dans les images de conteneurs et les workloads Kubernetes.

Publié dans: