Nachdem in den vorangegangenen Beiträgen unserer Serie gezeigt wurde, wie Docker Images erstellt und diese mit Docker und OpenShift als Container oder Pod verfügbar gemacht werden, möchten wir in diesem Beitrag zeigen, wie ein Container beziehungsweise Pod upgedated wird, wenn das zugrunde liegende Image aktualisiert wurde. Das kann manuell und automatisch erfolgen.
In diesem Blogbeitrag werden wir am Beispiel eines Nginx-Containers zeigen, wie Container beziehungsweise Pods aktualisiert werden.
Container Update mit Docker
Um einen Container mit Docker upzudaten, muss die neue Version des Images heruntergeladen, der laufende Container gestoppt und ein neuer Container mit dem neuen Image gestartet werden. Soll der neue Container den gleichen Namen bekommen wie der alte, muss der alte Container gelöscht werden, da es sonst zu einem Namenskonflikt kommt.
docker pull quay.io/centos7/httpd-24-centos7 :<version>
docker stop nginx
docker rm nginx
docker run --name nginx quay.io/centos7/httpd-24-centos7 :<version>
Pod Update mit OpenShift
In OpenShift gibt es verschiedene Wege, einen Pod nach einem Imageupdate neu auszurollen. Als Grundlage für unseren Pod dient die folgende deploymentConfig.yml.
apiVersion: apps.openshift.io/v1
kind: DeploymentConfig
metadata:
name: nginx
namespace: nginx-test
labels:
app: nginx-test
spec:
replicas: 1
strategy:
type: Rolling
template:
metadata:
labels:
app: nginx-test
tier: nginx-test
spec:
enableServiceLinks: true
containers:
- image: ' quay.io/centos7/httpd-24-centos7:A‘
name: nginx
Die deploymentConfig können wir mit folgendem Befehl in OpenShift anlegen:
Oc apply -f deploymentConfig.yml>
Für uns ist nur die Zeile interessant, in der das genutzte Image festgelegt wird. In unserem Beispiel nutzen wir das Nginx-Image aus unserer Registry in der Version A. Es ist auch möglich, als Version „latest“ zu nutzen, darauf wird später genauer eingegangen.
Nun soll Version B ausgerollt werden. Um das zu realisieren, haben wir zwei Möglichkeiten. Erstens, wir passen das genutzte Image in unserer deploymentConfig.yml an und verwenden die aktualisierte deploymentConfig.yml. Zweitens können wir das Image direkt setzen, wodurch die hinterlegte deploymentConfig.yml direkt in OpenShift angepasst wird.
#Für eine angepasste deploymentConfig.yml
oc apply -f deploymentConfig.yml
# Image direkt setzen
oc set image dc/nginx nginx=' quay.io/centos7/httpd-24-centos7 :B
In beiden Fällen wird das Image in der – in OpenShift hinterlegten – deploymentConfig.yml angepasst und OpenShift rollt den Pod automatisch neu aus.
Weiter oben wurde erwähnt, dass es auch möglich ist, als Image-Version „latest“ zu nutzen. Da OpenShift aber nur bei Änderungen an der deploymentConfig.yml den Pod neu ausrollt und wir die Image-Version für „latest“ nicht anpassen müssen, muss der Rollout der neuen Version anders angestoßen werden. Das machen wir mit folgendem Befehl.
oc rollout latest nginx
So können in OpenShift Pods aktualisiert werden. Damit während des Upgrades unser Service trotzdem erreichbar bleibt, bietet OpenShift verschiedene Möglichkeiten für ein Zero Downtime Deployment.
Zero Downtime Deployments
OpenShift stellt dafür drei verschiedene Varianten zur Verfügung. Das Canary Deployment, das Green/Blue Deployment und das A/B Deployment. In unserem Beispiel gehen wir davon aus, dass unser Service mit drei Pods läuft.
Rolling Update / Canary Deployment
Beim Canary Deployment werden die Pods des Services nacheinander neu ausgerollt. Das bedeutet, dass Pod 1 und 2 noch laufen, während Pod 3 gestoppt und neu deployed wird. Somit sind immer zwei Pods unseres Service erreichbar und es entsteht für kurze Zeit ein Mischbetrieb von Pods mit Version Aund B.
Green / Blue Deployment
Das Green/Blue Deployment erstellt parallel zu den Pods mit Version A die gleiche Anzahl an Pods mit Version B. Und erst nachdem das Ausrollen der Pods von Version B erfolgreich ist, wird die Route zur neuen Version des Services geändert.
Eine andere Möglichkeit das Green/Blue Deployment zu nutzen, ist beide Versionen gleichzeitig mit der gleichen Route anzusprechen und den Traffic gezielt zwischen beiden Versionen des Services aufzuteilen. Dabei kann frei gewählt werden, wie viel Traffic zu welcher Version geroutet wird. Zum Beispiel kann zu Beginn des Rollouts 90% des Traffics zu Version A und 10% zu Version B geroutet werden und wenn das funktioniert, wird der Anteil des Traffics, der zu Version Bgeroutet wird sukzessive erhöht, bis 100% erreicht sind und Version A abgeschaltet werden kann. Somit kann die Last auf der neuen Version langsam erhöht und bei Problemen sofort reagiert werden.
Automatische Container Updates
Als Entwickler möchte man neue Versionen seiner Software nicht selbst von Hand ausrollen, sondern im besten Fall automatisiert, sobald die neue Version freigegeben wurde. Dafür gibt es Docker Watchtower, OpenShift ImageStreams und podman auto-update.
Docker Watchtower
Watchtower ist ein containerbasierter Service, um Container und Basis Images automatisch upzudaten. Dafür schaut Watchtower regelmäßig nach, ob sich die Base-Images der zu überwachenden Container geändert haben. Wenn ein neues Basis Image zur Verfügung steht, lädt Watchtower das Image, stoppt und löscht den aktuellen Container und rollt einen neuen Container mit der neuen Image Version neu aus. Da der alte Container gelöscht und der neue Container erst danach deployed wird, kommt es zu Ausfallzeiten.
Mit folgendem Befehl wird ein Watchtower Container gestartet, der alle Docker Container überwacht und bei Bedarf aktualisiert.
docker run -d --name watchtower -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower
Wie zu sehen ist, wird der docker Socket in Watchtower eingebunden, wodurch Watchtower die volle Kontrolle über docker erhält, was ein potenzielles Sicherheitsrisiko ist.
OpenShift ImageStreams
ImageStreams werden in OpenShift genutzt um, ähnlich wie Watchtower, regelmäßig die Versionen der Images zu überprüfen und bei Änderungen am jeweiligen Image werden die Pods, die das Image nutzen, neu ausgerollt.
Die entsprechende imageStream.yml für unser obiges Beispiel mit dem Nginx-Container sieht wie folgt aus. Dabei sind die fett markierten Zeilen der Trigger, um alle 15 Minuten das Image in der remote-registry zu überprüfen.
kind: ImageStream
apiVersion: image.openshift.io/v1
metadata:
name: nginx
labels:
app: nginx-test
template: nginx-test
spec:
lookupPolicy:
local: true
tags:
- name: latest
annotations: null
from:
kind: DockerImage
name: ' quay.io/centos7/httpd-24-centos7 :latest'
generation: 2
importPolicy:
scheduled: true
referencePolicy:
type: Source
Mit oc apply richten wir den ImageStream in OpenShift ein.
oc apply -f imageStream.yml
Damit der ImageStream für unseren Nginx genutzt wird, muss die deploymentConfig.yml angepasst werden. Zum einen muss das genutzte Image angepasst werden, zum anderen ein trigger hinzugefügt werden, damit bei Image-Änderungen auch die Pods neu ausgerollt werden.
apiVersion: apps.openshift.io/v1
kind: DeploymentConfig
metadata:
name: nginx
namespace: nginx-test
labels:
app: nginx-test
spec:
replicas: 1
strategy:
type: Rolling
template:
metadata:
labels:
app: nginx-test
tier: nginx-test
spec:
enableServiceLinks: true
containers:
- image: nginx:latest
name: nginx
ports:
- containerPort: 8080
protocol: TCP
triggers:
- type: ImageChange
imageChangeParams:
automatic: true
from:
kind: ImageStreamTag
name: nginx:latest
containerNames:
- nginx
Dem ImageStream haben wir den Namen nginx gegeben, weshalb wir in der deploymentConfig.yml als Image-Namen nur nginx und den entsprechenden Tag „latest“ angeben müssen. Der Trigger wird auf ImageChanges eingestellt und nutzt den ImageStream um den Nginx-Container im Pod zu aktualisieren.
podman auto-update
Podman bietet mit seiner auto-update Funktionalität auch eine Möglichkeit, mit podman ausgerollte Container zu aktualisieren. Damit der Container erneuert werden kann, muss das io.containers.autoupdate=registry Label gesetzt und der Container durch Systemd gemanaged werden.
Das Label setzen wir beim Erstellen des Containers mit und im Anschluss lassen wir uns durch podman eine Service-Datei für Systemd erstellen.
#Start the container
Podman run –p 8080:8080 –label "io.containers.autoupdate=registry"--name nginx quay.io/centos7/httpd-24-centos7:latest
#Create Systemd service file
podman generate systemd –new –name –files nginx
Damit wird im aktuellen Verzeichnis eine Datei mit dem Namen container-nginx.service erstellt. Im Anschluss müssen wir den via podman gestarteten Container löschen, die Datei in den Standard Systemd Pfad verschieben und via Systemd neu starten.
#Stop and remove the Container
podman container rm –f nginx
#Move systemd-service-file
mv container-nginx.service ~/.config/systemd/user/
#Reload Systemd daemens and start Container via Systemd
systemctl --user start container-nginx.service
Nun läuft der Container und wird durch Systemd verwaltet. Jetzt kann podman auto-update genutzt werden, um den Container mit einer neueren Version – so lange eine vorhanden ist – manuell neu auszurollen.
podman auto-update
Natürlich ist unser Ziel nicht, jedes Mal von Hand podman auto-update auszuführen, weswegen wir diesen Schritt noch mit Hilfe eines direkt von podman bereitgestellten Systemd-Timers automatisieren. Dieser Timer führt standardmäßig alle 24 Stunden podman auto-update aus.
sudo systemctl enable --now podman-auto-update.timer
Zusammenfassung
Viele Wege führen nach Rom, heißt es. Mindestens genauso viele Möglichkeiten gibt es, um Container beziehungsweise Pods zu aktualisieren. Mit wenigen Handgriffen lassen sich Updates sowohl manuell oder automatisiert triggern, um eine neuere Version zu implementieren. Kombiniert mit den Deployment-Strategien für unterbrechungsfreie Rollouts können Sie sicherstellen, dass Ihre Anwendungen ohne Downtime weiter verfügbar sind. Und nun: Probieren Sie es aus!
Gern stehen wir Ihnen mit unserem Know-how zur Verfügung, sprechen Sie uns an.