Erstellen Sie den Docker-Container bei Dateiänderungen neu

86

Zum Ausführen einer ASP.NET Core-Anwendung habe ich eine Docker-Datei generiert, die die Anwendung erstellt und den Quellcode in den Container kopiert, der von Git mit Jenkins abgerufen wird. In meinem Arbeitsbereich mache ich also Folgendes in der Docker-Datei:

WORKDIR /app
COPY src src

Während Jenkins die Dateien auf meinem Host korrekt mit Git aktualisiert, wendet Docker dies nicht auf mein Image an.

Mein grundlegendes Skript zum Erstellen:

#!/bin/bash
imageName=xx:my-image
containerName=my-container

docker build -t $imageName -f Dockerfile  .

containerRunning=$(docker inspect --format="{{ .State.Running }}" $containerName 2> /dev/null)

if [ "$containerRunning" == "true" ]; then
        docker stop $containerName
        docker start $containerName
else
        docker run -d -p 5000:5000 --name $containerName $imageName
fi

Ich habe verschiedene Dinge wie --rmund --no-cacheParameter für docker runund auch das Stoppen / Entfernen des Containers ausprobiert, bevor der neue erstellt wird. Ich bin mir nicht sicher, was ich hier falsch mache. Es scheint, dass Docker das Image korrekt aktualisiert, da der Aufruf von COPY src srczu einer Layer-ID und keinem Cache-Aufruf führen würde:

Step 6 : COPY src src
 ---> 382ef210d8fd

Was ist die empfohlene Methode zum Aktualisieren eines Containers?

Mein typisches Szenario wäre: Die Anwendung wird auf dem Server in einem Docker-Container ausgeführt. Jetzt werden Teile der App aktualisiert, z. B. durch Ändern einer Datei. Jetzt sollte der Container die neue Version ausführen. Docker scheint zu empfehlen, ein neues Image zu erstellen, anstatt einen vorhandenen Container zu ändern. Daher halte ich die allgemeine Art der Neuerstellung für richtig, aber einige Details in der Implementierung müssen verbessert werden.

Löwe
quelle
Können Sie die genauen Schritte auflisten, die Sie zum Erstellen Ihres Containers unternommen haben, einschließlich Ihres Erstellungsbefehls und der gesamten Ausgabe jedes Befehls?
BMitch

Antworten:

136

Nach einigen Recherchen und Tests stellte ich fest, dass ich einige Missverständnisse über die Lebensdauer von Docker-Containern hatte. Ein einfacher Neustart eines Containers führt nicht dazu, dass Docker ein neues Image verwendet, wenn das Image in der Zwischenzeit neu erstellt wurde. Stattdessen ruft Docker das Bild erst ab, bevor der Container erstellt wird. Der Status nach dem Ausführen eines Containers bleibt also bestehen.

Warum ist das Entfernen erforderlich?

Daher reicht das Neuerstellen und Neustarten nicht aus. Ich dachte, Container funktionieren wie ein Dienst: Stoppen Sie den Dienst, nehmen Sie Ihre Änderungen vor, starten Sie ihn neu und sie würden angewendet. Das war mein größter Fehler.

Da Container permanent sind, müssen Sie sie zuerst mit entfernen docker rm <ContainerName>. Nachdem ein Container entfernt wurde, können Sie ihn nicht einfach starten docker start. Dies muss mit erfolgen docker run, das selbst das neueste Image zum Erstellen einer neuen Container-Instanz verwendet.

Container sollten so unabhängig wie möglich sein

Mit diesem Wissen ist es verständlich, warum das Speichern von Daten in Containern als schlechte Praxis eingestuft wird, und Docker empfiehlt stattdessen Datenmengen / Hosting-Direktoren : Da ein Container zerstört werden muss, um Anwendungen zu aktualisieren, gehen auch die darin gespeicherten Daten verloren. Dies führt zu zusätzlicher Arbeit beim Herunterfahren von Diensten, Sichern von Daten usw.

Es ist also eine clevere Lösung, diese Daten vollständig aus dem Container auszuschließen: Wir müssen uns keine Sorgen um unsere Daten machen, wenn sie sicher auf dem Host gespeichert sind und der Container nur die Anwendung selbst enthält.

Warum -rfkann Ihnen nicht wirklich helfen

Der docker runBefehl hat einen Bereinigungsschalter namens -rf. Dadurch wird das Verhalten der dauerhaften Aufbewahrung von Docker-Containern gestoppt. Mit -rfDocker wird der Container nach dem Verlassen zerstört. Dieser Schalter hat jedoch zwei Probleme:

  1. Docker entfernt auch die Volumes ohne einen dem Container zugeordneten Namen, wodurch Ihre Daten möglicherweise zerstört werden
  2. Mit dieser Option ist es nicht möglich, Container mit -dswitch im Hintergrund auszuführen

Während der -rfSwitch eine gute Option ist, um Arbeit während der Entwicklung für schnelle Tests zu sparen, ist er in der Produktion weniger geeignet. Insbesondere wegen der fehlenden Option, einen Container im Hintergrund auszuführen, was meistens erforderlich wäre.

So entfernen Sie einen Behälter

Wir können diese Einschränkungen umgehen, indem wir einfach den Container entfernen:

docker rm --force <ContainerName>

Der --force(oder -f) Schalter, der SIGKILL zum Ausführen von Containern verwendet. Stattdessen können Sie den Container auch vorher anhalten:

docker stop <ContainerName>
docker rm <ContainerName>

Beide sind gleich. docker stopverwendet auch SIGTERM . Die Verwendung von --forceswitch verkürzt jedoch Ihr Skript, insbesondere bei Verwendung von CI-Servern: docker stopGibt einen Fehler aus, wenn der Container nicht ausgeführt wird. Dies würde dazu führen, dass Jenkins und viele andere CI-Server den Build fälschlicherweise als fehlgeschlagen betrachten. Um dies zu beheben, müssen Sie zuerst überprüfen, ob der Container wie in der Frage ausgeführt ausgeführt wird (siehe containerRunningVariable).

Vollständiges Skript zum Wiederherstellen eines Docker-Containers

Nach diesem neuen Wissen habe ich mein Skript folgendermaßen repariert:

#!/bin/bash
imageName=xx:my-image
containerName=my-container

docker build -t $imageName -f Dockerfile  .

echo Delete old container...
docker rm -f $containerName

echo Run new container...
docker run -d -p 5000:5000 --name $containerName $imageName

Das funktioniert perfekt :)

Löwe
quelle
4
"Ich habe festgestellt, dass ich einige Missverständnisse über die Lebensdauer von Docker-Containern hatte." Sie haben mir die Worte direkt aus dem Mund genommen. Vielen Dank für diese ausführliche Erklärung. Ich würde dies Docker-Neulingen empfehlen. Dies verdeutlicht den Unterschied zwischen VM und Container.
Vince Banzon
2
Nach Ihrer Erklärung habe ich notiert, was ich mit meinem vorhandenen Bild gemacht habe. Damit die Änderungen beibehalten werden, habe ich eine neue Docker-Datei erstellt, um ein neues Image zu erstellen, das bereits Änderungen enthält, die ich hinzufügen möchte. Auf diese Weise wird das neu erstellte Bild (etwas) aktualisiert.
Vince Banzon
Entspricht die --force-recreateOption für Docker Compose der hier beschriebenen? Und wenn ja, wäre es nicht wert, stattdessen diese Lösung zu verwenden (sorry, wenn diese Frage dumm ist, aber ich bin ein Docker Noob ^^)
cglacet
2
@cglacet Ja, es ist ähnlich, nicht direkt vergleichbar. docker-composeIst aber schlauer als die einfachen dockerBefehle. Ich arbeite regelmäßig mit docker-composeund die Änderungserkennung funktioniert gut, daher verwende ich sie --force-recreatesehr selten. Dies docker-compose up --buildist nur wichtig, wenn Sie ein benutzerdefiniertes Image ( buildDirektive in Compose-Datei) erstellen, anstatt ein Image vom z. B. Docker-Hub zu verwenden.
Löwe
33

Wenn Änderungen in Dockerfile oder Compose oder Anforderungen vorgenommen werden, führen Sie sie erneut mit aus docker-compose up --build. Damit Bilder neu erstellt und aktualisiert werden

Yunus
quelle
1
Wenn Sie einen MySQL-Docker-Container als einen Dienst haben, wäre die Datenbank danach leer, wenn Sie ein Volume für verwenden würden /opt/mysql/data:/var/lib/mysql?
Martin Thoma
Für mich scheint es kein Nachteil zu sein, nur immer --buildin lokalen Entwicklungsumgebungen zu arbeiten. Die Geschwindigkeit, mit der Docker die Dateien erneut kopiert, von denen sonst angenommen wird, dass sie nicht kopiert werden müssen, dauert nur ein paar Millisekunden und spart eine große Anzahl von WTF-Momenten.
Danack
0

Sie können buildfür einen bestimmten Dienst ausgeführt werden, indem Sie dort ausführen , docker-compose up --build <service name>wo der Dienstname mit dem Aufruf in Ihrer Docker-Compose-Datei übereinstimmen muss.

Beispiel Nehmen wir an, dass Ihre Docker-Compose-Datei viele Dienste enthält (.net-App - Datenbank - Verschlüsseln ... usw.) und Sie nur die .net-App aktualisieren möchten, die wie applicationin der Docker-Compose-Datei benannt ist. Sie können dann einfach laufendocker-compose up --build application

Zusätzliche Parameter Wenn Sie Ihrem Befehl zusätzliche Parameter hinzufügen möchten, z. B. -dzum Ausführen im Hintergrund, muss der Parameter vor dem Dienstnamen stehen: docker-compose up --build -d application

Aamer
quelle