Angenommen, Sie führen einen Dienst in einem Container aus und es gibt eine neue Version des Dienstes, die über ihr Docker-Image verfügbar ist. In einem solchen Fall möchten Sie den Docker-Container aktualisieren.
Das Aktualisieren eines Docker-Containers ist kein Problem, aber das Aktualisieren eines Docker-Containers ohne Ausfallzeit ist eine Herausforderung.
Verwirrt? Lassen Sie mich Ihnen beide Wege nacheinander zeigen.
Methode 1:Aktualisieren des Docker-Containers auf das neueste Image (führt zu Ausfallzeiten)
Diese Methode besteht im Wesentlichen aus diesen Schritten:
- Laden Sie das neueste Docker-Image herunter
- Beenden und entfernen Sie den Container, auf dem das alte Docker-Image ausgeführt wird
- Erstellen Sie einen neuen Container mit dem neu gezogenen Docker-Image
Willst du die Befehle? Hier bitte.
Listen Sie die Docker-Images auf und rufen Sie das Docker-Image mit einem Update ab. Holen Sie sich die neuesten Änderungen an diesem Image mit dem Docker-Pull-Befehl:
docker pull image_name
Rufen Sie nun die Container-ID oder den Namen des Containers ab, der das ältere Docker-Image ausführt. Verwenden Sie dazu den Befehl docker ps. Beenden Sie diesen Container:
docker stop container_ID
Und entfernen Sie den Container:
docker rm container_id
Der nächste Schritt besteht darin, einen neuen Container mit denselben Parametern auszuführen, die Sie zum Ausführen des vorherigen Containers verwendet haben. Ich glaube, Sie kennen diese Parameter, weil Sie sie ursprünglich erstellt haben.
docker run --name=container_name [options] docker_image
Sehen Sie das Problem bei diesem Ansatz? Sie müssen den laufenden Container stoppen und dann einen neuen erstellen. Dies führt zu einer Ausfallzeit des laufenden Dienstes.
Die Ausfallzeit, auch nur für eine Minute, kann große Auswirkungen haben, wenn es sich um ein geschäftskritisches Projekt oder einen Webdienst mit hohem Datenverkehr handelt.
Möchten Sie einen sichereren und besseren Ansatz für dieses Problem kennen? Lesen Sie den nächsten Abschnitt.
Methode 2:Aktualisieren des Docker-Containers in einem Reverse-Proxy-Setup (mit keine oder minimale Ausfallzeit)
Wenn Sie nach einer unkomplizierten Lösung gesucht haben, muss ich Sie leider enttäuschen, aber das wird keine sein, denn hier müssen Sie Ihre Container in einer Reverse-Proxy-Architektur mit Docker Compose bereitstellen.
Wenn Sie kritische Dienste mithilfe von Docker-Containern verwalten möchten, wird Ihnen die Reverse-Proxy-Methode auf lange Sicht sehr helfen.

Lassen Sie mich drei Hauptvorteile des Reverse-Proxy-Setups auflisten:
- Sie können mehrere öffentlich zugängliche Dienste auf demselben Server bereitstellen. Keine Portblockierung hier.
- Der Server von Let's Encrypt kümmert sich um die SSL-Bereitstellung für alle Dienste und alle Container.
- Sie können die Container aktualisieren, ohne die laufenden Dienste zu beeinträchtigen (für die meisten Webdienste).
Wenn Sie mehr erfahren möchten, können Sie sich das offizielle Nginx-Glossar ansehen, das die allgemeinen Verwendungszwecke eines Reverse-Proxys und den Vergleich mit einem Load Balancer hervorhebt.
Wir haben ein großartiges, ausführliches Tutorial zum Einrichten von Nginx-Reverse-Proxys, um mehr als eine Instanz von Webdiensten zu hosten, die in Containern auf demselben Server ausgeführt werden. Daher werde ich hier nicht noch einmal darauf eingehen. Sie sollten Ihre Container zuerst mit dieser Architektur einrichten. Vertrauen Sie mir, das ist die Mühe wert.
In diesem Tutorial habe ich eine Schritt-für-Schritt-Methode entwickelt, die bei Ihren täglichen DevOps-Aktivitäten sehr hilfreich sein kann. Diese Anforderung kann nicht nur sehr notwendig sein, wenn Sie Ihre Container aktualisieren, sondern auch, wenn Sie eine sehr notwendige Änderung an einer Ihrer laufenden Apps vornehmen möchten, ohne die unschätzbare Betriebszeit zu opfern.
Von hier an gehen wir davon aus, dass Sie Ihre Webanwendungen unter einem Reverse-Proxy-Setup ausführen, das sicherstellt, dass die Umleitung für den neuen aktuellen Container nach den Konfigurationsänderungen, die wir vornehmen werden, wie erwartet funktioniert.Ich werde zuerst die Schritte dieser Methode zeigen, gefolgt von einem Beispiel aus dem wirklichen Leben.
Schritt 1:Docker-Compose-Datei aktualisieren
Zunächst müssen Sie die vorhandene Docker-Compose-Datei mit der Versionsnummer des neuesten Images bearbeiten. Es kann im Docker Hub angezeigt werden, insbesondere im Abschnitt „Tags“ der Anwendung.
Wechseln Sie in das Anwendungsverzeichnis und bearbeiten Sie die Docker-Compose-Datei mit einem Befehlszeilen-Texteditor. Ich habe hier Nano verwendet.
[email protected]:~/web-app$ nano docker-compose.yml
Innerhalb von
services:
, aktualisieren Sieimage:web-app:x.x.x
mit der neuesten Versionsnummer und speichern Sie die Datei.Sie fragen sich vielleicht, warum Sie nicht trotzdem das neueste Tag verwenden, anstatt die Versionsnummer manuell anzugeben? Ich habe es absichtlich getan, da ich bemerkt habe, dass es beim Aktualisieren von Containern zu einer zeitweiligen Verzögerung des neuesten Tags kommen kann, wenn tatsächlich die neueste Version der dockerisierten Anwendung abgerufen wird. Wenn Sie die Versionsnummer direkt verwenden, können Sie immer absolut sein sicher.
Schritt 2:Skalieren Sie einen neuen Container hoch
Wenn Sie den folgenden Befehl verwenden, wird basierend auf den neuen Änderungen, die in der Docker-Compose-Datei vorgenommen wurden, ein neuer Container erstellt.
[email protected]:~/web-app$ docker-compose up -d --scale web-app=2 --no-recreate
Beachten Sie, dass der vorherige Container noch aktiv ist. Die
--scale
Flag wird verwendet, um wie angegeben zusätzliche Container zu erstellen. Hier,Web-App
wurde als Dienstname für die Webanwendung festgelegt.Obwohl Sie die Skalierung auf bis zu 2 Container angeben, wird
--no-recreate
stellt sicher, dass nur einer hinzugefügt wird, da Ihr alter Container bereits ausgeführt wird.Um mehr über die
--scale
zu erfahren und--no-recreate
Flag finden Sie auf der offiziellen Docker-Compose-Up-Dokumentationsseite.Schritt 3:Entfernen Sie den alten Container
Geben Sie nach Schritt 2 etwa 15–20 Sekunden Zeit, damit die neuen Änderungen wirksam werden, und entfernen Sie dann den alten Container:
[email protected]:~/web-app$ docker rm -f old-web-app
Bei verschiedenen Web-Apps unterscheiden sich die reflektierten Änderungen, nachdem Sie den obigen Befehl ausgeführt haben (als Bonus-Tipp ganz unten in diesem Tutorial besprochen).
Schritt 4:Herunterskalieren auf das Single-Container-Setup wie zuvor
Für den letzten Schritt skalieren Sie noch einmal auf die Konfiguration mit einem einzelnen Container herunter:
[email protected]:~/web-app$ docker-compose up -d --scale web-app=1 --no-recreate
Ich habe diese Methode mit Ghost-, WordPress-, Rocket.Chat- und Nextcloud-Instanzen getestet. Abgesehen davon, dass Nextcloud für einige Sekunden in den Wartungsmodus wechselt, funktioniert das Verfahren bei den anderen drei sehr gut.
Discourse ist jedoch eine andere Geschichte und kann in diesem Fall aufgrund seines hybriden Modells eine sehr knifflige Ausnahme darstellen.
Das Fazit lautet:Je mehr die Web-App beim Dockerisieren die Standard-Docker-Praxis verwendet, desto bequemer wird es, alle Web-App-Container täglich zu verwalten.Real-Life-Beispiel:Aktualisieren einer Live-Ghost-Instanz ohne Ausfallzeit
Wie versprochen, werde ich Ihnen ein Beispiel aus dem wirklichen Leben zeigen. Ich werde Ihnen zeigen, wie Sie Ghost, das im Docker-Container ausgeführt wird, ohne Ausfallzeiten auf eine neuere Version aktualisieren können.
Ghost ist ein CMS und wir verwenden es für das Linux-Handbuch. Das hier gezeigte Beispiel verwenden wir, um unsere Ghost-Instanz zu aktualisieren, auf der diese Website ausgeführt wird.
Angenommen, ich habe eine vorhandene Konfiguration, die auf einer älteren Version basiert, die sich unter
/home/avimanyu/ghost
befindet :version: '3.5' services: ghost: image: ghost:3.36 volumes: - ghost:/var/lib/ghost/content environment: - VIRTUAL_HOST=blog.domain.com - LETSENCRYPT_HOST=blog.domain.com - url=https://blog.domain.com - NODE_ENV=production restart: always networks: - net volumes: ghost: external: true networks: net: external: true
Beachten Sie, dass die obige Docker-Compose-Konfiguration auf einer bereits vorhandenen, hier beschriebenen Nginx-Docker-Konfiguration basiert, die in einem Netzwerk namens
net
ausgeführt wird . Sein Docker-Volume wurde ebenfalls manuell mitdocker volume create ghost-blog
erstellt .Wenn ich es mit
docker ps
überprüfe :CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2df6c27c1fe3 ghost:3.36 "docker-entrypoint.s…" 9 days ago Up 7 days 2368/tcp ghost_ghost-blog_1 89a5a7fdcfa4 jrcs/letsencrypt-nginx-proxy-companion "/bin/bash /app/entr…" 9 days ago Up 7 days letsencrypt-helper 90b72e217516 jwilder/nginx-proxy "/app/docker-entrypo…" 9 days ago Up 7 days 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp reverse-proxy
Zum jetzigen Zeitpunkt ist dies eine ältere Version von Ghost. Zeit, es auf die neueste Version 3.37.1 zu aktualisieren! Also überarbeite ich es im Bildabschnitt wie folgt:
version: '3.5' services: ghost-blog: image: ghost:3.37.1 volumes: - ghost-blog:/var/lib/ghost/content environment: - VIRTUAL_HOST=blog.domain.com - LETSENCRYPT_HOST=blog.domain.com - url=https://blog.domain.com - NODE_ENV=production restart: always networks: - net volumes: ghost-blog: external: true networks: net: external: true
Nun, um die Skalierungsmethode sinnvoll einzusetzen:
[email protected]:~/ghost$ docker-compose up -d --scale ghost-blog=2 --no-recreate
Mit dem obigen Befehl bleibt der ältere Container unberührt, aber ein neuer kommt mit derselben Konfiguration, aber basierend auf der neuesten Version von Ghost hinzu:
[email protected]:~/ghost$ docker-compose up -d --scale ghost-blog=2 --no-recreate Pulling ghost (ghost:3.37.1)... 3.37.1: Pulling from library/ghost bb79b6b2107f: Already exists 99ce436c3449: Already exists f7bdc31da5f5: Already exists 7a1300b9ff59: Already exists a495c68fa838: Already exists 6e362a39ec35: Already exists b68b4f3c36f7: Already exists 41f8b02d4a71: Pull complete 3ecc736ea4e5: Pull complete Digest: sha256:595c759980cd22e99037811397012908d89efb799776db222a4be6d4d892917c Status: Downloaded newer image for ghost:3.37.1 Starting ghost_ghost-blog_1 ... done Creating ghost_ghost-blog_2 ... done
Hätte ich den konventionellen Ansatz mit
docker-compose up -d
verwendet Stattdessen wäre ich nicht umhin gekommen, den bestehenden Container auf Basis des neuesten Ghost-Images neu zu erstellen.Bei der Neuerstellung wird der ältere Container entfernt und an seiner Stelle ein neuer mit denselben Einstellungen erstellt. Dies ist der Fall, wenn Ausfallzeiten auftreten und die Site nicht mehr zugänglich ist.
Aus diesem Grund sollten Sie den
--no-recreate
verwenden Flag beim Hochskalieren.Jetzt habe ich also zwei Container, die auf der Grundlage derselben Geisterkonfiguration ausgeführt werden. Dies ist der entscheidende Teil, in dem wir Ausfallzeiten vermeiden:
[email protected]:~/ghost$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f239f677de54 ghost:3.37.1 "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 2368/tcp ghost_ghost-blog_2 2df6c27c1fe3 ghost:3.36 "docker-entrypoint.s…" 9 days ago Up 7 days 2368/tcp ghost_ghost-blog_1 89a5a7fdcfa4 jrcs/letsencrypt-nginx-proxy-companion "/bin/bash /app/entr…" 9 days ago Up 7 days letsencrypt-helper 90b72e217516 jwilder/nginx-proxy "/app/docker-entrypo…" 9 days ago Up 7 days 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp reverse-proxy
Beachten Sie, dass der Name des älteren Containers
ghost_ghost-blog_1
ist . Überprüfen Sie Ihren Domainnamen und Sie finden ihn unter blog.domain.com weiterhin erreichbar . Wenn Sie das Ghost-Admin-Panel unter blog.domain.com/ghost aktualisieren Nach dem Hochskalieren würde es versuchen, sich selbst zu laden, bis Sie den alten Container entfernen:[email protected]:~/ghost$ docker rm -f ghost_ghost-blog_1
Aber für den Ghost-Blog selbst gibt es überhaupt keine Ausfallzeit! Auf diese Weise können Sie also sicherstellen, dass beim Aktualisieren von Ghost-Blogs keine Ausfallzeiten auftreten.
Verkleinern Sie schließlich die Konfiguration auf ihre ursprüngliche Einstellung:
[email protected]:~/ghost$ docker-compose up -d --scale ghost-blog=1 --no-recreate Starting ghost_ghost-blog_2 ... done
Wie bereits erwähnt, spiegeln sich die Änderungen nach dem Entfernen alter Container in den jeweiligen Web-Apps wider, aber sie verhalten sich aufgrund unterschiedlicher App-Designs offensichtlich unterschiedlich.
Hier sind einige Beobachtungen:
Auf WordPress :Stellen Sie sicher, dass Sie define( 'AUTOMATIC_UPDATER_DISABLED', true ) hinzufügen; als letzte Zeile in der Datei wp-config.php unter /var/www/html und mounten Sie /var/www/html/wp-content anstelle von /var/www/html als Volume. Einzelheiten finden Sie hier. Nach Schritt 3 zeigt das WordPress-Admin-Panel an, dass Ihr WordPress auf dem neuesten Stand ist, und fordert Sie auf, fortzufahren und Ihre Datenbank zu aktualisieren. Das Update erfolgt schnell ohne Ausfallzeiten auf der WordPress-Seite und das war's!
Auf Rocket.Chat :Die
Admin>Info
kann ca. 15-20 Sekunden dauern Seite, um anzuzeigen, dass Sie die neueste Version verwenden, noch bevor Sie Schritt 3 ausführen. Keine Ausfallzeit mehr!Auf Nextcloud :Nach Schritt 2 würde Nextcloud für einige Sekunden in den Wartungsmodus wechseln und dann Ihre Dateien erneut laden. Unter
Administration> Übersicht> Sicherheits- und Einrichtungswarnungen
, erhalten Sie möglicherweise eine Warnung wie „Ihr Webserver ist nicht richtig eingerichtet, um „./well-known/carddav“ aufzulösen“. Dies liegt daran, dass Ihr alter Container noch ausgeführt wird. Sobald Sie ihn mit Schritt 3 entfernen, würde diese Warnung angezeigt nicht mehr vorhanden. Stellen Sie sicher, dass Sie ihm etwas Zeit geben, bevor Sie auf Ihre Nextcloud-URL zugreifen, da diese einen 502 Bad Gateway-Fehler anzeigen könnte, bis Ihr Nginx-Container den neu skalierten Nextcloud-Container basierend auf der neuesten Version sieht.Bonustipps
Hier sind ein paar Tipps und Dinge, die Sie bei dieser Methode beachten sollten.
Tipp 1
Stellen Sie sicher, dass Sie den neu skalierten und aktuellen Containern genügend Zeit geben, damit die Nginx-Container sie endlich bestätigen können, bevor Sie die alten in Schritt 2 entfernen, um die Ausfallzeit über verschiedene Anwendungen hinweg auf ein Minimum zu reduzieren.
Bevor Sie mit dem oben beschriebenen Schritt 2 fortfahren, ist es ratsam, das Verhalten Ihrer Anwendung in Ihrem Browser zu beobachten (sowohl als Seitenaktualisierung nach der Anmeldung als auch als neuer Seitenzugriff in einem privaten Browserfenster ohne Cache), nachdem Sie Schritt 1 ausgeführt haben.
Da jede Anwendung anders gestaltet ist, wäre diese Maßnahme sehr hilfreich, bevor Sie Ihre alten Container herunterfahren.
Tipp 2
Obwohl dies eine sehr einfallsreiche Methode sein kann, um Ihre Container auf die neuesten Versionen der Apps zu aktualisieren, die sie ausführen, beachten Sie, dass Sie dieselbe Methode auch verwenden können, um Konfigurationsänderungen vorzunehmen oder Umgebungseinstellungen zu ändern, ohne dass es zu Ausfallzeiten kommt.
Dies kann entscheidend sein, wenn Sie ein Problem beheben oder eine Änderung vornehmen müssen, die Sie möglicherweise in einem Live-Container für erforderlich halten, ihn dabei aber nicht herunterfahren möchten. Nachdem Sie Ihre Änderungen vorgenommen haben und sicher sind, dass das Problem behoben ist, können Sie das ältere problemlos herunterfahren.
Wir waren selbst damit konfrontiert, als wir feststellten, dass die Protokollrotation in einem unserer Live-Container nicht aktiviert war. Wir haben die notwendigen Änderungen vorgenommen, um es zu aktivieren, und gleichzeitig mit dieser Methode Ausfallzeiten vermieden.
Tipp 3
Wenn Sie Ihren Container in der YML-Datei speziell benannt haben, indem Sie
container_name
verwenden , funktioniert die Methode nicht wie erwartet, da sie die Erstellung eines neuen Containernamens beinhaltet.Sie können diesen Konflikt vermeiden, indem Sie diese Aufgabe der Containerbenennung Docker Compose überlassen (wird automatisch gemäß seiner Namenskonvention ausgeführt). Docker Compose benennt seine Container als
directory-name_service-name_1
. Die Zahl am Ende würde sich jedes Mal erhöhen, wenn der Container aktualisiert wird (bis Sie aus irgendeinem Grund docker-compose down verwenden).Falls Sie bereits einen Dienst verwenden, indem Sie Container mit benutzerdefinierten Namen in Ihrer Docker-Compose-Datei verwenden, kommentieren Sie zur Verwendung dieser Methode einfach die Zeile (mit einem #-Präfix) aus, die
container_name
enthält innerhalb der Dienstdefinition.Nachdem Sie den neuen Container für das obige mit Schritt 1 wie in diesem Tutorial beschrieben erstellt haben, wäre für Schritt 2 der Name des alten Containers (der nicht gestoppt wurde, um Ausfallzeiten zu vermeiden) so, wie er zuvor mit
container_name
(kann auch mit `docker ps
überprüft werden ` bevor Sie den alten Container entfernen).Haben Sie etwas Neues gelernt?
Ich weiß, dass dieser Artikel etwas lang ist, aber ich hoffe, er hilft unserer DevOps-Community bei der Bewältigung von Ausfallzeiten bei Produktionsservern, auf denen dockerisierte Webanwendungen ausgeführt werden. Ausfallzeiten haben sowohl auf individueller als auch auf geschäftlicher Ebene enorme Auswirkungen, weshalb die Behandlung dieses Themas eine absolute Notwendigkeit war.
Bitte nehmen Sie an der Diskussion teil und teilen Sie Ihre Gedanken, Rückmeldungen oder Vorschläge im Kommentarbereich unten mit.