Datum
March 18, 2021
Kategorie
DevOps
Lesedauer
4 Minuten

Kubernetes ohne Docker ist wie ein Fisch ohne Fahrrad

Photo by Stephen Crowley on Unsplash

Anfang Dezember wurde ich durch eine Nachricht in Panik versetzt: „Kubernetes is deprecating Docker as a container runtime after v1.20“. Wie passt das zusammen? Für mich waren Kubernetes und Docker immer eng verknüpft. Was es mit dieser Deprecation auf sich hat, möchte ich kurz beleuchten.
So viel schon einmal vorab: Entwickler – keine Panik – Docker Container und Images gibt es weiterhin. K8s-Administratoren möchte ich hier mögliche Wege zeigen und einen davon detailliert beschreiben.

Auch wenn Docker weiterhin mit Kubernetes funktioniert, bleibt die Tatsache, dass Kubernetes die Zwischenschicht „Dockershim“ zum nächsten Release abschaltet und man dann selbst für die Laufzeitumgebung verantwortlich ist. Die Grundlage für diese Entscheidung ist die Tatsache, dass Docker nicht mit dem standardisierten CRI (Container Runtime Interface) kompatibel ist. Das Kubernetes-Entwicklerteam hat diesen Schritt seit über drei Jahren geplant und vollzieht ihn nun mit der Version 1.20.

Um eine sicherere und vor allem besser standardisierte Umgebung für Container zu ermöglichen, wurde die Open Container Initiative der Linux Foundation ins Leben gerufen. Diese stellt eine Spezifikation für Container-Umgebungen bereit, an der sich viele neue Lösungen orientieren, z.B. Podman von Red Hat. Die Idee dahinter: Wenn sich alle Container-Lösungen an die Spezifikationen halten, kann ich mir die Lösung aussuchen, die meinen Use Case am besten bedient, z.B. im Bereich Performance. Um alle diese Lösungen an Kubernetes anbinden zu können, gibt es das „Container Runtime Interface using OCI (Open Container Initiative) compliant runtimes“ (CRI-O). Das vereinfacht die Auswahl bzw. den Austausch der grundlegenden Container-Lösung auf den zu verwaltenden Hosts erheblich. Ich bin mit meiner (OCI-kompatiblen) Container-Umgebung auf den Systemen nicht mehr zufrieden? Kein Problem, ich kann sie durch eine andere austauschen!

Die prominente Ausnahme, die die Spezifikation nicht bedient: Docker! Und hier fangen die Schwierigkeiten für eine umfassende Orchestrierungslösung – in diesem Fall Kubernetes – an.

Doch was passiert im Kubernetes?

Kubernetes bringt das Kubelet mit und man kann es so aufsetzen, dass es Docker verwendet. Docker bringt in den letzten Versionen immer Containerd mit. Containerd ist eine Open Source Software der Open Container Initiative.
Wenn man Kubernetes mit Docker verwendet, wird intern Dockershim angewendet.

Verwendet man jedoch Containerd direkt – ohne Docker – ergibt sich folgendes Bild.

Eine weitere Alternative wäre die Verwendung einer beliebigen OCI-kompatiblen Runtime (CRI-O), die in Zukunft auch den einfachen Austausch zu einer anderen OCI-kompatiblen Runtime zulässt.

Eine mögliche Lösung

Den am wenigsten aufwändigen Weg, die Umstellung zu Containerd als CRI Runtime, möchte ich jetzt an einem auf Ubuntu basierenden System beschreiben.

Um Containerd direkt aus Kubernetes (remote) anzusprechen, ist es nötig, das CRI-Plugin von Containerd (ab Version 1.1) zu aktivieren.

Mit
kubectl get nodes -o wide

kann man sich die aktuelle Runtime-Umgebung anzeigen lassen.

Wenn man Docker laut der offiziellen Anleitung installiert hat, ist bereits ein funktionierender Containerd installiert.
In diesem Fall ist es nur noch nötig, Kubelet so zu konfigurieren, dass Containerd benutzt wird.

Die folgenden Schritte müssen auf jeder Worker Node und zu guter Letzt auf dem Master durchgeführt werden.
Ich habe folgende Reihenfolge gewählt: kworker2, kworker1, kmaster.

Wer möchte, kann mit einem
watch kubectl get nodes,pods -o wide

das System während der Umstellung beobachten.

Als Erstes wird das Scheduling auf kworker2 deaktiviert
kubectl cordon kworker2

und alle laufenden Pods werden von kworker2 an kworker1 übergeben
kubectl drain kworker2 --ignore-daemonsets

Falls man den oben genannten watch kubectl get nodes,pods -o wide in einem anderen Terminal gestartet hat, kann man jetzt sehen, dass keine Pods auf kworker2 aktiv sind.

Nun verbinden wir uns mit der Konsole auf kworker2 und stoppen den Kubelet- und Docker-Service.
systemctl stop kubelet

systemctl stop docker

Wenn gewünscht, kann man jetzt Docker komplett deinstallieren:
apt remove --purge docker-ce docker-ce-cli

Der nächste Schritt ist die Containerd-Konfiguration.

In /etc/containerd/config.toml muss folgende Zeile auf Kommentar gesetzt werden.
disabled_plugins=["cri"]

Damit aktiviert man das oben genannte CRI-Plugin.

Nach dieser Änderung muss man den Containerd-Service restarten:
systemctl restart containerd

Der nächste Schritt ist die Anpassung der Kubelet-Konfiguration.

In der Datei /var/lib/kubelet/kubeadm-flags.env fügt man folgende Argumente hinzu:
--container-runtime=remote
--container-runtime-endpoint=unix:///run/containerd/containerd.sock

Jetzt muss der Kubelet-Service auf kworker2 wieder gestartet werden:

systemctl start kubelet

Und kworker2 wird wieder aktiviert:
kubectl uncordon kworker2

Die oben genannten Schritte werden nun analog für kworker1 durchgeführt.
Also:
kubectl cordon kworker1

kubectl drain kworker1 --ignore-daemonsets

Nun verbinden wir uns mit kworker1 und stoppen den Kubelet- und Docker-Service.

systemctl stop kubelet
systemctl stop docker

apt remove --purge docker-ce docker-ce-cli


In /etc/containerd/config.toml setzen wir die folgende Zeile auf Kommentar.
#disabled_plugins=["cri"]

systemctl restart containerd

In der Datei /var/lib/kubelet/kubeadm-flags.env fügt man folgende Argumente hinzu:
--container-runtime=remote
--container-runtime-endpoint=unix:///run/containerd/containerd.sock

Jetzt muss der Kubelet-Service auf kworker1 wieder gestartet werden:
systemctl start kubelet

Anschließend den kworker1 wieder aktivieren:
kubectl uncordon kworker1

Der letzte Schritt ist, die oben genannten Schritte auf kmaster auszuführen.
Ich erspare mir und euch, die Schritte noch einmal darzustellen.

Nachdem das kubectl uncordon kmaster durchgeführt wurde, prüft man noch einmal, ob die Container Runtime nun komplett auf Containerd umgestellt ist.
kubectl get nodes -o wide

Fertig! Damit wäre eure bestehende Kubernetes-Umgebung von Docker auf Containerd umgestellt.

Für die Umstellung einer umfangreichen Server-Landschaft sollte man diese Schritte natürlich automatisiert, z.B. mit ansible, durchführen.

Ich hoffe, ich konnte euch die Gründe für die Docker Deprecation in Kubernetes näherbringen und eine mögliche Lösung mit Containerd zeigen.

Stellt sich nur noch die Frage: Wieso haben Fische Schuppen ? …. Damit sie ihre Fahrräder unterstellen können. 😉