Présentation
Il existe de nombreux articles sur l'exécution de conteneurs et l'écriture de
docker-compose.yml . Mais pour moi pendant longtemps, la question n'était pas claire de savoir comment procéder correctement si un conteneur ne devait pas être lancé jusqu'à ce qu'un autre conteneur soit prêt à traiter ses demandes ou à effectuer une certaine quantité de travail.
Cette question est devenue pertinente après que nous avons commencé à utiliser activement
docker-compose , au lieu de lancer des dockers individuels.
À quoi ça sert?
En effet, laissons l'application dans le conteneur B dépendre de la disponibilité du service dans le conteneur A. Et au démarrage, l'application dans le conteneur B ne reçoit pas ce service. Que faut-il faire?
Il y a deux options:
- le premier est de mourir (de préférence avec un code d'erreur)
- la seconde consiste à attendre, puis à mourir de toute façon, si l'application dans le conteneur B n'a pas répondu pendant le délai imparti
Après la mort du conteneur B,
docker-compose (en fonction de la configuration bien sûr) le redémarrera et l'application dans le conteneur B tentera à nouveau d'accéder au service dans le conteneur A.
Cela continuera jusqu'à ce que le service du conteneur A soit prêt à répondre aux demandes ou jusqu'à ce que nous remarquions que le conteneur est constamment surchargé.
Et en fait, c'est la voie normale pour une architecture multi-conteneurs.
Mais, en particulier, nous avons été confrontés à une situation où le conteneur A démarre et prépare les données pour le conteneur B.L'application dans le conteneur B n'est pas en mesure de vérifier si les données sont prêtes ou non, elle commence immédiatement à travailler avec elles. Par conséquent, nous devons recevoir et traiter nous-mêmes le signal de disponibilité des données.
Je pense que vous pouvez toujours donner quelques cas d'utilisation. Mais surtout, vous devez comprendre exactement pourquoi vous faites cela. Sinon, il est préférable d'utiliser les outils de
composition Docker standard.
Un peu d'idéologie
Si vous lisez attentivement la documentation, tout y est écrit. À savoir, chaque
l'unité est indépendante et doit veiller à ce que tous les services
avec lequel il va travailler, sont à sa disposition.
Par conséquent, la question n'est pas de démarrer ou de ne pas démarrer le conteneur, mais de
à l'intérieur du conteneur, vérifiez l'état de préparation de tous les services requis et seulement
puis transférez le contrôle à l'application conteneur.
Comment est-il mis en œuvre
Pour résoudre ce problème, la description de
docker-compose m'a beaucoup aidé,
cette partie
et
un article sur la bonne utilisation de
point d'
entrée et
cmd .
Donc, ce que nous devons obtenir:
- il y a une annexe A que nous avons enveloppée dans le conteneur A
- il démarre et commence à répondre OK sur le port 8000
- et aussi, il y a l'application B, que nous commençons à partir du conteneur B, mais elle devrait commencer à fonctionner pas plus tôt que l'application A commencera à répondre aux demandes sur le port 8000
La documentation officielle propose deux façons de résoudre ce problème.
La première consiste à écrire votre propre point d'
entrée dans le conteneur, qui effectuera toutes les vérifications, puis à démarrer l'application de travail.
La seconde consiste à utiliser le fichier de commandes déjà écrit
wait-for-it.sh .
Nous avons essayé dans les deux sens.
Écrire votre propre point d'entrée
Qu'est-ce que le point d'
entrée ?
Il s'agit uniquement du fichier exécutable que vous spécifiez lors de la création du conteneur dans le
Dockerfile dans le champ
ENTRYPOINT . Ce fichier, comme déjà mentionné, effectue des vérifications, puis lance l'application principale du conteneur.
Donc, ce que nous obtenons:
Créez un dossier
Entrypoint .
Il a deux sous-dossiers -
container_A et
container_B . Nous y créerons nos conteneurs.
Pour le conteneur A, prenons un simple serveur http sur python. Après le démarrage, il commence à répondre pour obtenir des demandes sur le port 8000.
Pour rendre notre expérience plus explicite, nous avons défini un délai de 15 secondes avant de démarrer le serveur.
Il s'avère que le
fichier docker suivant
pour le conteneur A :
FROM python:3 EXPOSE 8000 CMD sleep 15 && python3 -m http.server --cgi
Pour le conteneur B, créez le
fichier docker suivant
pour le conteneur B :
FROM ubuntu:18.04 RUN apt-get update RUN apt-get install -y curl COPY ./entrypoint.sh /usr/bin/entrypoint.sh ENTRYPOINT [ "entrypoint.sh" ] CMD ["echo", "!!!!!!!! Container_A is available now !!!!!!!!"]
Et mettez notre exécutable entrypoint.sh dans le même dossier. Nous allons l'avoir comme ça
Que se passe-t-il dans le conteneur B:
- Quand il démarre, il démarre ENTRYPOINT , c'est-à-dire lance entrypoint.sh
- entrypoint.sh , en utilisant curl , commence à interroger le port 8000 pour le conteneur A. Il le fait jusqu'à ce qu'il reçoive une réponse 200 (c'est-à-dire que curl se terminera dans ce cas par un résultat nul et la boucle se terminera)
- Lorsque 200 est reçu, la boucle se termine et le contrôle passe à la commande spécifiée dans la variable $ cmd . Et cela indique ce que nous avons indiqué dans le fichier docker dans le champ CMD , c'est-à-dire echo "!!! Container_A est disponible maintenant !!!!!!!!" Pourquoi en est-il ainsi, est décrit dans l' article ci-dessus
- Nous imprimons - !!! Container_A est disponible maintenant !!! et conclure.
Nous allons tout démarrer avec
docker-compose .
docker-compose.yml nous avons ici ceci:
version: '3' networks: waiting_for_conteiner: services: conteiner_a: build: ./conteiner_A container_name: conteiner_a image: conteiner_a restart: unless-stopped networks: - waiting_for_conteiner ports: - 8000:8000 conteiner_b: build: ./conteiner_B container_name: conteiner_b image: waiting_for_conteiner.entrypoint.conteiner_b restart: "no" networks: - waiting_for_conteiner
Ici, dans
conteiner_a, il n'est pas nécessaire de spécifier les
ports: 8000: 8000 . Cela a été fait afin de pouvoir vérifier le fonctionnement du serveur http fonctionnant dedans de l'extérieur.
En outre, le conteneur B ne redémarre pas après l'arrêt.
Nous lançons:
docker-compose up —-build
Nous voyons que pendant 15 secondes, il y a un message sur l'indisponibilité du conteneur A, puis
conteiner_b | Conteiner_A is unavailable - sleeping conteiner_b | % Total % Received % Xferd Average Speed Time Time Time Current conteiner_b | Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> conteiner_b | <html> conteiner_b | <head> conteiner_b | <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> conteiner_b | <title>Directory listing for /</title> conteiner_b | </head> conteiner_b | <body> conteiner_b | <h1>Directory listing for /</h1> conteiner_b | <hr> conteiner_b | <ul> conteiner_b | <li><a href=".dockerenv">.dockerenv</a></li> conteiner_b | <li><a href="bin/">bin/</a></li> conteiner_b | <li><a href="boot/">boot/</a></li> conteiner_b | <li><a href="dev/">dev/</a></li> conteiner_b | <li><a href="etc/">etc/</a></li> conteiner_b | <li><a href="home/">home/</a></li> conteiner_b | <li><a href="lib/">lib/</a></li> conteiner_b | <li><a href="lib64/">lib64/</a></li> conteiner_b | <li><a href="media/">media/</a></li> conteiner_b | <li><a href="mnt/">mnt/</a></li> conteiner_b | <li><a href="opt/">opt/</a></li> conteiner_b | <li><a href="proc/">proc/</a></li> conteiner_b | <li><a href="root/">root/</a></li> conteiner_b | <li><a href="run/">run/</a></li> conteiner_b | <li><a href="sbin/">sbin/</a></li> conteiner_b | <li><a href="srv/">srv/</a></li> conteiner_b | <li><a href="sys/">sys/</a></li> conteiner_b | <li><a href="tmp/">tmp/</a></li> conteiner_b | <li><a href="usr/">usr/</a></li> conteiner_b | <li><a href="var/">var/</a></li> conteiner_b | </ul> conteiner_b | <hr> conteiner_b | </body> conteiner_b | </html> 100 987 100 987 0 0 98700 0 --:--:-- --:--:-- --:--:-- 107k conteiner_b | Conteiner_A is up - executing command conteiner_b | !!!!!!!! Container_A is available now !!!!!!!!
Nous obtenons une réponse à votre demande, imprimez
!!! Container_A est disponible maintenant !!!!!!!! et conclure.
Utilisation de wait-for-it.sh
Il vaut la peine de dire tout de suite que ce chemin n'a pas fonctionné pour nous comme décrit dans la documentation.
À savoir, il est connu que si
ENTRYPOINT et
CMD sont écrits dans le
Dockerfile , alors lorsque le conteneur démarre, la commande de
ENTRYPOINT sera exécutée et le contenu de
CMD lui sera transmis en tant que paramètres.
Il est également connu que
ENTRYPOINT et
CMD spécifiés dans le
Dockerfile peuvent être redéfinis dans
docker-compose.ymlLe
format de démarrage
wait-for-it.sh est le suivant:
wait-for-it.sh __ -- ___
Ensuite, comme indiqué dans l'
article , nous pouvons définir un nouveau
ENTRYPOINT dans
docker-compose.yml , et le
CMD sera remplacé à partir du
Dockerfile .
Ainsi, nous obtenons:
Le fichier Docker pour le conteneur A reste inchangé:
FROM python:3 EXPOSE 8000 CMD sleep 15 && python3 -m http.server --cgi
Fichier Docker pour le conteneur B FROM ubuntu:18.04 COPY ./wait-for-it.sh /usr/bin/wait-for-it.sh CMD ["echo", "!!!!!!!! Container_A is available now !!!!!!!!"]
Docker-compose.yml ressemble à ceci:
version: '3' networks: waiting_for_conteiner: services: conteiner_a: build: ./conteiner_A container_name: conteiner_a image: conteiner_a restart: unless-stopped networks: - waiting_for_conteiner ports: - 8000:8000 conteiner_b: build: ./conteiner_B container_name: conteiner_b image: waiting_for_conteiner.wait_for_it.conteiner_b restart: "no" networks: - waiting_for_conteiner entrypoint: ["wait-for-it.sh", "-s" , "-t", "20", "conteiner_a:8000", "--"]
Nous exécutons la commande
wait-for-it , lui demandons d'attendre 20 secondes jusqu'à ce que le conteneur A prenne vie et spécifions un autre paramètre
«-» , qui devrait séparer les paramètres
wait-for-it du programme qu'il lancera après son achèvement.
Nous essayons!
Et malheureusement, nous n'obtenons rien.
Si nous vérifions avec quels arguments nous
exécutons l'
attente , alors nous verrons que seul ce que nous avons spécifié dans le point d'
entrée lui est transmis , la
CMD du conteneur n'est pas attachée.
Option de travail
Ensuite, il n'y a qu'une seule option. Ce que nous avons spécifié dans le
CMD dans le
Dockerfile , nous devons le transférer à la
commande dans
docker-compose.yml .
Ensuite,
laissez le Dockerfile du conteneur B inchangé, et
docker-compose.yml ressemblera à ceci:
version: '3' networks: waiting_for_conteiner: services: conteiner_a: build: ./conteiner_A container_name: conteiner_a image: conteiner_a restart: unless-stopped networks: - waiting_for_conteiner ports: - 8000:8000 conteiner_b: build: ./conteiner_B container_name: conteiner_b image: waiting_for_conteiner.wait_for_it.conteiner_b restart: "no" networks: - waiting_for_conteiner entrypoint: ["wait-for-it.sh", "-s" ,"-t", "20", "conteiner_a:8000", "--"] command: ["echo", "!!!!!!!! Container_A is available now !!!!!!!!"]
Et dans cette version, cela fonctionne.
En conclusion, il faut dire qu'à notre avis, la bonne voie est la première. Il est le plus polyvalent et vous permet d'effectuer un contrôle de préparation de toutes les manières possibles.
L'attente est juste un utilitaire utile que vous pouvez utiliser séparément ou en l'intégrant dans votre
entrypoint.sh .