Les conteneurs comme implémentation privilégiée des architectures Cloud Native

La valeur apportée par l’utilisation de conteneurs Docker dans la construction d’architectures applicatives modernes, dites Cloud Native, n’est plus à démontrer. Les principaux arguments en faveur de ce type de technologie sont les suivants :

  • Une rapidité d’expérimentation et de déploiement inégalée. Envie de tester un nouvel outil? Rien de plus simple que de récupérer l’image sur un registre pour la lancer quasi instantanément.
  • Une portabilité idéale avec le paradigme build once – run everywhere qui permet de déployer un même conteneur Docker tout aussi bien sur son poste de développement, sur une infrastructure on premise ou sur n’importe quel fournisseur Cloud du marché.
  • La reproductibilité des environnements, réduisant ainsi considérablement le fameux syndrome connu de l’ensemble des développeurs : « Chez moi, ça marche très bien! »
  • Un atout majeur pour la mise en place des pratiques DevOps en standardisant le packaging des applications.

Cependant, le déploiement d’une infrastructure Docker en production soulève un certain nombre de problématiques. La technologie a su convaincre très rapidement les équipes de développement, son appropriation par les équipes de production est en cours de généralisation. Ce billet retrace la démarche suivie par CACD2 pour la construction d’une architecture Container as a Service (ou CaaS) en production.

Docker par la pratique

Pour parcourir l’ensemble des étapes et des interrogations qui entourent l’utilisation d’architectures de conteneurs, prenons l’exemple d’une application qui a été déployée sur l’usine logicielle CACD2 : l’outil de référence d’analyse de qualimétrie de code SonarQube.

Déploiement d’un conteneur Docker sur son poste de travail

Pour déployer une instance Sonarqube sur son poste local, rien de plus simple. Après avoir installé l’outil Docker sur poste, il suffit de télécharger l’image Docker depuis le registre public à l’aide d’un docker pull puis de lancer le conteneur à l’aide d’un docker run. Une commande cURL sur le port d’écoute du conteneur permet de vérifier que l’application est bien disponible. Et voilà!

SonarQube avec Docker

 

🙁Mais un conteneur Docker, c’est bien stateless n’est-ce pas? Si son exécution est arrêtée, je perds mes données il me semble? C’est loin d’être propre…

Déploiement avec docker-compose sur son poste de travail

En effet, d’autant plus que l’image Docker que nous utilisons dans cet exemple utilise une base de données embarquée HSQL pour gérer la persistance de données. Nous devons donc mettre en place un deuxième conteneur pour la base de données avec un mécanisme de volume persistant pour ne pas perdre nos données à chaque redémarrage. Nous devons donc également configurer la connexion entre ces deux conteneurs pour permettre l’accès à la base de données depuis le conteneur applicatif.

Attention, il n’est pas recommandé d’exécuter des containers de base de données avec Docker pour un environnement productif, prenons cet exemple à titre illustratif uniquement.

L’outil docker-compose a été créé pour faciliter la création de ces environnements multi-conteneurs. Il permet de définir une description en format YAML de la configuration attendue des différents conteneurs à déployer. Il se charge également de créer un réseau virtuel dédié à cet ensemble de conteneurs par lequel ils peuvent communiquer entre eux par simple résolution DNS.

L’exemple suivant illustre l’utilisation de docker-compose pour mettre en place un environnement constitué d’un conteneur applicatif SonarQube et d’une base de données PostgreSQL. Les données manipulées par la base PostgreSQL sont gérées sous forme de volume Docker stocké sur le système de fichiers local de la station de travail elle-même.

SonarQube avec docker-compose

Production ready? Pas si sûr…

Une prod sous docker-compose?

Même si docker-compose présente quelques atouts pour la gestion des environnements multi-conteneurs, il présente cependant un certain nombre de limitations fortes qui ne permettent pas d’en avoir une utilisation productive.

  • Sauf à le coupler avec un orchestrateur de conteneurs comme Swarm, docker-compose ne permet pas d’exécuter des conteneurs sur un parc de machines distribuées.
  • docker-compose ne propose pas de solution de surveillance de l’état de santé des conteneurs. Si un conteneur s’arrête pour une raison indéterminée, l’application devient tout simplement indisponible.
  • L’outil présente une scalabilité limitée car il ne permet pas de créer plusieurs instances d’un même service pour assurer une répartition de charge.

Kubernetes, l’orchestrateur de conteneurs de référence retenu par CACD2

Kubernetes in a nutshell

 

Production-Grade Container Scheduling and Management

Kubernetes is an open-source system for automating deployment, scaling, and management of containerized applications.

Conçu à l’origine par Google puis reversé à la Cloud Native Computing Foundation, Kubernetes (ou k8s) est l’orchestrateur de conteneurs qui émerge comme solution de référence retenue par le marché.

Architecture de Kubernetes

Kubernetes est construit selon une architecture masters / slaves. Le master k8s expose une API REST aux ingénieurs devops pour interagir avec le cluster constitué d’un ensemble de slaves, ou nodes. Cette API est appelée en interne par la ligne de commande kubectl utilisée quotidiennement par les administrateurs k8s.

Les ingénieurs devops définissent l’état attendu du cluster dans un ensemble de fichiers de description YAML. Par exemple, pour assurer la haute disponibilité et la répartition de charge, un état attendu peut demander le déploiement de trois instances d’un même service, construit à partir d’une image Docker, avec une répartition de charge en round-robin entre ces trois instances. La responsabilité du master k8s est de s’assurer en permanence que la configuration effective du cluster est alignée avec la configuration souhaitée définie dans les descripteurs de déploiement.

Pour ce faire, le master k8s communique en permanence avec les différents slaves sur lesquels s’exécutent les conteneurs Docker. Des mécanismes de supervision personnalisables permettent de s’assurer que les conteneurs lancés sont effectivement disponibles. Lorsque le master k8s détecte un écart entre la configuration effective et la configuration souhaitée, par exemple suite à la perte d’un conteneur, il se charge de relancer une nouvelle instance de ce conteneur.

L’unité de déploiement sur un slave Kubernetes est un pod. Un pod est une abstraction Kubernetes qui regroupe un ensemble de conteneurs qui partagent des ressources communes : une unique adresse IP pour l’ensemble des conteneurs et du stockage partagé par exemple. Cette unité de déploiement est couramment englobée dans des abstractions de plus haut niveau (services, replica set, déploiement par exemple), chacune de ces couches apportant un jeu de fonctionnalités supplémentaires pour mettre en place une infrastructure de conteneurs robuste et scalable.

Nous ne rentrerons pas dans plus de détails sur le fonctionnement interne de Kubernetes, les ressources en ligne de qualité sont nombreuses pour appréhender ce nouvel outil!

Vue d’ensemble de l’utilisation de Kubernetes chez CACD2

Pour sa couverture fonctionnelle conséquente, nous avons retenu Kubernetes comme orchestrateur de conteneurs pour mettre en place notre plateforme de Container as a Service (CaaS) sur le cloud public AWS.

Pour assurer une isolation minimale des environnements par entité cliente et par niveau de service (développement / production), nous avons mis en place un cluster Kubernetes par compte client. L’usine de développement CACD2, entièrement déployée sur le CaaS, dispose également d’un compte AWS distinct sur lequel un cluster Kubernetes est installé automatiquement selon nos pratiques d’Infrastructure as Code.

Ci-dessous, un aperçu de notre architecture de conteneurs.

Vue logique de l’architecture CaaS CACD2

Notre usine de développement et nos environnements de développement et de qualification pour le compte de nos différents clients sont construits sur une architecture CaaS Kubernetes déployée sur AWS. Les différents clusters k8s sont installés sur un ensemble d’instances EC2 en suivant les meilleures pratiques d’infrastructure as code. Les applications constituant l’usine de développement CACD2 sont installées sur le cluster k8s. Par exemple, et de manière non exhaustive : Jenkins pour l’orchestration des pipelines devops, SonarQube pour l’analyse de qualimétrie et de sécurité, Nexus comme entrepôt d’images Docker, SeleniumGrid pour les tests automatisés d’écrans. Ces applications peuvent avoir des besoins de stockage spécifiques qui sont adressés en tirant profit des différentes typologies de stockage offerts par AWS : EBS pour du stockage bloc, EFS pour du stockage fichier et S3 pour du stockage objet.

Helm comme facilitateur de déploiement Kubernetes

La gestion des fichiers de configuration Kubernetes peut devenir fastidieuse lorsque les solutions à déployer deviennent de plus en plus complexes :

  • Les fichiers de description YAML se démultiplient sans structuration imposée.
  • La lisibilité et la maintenabilité de ces fichiers peuvent être compromises.
  • Aucune solution n’est proposée pour gérer le paramétrage d’une même solution sur plusieurs environnements.
  • Le partage et la réutilisation de configuration k8s ne sont pas faciles à mettre en place.
  • La commande kubectl qui permet d’interagir avec le cluster présente une API de trop bas niveau pour répondre à ces problématiques de déploiement à l’échelle.

Helm à la rescousse!

L’utilitaire Helm, le package manager de Kubernetes, a été construit pour répondre à cette problématique. Les quatre piliers suivants ont structuré la construction de l’outil :

  • L’installation de ressources sur un cluster Kubernetes doit être aussi facile qu’avec les gestionnaires de paquets standards de type yum, apt, brew.
  • Les équipes doivent être en mesure de collaborer sur la construction des fichiers de description de déploiement.
  • Les releases déployées sur Kubernetes doivent être reproductibles.
  • Les packages déployés sur Kubernetes doivent pouvoir être partageables.

Helm introduit ainsi les notions suivantes pour faciliter le déploiement de ressources sur un cluster K8s :

  • Chart: à l’image d’un paquet installé sur un système d’exploitation, un chart représente un ensemble de ressources à déployer sur le cluster.
  • Repository: une collection de charts fiabilisés et partagés, à l’image d’un repository npm, yum ou autre. Le repository central public disponible à cette adresse contient plus de 160 charts stables pour permettre de déployer rapidement les solutions Open Source de référence sur un cluster Kubernetes. Il est également possible d’installer un dépôt de charts privé à l’aide de l’outil ChartMuseum par exemple (solution retenue comme dépôt privé CACD2).
  • Release: un chart déployé dans un cluster Kubernetes devient une release que l’on peut désinstaller ou mettre à jour.

 

Architecture de la solution Helm

La solution Helm est constituée de deux composants principaux. Le client Helm est une interface en ligne de commande qui assure les responsabilités suivantes :

  • La gestion des dépôts
  • L’interaction avec le serveur Tiller
    • Pour l’envoi des charts à installer
    • Pour l’interaction avec les releases installées sur le cluster
    • Pour l’envoi des demandes de mise à jour de déinstallation des releases

Le serveur Tiller est un composant déployé dans le cluster Kubernetes qui s’interface avec les API K8s pour assurer les fonctionnalités suivantes:

  • écoute des demandes en provenance des clients Helm
  • construction des releases
  • installation des charts sur Kubernetes
  • gestion du cycle de vie des releases

Exemple d’installation d’un chart avec Helm

Pour installer le chart officiel SonarQube sur un cluster Kubernetes avec Helm, il faut tout d’abord configurer le poste de déploiement pour qu’il se connecte sur le cluster k8s avec la commande kubectl. Une fois cette configuration appliquée et le client Helm installé, l’installation (avec les paramètres par défaut) est des plus aisées!

Installation de SonarQube sur un cluster Kubernetes avec Helm

 

L’utilisation de Helm nous a permis d’accélérer considérablement la construction de notre usine logicielle en tirant profit des charts fiabilisés par la communauté. Cependant, nous avons été amenés à appliquer des modifications sur certains charts, principalement pour assurer une meilleure gestion des problématiques de stockage.

Focus sur l’utilisation de Kubernetes dans la chaîne de CI / CD

Pour industrialiser la construction d’applications Cloud Native, CACD2 a mis en place des pipelines CI / CD (Continous Integration / Continous Delivery) pour construire, tester et déployer en continu des applications sur un environnement CaaS. Ci-dessous, un exemple de représentation graphique du pipeline Jenkins de construction d’une image Docker déclenché suite au commit d’un développeur sur le dépôt git du projet.

Pipeline de publication d’images Docker

Les étapes suivantes sont exécutées de manière automatique suite au commit du développeur :

  • La récupération de la dernière version du code source par Jenkins.
  • La construction du projet à partir de ses sources, à l’aide de Gradle ou Maven par exemple pour des projets Java.
  • Des tests unitaires et d’intégration pour s’assurer du bon fonctionnement du code modifié.
  • L’analyse de qualité et de sécurité du code source avec l’outil SonarQube pour s’assurer que le code source modifié est conforme aux barrières de qualité.
  • La construction de l’image Docker intégrant la dernière version du code source modifié.
  • La publication de cette image Docker dans le registre privé CACD2 (Sonatype Nexus).

A la fin de ce pipeline, une nouvelle image Docker est disponible dans le registre, elle est éligible à un nouveau déploiement sur le CaaS piloté par un pipeline Jenkins dédié représenté ci-dessous.

Pipeline de déploiement

Ce pipeline de déploiement, dont le fonctionnement interne sera décrit dans un billet distinct, permet de construire le chart Helm modélisant la solution mise à jour à déployer sur un cluster Kubernetes. Une fois ce chart construit et déployé sur le dépôt interne de charts CACD2, il peut être déployé sur l’environnement d’intégration continue pour être soumis à une batterie de tests automatisés. Si ces tests sont concluants, le chart peut être promu sur un environnement de qualification sur lequel une campagne de tests fonctionnels pourra être déroulée avant livraison du package au client.

En interne, ce pipeline Jenkins s’exécute lui-même sur un cluster Kubernetes. Ce mode de fonctionnement permet d’instancier des slaves Jenkins dynamiquement à partir d’images Docker fiabilisées. Ceci permet de garantir la stabilité et la pérennité du build tout en offrant une élasticité interessante permettant de répondre aux pics de charge ponctuels.

Vue infrastructure du pipeline CI / CD déployé sur le CaaS

 

Conclusion

Dans une approche d’infrastructure as code, nous avons mis en place une plateforme de CI / CD permettant de déployer de manière continue des applications Cloud Native construites à l’aide de conteneurs Docker. Nous avons mis en place cette usine de développement sur une plateforme CaaS en mode Cloud Public, tirant ainsi profit de l’élasticité et de la fiabilité offertes par ce type de plateforme.

La mise en place d’une infrastructure de conteneurs en production est grandement facilitée en retenant un orchestrateur de conteneurs comme Kubernetes. La pleine maîtrise de l’outil nécessite cependant un bon niveau de maturité sur les piles logicielles élémentaires comme Docker et sur les bonnes pratiques de conception d’application Cloud Native. Nous avons également découvert que l’utilisation d’un gestionnaire de paquets comme Helm permet grandement de faciliter le déploiement d’applications sur un environnement CaaS.

Si jamais vous voulez en savoir davantage sur la mise en place de cette infrastructure CaaS, n’hésitez pas à nous solliciter dans la section Commentaires de ce billet!