Alors jusqu’à maintenant nous avons utilisé docker dans différents exemples ou soit nous voulions expliquer certains aspects de son fonctionnement soit parce que tout simplement c'était plus simple pour traiter le sujet en question.
À chaque fois nous n’avions eu besoin que d’un container ce qui ne posait pas de problème sauf dans ce dernier exemple!
Du coup on va voir comment s’en défaire et ça va justement être l’occasion de parler un peu de docker-compose [2].
Docker compose est justement la pour faciliter l’orchestration et le lancement de container docker dans un contexte commun cohérent [24].
Dit comme ça, c’est pas très concret, alors prenons un exemple simple! Prenons le cas où vous souhaiteriez mettre en place un environnement d'intégration continu comportant une instance Jenkins [3], une instance Sonar [4] et une instance Artifactory [5].
Docker-compose pour l’IC
Faire cela avec docker-compose est très simple: il suffit de définir un fichier [6] docker-compose.yml contenant la description suivante:1 2 3 4 5 6 7 8 9 10 | version: "3.2" services: tc-artifactory: image: docker.bintray.io/jfrog/artifactory-oss:latest tc-sonar: image: sonarqube:7.9.1-community tc-jenkins: image: jenkins/jenkins:2.176.2-alpine |
Pour exploiter ce fichier, il ne reste plus qu’à lancer la commande suivante (-d pour daemon):
1 | $ docker-compose up -d
|
Avec un petit docker ps, on constate alors que nous avons bien nos trois instances.
1 2 3 4 5 | $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES eededc622db4 sonarqube:7.9.1-community "./bin/run.sh" 18 minutes ago Up 3 seconds 9000/tcp tc-ic-base_tc-sonar_1 ec55466c74dc jenkins/jenkins:2.176.2-alpine "/sbin/tini -- /usr/…" 18 minutes ago Up 2 seconds 8080/tcp, 50000/tcp tc-ic-base_tc-jenkins_1 2c02dc1c451f docker.bintray.io/jfrog/artifactory-oss:latest "/entrypoint-artifac…" 18 minutes ago Up 3 seconds 8081/tcp tc-ic-base_tc-artifactory_1 |
Bon du coup on essaye d'accéder aux interfaces web de nos services? et … la ca ne marche pas… évidemment! nous avons bien lancé chacun des instances mais nous n’avons pas spécifié de port mapping.
Qu’est ce que le port mapping ?
Alors ce qu’il faut comprendre en premier lieu avant de parler port mapping c’est la manière dont docker-compose va isoler les containers. Pour cela, il va construire un réseau spécifique pour ces derniers à base de bridge [7] et d’IpTable [8]. Bon vous ne connaissez pas? ce n’est pas grave mais dans l’absolue, ce que docker fait c’est en quelque sorte l'équivalent de la virtualisation de machine mais appliqué au réseau. L'intérêt de cela également c’est que dans ce réseau, docker-compose va devoir être capable de retrouver les container en leur attribuant une IP et un hostname par défaut, celui du nom du service déclaré dans le fichier docker-compose.yml.Donc on ne n’à pas de câble ethernet, ni de switch, ni de router mais c’est comme si! Et nos container vont s'exécuter dans un réseau privé où il vont pouvoir mener leur petite vie isolée du reste du monde (pour aller plus loin [9-10]).
Le port mapping consiste alors à rendre accessible le port d’un où plusieurs des containers directement sur la machine hôte comme si c'était elle qui exposer le service. Cela revient donc à utiliser le paramètre -p de la commande docker run.
Du coup notre conf docker-compose.yml devient:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | version: "3.2" services: tc-artifactory: image: docker.bintray.io/jfrog/artifactory-oss:latest ports: - 8081:8081 tc-sonar: image: sonarqube:7.9.1-community ports: - 9000:9000 tc-jenkins: image: jenkins/jenkins:2.176.2-alpine ports: - 8080:8080 - 50000:50000 |
Alors si vous vous demandez pourquoi ce port mapping, je vous invite à consulter la documentation des différents outils puisque finalement cette configuration est fortement conditionner avec le fonctionnement de chacun d’eux. À noter que ici nous avons fait le choix de reporter les ports sur leur homologue direct, mais dans l’absolue, rien ne nous y oblige.
Voila alors pour faire bien et si vous avez jeté un oeil à la doc des différents outils, il faudrait compléter cette configuration par l’utilisation de quelques montages disque. Ici l’idée est que aucun des containers n’utilise directement son filesystem pour stocker des fichiers de configurations, des clefs, où même des logs si ces derniers ne sont pas agrégé par un kibana etc….
Nous avons donc maintenant les bases pour appliquer cela à notre application ReST [11].
Docker-compose exemple avec ReST
Dans cet exemple où nous avions mis en oeuvre deux composant ReST, nous avions aussi tenté de faire un packaging avec docker [1]. Souvenez vous nous avions même du spécifier dans la conf de notre deuxième application le hostname de la machine hôte… ce que nous avions trouvé très mal!Avec docker compose, normalement on devrait pouvoir s’affranchir de ce problème. Dans docker-compose, nos deux containers vont vivre dans un réseau isolé mais du coup elle vont aussi posséder leur propre hostname. Ces derniers étant conditionné par la configuration déclaré dans le docker-compose.
Du coup on peut alors piloter par configuration toute la partie déploiement du notre application (celle ci étant composé des deux containers) sans oublier de spécifier dans la conf applicative les hostnames que porteront les container
1 | client = Client(host='http://rest-people:5001/humans') |
On va donc déclarer donc deux services associés aux deux containers (on les appelle services car dans les fait, on pourrait associer à un service plusieurs container du même type via l’utilisation de la propriété scale… mais c’est une autre histoire)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | version: '3' services: rest-people: image: rest-people build: context: . dockerfile: Dockerfile_people rest-people-stat: image: rest-people-stat build: context: . dockerfile: Dockerfile_stat ports: - "5002:5002" |
On spécifie, l’image produite et le build utilisant le dockerfile adéquat. on effectue un port mapping pour exposer sur la machine hôte 5002 et on lance.
Donc, si on va sur localhost:5002, grâce au port mapping, la requête http effectué sur la machine hôte est renvoyée sur le container rest-people-stat qui lui, va interroger l’autre container pour avoir les données.
Bon par contre on notera que fait comme ça on ne comprend pas forcément que d’une rest-people-stat écoute sur le port 5002 et de même que rest-people sur 5001.
Surtout si on se base sur les Dockerfile respectif. Alors pour cela il existe un moyen d'être plus clair, l’utilisation du mot clef EXPOSE.
EXPOSE
Attention, justement, j’en parle ici car il y a souvent une confusion entre ce mot clé et ce qu’il fait.EXPOSE [12] n’expose rien !!! seul le port mapping le fait que quand on est à l'extérieur du réseau local virtuel dédié au container on puisse interroger un container spécifique. En dehors de cela, c’est impossible.
EXPOSE n’est qu’un flag permettant à la lecture du dockerfile de connaître les différents point d’entrée applicative du container et d'éventuellement si le besoin se fait sentir de faire un port mapping avec…. ce qui n’est pas forcement nécessaire.
Et si nos deux containers parviennent à se parler c’est juste parce qu'ils sont dans le même réseau.
Ainsi EXPOSE est juste un Flag à intérêt documentaire. Mais il importe de le préciser si vous souhaitez que les utilisateurs de vos images puissent les utiliser facilement.
Ici nous allons donc juste modifier nos Dockerfile pour ajouter ces précisions:
- EXPOSE 5001
- EXPOSE 5002
Dépendance entre container.
Autre point important, les dépendances entre les containers. Que se passerait il si le container stat avait démarré avant le container people et que ce dernier se crashe lamentablement?Et bien le premier container aura démarré et fournira un service illusoire. Et même dans le cas où people n’est juste pas prêt, le service sera proposé mais impossible à fournir.
Pour pallier à cela, il est possible de définir des dépendances entre les services avec le mot clef depends_on [13].
1 2 3 4 5 6 7 8 9 | rest-people-stat: image: rest-people-stat build: context: . dockerfile: Dockerfile_stat ports: - "5002:5002" depends_on: - rest-people |
Un peu de paramétrage
Enfin dernier point, il est souvent nécessaire de faire passer des données au contexte de construction et de démarrage de docker-compose ou d'exécution du container. Pour cela, il est possible d’utiliser deux mécanismes:- les variables d’environnement
- les arguments
1 2 3 4 5 6 7 8 9 10 11 12 | rest-people-stat: image: rest-people-stat build: context: . dockerfile: Dockerfile_stat ports: - "5002:5002" restart: always depends_on: - rest-people environment: - APP_VARIABLE=${APP_VARIABLE} |
( On notera l’utilisation dans cet exemple de la propriété restart permettant de donner une politique de redémarrage au conteneur si celui ci se crashe)
Les arguments [16] à l’inverse ont pour but d'être exploité lors de la construction de l’image docker (on est à un niveau méta). Cela va donc conditionner durablement l’utilisation de cette donnée dans le conteneur. Son utilisation peut être par exemple pour injecter le git hash ayant produit l’image.
1 2 3 4 5 6 7 8 9 10 11 12 | rest-people: image: rest-people build: context: . dockerfile: Dockerfile_people args: - gitcommithash=${gitHash} ports: - "5001:5001" expose: - "5001" restart: always |
Injecter le hash de git dans le docker compose build avec la commande suivante:
1 | $ export gitHash=$(git rev-parse HEAD);docker-compose build |
Un peu plus loin
Nous venons de voir un exemple plutôt basique de l’utilisation de docker-compose avec un paramétrage.Il faut savoir que docker-compose ne se limite pas à cela et est une marche obligé vers docker-swarm [17] qui lui va vraiment gérer de l’orchestration. Nous aurons l’occasion d’en parler ainsi que de son concurrent. En attendant sachez qu’il est déjà possible de faire pas mal de configuration supplémentaire pour le management de vos containers.
Entre autre par exemple, il est possible d’ajouter une politique de gestion du signal de vie de votre application, un healthcheck [18]. De même il est possible de préciser la politique d'agrégation des logs en utilisant par exemple un flux syslog [18] ou de flux json, fluentd, etc...
Nous ferons peut être de petits articles sur ces points mais la référence en la matière reste la documentation officielle.
Nous n’avons pas beaucoup parler non plus de la configuration du réseau par défaut [20] construit pas docker qui bien souvent n’est pas nécessaire. La encore nous y reviendrons peut être.
Enfin on peut aussi évoquer le montage de volumes [21], ou de montage en ram, mais si vous êtes déjà familier des systèmes unix, cela ne devrait pas être un problème, sinon intéressez vous d’abord à mount, fstab et tmpfs [22]!
Mais nous passerons forcément sur tout ces points tôt ou tard (et si vous voulez aller un peu plus loin [23]).
Si vous souhaitez avec une alternative vous pouvez essayer Crane, on en parle ici [25].
Références:
[1] https://un-est-tout-et-tout-est-un.blogspot.com/2019/08/python-dans-docker.html[2] https://docs.docker.com/compose/
[3] https://hub.docker.com/r/jenkins/jenkins
[4] https://hub.docker.com/_/sonarqube
[5] https://www.jfrog.com/confluence/display/RTF/Installing+with+Docker
[6] https://docs.docker.com/compose/compose-file/
[7] https://www.commentcamarche.net/contents/609-equipements-reseau-le-pont-bridge
[8] https://www.howtogeek.com/177621/the-beginners-guide-to-iptables-the-linux-firewall/
[9] https://success.docker.com/article/networking
[10] https://docs.docker.com/config/containers/container-networking/
[11] https://un-est-tout-et-tout-est-un.blogspot.com/2019/08/rest-avec-python.html
[12] https://stackoverflow.com/questions/22111060/what-is-the-difference-between-expose-and-publish-in-docker
[13] https://stackoverflow.com/questions/35832095/difference-between-links-and-depends-on-in-docker-compose-yml
[14] https://docs.docker.com/compose/env-file/
[15] https://docs.docker.com/compose/environment-variables/
[16] https://docs.docker.com/compose/compose-file/#args
[17] https://docs.docker.com/engine/swarm/
[18] https://docs.docker.com/engine/reference/builder/#healthcheck
[19] https://docs.docker.com/config/containers/logging/configure/
[20] https://docs.docker.com/compose/compose-file/#network-configuration-reference
[21] https://docs.docker.com/storage/volumes/
[22] https://doc.ubuntu-fr.org/mount_fstab
[23] http://www.ageekslab.com/docker/docker4/
[24] https://web.leikir.io/docker-compose-un-outil-desormais-indispensable/
[25] https://blog.ippon.fr/2015/03/26/orchestration-de-containers-docker-docker-compose-et-crane/
Aucun commentaire:
Enregistrer un commentaire