Premiers pas avec Docker : simplifier DevOps

Publié: 2022-03-11

Si vous aimez les baleines, ou si vous êtes simplement intéressé par une livraison continue rapide et sans douleur de votre logiciel en production, je vous invite à lire ce didacticiel Docker d'introduction. Tout semble indiquer que les conteneurs logiciels sont l'avenir de l'informatique, alors allons faire un petit plongeon avec les baleines à conteneurs Moby Dock et Molly.

Docker, représenté par un logo avec une baleine à l'allure amicale

Docker, représenté par un logo avec une baleine sympathique, est un projet open source qui facilite le déploiement d'applications à l'intérieur de conteneurs logiciels. Ses fonctionnalités de base sont activées par les fonctionnalités d'isolation des ressources du noyau Linux, mais il fournit en plus une API conviviale. La première version a été publiée en 2013, et elle est depuis devenue extrêmement populaire et est largement utilisée par de nombreux grands acteurs tels qu'eBay, Spotify, Baidu, etc. Lors du dernier cycle de financement, Docker a décroché 95 millions de dollars et est en passe de devenir un incontournable des services DevOps.

Analogie du transport de marchandises

La philosophie derrière Docker pourrait être illustrée par une simple analogie suivante. Dans l'industrie du transport international, les marchandises doivent être transportées par différents moyens comme les chariots élévateurs, les camions, les trains, les grues et les navires. Ces marchandises se présentent sous différentes formes et tailles et ont des exigences de stockage différentes : sacs de sucre, bidons de lait, plantes, etc. Historiquement, c'était un processus pénible nécessitant une intervention manuelle à chaque point de transit pour le chargement et le déchargement.

Une charrette tirée par un cheval, une camionnette et un camion de transport, tous transportant des marchandises

Tout a changé avec l'adoption des conteneurs intermodaux. Comme ils sont disponibles dans des tailles standard et sont fabriqués en tenant compte du transport, toutes les machines appropriées peuvent être conçues pour les gérer avec une intervention humaine minimale. L'avantage supplémentaire des conteneurs scellés est qu'ils peuvent préserver l'environnement interne comme la température et l'humidité pour les marchandises sensibles. En conséquence, l'industrie du transport peut cesser de se soucier des marchandises elles-mêmes et se concentrer sur leur acheminement de A à B.

Transport par conteneurs maritimes par voie terrestre et maritime

Et c'est là qu'intervient Docker et apporte des avantages similaires à l'industrie du logiciel.

En quoi est-ce différent des machines virtuelles ?

En un coup d'œil, les machines virtuelles et les conteneurs Docker peuvent sembler similaires. Cependant, leurs principales différences apparaîtront lorsque vous examinerez le schéma suivant :

Tableau comparatif des machines virtuelles (VM) et des conteneurs

Les applications exécutées sur des machines virtuelles, à l'exception de l'hyperviseur, nécessitent une instance complète du système d'exploitation et de toutes les bibliothèques de prise en charge. Les conteneurs, en revanche, partagent le système d'exploitation avec l'hôte. L'hyperviseur est comparable au moteur de conteneur (représenté par Docker sur l'image) dans le sens où il gère le cycle de vie des conteneurs. La différence importante est que les processus s'exécutant à l'intérieur des conteneurs sont identiques aux processus natifs sur l'hôte et n'introduisent aucun surcoût associé à l'exécution de l'hyperviseur. De plus, les applications peuvent réutiliser les bibliothèques et partager les données entre les conteneurs.

Comme les deux technologies ont des forces différentes, il est courant de trouver des systèmes combinant des machines virtuelles et des conteneurs. Un exemple parfait est un outil nommé Boot2Docker décrit dans la section d'installation de Docker.

Architecture Docker

Architecture Docker

En haut du diagramme d'architecture, il y a des registres. Par défaut, le registre principal est le Docker Hub qui héberge les images publiques et officielles. Les organisations peuvent également héberger leurs registres privés si elles le souhaitent.

Sur le côté droit, nous avons des images et des conteneurs. Les images peuvent être téléchargées à partir des registres explicitement ( docker pull imageName ) ou implicitement lors du démarrage d'un conteneur. Une fois l'image téléchargée, elle est mise en cache localement.

Les conteneurs sont les instances des images - ils sont la chose vivante. Il peut y avoir plusieurs conteneurs en cours d'exécution basés sur la même image.

Au centre, il y a le démon Docker responsable de la création, de l'exécution et de la surveillance des conteneurs. Il prend également en charge la construction et le stockage des images. Enfin, sur le côté gauche se trouve un client Docker. Il communique avec le démon via HTTP. Les sockets Unix sont utilisés sur la même machine, mais la gestion à distance est possible via une API basée sur HTTP.

Installation de Docker

Pour les dernières instructions, vous devez toujours vous référer à la documentation officielle.

Docker fonctionne nativement sur Linux, donc selon la distribution cible, cela peut être aussi simple que sudo apt-get install docker.io . Reportez-vous à la documentation pour plus de détails. Normalement, sous Linux, vous ajoutez sudo aux commandes Docker, mais nous l'ignorerons dans cet article pour plus de clarté.

Comme le démon Docker utilise des fonctionnalités de noyau spécifiques à Linux, il n'est pas possible d'exécuter Docker de manière native sous Mac OS ou Windows. Au lieu de cela, vous devez installer une application appelée Boot2Docker. L'application se compose d'une machine virtuelle VirtualBox, de Docker lui-même et des utilitaires de gestion Boot2Docker. Vous pouvez suivre les instructions d'installation officielles pour MacOS et Windows pour installer Docker sur ces plates-formes.

Utiliser Docker

Commençons cette section par un exemple rapide :

 docker run phusion/baseimage echo "Hello Moby Dock. Hello Molly."

Nous devrions voir cette sortie :

 Hello Moby Dock. Hello Molly.

Cependant, il s'est passé beaucoup plus de choses dans les coulisses que vous ne le pensez :

  • L'image 'phusion/baseimage' a été téléchargée depuis Docker Hub (si elle n'était pas déjà dans le cache local)
  • Un conteneur basé sur cette image a été démarré
  • La commande echo a été exécutée dans le conteneur
  • Le conteneur a été arrêté à la sortie de la commande

Lors de la première exécution, vous remarquerez peut-être un délai avant que le texte ne soit imprimé à l'écran. Si l'image avait été mise en cache localement, tout aurait pris une fraction de seconde. Les détails sur le dernier conteneur peuvent être récupérés en exécutant docker ps -l :

 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES af14bec37930 phusion/baseimage:latest "echo 'Hello Moby Do 2 minutes ago Exited (0) 3 seconds ago stoic_bardeen

Prendre la prochaine plongée

Comme vous pouvez le constater, exécuter une simple commande dans Docker est aussi simple que de l'exécuter directement sur un terminal standard. Pour illustrer un cas d'utilisation plus pratique, tout au long de cet article, nous verrons comment nous pouvons utiliser Docker pour déployer une application de serveur Web simple. Pour garder les choses simples, nous allons écrire un programme Java qui gère les requêtes HTTP GET à '/ping' et répond avec la chaîne 'pong\n'.

 import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; public class PingPong { public static void main(String[] args) throws Exception { HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0); server.createContext("/ping", new MyHandler()); server.setExecutor(null); server.start(); } static class MyHandler implements HttpHandler { @Override public void handle(HttpExchange t) throws IOException { String response = "pong\n"; t.sendResponseHeaders(200, response.length()); OutputStream os = t.getResponseBody(); os.write(response.getBytes()); os.close(); } } }

Fichier Docker

Avant de vous lancer et de créer votre propre image Docker, il est recommandé de vérifier d'abord s'il en existe une dans le Docker Hub ou dans les registres privés auxquels vous avez accès. Par exemple, au lieu d'installer Java nous-mêmes, nous utiliserons une image officielle : java:8 .

Pour construire une image, nous devons d'abord décider d'une image de base que nous allons utiliser. Il est désigné par l'instruction FROM . Ici, il s'agit d'une image officielle pour Java 8 du Docker Hub. Nous allons le copier dans notre fichier Java en émettant une instruction COPY . Ensuite, nous allons le compiler avec RUN . L'instruction EXPOSE indique que l'image fournira un service sur un port particulier. ENTRYPOINT est une instruction que nous voulons exécuter lorsqu'un conteneur basé sur cette image est démarré et CMD indique les paramètres par défaut que nous allons lui passer.

 FROM java:8 COPY PingPong.java / RUN javac PingPong.java EXPOSE 8080 ENTRYPOINT ["java"] CMD ["PingPong"]

Après avoir enregistré ces instructions dans un fichier appelé "Dockerfile", nous pouvons construire l'image Docker correspondante en exécutant :

 docker build -t toptal/pingpong .

La documentation officielle de Docker contient une section dédiée aux meilleures pratiques concernant l'écriture de Dockerfile.

Conteneurs en cours d'exécution

Une fois l'image construite, nous pouvons lui donner vie en tant que conteneur. Il existe plusieurs façons d'exécuter des conteneurs, mais commençons par une simple :

 docker run -d -p 8080:8080 toptal/pingpong

-p [port-on-the-host]:[port-in-the-container] indique le mappage des ports sur l'hôte et le conteneur respectivement. De plus, nous demandons à Docker d'exécuter le conteneur en tant que processus démon en arrière-plan en spécifiant -d . Vous pouvez tester si l'application du serveur Web est en cours d'exécution en essayant d'accéder à « http://localhost:8080/ping ». Notez que sur les plates-formes où Boot2docker est utilisé, vous devrez remplacer "localhost" par l'adresse IP de la machine virtuelle sur laquelle Docker est exécuté.

Sous Linux :

 curl http://localhost:8080/ping

Sur les plates-formes nécessitant Boot2Docker :

 curl $(boot2docker ip):8080/ping

Si tout se passe bien, vous devriez voir la réponse :

 pong

Hourra, notre premier conteneur Docker personnalisé est vivant et nage ! Nous pourrions également démarrer le conteneur en mode interactif -i -t . Dans notre cas, nous remplacerons la commande entrypoint afin de nous présenter un terminal bash. Nous pouvons maintenant exécuter toutes les commandes que nous voulons, mais quitter le conteneur l'arrêtera :

 docker run -i -t --entrypoint="bash" toptal/pingpong

Il existe de nombreuses autres options disponibles pour démarrer les conteneurs. Couvrons quelques autres. Par exemple, si nous voulons conserver les données en dehors du conteneur, nous pouvons partager le système de fichiers hôte avec le conteneur en utilisant -v . Par défaut, le mode d'accès est en lecture-écriture, mais peut être changé en mode lecture seule en ajoutant :ro au chemin du volume intra-conteneur. Les volumes sont particulièrement importants lorsque nous devons utiliser des informations de sécurité telles que des informations d'identification et des clés privées à l'intérieur des conteneurs, qui ne doivent pas être stockées sur l'image. De plus, cela pourrait également empêcher la duplication des données, par exemple en mappant votre référentiel Maven local au conteneur pour vous éviter de télécharger Internet deux fois.

Docker a également la capacité de relier des conteneurs entre eux. Les conteneurs liés peuvent communiquer entre eux même si aucun des ports n'est exposé. Cela peut être réalisé avec –link other-container-name . Ci-dessous un exemple combinant les paramètres mentionnés ci-dessus :

 docker run -p 9999:8080 --link otherContainerA --link otherContainerB -v /Users/$USER/.m2/repository:/home/user/.m2/repository toptal/pingpong

Autres opérations sur les conteneurs et les images

Sans surprise, la liste des opérations que l'on pourrait appliquer aux conteneurs et aux images est assez longue. Par souci de brièveté, examinons-en quelques-unes :

  • stop - Arrête un conteneur en cours d'exécution.
  • start - Démarre un conteneur arrêté.
  • commit - Crée une nouvelle image à partir des modifications d'un conteneur.
  • rm - Supprime un ou plusieurs conteneurs.
  • rmi - Supprime une ou plusieurs images.
  • ps - Répertorie les conteneurs.
  • images - Répertorie les images.
  • exec - Exécute une commande dans un conteneur en cours d'exécution.

La dernière commande pourrait être particulièrement utile à des fins de débogage, car elle vous permet de vous connecter à un terminal d'un conteneur en cours d'exécution :

 docker exec -i -t <container-id> bash

Docker Compose pour le monde des microservices

Si vous avez plus que quelques conteneurs interconnectés, il est logique d'utiliser un outil comme docker-compose. Dans un fichier de configuration, vous décrivez comment démarrer les conteneurs et comment ils doivent être liés les uns aux autres. Indépendamment du nombre de conteneurs impliqués et de leurs dépendances, vous pouvez tous les faire fonctionner avec une seule commande : docker-compose up .

Docker dans la nature

Examinons trois étapes du cycle de vie du projet et voyons comment notre sympathique baleine pourrait être utile.

Développement

Docker vous aide à garder votre environnement de développement local propre. Au lieu d'avoir plusieurs versions de différents services installés tels que Java, Kafka, Spark, Cassandra, etc., vous pouvez simplement démarrer et arrêter un conteneur requis si nécessaire. Vous pouvez aller plus loin et exécuter plusieurs piles de logiciels côte à côte en évitant la confusion des versions de dépendance.

Avec Docker, vous pouvez économiser du temps, des efforts et de l'argent. Si votre projet est très complexe à mettre en place, « dockerisez-le ». Passez par la douleur de créer une image Docker une fois, et à partir de ce moment, tout le monde peut simplement démarrer un conteneur en un clin d'œil.

Vous pouvez également avoir un "environnement d'intégration" exécuté localement (ou sur CI) et remplacer les stubs par de vrais services exécutés dans des conteneurs Docker.

Tests / Intégration continue

Avec Dockerfile, il est facile de réaliser des builds reproductibles. Jenkins ou d'autres solutions CI peuvent être configurées pour créer une image Docker pour chaque build. Vous pouvez stocker certaines ou toutes les images dans un registre Docker privé pour référence future.

Avec Docker, vous ne testez que ce qui doit être testé et retirez l'environnement de l'équation. Effectuer des tests sur un conteneur en cours d'exécution peut aider à rendre les choses beaucoup plus prévisibles.

Une autre caractéristique intéressante des conteneurs logiciels est qu'il est facile de créer des machines esclaves avec la même configuration de développement. Cela peut être particulièrement utile pour les tests de charge des déploiements en cluster.

Production

Docker peut être une interface commune entre les développeurs et le personnel d'exploitation, éliminant ainsi une source de friction. Cela encourage également l'utilisation des mêmes images/binaires à chaque étape du pipeline. De plus, la possibilité de déployer un conteneur entièrement testé sans différences d'environnement permet de s'assurer qu'aucune erreur n'est introduite dans le processus de construction.

Vous pouvez migrer en toute transparence des applications vers la production. Quelque chose qui était autrefois un processus fastidieux et feuilleté peut maintenant être aussi simple que :

 docker stop container-id; docker run new-image

Et si quelque chose ne va pas lors du déploiement d'une nouvelle version, vous pouvez toujours rapidement revenir en arrière ou passer à un autre conteneur :

 docker stop container-id; docker start other-container-id

… garanti de ne pas laisser de désordre ou de laisser les choses dans un état incohérent.

Sommaire

Un bon résumé de ce que fait Docker est inclus dans sa propre devise : Build, Ship, Run.

  • Build - Docker vous permet de composer votre application à partir de microservices, sans vous soucier des incohérences entre les environnements de développement et de production, et sans vous enfermer dans une plate-forme ou un langage.
  • Expédier - Docker vous permet de concevoir l'ensemble du cycle de développement, de test et de distribution d'applications, et de le gérer avec une interface utilisateur cohérente.
  • Run - Docker vous offre la possibilité de déployer des services évolutifs de manière sécurisée et fiable sur une grande variété de plates-formes.

Amusez-vous à nager avec les baleines !

Une partie de ce travail est inspirée d'un excellent livre Using Docker d'Adrian Mouat.