Kubernetes auf dem Raspberry Pi 4 für Anfänger: Single-Node Kubernetes Cluster mit K3s + Erste Kubernetes Grundlagen

Als Video ansehen
Bereitgestellt über YouTube

Kubernetes auf dem Raspberry Pi 4 für Anfänger: Single-Node Kubernetes Cluster mit K3s + Erste Kubernetes Grundlagen

Dieser Beitrag richtet sich an Einsteiger, die Kubernetes auf dem Raspberry Pi oder einem anderen Debian-System installieren möchten. Wir werden uns die wichtigsten Grundlagen ansehen, ohne zu sehr ins Detail zu gehen. Am Ende der Einrichtung folgt die Installation (Deployment) eines einfachen Webservers auf dem soeben erstellten Cluster. Der Fokus ist auf dem Raspberry Pi 4, lässt sich aber auf jedem Debian-System fast identisch umsetzen.

Warum setzt man Kubernetes ein?

Kubernetes ist eine quelloffene Plattform zur Bereitstellung und Verwaltung von Containern. Wer nur wenige einzelne Container einsetzt, dem reichen oft Docker und Docker-Compose aus. Doch wenn das ganze wächst, entstehen damit neue Probleme: Wie kann man z.B. das Deployment automatisieren, Ausfälle/Fehler erkennen und darauf reagieren, skalieren oder Hochverfügbarkeit erreichen? Um so etwas effizient umsetzen zu können, braucht es eine Abstraktionsschicht um die Container „herum“.

Docker Swarm wurde dafür von der wohl bekanntesten Container-Laufzeitumgebung Docker entwickelt. Wie bei einem Schwarm soll man mehrere Server kombinieren können, um darauf Container zu starten. Seit Jahren zeichnet sich jedoch ab, dass sich mit Kubernetes stattdessen eine quelloffene Alternative von Google durchsetzt. Mittlerweile gehört Swarm auch nicht mehr zu Docker, sondern wurde verkauft.

Was hat es mit K3s auf sich?

Man kann Kubernetes von Hand installieren. Dafür wird aber ein tiefes Verständnis der Kubernetes-Architektur benötigt. Durch die verschiedenen Komponenten ist es zudem aufwändig. Außer zu Lernzwecken wird Kubernetes daher üblicherweise über entsprechende Kubernetes-Distributionen installiert: Das automatisiert und vereinfacht die Einrichtung erheblich. Damit kein Chaos entsteht, gibt es die CNCF (Cloud Native Computing Fundation): Sie ist ein Projekt der Linux Foundation und zertifiziert Kubernetes-Distributionen, die sich an den „Standard“ (Upstream-Kubernetes) halten. So kann man zwischen den Distributionen wechseln, ohne dass die Programme die darauf laufen angepasst werden müssen.

Auf dem Raspberry Pi OS würde ich K3s empfehlen: Rancher hat bei dieser leichtgewichtigen Distribution das originale Kubernetes aufgeräumt und optimiert, sodass es in eine Binary mit knapp 100 MB passt. Zum Vergleich: Beim schwergewichtigeren Original sind es mehrere Gigabyte. K3s zielt damit auf schwächere Geräte wie u.a. den Raspberry Pi ab.

Man kann es aber auch als effizientere Kubernetes-Distribution auf normaler x86 Hardware installieren und profitiert von einer geringeren Ressourcenbelastung sowie reduzierter Angriffsfläche. Mittlerweile ist K3s auch keine Insellösung mehr, sondern seit August 2020 CNCF zertifiziert – also eine standardisierte Kubernetes-Implementierung.

Voraussetzungen für Kubernetes mit K3s

K3s hat ein paar Anforderungen, die je nach verwendetem Betriebssystem etwas variieren. Ich werde es am Raspberry Pi OS demonstrieren, man könnte aber auch Alpine oder CentOS nutzen.

Hardware

Ich nutze im Folgenden einen Raspberry Pi 4 mit 4 GB Arbeitsspeicher. Kleinere Modelle sind eben so möglich wie vorherigen Generationen. 512 MB Arbeitsspeicher sind jedoch das absolute Minimum. Da mit 512 MB kaum etwas für die Anwendungen selbst bleibt, die man auf Kubernetes betreiben möchte, empfehle ich 1 GB aufwärts – mehr ist tendenziell natürlich besser.

Was ist mit Docker?

Früher war Docker eine Voraussetzung für Kubernetes. Mittlerweile verabschiedet sich Kubernetes mit Version 1.23 von Docker – allerdings nur auf technischer Ebene. Auch hier gibt es Standards: Die Open Container Initiative (OCI) legt den Standard für Container und Images fest, den ihr vielleicht von Docker schon kennt. CRI (Container Runtime Interface) heißt die Schnittstelle zwischen Kubernetes und der Container-Laufzeitumgebung. Die wird von Docker nicht unterstützt, daher gibt es mit Dockershim eine Übersetzungsschicht.

Es gibt aber alternative Laufzeitumgebungen, die diese Standards ohne Zwischenschicht erfüllen. Beispielsweise Podman oder Containerd. K3s nutzt daher mittlerweile standardmäßig Containerd statt wie früher Docker. Wer möchte, kann auf Wunsch zurück Docker wechseln. Würde ich ohne guten Grund jedoch nicht empfehlen, da Docker wie gesagt ab Kubernetes 1.23 ohnehin nicht mehr unterstützt wird.

Iptables statt nftables

Das Raspberry Pi OS hat seine Standard-Firewall von iptable auf dessen Nachfolger nftables umgestellt:

$ update-alternatives --get-selections | grep iptables
iptables                       auto     /usr/sbin/iptables-nft

Wenn iptables wie hier auf /usr/sbin/iptables-nft zeigt, müssen wir dies umstellen. Derzeit ist K3s nur mit iptables kompatibel. Anschließend sollte das System neu gestartet werden:

sudo iptables -F
sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
# Falls Ipv6 verwendet wird
sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy

„Control Groups“ aktivieren

Kubernetes benötigt „Control Groups“, kurz cgroups. Diese Kontrollgruppen sind eine Kernelfunktionalität, um Ressourcen von Prozessen zu beschränken – etwa Prozessorauslastung oder Arbeitsspeicher. Da cgroups auf dem Raspberry Pi OS standardmäßig nicht aktiv sind, müssen wir cgroup_memory=1 cgroup_enable=memory ans Ende der Datei /boot/cmdline.txt einfügen:

$ cat /boot/cmdline.txt
console=serial0,115200 console=tty1 root=PARTUUID=67be036d-02 rootfstype=ext4 fsck.repair=yes rootwait cgroup_memory=1 cgroup_enable=memory

Damit unsere beiden Änderungen wirksam werden, erfolgt mit sudo reboot ein Neustart.

Kubernetes mit K3s installieren

Mittlerweile bietet K3s ein Installationsskript, womit die Installation stark vereinfacht wird. Wie immer empfehle ich, dies zunächst herunterzuladen und einen Blick darauf zu werfen:

curl -sfL https://get.k3s.io -o k3s-install.sh

Das Skript unterstützt mehrere Umgebungsvariablen zur Anpassung. Für den Einstieg sind die voreingestellten Standardwerte in den meisten Fällen ausreichend, sodass man es ohne weitere Konfiguration starten kann:

bash k3s-install.sh

Auf dem Raspberry Pi 4 dauert es keine Minute, bis die Installation abgeschlossen ist. Durch das Skript kann man k3s systemweit nutzen, k3s kubectl bietet Zugriff auf kubectl – das Standardwerkzeug, um Kubernetes-Cluster zu verwalten:

$ sudo k3s kubectl get nodes
NAME   STATUS   ROLES                  AGE   VERSION
pi     Ready    control-plane,master   88m   v1.22.6+k3s1

Hier sehen wir unseren Pi mit beiden Rollen (Control Plane zur Verwaltung, Master für die Anwendungen) auf Kubernetes 1.22.

Für einen einfachen Cluster mit nur einem Node, der lokal genutzt wird, müsst ihr daher nicht zwingend kubectl installieren. Ihr könnt es aber z.B. auf eurer Workstation installieren und direkt von dort auf euren K8s „Cluster“ zugreifen. Dazu muss nur die Konfigurationsdatei /etc/rancher/k3s/k3s.yaml vom Pi in ~/.kube/config kopiert werden, damit sich der Client verbinden kann.

Version wechseln/bestimmte Version installieren

Eine Liste aller derzeit unterstützten Versionen mit End of Life Datum finden sich in der Kubernetes-Dokumentation. Wenn ihr eine bestimmte Kubernetes-Version möchtet, müsst ihr die k3s Version wechseln und könnt diese mit der Umgebungsvariable INSTALL_K3S_VERSION angeben. K3s benennt seine Versionen gleich wie Kubernetes, d.H. mit K3s v1.21.9+k3s1 erhaltet ihr den Vorgänger 1.21 statt 1.22. Eine Übersicht aller Versionen: K3s Releases.

Für einen Versionswechsel müsst ihr zunächst eine bestehende Installation von K3s deinstallieren (dauert keine 10 Sekunden). ACHTUNG: Alles was in eurem Cluster liegt, geht dadurch verloren! Unbedingt vorher sichern falls notwendig oder am besten zum Experimentieren ein eigenes Testgerät benutzen.

sudo /usr/local/bin/k3s-uninstall.sh

Nun mit INSTALL_K3S_VERSION und der gewünschten Version (neu) installieren:

INSTALL_K3S_VERSION=v1.21.9+k3s1 bash k3s-install.sh

Und schon hat man in unter 3 Minuten einen neuen Kubernetes-Cluster auf dem Raspberry Pi 4, diesmal mit Version 1.21 statt 1.22:

$ sudo k3s kubectl get nodes
NAME   STATUS   ROLES                  AGE   VERSION
pi     Ready    control-plane,master   27s   v1.21.9+k3s1

Berechtigungen korrigieren: K3s ohne root ausführen

In unseren ersten Tests haben wir k3s mit sudo als root aufgerufen. Das lässt sich vermeiden, in dem wir den Eigentümer der Cluster-Konfiguration auf unseren Benutzer (im Standard pi) korrigieren:

$ sudo chown pi:pi /etc/rancher/k3s/k3s.yaml
$ k3s kubectl get no
NAME   STATUS   ROLES                  AGE     VERSION
pi     Ready    control-plane,master   4m48s   v1.21.9+k3s1

„Hallo Welt“: Erste Kubernetes-Objekte erstellen

Du besitzt nun einen Kubernetes-Cluster. Zwar nur mit einem einzigen Node und daher streng genommen kein Cluster – aber v.a. für den Anfang reicht das völlig aus, um die Grundlagen von Kubernetes zu erlernen oder Testumgebungen aufzubauen.

Schauen wir uns für eine Demo das kleinste mögliche Objekt an: Ein Pod. Er bündelt einen oder mehrere Container, die gemeinsame Ressourcen (z.B. Speicher) nutzen können. Er ist daher die einfachste (aber nicht Einzige) Möglichkeit, einen Container auf Basis eines Images zu erzeugen.

apiVersion: v1
kind: Pod
metadata:
  name: webserver
spec:
  containers:
  - name: nginx
    image: nginx:1.20
    ports:
    - containerPort: 80

Dazu legt man eine Yaml-Textdatei (z.B. nginx.yml) an. Dieses Yaml erzeugt einen Pod namens webserver, der einen Container startet: „nginx“ mit dem „nginx:1.20“ Image. Mit kubectl können wir das Objekt erstellen lassen. Falls ihr direkt auf dem Pi arbeitet, „k3s“ davor setzen:

$ k3s kubectl apply -f nginx.yml
pod/webserver created

Wie ihr seht, folgt auf kubectl immer ein Befehl, in diesem Beispiel apply. Der Status aller Pods lässt sich mit get pods einsehen:

$ k3s kubectl get pod
NAME        READY   STATUS              RESTARTS   AGE
webserver   0/1     ContainerCreating   0          4s

Beim erstmaligen Deployen dauert das Erstellen (je nach Internetgeschwindigkeit) eine Zeit, da zunächst das Image heruntergeladen werden muss. Anschließend sollten der Container auf „Running“ stehen:

$ k3s kubectl get pod
NAME        READY   STATUS    RESTARTS   AGE
webserver   1/1     Running   0          31s

Kubernetes erzeugt ein virtuelles Netzwerk und weist dem Pod eine IP-Adresse zu. Mit dem Befehl describe pod werden alle Informationen zu der Ressource (hier dem Pod) angezeigt.

$ k3s kubectl describe pod webserver
Name:         webserver
Namespace:    default
Priority:     0
Node:         pi/192.168.0.120
Start Time:   Mon, 07 Feb 2022 19:42:47 +0100
Labels:       <none>
Annotations:  <none>
Status:       Running
IP:           10.42.0.10
IPs:
  IP:  10.42.0.10
...

Die Restliche Ausgabe habe ich zur Übersicht abgeschnitten. Ich empfehle dir, zumindest später dennoch einen Blick darauf zu werfen – vor allem der untere „Events“ Bereich kann zur Fehlersuche hilfreich sein. Aber bleiben wir bei der IP: 10.42.0.10 hat Kubernetes dem Webserver-Pod zugewiesen. Über curl können wir auf dem Pi darüber unseren Nginx erreichen:

$ curl http://10.42.0.10
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Der Zugriff erscheint im AccessLog des Nginx. Dieser wird in Containern standardmäßig auf die Standardausgabe (stdout) geschrieben. Mit dem kubectl Befehl „logs“ können wir die Ausgabe einsehen:

$ k3s kubectl logs webserver
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2022/02/07 19:43:14 [notice] 1#1: using the "epoll" event method
2022/02/07 19:43:14 [notice] 1#1: nginx/1.20.2
2022/02/07 19:43:14 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2022/02/07 19:43:14 [notice] 1#1: OS: Linux 5.10.92-v8+
2022/02/07 19:43:14 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2022/02/07 19:43:14 [notice] 1#1: start worker processes
2022/02/07 19:43:14 [notice] 1#1: start worker process 31
2022/02/07 19:43:14 [notice] 1#1: start worker process 32
2022/02/07 19:43:14 [notice] 1#1: start worker process 33
2022/02/07 19:43:14 [notice] 1#1: start worker process 34
10.42.0.1 - - [07/Feb/2022:19:56:41 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.74.0" "-"

Die letzte Zeile stammt von unserem curl Testaufruf.

Wie geht es weiter?

Der curl Aufruf funktioniert nur auf dem Kubernetes-Node selbst – nicht außerhalb, etwa vom eigenen PC. Dies lässt sich mithilfe eines Ingress ändern. Um den Webserver mit eigenen Inhalten zu füllen, kann man Volumes oder Init-Container einsetzen. Das sind nur Beispiele, wie man sich an kleinen, praktischen Fällen in die restlichen Kubernetes-Objekte einarbeiten kann. Sich damit zu beschäftigen wäre für Anfänger der nächste Schritt.

Anschließend macht es Sinn, den Einstieg in „echte“ Cluster zu starten. Das heißt: Mehrere Nodes (z.B. Raspberry Pis, oder aber x86 VMs bzw. Server) und die damit entstehenden neuen Szenarien, wie beispielsweise Lastverteilung oder Hochverfügbarkeit.

Beides würde an dieser Stelle aber den Rahmen sprengen. Kubernetes hat im Vergleich zu Docker eine höhere Komplexität, an die ich mich langsam heran tasten würde – sonst wird man schnell überfahren und frustriert. Falls ihr euch für weitere Beiträge rund um Kubernetes interessiert, schreibt es gerne ins Forum.

Leave a Reply