Votre organisation a-t-elle déjà connu le scénario suivant ?
Un scientifique vient de finaliser un outil ou un pipeline analytique, mais personne dans l'équipe ne sait précisément comment le passer en production : comment simplifier le déploiement automatisé, faire évoluer (à la hausse comme à la baisse) les ressources de calcul du pipeline en optimisant les coûts, garantir la résilience face aux pannes matérielles ou logicielles imprévues, tout en facilitant le déploiement des mises à jour et la dépréciation des anciennes versions.
Avec autant de questions d'engineering critiques laissées en suspens, comment votre équipe scientifique peut-elle passer d'une logique de test et de développement en environnement académique à la conception d'outils scientifiques véritablement fiables et prêts pour la production, tout en limitant le temps humain nécessaire à la mise en place et à la maintenance d'un tel système ?
Souhaitez-vous exécuter ce type de workloads sur GCP ou AWS pour bénéficier de l'échelle mondiale, de la fiabilité d'infrastructure exceptionnelle et du rapport coût-efficacité qu'offrent ces plateformes face aux clusters de calcul on-premise ?
Peut-être aimeriez-vous aussi comprendre les principes DevOps fondamentaux qui sous-tendent l'exécution d'un tel workload dans le cloud ?
Je vais m'appuyer sur une démonstration détaillée pour montrer comment exploiter un exemple fonctionnel, à la fois sur Google Cloud Platform (GCP) et Amazon Web Services (AWS), illustrant l'exécution d'un workload de calcul scientifique réel (bio-informatique) à partir de plusieurs principes DevOps modernes. Sans plus attendre, entrons dans le vif du sujet.
En résumé, le code de l'exemple :
- Montre comment intégrer des outils de bio-informatique courants (FastQC et BWA) dans des images de conteneurs et les téléverser vers le registre d'images de chaque cloud
- Provisionne (et permet de démanteler rapidement) toute l'infrastructure cloud nécessaire à l'exécution des workloads via Terraform, un outil d'Infrastructure as Code
- Déploie un pipeline de workflow basé sur ces images et l'infrastructure cloud provisionnée vers le service Kubernetes entièrement managé de chaque cloud, un système de gestion et d'orchestration de l'exécution de conteneurs. Argo servira à orchestrer un workflow ou pipeline de tâches sur un cluster Kubernetes
En utilisant Docker, Terraform, Kubernetes et Argo, vous apprendrez à :
- Provisionner, mettre à jour et démanteler l'infrastructure nécessaire à l'exécution de vos workloads à l'aide de commandes simples
- Exécuter des workloads analytiques de bout en bout pilotés par DAG, avec relance automatique de chaque étape en cas d'erreur inattendue ou de panne d'infrastructure
- Centraliser la journalisation des workloads et la surveillance des métriques dans des outils cloud-native d'exploration de logs et de métriques
- Exécuter des workloads sur une infrastructure qui s'adapte automatiquement à la hausse (jusqu'à une capacité d'envergure mondiale) comme à la baisse (jusqu'à très peu, voire aucune ressource de calcul) selon les besoins, pour ne payer que les ressources CPU/RAM/stockage strictement nécessaires à vos tâches. Fini le temps des serveurs qui tournent à vide et gonflent inutilement la facture
- Déployer en toute fluidité de nouvelles versions logicielles et retirer progressivement les anciennes
Le code illustrant ces principes est disponible ici, et la partie 2 explique comment l'exploiter. Si vous souhaitez sauter la présentation des principes DevOps et passer directement au code, vous pouvez vous arrêter ici.
En revanche, si vous voulez une visite guidée des technologies qui rendent ces objectifs DevOps possibles, restez avec moi. Je vous conseille vivement de préparer un café bien serré : vous en aurez besoin pour venir à bout de cet article détaillé.
Voici un cours accéléré sur le DevOps, les conteneurs, Kubernetes, Terraform, et sur les raisons pour lesquelles leur usage est essentiel à toute exploitation logicielle réelle, en bio-informatique comme ailleurs.
Conteneurs : un déploiement d'outils à grande échelle
Le problème central que résolvent cet article et son code est celui du DevOps, ou Development Operations. La conteneurisation, Kubernetes et Terraform concourent au même objectif : simplifier le déploiement fiable de programmes à grande échelle. Comprendre les fondamentaux du DevOps moderne suppose une connaissance de base des conteneurs. Commençons par là.
La plupart des bio-informaticiens connaissent ce problème par cœur : vous voulez utiliser un programme open source, souvent issu du milieu académique, aux dépendances obsolètes. L'installation tourne au cauchemar de gestion de paquets, ses exigences de version pour Python, Perl et autres dépendances entrant en conflit avec les versions à jour installées localement.
Vous avez sans doute contourné le problème en créant des environnements virtuels avec Anaconda ou la commande venv de Python. Mais cette approche a ses limites. À un moment donné, cette manière de travailler devient intenable et plus coûteuse à maintenir qu'elle ne l'est utile. Les environnements virtuels peuvent convenir en développement et en test, mais ils ne tiennent tout simplement pas la route à grande échelle.
J'ai vu de nombreuses entreprises, petites comme grandes, le découvrir à leurs dépens. Elles s'y sont accrochées bien trop longtemps avant de jeter l'éponge.
Place à la conteneurisation
Un conteneur est un processus logiciel isolé qui embarque l'ensemble du code et des dépendances nécessaires à l'exécution rapide d'une application, avec des performances proches du bare-metal, dans n'importe quel environnement de calcul.
Une image de conteneur (ou image Docker) définit la manière dont un conteneur doit être construit et exécuté. Les conteneurs sont des processus qui exécutent les instructions empaquetées dans ces images.
Une image de conteneur se crée à partir d'un simple Dockerfile qui définit :
- Le système d'exploitation de base sur lequel l'image repose (cela n'empêche pas qu'un conteneur fondé sur cette image s'exécute sur d'autres OS hôtes)
- Les paquets à installer
- Les commandes shell à exécuter au lancement d'un conteneur basé sur l'image
Ce Dockerfile est utilisé par docker build pour produire une image immuable.
Comment assurer la redondance et la mise à l'échelle horizontale du service empaqueté dans l'image ?
En déployant des instances exécutables de l'image via plusieurs conteneurs s'exécutant sur plusieurs hôtes répartis dans plusieurs centres de données distincts, puis en répartissant la charge entre ces conteneurs. Compte tenu de l'échelle mondiale des fournisseurs cloud, le potentiel de scalabilité horizontale et de résilience aux pannes des conteneurs est, de fait, illimité.
Vous pourriez par exemple créer une image qui empaquette un outil tel que FastQC avec toutes ses dépendances. Les conteneurs lancés à partir de cette image FastQC peuvent ensuite être déployés dans pratiquement n'importe quel environnement de calcul, à n'importe quelle échelle, indépendamment du matériel, du logiciel ou de l'OS de la machine hôte. Une véritable aubaine.
Vous exécuteriez ce conteneur à peu près comme l'outil FastQC s'exécuterait seul. Il est par exemple très simple de transmettre des fichiers d'entrée à un conteneur FastQC et d'en récupérer les fichiers de sortie.
Chaque fournisseur cloud dispose de son propre registre de conteneurs vers lequel pousser et depuis lequel tirer des images. Le fonctionnement est similaire au push et au pull de branches dans un dépôt git. Par exemple, vous exécutez docker pull <image_name> pour récupérer une image FastQC hébergée dans le registre de votre cloud. L'image peut alors être tirée sur diverses ressources de calcul de votre environnement cloud, ce qui rend FastQC immédiatement prêt à être exécuté avec docker run <image_name>, sans recourir à des méthodes obscures pour gérer l'installation des dépendances.
La similitude avec les dépôts git ne s'arrête pas là. Les conteneurs peuvent également être étiquetés, avec des tags tels que latest ou v1.0.1, pour suivre quelle image correspond à quelle version du code. docker run <image_name>:<tag_name> permet de tirer et d'exécuter un conteneur basé sur une version d'image précise.
Conteneurs et machines virtuelles (VM)
Si les conteneurs vous semblent ressembler aux machines virtuelles, qui empaquettent elles aussi des logiciels et les rendent exécutables dans une grande variété d'environnements, vous vous demandez peut-être ce qui les distingue et pourquoi les conteneurs ont la préférence pour un DevOps scalable.
- Les conteneurs comme les VM peuvent s'exécuter sur un hôte unique, mais les conteneurs y parviennent sans la lourde perte de performance qu'imposent les VM, lesquelles font tourner leur propre système d'exploitation en plus du logiciel embarqué. Les conteneurs, eux, partagent l'OS hôte, ce qui les rend légers : on observe généralement une dégradation de performance d'environ 0,5 %.
Avec une machine virtuelle exécutée sur un hyperviseur moderne, vous aurez de la chance si vous ne constatez qu'une dégradation de 1 à 3 %. À grande échelle, ces écarts ont un impact significatif et tangible sur les coûts.
- Les conteneurs sont nettement plus rapides à construire et à déployer. Ils ouvrent la voie à un processus de développement Agile inenvisageable avec les VM, dont la construction peut prendre des minutes, voire des heures.
- Les conteneurs sont immuables et définis par un fichier unique, ce qui fiabilise l'intégration continue. Vous pouvez par exemple revenir à une version logicielle antérieure connue pour fonctionner, en ayant la certitude qu'elle n'a pas pu être altérée depuis son dernier déploiement.
- Les conteneurs encouragent la pratique d'architecture logicielle éprouvée consistant à privilégier des microservices distribués faiblement couplés plutôt que des applications monolithiques. Cette approche permet un déploiement plus rapide d'un correctif ou d'une nouvelle fonctionnalité, et empêche qu'un incident isolé dans une application monolithique ne fasse tomber l'ensemble des autres composants qui n'avaient pas nécessairement à être étroitement liés.
Idéalement, chaque programme déployé à grande échelle devrait disposer de sa propre image de conteneur, afin que le nombre de conteneurs déployés puisse évoluer indépendamment selon les besoins en ressources de chaque programme.
- Les conteneurs facilitent grandement la journalisation et la surveillance des métriques au niveau de l'OS et de l'application, car ils partagent les ressources de l'OS hôte et ont été pensés dès le départ pour une capture détaillée de logs et de métriques.
Gestion de l'exécution des conteneurs
Supposons que vous ayez conteneurisé un programme tel que FastQC et que vous l'ayez étiqueté avec les tags appropriés comme latest et v0.11.9. Comment exploiter cela pour non seulement permettre, mais aussi grandement simplifier l'exécution tolérante aux pannes et scalable de FastQC ?
Place à Kubernetes
Kubernetes (souvent abrégé en K8s) est une plateforme open source créée par Google en 2014, conçue comme un planificateur de tâches et un système de gestion de clusters open source destinés à favoriser l'adoption communautaire de la gestion automatisée des workloads conteneurisés. Elle s'est largement imposée grâce à la relative facilité avec laquelle elle permet des opérations à l'échelle mondiale.
Kubernetes est aujourd'hui le deuxième dépôt GitHub le plus populaire en nombre d'auteurs et d'issues, juste derrière le noyau Linux.
La documentation K8s propose une explication excellente, mais longue de l'utilité de Kubernetes. Pour résumer brièvement le meilleur de K8s, celui-ci permet :
- Le bin packing automatique : vous fournissez à Kubernetes un cluster de nœuds (serveurs cloud) qu'il peut utiliser pour exécuter des tâches conteneurisées. Vous lui indiquez la quantité de CPU, de mémoire et de stockage requise par chaque conteneur, et il place les conteneurs sur vos nœuds de manière à optimiser l'utilisation des ressources.
- L'auto-réparation : Kubernetes redémarre les conteneurs en échec (suite à des erreurs logicielles ou matérielles), les remplace, supprime ceux qui ne répondent pas à votre health check personnalisé, et ne les expose aux clients qu'une fois prêts à servir.
- La découverte de services et la répartition de charge : Kubernetes peut exposer un conteneur via un nom DNS ou sa propre adresse IP. En cas de charge élevée, il peut équilibrer et distribuer le trafic réseau entre plusieurs instances du conteneur, afin que le déploiement reste stable à grande échelle.
- Les déploiements et rollbacks automatisés : vous décrivez à Kubernetes l'état souhaité de vos conteneurs déployés, et il fait évoluer l'état réel vers cet état souhaité à un rythme contrôlé. Vous pouvez par exemple automatiser Kubernetes pour qu'il crée de nouveaux conteneurs (p. ex. des conteneurs de votre logiciel taggués v2), supprime les conteneurs existants (p. ex. taggués v1) et transfère toutes leurs ressources de calcul vers le nouveau conteneur. Cette migration peut s'effectuer d'un seul coup ou être personnalisée, par exemple en remplaçant 5 % des conteneurs existants toutes les cinq minutes. Si vous observez une hausse du taux d'erreur lors du déploiement, le rollback se résume à une seule commande.
Kubernetes entièrement managé
Bonne nouvelle : Kubernetes est aujourd'hui proposé comme service entièrement managé par les principaux fournisseurs cloud, parmi lesquels :
- GKE (Google Kubernetes Engine) sur Google Cloud
- EKS (Elastic Kubernetes Service) sur Amazon Web Services
Ces offres simplifient l'installation de Kubernetes et le provisionnement matériel pour faire abstraction de la création du cluster. Elles vous permettent de vous concentrer sur le lancement de workloads scalables sur un cluster K8s qui ne demande que quelques minutes et quelques clics dans la console pour être créé.
GKE comme EKS disposent d'une machine de plan de contrôle de cluster en mode serverless. Elle joue en quelque sorte le rôle de nœud maître, accompagnée de plusieurs machines de travail appelées nodes. Vous soumettez les conteneurs à exécuter au plan de contrôle, qui les planifie ensuite sur les nodes. Une fois en cours d'exécution sur les nodes, ces tâches sont appelées pods.
Pour récapituler : les pods (généralement un seul conteneur) s'exécutent sur les nodes, et la mise à l'échelle du nombre de pods est pilotée par la machine du plan de contrôle. Les offres K8s entièrement managées automatisent également la configuration de l'auto-scaling des nodes (intégré nativement à GKE ; sur AWS, EKS crée des modèles EC2 Auto Scaling). Vous obtenez ainsi à la fois l'auto-scaling des pods et des nodes avec un effort minimal grâce à GKE et EKS.
Lors de la configuration d'un cluster K8s, vous devriez également définir des groupes de nodes optionnels, c'est-à-dire la famille de ressources matérielles à utiliser dans votre cluster. Les groupes de nodes sont fréquemment spécifiés lors de la soumission de workloads de calcul scientifique.
Vous pourriez par exemple créer un groupe de nodes high cpu reposant sur du matériel optimisé pour le CPU et son coût, comme la famille c2 sur GCP ou la famille c5 sur AWS, puis y assigner les workloads à forte intensité CPU. Vous pourriez avoir un groupe de nodes GPU distinct utilisant la famille a2-highgpu sur GCP ou la famille p3 sur AWS, auquel sont soumis les workloads exploitant le GPU. De cette manière, des types de machines distincts peuvent évoluer indépendamment les uns des autres, en lien direct avec les workloads qu'ils sont chargés d'exécuter.
En revanche, si vous utilisez le mode Autopilot de GKE plutôt que le mode Standard, vous n'aurez pas à spécifier de groupes de nodes pour votre cluster. Le provisionnement des ressources matérielles est totalement abstrait : GKE Autopilot fait figure de précurseur dans l'industrie DevOps en orientant l'écosystème K8s vers une approche plus serverless du calcul à l'échelle mondiale. Avec GKE Autopilot, vous soumettez simplement votre job K8s, qui définit les besoins en CPU/RAM/stockage du conteneur, et GKE adapte automatiquement les ressources de calcul en arrière-plan selon vos spécifications. ECS Fargate est l'équivalent AWS de GKE Autopilot.
Une infrastructure reproductible et automatisée
Pour relier la conteneurisation et l'exécution robuste des conteneurs sur Kubernetes, il y a Terraform, un outil open source d'infrastructure as code qui permet de définir, à l'aide d'un texte YAML facile à lire, l'état souhaité de votre infrastructure cloud. Une seule commande suffit pour créer, mettre à jour ou démanteler votre infrastructure cloud.
Là où les conteneurs rendent les versions logicielles immuables, faciles à reproduire et à mettre à l'échelle, Terraform rend l'infrastructure cloud sur laquelle ces conteneurs s'exécutent facile à répliquer, mettre à jour ou supprimer, tout en étant entièrement versionnable.
En migrant votre logiciel vers des conteneurs, votre système de gestion des workloads vers Kubernetes, et en codifiant votre infrastructure cloud avec Terraform, vous posez les bases d'une entreprise capable de passer du mode stealth à des opérations mondiales, et de se remettre rapidement des sources de pannes courantes telles que les nouvelles versions buguées et les incidents matériels, avec très peu de modifications fondamentales requises dans votre écosystème DevOps.
Mettre ces nouvelles connaissances en pratique
Merci pour votre temps : j'espère sincèrement que cet article vous aura aidé à mieux comprendre le DevOps. Si vous souhaitez mettre en pratique les compétences abordées en suivant un code de démonstration fonctionnel, poursuivez avec la seconde partie de cette série en deux volets.
Merci de votre lecture ! Pour rester informé, suivez-nous sur le DoiT Engineering Blog, la page LinkedIn DoiT et le compte Twitter DoiT. Pour découvrir nos opportunités de carrière, rendez-vous sur https://careers.doit-intl.com.