Découvrez les caractéristiques d’un conteneur
Maintenant que vous connaissez les différents personas interagissant avec Kubernetes, et leurs différentes missions, David vous demande de travailler avec Alice pour déployer votre premier conteneur.
Votre mission sera de créer le conteneur du site e-commerce de LiveCorp. Il faudra alors produire le fichier pour concevoir ce conteneur, puis le construire et enfin le stocker afin de le diffuser pour le déployer sur Kubernetes.
Mais c’est quoi un conteneur au fait ?
Avant la création des conteneurs, la façon de déployer une application dépendait souvent de l’environnement. Malheureusement, chaque équipe avait sa propre façon de déployer son application.
Mais plus les déploiements se rapprochent de la production, plus le déploiement doit être standardisé. Par exemple, pour le site e-commerce, il est possible que les développeurs n’aient pas la même version des frameworks sur l’environnement de développement que celui de production. De plus, l’environnement de production est souvent plus sécurisé que celui de développement, ce qui amène à des différences de comportements entre les environnements.
Essayons de faire un parallèle avec le transport maritime (puisqu’on parle de conteneurs). Avant les années 1960, le transport par cargos vivait la même chose. Chaque transporteur avait sa façon de transporter les biens. Par exemple, le transport sera différent si c’est du café, des céréales ou des voitures. Même chose pour le moyen de transport qui sera plus efficace par bateau que par camion.
En 1960 a été inventé le conteneur intermodal. Ceux qui envoyaient des biens s'occupaient de les conteneuriser et étaient responsables de ce qu’il y a à l’intérieur du conteneur. Les transporteurs étaient responsables du conteneur en lui-même. De plus, ce conteneur intermodal étant standardisé, cela a permis le développement de porte-conteneurs beaucoup plus efficaces.
Attendez, vous voulez dire que c’est la même chose pour les conteneurs informatiques ?
Tout à fait ! La création de la technologie des conteneurs a pu standardiser la façon de déployer une application, quel que soit l’environnement utilisé.
Les développeurs sont alors responsables de fournir un conteneur qui fonctionne, alors que les ops sont responsables de faire tourner correctement les conteneurs.
Un conteneur est une unité logicielle portable qui regroupe le code d'une application, ses ressources et ses dépendances dans un package isolé. Les conteneurs sont légers et rapides à exécuter, car ils ne contiennent pas leur propre système d'exploitation complet. Ils partagent le noyau du système d'exploitation hôte, ce qui réduit considérablement leur taille et leur complexité.
Et comment fabrique-t-on ces conteneurs ?
Maintenant que vous savez ce qu’est un conteneur et à quoi il sert, il est justement temps de voir comment le créer. C’est assez simple, vous allez voir.
Le Dockerfile
Afin d'utiliser un conteneur applicatif, vous devez créer tout d’abord une image à l’aide d’un Dockerfile. Une image est un package autonome contenant tout ce dont une application a besoin pour s'exécuter : le code, les dépendances, les bibliothèques, et les configurations.
Le Dockerfile est la recette de cuisine afin de créer une image. C’est un fichier texte qui définit les instructions pour construire l’image. Il consiste en une série d'instructions, chacune exécutée dans un environnement isolé. Chaque instruction exécutée va créer une nouvelle couche dans l’image, couche qui est immuable.
L’immuabilité de ces couches permet de réutiliser des couches qui existent déjà, ou de les utiliser comme cache à chaque reconstruction de l’image. Pour savoir si une couche a changé ou pas, les conteneurs s'appuient sur un algorithme de hachage, le SHA1 (Secure Hash Algorithm 1) de chaque couche. Si une instruction change, le SHA1 de la couche changera.
Le Dockerfile va alors créer une image immuable qui contiendra exactement ce qui a été spécifié dans le Dockerfile. Si une instruction change, il est donc nécessaire de reconstruire une nouvelle image en relançant le processus de build.
Dans ce modèle, les systèmes d'exploitation, les applications et les configurations sont définis comme des images immuables qui ne sont jamais modifiées une fois déployées en production.
Cela a plusieurs avantages comme la reproductibilité, l’automatisation et la facilité de gestion. Les modifications de l'infrastructure sont effectuées en déployant de nouvelles images, ce qui minimise les risques d'erreurs de configuration.
Comme les images ne changent pas, l'infrastructure peut être facilement reproduite dans différents environnements en déployant les mêmes images.
Enfin, ces images peuvent être facilement versionnées et gérées à l'aide d'outils tels qu’un registre d’images.
L'utilisation de conteneurs et l'adoption de l'infrastructure immuable permettent de créer des applications et des systèmes plus fiables, évolutifs et faciles à gérer.
Lors de la création de l’image du site e-commerce, vous allez donc créer une image immuable qui sera la même entre les différents environnements. Les problèmes inhérents aux différences entre environnements sont donc résolus.
Créez votre première image Docker
Installez Docker
Avant de pouvoir créer et construire votre première image Docker, vous devez installer Docker sur votre poste de travail. Vous pouvez télécharger Docker à ce lien.
Une fois l’installation terminée, il est maintenant temps de créer l’image du site e-commerce ! Pour créer cette image, il faut passer par deux étapes : la création d’un Dockerfile et le build de l’image à partir du Dockerfile.
Vous demandez à Alice de vous expliquer le contenu d’un Dockerfile. Alice vous guide alors pas à pas pour créer votre premier Dockerfile.
La création du Dockerfile
La première instruction d’un Dockerfile est toujours FROM. Cette instruction indiquera au conteneur de quelle image de base commencer. L’image de base la plus simple est scratch, qui ne contient absolument rien. Cependant, des images déjà préconçues et maintenues sont disponibles sur le hub Docker. Ces images peuvent être des images de base comme des systèmes d’exploitation comme Debian ou Ubuntu ; des langages de développement comme Python ou Java ; ou encore des middlewares comme Nginx ou MySQL.
La première étape est de créer un fichier Dockerfile à la racine du projet avec comme première instruction FROM
. Cette instruction sera l’image de base de laquelle vous allez partir pour créer votre image. Dans notre cas, nous allons utiliser l’image node:18-alpine
FROM node:18-alpine
Une fois l’image définie, il va falloir se placer dans le répertoire de travail du conteneur. C’est là qu’intervient l’instruction WORKDIR
, qui définit le répertoire courant. Dans notre cas, le répertoire sera /app
.
WORKDIR /app
Vous devez ensuite copier toute l’application présente à la racine du projet dans le conteneur avec l’instructionCOPY
COPY . .
Une fois l’application copiée, vous devez alors compiler et installer l’application dans le conteneur. Dans le cas de node, cela passe par la commandeyarn install --production
. L’instruction pour exécuter cette commande est RUN
RUN yarn install --production
Maintenant que l’application est compilée et installée, vous devez indiquer au conteneur quelle commande exécuter au lancement du conteneur. L’instruction CMD
indiquera alors la commande à exécuter.
CMD ["node", "src/index.js"]
Enfin, la dernière instruction à ajouter est l’instruction EXPOSE
qui indique sur quel port écoute l’application. Cela sera important pour la suite du cours.
EXPOSE 3000
Votre Dockerfile doit maintenant ressembler à cela :
FROM node:18-alpine WORKDIR /app COPY . . RUN yarn install --production CMD ["node", "src/index.js"] EXPOSE 3000
La construction de l’image
La création du Dockerfile en lui-même n’est qu’une étape dans la création de votre image. Vous devez maintenant créer l’image.
La commande à exécuter est :
docker build .
Le .
est le répertoire où se trouve le Dockerfile ; dans notre cas, à la racine de notre projet.
Vous pouvez vérifier que l’image a bien été créée avec la commande docker images
docker images REPOSITORY TAG IMAGE ID CREATED SIZE <none> <none> 9f7322572dcd 7 seconds ago 220MB
Vous remarquez que votre image n’a pas de nom, ce qui va être compliqué au moment de l’exécuter.
Pour ajouter un nom à votre image, vous devez ajouter le flag -t
et lui donner un nom :
docker build . -t mon_image
Maintenant, votre image a bien son petit nom :
docker images REPOSITORY TAG IMAGE ID CREATED SIZE mon_image latest 9f7322572dcd 2 minutes ago 220MB
Dans la suite du chapitre, vous découvrirez comment relier plusieurs conteneurs ensemble par le réseau.
Comprenez le fonctionnement du réseau dans les conteneurs
Quand vous exécutez une image, chaque conteneur va recevoir sa propre adresse IP, différente de celle de la machine sur laquelle il s’exécute.
Mais comment les conteneurs peuvent se voir et discuter entre eux ?
Par défaut, lorsque vous lancez un conteneur, il est créé sur un réseau "bridge". Ce réseau virtuel est indépendant du réseau physique de votre machine hôte et permet aux conteneurs de communiquer entre eux de manière isolée.
Cependant, ce réseau étant indépendant du réseau de la machine, seuls les conteneurs peuvent discuter entre eux via le port exposé dans le Dockerfile.
C’est là où l’instruction EXPOSE
du Dockerfile prend tout son sens.
Afin d’exposer ce port publiquement, il faut le mapper sur un port de la machine. Seulement dans ce cas l’application conteneurisée pourra discuter avec le monde extérieur.
Voici la commande pour exposer le conteneur publiquement :
docker run -p 8080:80 nginx
Lister les réseaux existants
La commande suivante permet de lister tous les réseaux existants sur votre machine:
docker network ls NETWORK ID NAME DRIVER SCOPE d5f8055687f5 bridge bridge local 36ad987601e3 host host local a53fbadf9181 minikube bridge local 2a2fd0fe463c none null local
Vous retrouvez les trois réseaux précédemment cités, “bridge”, “host” et “null”. Un quatrième réseau a été créé par minikube afin de pouvoir exposer vos futurs déploiements.
Créer un réseau
La commande suivante permet de créer votre propre réseau pour faire discuter vos différents conteneurs :
docker network create --driver bridge mon-bridge cb2cfcb6b07220e308c6b292ba751deeab8a56ebbd5d6655008a01e1a82d9bf9 docker network ls NETWORK ID NAME DRIVER SCOPE d5f8055687f5 bridge bridge local 36ad987601e3 host host local a53fbadf9181 minikube bridge local cb2cfcb6b072 mon-bridge bridge local 2a2fd0fe463c none null local
Ici, vous pouvez voir qu’un nouveau réseau a été créé. Vous pouvez alors l'inspecter afin de voir ses caractéristiques :
docker network inspect mon-bridge [ { "Name": "mon-bridge", "Id": "cb2cfcb6b07220e308c6b292ba751deeab8a56ebbd5d6655008a01e1a82d9bf9", "Created": "2024-05-26T15:57:11.116617718Z", "Scope": "local", "Driver": "bridge", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": {}, "Config": [ { "Subnet": "172.20.0.0/16", "Gateway": "172.20.0.1" } ] }, "Internal": false, "Attachable": false, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": {}, "Options": {}, "Labels": {} } ]
La commande vous montre alors les différentes options appliquées pour la création des conteneurs dans ce réseau. Les deux options importantes ici sont Subnet et Gateway. Subnet est le sous réseau utilisé par les conteneurs. Lors de la création d’un conteneur, le conteneur prendra alors une adresse IP au hasard dans le pool disponible. Gateway est la passerelle de sortie si un des conteneurs dans le sous réseau ne connaît pas l’adresse IP recherchée.
Maintenant, si vous connectez deux conteneurs à ce réseau, les deux conteneurs pourront se parler entre eux grâce à leur adresse IP.
docker run -dit --name alpine1 --network mon-bridge alpine docker run -dit --name alpine2 --network mon-bridge alpine docker network inspect mon-bridge [ { "Name": "mon-bridge", "Id": "cb2cfcb6b07220e308c6b292ba751deeab8a56ebbd5d6655008a01e1a82d9bf9", "Created": "2024-05-26T15:57:11.116617718Z", "Scope": "local", "Driver": "bridge", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": {}, "Config": [ { "Subnet": "172.20.0.0/16", "Gateway": "172.20.0.1" } ] }, "Internal": false, "Attachable": false, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": { "0fad59687105db4154a3f953e919039ac2146948288971a4d4616acd3c0380d2": { "Name": "alpine1", "EndpointID": "25449b5ea212cb310824e06b52ca37b43a648e304ef4829036af036bc7a1ee62", "MacAddress": "02:42:ac:14:00:02", "IPv4Address": "172.20.0.2/16", "IPv6Address": "" }, "2822a672c1bf4fb5f7e89fa21cddd03b89bd302aac614636eb2473df0cc4c1f0": { "Name": "alpine2", "EndpointID": "384e892be94c6a540b18c18f4728b9c5f13699e66d20b5f5201d4a1a1cb93dd3", "MacAddress": "02:42:ac:14:00:03", "IPv4Address": "172.20.0.3/16", "IPv6Address": "" } }, "Options": {}, "Labels": {} } ]
Les deux conteneurs peuvent maintenant discuter entre eux. Lors de l’inspection du réseau, le conteneur “alpine1” a l’adresse IP 172.20.0.2 et le conteneur “alpine2” a l’adresse IP 172.20.0.3.
Vous pouvez alors vous connecter sur les deux conteneurs et faire un ping de l’autre conteneur.
docker exec alpine1 ping -c 1 172.20.0.3 PING 172.20.0.3 (172.20.0.3): 56 data bytes 64 bytes from 172.20.0.3: seq=0 ttl=64 time=0.101 ms
docker exec alpine2 ping -c 1 172.20.0.2 PING 172.20.0.2 (172.20.0.2): 56 data bytes 64 bytes from 172.20.0.2: seq=0 ttl=64 time=0.101 ms
La communication entre les deux conteneurs passe alors par le réseau “mon-bridge”.
Mais le moteur d’exécution permet aussi de faire de la résolution de nom DNS automatiquement. Il n’est alors plus nécessaire de connaître l’adresse IP du conteneur, mais seulement son nom, généralement automatiquement généré par le moteur d’exécution.
docker exec alpine1 ping -c 1 alpine2 PING 172.20.0.3 (172.20.0.3): 56 data bytes 64 bytes from 172.20.0.3: seq=0 ttl=64 time=0.101 ms
docker exec alpine2 ping -c 1 alpine1 PING 172.20.0.2 (172.20.0.2): 56 data bytes 64 bytes from 172.20.0.2: seq=0 ttl=64 time=0.101 ms
Déployez votre conteneur
Maintenant que vous savez builder votre image, il est temps de la déployer dans minikube.
Tout d’abord, il va falloir indiquer à Docker où builder votre image. Effectivement, pour l’instant votre image n’est disponible qu’en local et minikube n’y a pas accès. Il va donc falloir rebuilder votre image afin que celle-ci soit disponible pour minikube. Pas d’inquiétude, cette étape est rapide !
À la racine de votre projet, où se trouve le Dockerfile que vous avez créé, exécutez les commandes suivantes :
eval $(minikube docker-env) docker build . -t mon_image
Ces commandes vont alors reconstruire l’image et la rendre disponible pour minikube. Une fois l’image finie d’être construite, vous allez pouvoir la déployer dans minikube avec les commandes suivantes :
minikube kubectl -- create deployment hello-minikube --image=mon_image minikube kubectl -- expose deployment hello-minikube --type=LoadBalancer --port=3000
Ces deux commandes vont indiquer à minikube de déployer votre image et de la rendre accessible sur le port 3000.
Une fois le déploiement terminé, l’application devrait être accessible sur le port 3000 de votre machine.
Dans le screencast qui suit, vous verrez comment j'ai fait pour créer un Docker file et la première image Docker :
Dans ce screencast, vous avez vu comment :
créer un Docker File
créer la première image Docker
Vous verrez la suite des opérations dans le chapitre suivant et le prochain screencast.
À vous de jouer
Contexte
Alice vous demande maintenant de créer l’image du site e-commerce. Le site est basé sur Java et écoute sur le port 8080.
Alice vous envoie un mail afin de conteneuriser la nouvelle livraison de l’application. Dans ce mail se trouvent les instructions ainsi qu’un zip contenant l’application.
Tu trouveras dans ce mail la nouvelle version de l’application. Ta mission est de créer une image contenant l’application afin de la déployer. L’application est une application Java qui écoute sur le port 8080. La commande à lancer est
java -jar monapplication.jar
Bon courage !
Consignes
Créez un Dockerfile ;
Buildez l’image ;
Démarrez l’image.
En résumé
Le Dockerfile sert à créer une image.
C’est une suite d’instructions qui indique comment créer l’image.
L’image doit être reconstruite à chaque changement.
Chaque conteneur reçoit sa propre adresse IP.
Le moteur d’exécution s’occupe de connecter les conteneurs entre eux par le réseau.
Les conteneurs se connectent par un port exposé sur la machine.
Dans le prochain chapitre, vous allez apprendre à rendre disponible publiquement votre image nouvellement créée dans une registry.