Einführung
Es gibt viele Artikel über das Ausführen von Containern und das Schreiben von
docker-compose.yml . Aber für mich war lange Zeit nicht klar, wie ich richtig vorgehen sollte, wenn ein Container erst gestartet werden sollte, wenn ein anderer Container bereit ist, seine Anforderungen zu verarbeiten oder eine gewisse Arbeit auszuführen.
Diese Frage wurde relevant, nachdem wir begonnen hatten,
Docker-Compose aktiv zu verwenden, anstatt einzelne Docker zu starten.
Wofür ist es?
Lassen Sie die Anwendung in Container B tatsächlich von der Verfügbarkeit des Dienstes in Container A abhängen. Beim Start erhält die Anwendung in Container B diesen Dienst nicht. Was soll es tun?
Es gibt zwei Möglichkeiten:
- Das erste ist zu sterben (vorzugsweise mit einem Fehlercode)
- Die zweite besteht darin, zu warten und dann trotzdem zu sterben, wenn die Anwendung in Container B nicht auf das zugewiesene Zeitlimit reagiert hat
Nach dem Tod von Container B wird
Docker-Compose (natürlich abhängig von der Konfiguration) neu gestartet und die Anwendung in Container B versucht erneut, den Dienst in Container A zu erreichen.
Dies wird so lange fortgesetzt, bis der Dienst in Container A bereit ist, auf Anforderungen zu antworten, oder bis wir feststellen, dass der Container ständig überlastet wird.
Tatsächlich ist dies der normale Weg für die Multi-Container-Architektur.
Insbesondere waren wir jedoch mit einer Situation konfrontiert, in der Container A gestartet wird und Daten für Container B vorbereitet. Die Anwendung in Container B weiß nicht, wie sie überprüfen kann, ob die Daten bereit sind oder nicht, und beginnt sofort mit der Arbeit mit ihnen. Daher müssen wir das Signal zur Datenbereitschaft selbst empfangen und verarbeiten.
Ich denke, dass Sie noch einige Anwendungsfälle nennen können. Vor allem aber müssen Sie genau verstehen, warum Sie dies tun. Andernfalls ist es besser, die Standard-
Docker-Compose- Tools zu verwenden.
Ein bisschen Ideologie
Wenn Sie die Dokumentation sorgfältig lesen, ist dort alles geschrieben. Nämlich jeder
Das Gerät ist unabhängig und muss darauf achten, dass alle Dienste mit
mit denen er arbeiten wird, stehen ihm zur Verfügung.
Daher geht es nicht darum, den Container zu starten oder nicht zu starten, sondern um
Überprüfen Sie im Container die Bereitschaft aller erforderlichen Dienste und nur
Übertragen Sie dann die Steuerung auf die Containeranwendung.
Wie wird es umgesetzt?
Um dieses Problem zu lösen
, hat mir die
Docker-Compose- Beschreibung sehr geholfen,
dieser Teil davon
und
einen Artikel über die ordnungsgemäße Verwendung von
Entrypoint und
Cmd .
Also, was wir brauchen:
- Es gibt einen Anhang A, den wir in Behälter A eingewickelt haben
- es startet und reagiert auf Port 8000 OK
- Außerdem gibt es Anwendung B, die wir von Container B aus starten. Sie sollte jedoch nicht früher funktionieren, als Anwendung A auf Anforderungen an Port 8000 reagiert
Die offizielle Dokumentation bietet zwei Möglichkeiten, um dieses Problem zu lösen.
Der erste besteht darin, einen eigenen
Einstiegspunkt in den Container zu schreiben, der alle Überprüfungen durchführt, und dann die Arbeitsanwendung zu starten.
Die zweite besteht darin, die bereits geschriebene Batch-Datei
wait-for-it.sh zu verwenden .
Wir haben beide Möglichkeiten ausprobiert.
Schreiben Sie Ihren eigenen Einstiegspunkt
Was ist der
Einstiegspunkt ?
Dies ist nur die ausführbare Datei, die Sie beim Erstellen des Containers in der
Docker- Datei im Feld
ENTRYPOINT angeben . Diese Datei führt, wie bereits erwähnt, Überprüfungen durch und startet dann die Hauptanwendung des Containers.
Also was wir bekommen:
Erstellen Sie einen
Entrypoint- Ordner.
Es hat zwei Unterordner -
container_A und
container_B . Wir werden unsere Container in ihnen erstellen.
Nehmen wir für Container A einen einfachen http-Server auf Python. Nach dem Start beginnt er zu antworten, um Anfragen an Port 8000 zu erhalten.
Um unser Experiment deutlicher zu machen, haben wir eine Verzögerung von 15 Sekunden festgelegt, bevor wir den Server starten.
Es stellt sich die folgende
Docker-Datei für Container A heraus :
FROM python:3 EXPOSE 8000 CMD sleep 15 && python3 -m http.server --cgi
Erstellen Sie
für Container B die folgende
Docker-Datei für Container 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 !!!!!!!!"]
Und legen Sie unsere ausführbare Datei entrypoint.sh im selben Ordner ab. Wir werden es so haben
Was ist los in Container B:
- Wenn es startet, startet es ENTRYPOINT , d.h. startet entrypoint.sh
- entrypoint.sh beginnt mit curl mit dem Abfragen von Port 8000 für Container A. Dies geschieht so lange, bis eine Antwort von 200 empfangen wird (d. h . curl endet in diesem Fall mit einem Ergebnis von Null und die Schleife endet).
- Wenn 200 empfangen wird, endet die Schleife und die Steuerung wird an den in der Variablen $ cmd angegebenen Befehl übergeben. Und es zeigt an, was wir in der Docker-Datei im CMD- Feld angegeben haben, d. H. echo "!!! Container_A ist jetzt verfügbar !!!!!!!!" Warum dies so ist, wird im obigen Artikel beschrieben
- Wir drucken - !!! Container_A ist ab sofort verfügbar !!! und schließen.
Wir werden alles mit
Docker-Compose beginnen .
docker-compose.yml hier haben wir folgendes:
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
Hier in
conteiner_a müssen keine
Ports angegeben werden: 8000: 8000 . Dies wurde durchgeführt, um den Betrieb des darin ausgeführten http-Servers von außen überprüfen zu können.
Außerdem wird Container B nach dem Herunterfahren nicht neu gestartet.
Wir starten:
docker-compose up —-build
Wir sehen, dass für 15 Sekunden eine Meldung über die Nichtverfügbarkeit von Container A angezeigt wird, und dann
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 !!!!!!!!
Wir bekommen eine Antwort auf Ihre Anfrage, drucken
!!! Container_A ist ab sofort verfügbar !!!!!!!! und schließen.
Verwenden von wait-for-it.sh
Es ist sofort zu sagen, dass dieser Weg bei uns nicht funktioniert hat, wie in der Dokumentation beschrieben.
Es ist nämlich bekannt, dass, wenn
ENTRYPOINT und
CMD in die
Docker-Datei geschrieben
werden , beim
Starten des Containers der Befehl von
ENTRYPOINT ausgeführt wird und der Inhalt von
CMD als Parameter an ihn übergeben wird.
Es ist auch bekannt, dass
ENTRYPOINT und
CMD, die in der
Docker-Datei angegeben sind, in
docker -compose.yml überschrieben werden
könnenDas
Startformat wait-for-it.sh lautet wie folgt:
wait-for-it.sh __ -- ___
Dann können wir, wie im
Artikel angegeben , einen neuen
ENTRYPOINT in
docker-compose.yml definieren , und die
CMD wird aus der
Docker-Datei ersetzt .
Also bekommen wir:
Docker-Datei für Container A bleibt unverändert:
FROM python:3 EXPOSE 8000 CMD sleep 15 && python3 -m http.server --cgi
Docker-Datei für Container 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 sieht
folgendermaßen aus:
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", "--"]
Wir führen den Befehl
wait-for-it aus , weisen ihn an, 20 Sekunden zu warten, bis Container A zum Leben erweckt wird, und geben einen weiteren Parameter
"-" an , der die
Wait-for-it- Parameter von dem Programm trennen soll, das nach Abschluss des Vorgangs gestartet wird.
Wir versuchen es!
Und leider bekommen wir nichts.
Wenn wir überprüfen, mit welchen Argumenten wir wait-for-it ausführen, werden wir
feststellen, dass nur das übergeben wird, was wir im
Einstiegspunkt angegeben haben. Die
CMD aus dem Container wird nicht angehängt.
Arbeitsoption
Dann gibt es nur eine Option. Was wir in der
CMD in der
Docker-Datei angegeben haben, müssen wir auf den
Befehl in
docker-compose.yml übertragen .
Lassen Sie dann die Docker-Datei von Container B unverändert, und
docker-compose.yml sieht folgendermaßen aus:
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 !!!!!!!!"]
Und in dieser Version funktioniert es.
Abschließend muss gesagt werden, dass unserer Meinung nach der richtige Weg der erste ist. Es ist das vielseitigste und ermöglicht es Ihnen, eine Bereitschaftsprüfung auf jede mögliche Weise durchzuführen.
Wait-for-it ist nur ein nützliches Dienstprogramm, das Sie entweder separat oder durch Einbetten in Ihre
entrypoint.sh verwenden können .