Installation eines minimal Kubernetes Clusters
Containerize all the things! Seit vielen Jahren ist die Container-Technologie aus der IT-Welt nicht mehr wegzudenken. Mittlerweile gibt es viele Lösungen und Varianten, einige sogar schon wieder veraltet. Über die Container-Engine hinaus gibt es auch Komplettlösungen, die eine Verwaltung bzw. Orchestrierung bieten - wie zum Beispiel Kubernetes.
Kubernetes ist vermutlich jedem bekannt, schließlich gibt es Kubernetes, oder abgekürzt K8s, auch bereits seit 2014/2015. Für mich ist daher umso erstaunlicher, dass ich in all den Jahren nie wirklich mit Containern in Berührung gekommen bin. Aber das IT-Feld ist breit und ich behaupte, dass man sich komplexere Container-Plattformen nicht mal eben nebenbei aneignet (abgesehen von vielleicht mal ein Docker Image pullen und starten). Zusätzlich sollte ein regelmäßiger Kontakt vorhanden sein bzw. damit arbeiten, sonst ist das Wissen wieder sehr sehr schnell verflogen.
Als IT-ler lernt man nie aus und das Thema hat mich immer getriggert, daher war es vor einiger Zeit endlich soweit und ich habe mich privat etwas mehr mit dem Thema auseinandergesetzt. Dieser Artikel ist Start einer kleineren Serie von zusammenhängenden Themen. Es beginnt mit der Installation eines minimalen K8s Clusters auf Proxmox, geht weiter in das Deployment von AWX und endet mit der Erstellung von neuen Debian VMs per Ansible/AWX auf Proxmox.
Ich beschreibe hier eine absolute manuelle minimale Installation, ohne Lösungen wie Kubespray, welche in der Lage sind K8s automatisiert zu installieren.
Voraussetzungen
Wie schon erwähnt, werde ich hier die Installation einer minimalen Variante beschreiben. Das bedeutet, es wird sich um einen 3-Node Cluster handeln: Einen Master (Control-Plane) und zwei Worker Nodes. Als Basis verwendet ich ganz normale Debian 12 VMs auf meiner Proxmox Umgebung.
VM | Role | CPU | RAM | HDD |
kube01 | Master | 1 Socket / 2 Cores / Type 'host' | 8 GB | 30 GB |
kube02 | Worker | 1 Socket / 2 Cores / Type 'host' | 16 GB | 30 GB |
kube03 | Worker | 1 Socket / 2 Cores / Type 'host' | 16 GB | 30 GB |
Warum hier der CPU Typ 'host' gewählt wurde dürfte auf der Hand liegen. Die Performance steigt erheblich, wenn die Workloads nicht in einer virtualisierten CPU laufen, sondern direkt an die physikalische CPU durchgereicht werden. Der zugewiesene Plattenplatz ist für den Anfang komplett ausreichend. Da es sich um VMs mit logical volumes handelt, können diese jederzeit vergrößert werden.
VM Basis-Konfigurationen
Alle drei Debian 12 VMs sollten auf einem aktuellen Stand und gegenseitig per DNS erreichbar sein. UFW als Firewall wird vorausgesetzt, zudem wird für externe Repositories gpg benötigt. Der qemu-guest-agent sollte auf dem System natürlich ebenfalls laufen.
Vorbereitungen
SWAP deaktivieren
Aus weiteren Perfomancegründen wird empfohlen SWAP auf allen K8s Nodes zu deaktiveren:
$ swapoff -a
$ sed -i '/ swap / s/^\\(.\*\\)$/#\\1/g' /etc/fstab
UFW Firewall konfigurieren
Master-Node:
$ ufw allow 6443/tcp
$ ufw allow 2379/tcp
$ ufw allow 2380/tcp
$ ufw allow 10250/tcp
$ ufw allow 10251/tcp
$ ufw allow 10252/tcp
$ ufw allow 10255/tcp
$ ufw reload
Worker-Nodes:
$ ufw allow 10250/tcp
$ ufw allow 30000:32767/tcp
$ ufw reload
Container-Engine 'containerd'
Als Container-Engine ist meine Wahl auf containerd gefallen, welches eine reine Engine darstellt und sich darauf fokussiert, eine (Low-Level) Container-Runtime bereitzustellen. Docker auf der anderen Seite als Alternative bietet ein enormes Tool-Set (stellt eine eigene High-Level Container Plattform dar), welches bei einer Kubernetes Installation nicht benötigt wird, da nur eine Engine, bzw. Runtime laufen muss.
Die Installation und Konfiguration von containerd erfolgt auf allen Nodes.
Kernel-Parameter setzen
Folgende Kernel-Parameter auf allen Nodes setzen:
$ cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf
overlay
br_netfilter
EOF
$ modprobe overlay
$ modprobe br_netfilter
$ cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
$ sysctl --system
Installation von containerd
Die Installation erfolgt über das OS-seitige Paketmanagement und den regulären Repositories:
$ apt -y install containerd
Konfiguration von containerd
Damit containerd mit Kubernetes zusammenarbeitet sind folgende Konfigurationen notwendig:
$ containerd config default | sudo tee /etc/containerd/config.toml >/dev/null 2>&1
Anpassen der config.toml:
nano /etc/containerd/config.toml
Im Bereich [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = false
ändern in
SystemdCgroup = true
Achtung: Es gibt ebenfalls einen Parameter
systemd_cgroup = false
unter [plugins."io.containerd.grpc.v1.cri"].
Dieser darf nicht geändert werden, sondern muss auf 'false' stehenbleiben.
containerd aktivieren und starten:
$ systemctl enable containerd
$ systemctl restart containerd
Installation von Kubernetes
Die Installation erfolgt auf allen Nodes. Im folgenden wird die Version 1.31.6 installiert. Natürlich kann dies auf den aktuellen Stand angepasst werden (Achtung: API Änderungen beachten).
Hinzufügen des Kubernetes-Repositories:
$ echo "deb \[signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg\] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list
$ curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
Installation der Tools:
$ apt update
$ apt -y install kubelet kubeadm kubectl
$ apt-mark hold kubelet kubeadm kubectl
Installation des Master-Nodes (Control-Plane)
kubelet bietet nicht mehr die Installation per Kommandozeile an. Diese sind deprecated. Stattdessen erfolgt die Installation per kubeadm und einer Definitionsdatei (Name frei wählbar).
$ nano kubelet.yml
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: "1.31.6"
controlPlaneEndpoint: "kube01"
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
Version und Endpoint entsprechend bei Bedarf anpassen.
Deploy der Control-Plane auf den Master-Node:
$ kubeadm init --config kubelet.yml
Sofern alles funktioniert hat, sollte die Ausgabe folgendermaßen aussehen:
Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Alternatively, if you are the root user, you can run:
export KUBECONFIG=/etc/kubernetes/admin.conf
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
You can now join any number of control-plane nodes by copying certificate authorities
and service account keys on each node and then running the following as root:
kubeadm join kube01:6443 --token abcd --discovery-token-ca-cert-hash sha256:1234 --control-plane
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join kube01:6443 --token abcd --discovery-token-ca-cert-hash sha256:1234
Admin-Konfiguration ins root-Home auf dem Master-Node kopieren:
$ mkdir -p $HOME/.kube
$ cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ chown $(id -u):$(id -g) $HOME/.kube/config
Der Status des Clusters und er Nodes kann folgendermaßen abgefragt werden:
$ kubectl get nodes
$ kubectl cluster-info
Dort sollte jetzt nur die Control-Plane bzw. der Master-Node zu sehen sein.
Deploy der Worker Nodes
Auf allen Worker-Nodes ausführen (Token und Hash wurden nach dem Deploy der Control-Plane aufgeführt):
kubeadm join kube01:6443 --token abcd --discovery-token-ca-cert-hash sha256:1234
Prüfen des Status:
$ kubectl get nodes
$ kubectl cluster-info
Dort sollten jetzt auch die beiden neuen Worker-Node zu sehen sein.
Hinweis: Hier steht nach der Installatio noch 'NotReady'. Damit die Nodes untereinander funktionieren, muss noch ein Pod-Network über den Cluster gespannt werden.
Installation des Network-Addon 'calico'
UFW auf allen Nodes konfigurieren:
$ ufw allow 179/tcp
$ ufw allow 4789/udp
$ ufw allow 51820/udp
$ ufw allow 51821/udp
$ ufw reload
Calico deployen (direkt aus dem Github-Repo):
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.2/manifests/calico.yaml
Die Version kann/sollte man anpassen, das Repository kann natürlich ebenfalls vorher geklont/heruntergeladen werden.
Prüfen der calico-Pods:
$ kubectl get pods -n kube-system
Diese sollten auf "Running" stehen.
Ab jetzt können Deployments/Pods auf dem Cluster ausgerollt werden.
Einfaches Nginx Webserver Deployment
Ein simples Nginx Deployment könnte so aussehen:
nano sample-webpage.yml
---
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: default
name: sample-webpage-deployment
labels:
app: sample-webpage
spec:
replicas: 1
selector:
matchLabels:
app: sample-webpage
template:
metadata:
labels:
app: sample-webpage
spec:
containers:
- name: nginx-container
image: nginx:latest
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
namespace: default
name: sample-webpage-service
spec:
selector:
app: sample-webpage
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30080
type: NodePort
Definiert wird das Deployment mit einem Replica und dem Nginx Container. Zusätzlich wird ein Service erstellt, über den das Deployment erreichbar ist. In diesem Fall wird der Typ 'NodePort' verwendet, da noch keine Loadbalancer o.Ä. auf dem Cluster laufen. Deployment und Service können natürlich auch in separate Dateien ausgelagert werden.
Ausrollen des Deployments und des Services:
kubectl apply -f sample-webpage.yml
Prüfen, ob der Pod gestartet wurde:
kubectl get pods -o wide
Hinweis: -o wide zeigt, auf welchem Worker-Node der Pod läuft.
Prüfen, ob der Service läuft und mit dem angegebenen Port:
kubectl get services
Der Pod und Service laufen und sind unter Port 80 bzw. 30080 erreichbar. Da wir wissen, dass der Pod auf Node 3 (kube03) läuft, kann ein Testaufruf aus einem Webbrowser erfolgen:
Hier natürlich die IP des jeweiligen Worker-Node anpassen.
Fazit
Die Installation eines einfachen 3-Node Kubernetes Clusters ist simpler als gedacht. Zugegeben in dieser einfachsten aller Formen (außer K3s und minikube) läuft darauf auch nicht viel, aber ich habe damals schon zu Beginn mit deutlich mehr Hürden gerechnet.
Im nächsten Artikel beschreibe ich, wie Ingress und AWX installiert wird.
Fragen, Kommentare und Anregungen wie immer auf Mastodon.
Datum | Änderung |
11.07.2025 | Veröffentlichung |