Kubernetes und helm charts
Autor: Dr. Götz Güttich, Senior Security Consultant bei Twinsoft
In diesem Beitrag befassen wir uns mit der Funktionsweise von „helm“ und den dazugeörigen „charts“. helm ist eine Paketverwaltung für Kubernetes und übernimmt im Betrieb ähnliche Aufgaben wie apt und dnf unter Linux oder pip für Python. Die charts sind die dazugehörigen Pakete. helm reduziert die Komplexität von Kubernetes-Umgebungen, ermöglicht standardisierte Deployments und vereinfacht Updates beziehungsweise Rollbacks.

Da wird über keinen Test-Kubernetes-Cluster verfügen, richten wir uns zunächst eine Testumgebung mit Hilfe von Minikube ein. Minikube ermöglicht es, einen lokalen Kubernetes-Cluster auf dem eigenen Endpoint zu betreiben. Das Tool eignet sich hervorragend für Schulungen, Tests und Entwicklungsarbeiten.
Nach der Installation von Minikube werden wir zunächst einmal einen nginx-Webserver mit helm in Kubernetes als Pod zum Laufen bringen, Ingress und DNS so konfigurieren, dass man auf diesen Webserver zugreifen kann, die Überwachungsfunktionen des Systems nutzen, um den Ressourcenverbrauch zu analysieren und eine WordPress-Installation mit einer Datenbank mit Hilfe von helm vornehmen. Danach skalieren wir die Zahl der Pods automatisch nach Last mit Hilfe des Kubernetes Autoscalers und erzeugen ein eigenes helm chart.

Noch kurz zum Verständnis: Pods sind logische Hosts, auf ihnen laufen einer oder mehrere Container, sie haben eine externe IP-Adresse, sie sind über Namen und Namespace identifizierbar und sie teilen Storage-Ressourcen. Die interne Kommunikation findet via IPC (Inter Process Communication) statt. Pods sind kurzlebig und werden über Controller gehandhabt.
Minikube installieren
Als Testsystem kommt ein Ubuntu 26.04 zum Einsatz. Dieses sollte mindestens 2 CPUs und 4 GByte RAM mitbringen. Eine der Grundvoraussetzungen für den Betrieb von Minikube ist eine vorhandene Docker -Umgebung zum Betreiben von Kubernetes. Deswegen bringen wir erstmal das installierte Linux auf den aktuellen Stand und installieren danach curl, Minikube und Docker.
sudo apt install curl
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
sudo snap install kubectl –-classic
sudo apt install docker.io
sudo systemctl enable –now docker
sudo apt install docker-compose
sudo usermod -aG docker $USER && newgrp docker
Jetzt können wir Minikube starten:
minikube start –-driver=docker
Nachdem Minikubes Startvorgang abgeschlossen wurde, lässt sich der Status des Clusters abfragen:
minikube status
Die Ausgabe sollte so aussehen:
minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured
Mit dem Befehl:
kubectl get nodes
bringen wir in Erfahrung, dass unser Node „Ready“ ist. Jetzt kann es daran gehen, helm zu installieren, ein Repo hinzuzufügen und letzteres auf den aktuellen Stand zu bringen:
sudo snap install helm –classic
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
Jetzt kann es daran gehen, ein chart im Minikube-Cluster zu installieren. Für diesen Test nehmen wir „nginx“:
helm install my-release bitnami/nginx
Der Status der Pods lässt sich immer mit
kubectl get pods
überprüfen. Hier sollte jetzt etwas wie das Folgende erscheinen:
NAME READY STATUS RESTARTS AGE
my-release-594c4c58bf-vdhnm 1/1 Running 1 (13d ago) 13d
Weitere wichtige Befehle:
helm list
zeigt alle installierten Apps und
helm search repo nginx
zeigt die in den Repositories verfügbaren Versionen der Apps.
helm status my-release
präsentiert die Dokumentation des chart-Erstellers und
helm get values my-release
zeigt die beim Installieren gesetzten Einstellungen. Entfernen lässt ich das chart über
helm uninstall my-release
Das Aktualisieren der Software
Um Minikube und Kubernetes im laufenden Betrieb zu aktualisieren sind diverse Schritte erforderlich. Zunächst einmal ist Minikube selbst auf den aktuellen Stand zu bringen:
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-latest.x86_64.rpm
sudo install minikube-linux-amd64 /usr/local/bin/minikube
Die einfachste Option, Kubernetes zu aktualisieren ist eine Neuerstellung des Clusters:
minikube delete
minikube start –-driver=docker –-kubernetes-version=latest
kubectl und helm werden über snap aktualisiert:
sudo snap refresh helm
sudo snap refresh kubectl
Weitere wichtige Befehle für Minikube sind
minikube pause
Das friert den Cluster ein. Wieder in Betrieb nehmen lässt er sich mit
minikube unpause
Ausschalten lässt sich das System mit
minikube stop
helm in der Praxis
Nachdem wir jetzt alles installiert und auf den aktuellen Stand gebracht haben, können wir einen neuen Cluster erzeugen und mit der eigentlichen Arbeit beginnen:
minikube delete
minikube start –-driver=docker
Jetzt installieren wir erst einmal ein neues nginx-pod. Wir nennen es „my-nginx“ und setzen den Service-Typ auf „LoadBalancer“. Letzteres muss angegeben werden, damit die Webseite später über das Netz erreichbar ist:
helm install my-nginx bitnami/nginx –set service.type=LoadBalancer
Kubernetes lädt jetzt das Image und startet den Container. Der aktuelle Status lässt sich wieder mit
kubectl get pods
überprüfen. Sobald unter “STATUS” Running steht, ist nginx bereit.
Um nun auf den Webserver zuzugreifen, kann entweder ein Tunnel oder ein weitergeleiteter Port zum Einsatz kommen. Für den Tunnel müsste der Befehl
minikube tunnel
in einem zusätzlichen Terminalfenster ausgeführt werden. Das Port-Forwarding wird über
kubectl port-forward svc/my-nginx 8080:80
aktiviert. Danach ist der Server über die URL http://localhost:8080 erreichbar.
Einer der großen Vorteile von helm ist, dass sich Anwendungen ändern lassen, ohne sie zu löschen. Wenn wir beispielsweise die Anzahl der Instanzen von nginx erhöhen möchten, um Hochverfügbarkeit zu simulieren, reicht ein
helm upgrade my-nginx bitnami/nginx –set replicaCount=3
Jetzt kann man mit
kubectl get pods
ansehen, wie das System zwei weitere Instanzen hochfährt. Löscht man Instanzen während des Betriebs durch
kubectl delete pod {Instanzname}
so startet das System gleich wieder neue, um die Zahl der definierten Instanzen wieder zu erreichen.
Den Ressourcenverbrauch analysieren
Um herauszufinden, wieviel CPU und RAM der nginx verbraucht, müssen wir das metrics-server-Addon aktivieren:
minikube addons enable metrics-server
Jetzt zeigt
kubectl top pods
den Ressourcenverbrauch an.
Ingress und DNS konfigurieren
Momentan greifen wir per localhost auf den Web-Server zu. Sinnvoller ist es, den nginx über die Domäne http://mein-nginx.local erreichbar zu machen. Dazu installieren wir einen Ingress-Controller, der als zentraler Einstiegspunkt den Traffic annimmt, konfigurieren eine Ingress-Ressource und passen die hosts-Datei so an, dass der Browser die Domäne findet. Letzteres ließe sich auch über eine DNS-Konfiguration realisieren, da das Anpassen von DNS-Servern aber nicht der Fokus dieses Playbooks ist, wählen wir die hosts-Datei. Los geht es mit
minikube addons enable ingress
Jetzt prüfen wir, ob der Controller-Pod hochgefahren ist:
kubectl get pods -n ingress-nginx
Sobald der Controller-Pod auf „running“ steht, können wir die Ingress-Ressource erstellen. Dazu verwenden wir eine yaml-Datei, in der steht „Alles was mit dem Hostnamen ‚mein-nginx.local‘ ankommt, soll auf Port 80 des Service ‚my-nginx‘ weitergeleitet werden. Wir nennen die Datei „ingress.yaml“ und sie sieht folgendermaßen aus:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
– host: mein-nginx.local
http:
paths:
– path: /
pathType: Prefix
backend:
service:
name: my-nginx
port:
number: 80
Anwenden lässt sich die Datei nun mit
kubectl apply -f ingress.yaml
Jetzt finden wir die IP-Adresse des Minikube-Knotens heraus:
minikube ip
Diese wird nun in die /etc/hosts-Datei eingetragen:
{IP-Adresse} mein-nginx.local
Jetzt lässt sich der Webserver über die URL http://mein-nginx.local im Browser öffnen.
Wenn wir einen zweiten Dienst über die gleiche IP-Adresse anbieten wollen, müssen wir nun lediglich diesen zweiten Dienst (eine andere Webseite oder ein anderer Server wie Apache) in Kubernetes starten, in der ingress.yaml eine zweite Regel hinzufügen, beispielsweise für die URL „meine-app.local“ und die URL der zweiten App mit der gleichen Minikube-IP-Adresse in die hosts-Datei eintragen.
Dashboard und Monitoring
Um die Monitoring-Funktionen zu aktivieren, müssen (falls nicht schon geschehen) folgende addons aktiviert werden:
minikube addons enable dashboard
minikube addons enable metrics-server
Dann öffnet Minikube eine Kubernetes-Dashboard-Seite, die die vorhandenen Workloads/Pods, Services und ConfigMaps anzeigt. Klickt der Anwender auf einen Pod-Eintrag, lassen sich die Live-Logs über das kleine Linien-Symbol oben anzeigen.
Unter Services sollten jetzt der nginx-Dienst und der Ingress-Controller erscheinen. Die ConfigMaps ermöglichen es, die Konfigurationsdaten von Container-Images zu trennen, also beispielsweise unter nginx eine andere Index.html anzuzeigen als die Default-Webseite. Während ConfigMaps unkritische Daten enthalten, existieren für sensible Daten die so genannten Secrets, die ähnlich funktionieren aber auf sichere Art und Weise Passwörter, Tokens und Keys bereitstellen können.
Das Dashboard zeigt uns an, dass es praktisch keine Last auf dem nginx-Server gibt. Jetzt werden wir Last erzeugen, um das Echtzeit-Monitoring unter die Lupe zu nehmen. Dazu nutzen wir den Befehl „ab“ (Apache Benchmark). Dieser lässt sich folgendermaßen installieren:
sudo apt install apache2-utils
Nun können wir 50.000 Anfragen mit 100 gleichzeitigen Verbindungen schicken:
ab -n 50000 -c 100 http://mein-nginx.local/
Jetzt steigt die CPU-Auslastung des nginx-Pods sprunghaft an, was im Dashboard klar zu sehen ist.
Installation von WordPress mit Datenbank als zweite Instanz parallel zu nginx
Zunächst installieren wir WordPress mit MariaDB und einem eigenen Namen sowie einer Ingress-Anbindung. Außerdem setzen wir ein Passwort, damit wir uns einloggen können:
helm install mein-blog bitnami/wordpress \
–set wordpressUsername=admin \
–set wordpressPassword=geheim \
–set mariadb.auth.rootPassword=geheim-db \
–set service.type=ClusterIP \
–set ingress.enabled=true \
–set ingress.hostname=mein-blog.local \
–set ingress.annotations.“kubernetes\.io/ingress\.class“=nginx
Wenn wir jetzt mit
kubectl get pods
nachsehen, so beobachten wir, wie das WordPress-Pod und das MariaDB-Pod erstellt werden. Dazu kommen zwei Persistent Volumes, eines für die Datenbank und eines für WordPress. Letztere verhindern, dass Blog-Posts verloren gehen, wenn die Maschine neu startet. Im nächsten Schritt tragen wie die URL „mein-blog.local“ mit der Minikube-IP in die hosts-Datei ein. Dazu schreiben wir sie einfach in die gleiche Zeile hinter die nginx-Domain.
Im Dashboard kann man nun unter „Pods“ die Interaktionen zwischen WordPress und MariaDB sehen und unter Persistent Volume Claims (PVC) findet sich der reservierte Speicherbereich. Das Blog selbst lässt sich über http://mein-blog.local aufrufen und nutzen. Wird ein Blogpost erstellt und das Pod gelöscht, so startet Kubernetes sofort ein neues Pod und die Daten des Blogposts gehen nicht verloren. Auf diese Weise lassen sich mehrere Anwendungen sauber getrennt und hochverfügbar auf einer Hardware ausführen.
Das Autoscaling
Wenden wir uns jetzt dem Autoscaling zu. Wir möchten dabei erreichen, dass standardmäßig in unserem Cluster 2 nginx-Pods laufen. Steigt die Last, so sollen weitere Pods (bis maximal 5) hinzukommen. Sinkt die Last wieder, so sollen die zusätzlichen Pods automatisch abgebaut werden. Dazu verwenden wir den Horizontal Pod Autoscaler (HPA) von Kubernetes. Diese behält die CPU- und Speicherlast im Blick und schaltet dynamisch Instanzen dazu oder weg.
Damit Kubernetes erkennen kann, wann ein Pod ausgelastet ist, müssen wir festlegen, wieviel CPU es verbrauchen darf. Deswegen aktualisieren wir die nginx-Instanz mit helm und geben ihr neue Limits mit. Gleichzeitig setzen wir die Zahl der standardmäßig aktiven Instanzen auf 2:
helm upgrade my-nginx bitnami/nginx \
–reuse-values \
–set replicaCount=2 \
–set resources.requests.cpu=100m \
–set resources.limits.cpu=200m
100m steht hier für 100 Millicores, also 10 % eines CPU-Kerns. Jetzt folgt die Regel, die besagt „Wenn die Pods im Schnitt mehr als 50 % ihrer angeforderten CPU verbrauchen, skaliere hoch bis auf maximal 5“:
kubectl autoscale deployment my-nginx –cpu-percent=50 –min=2 –max=5
Mit
kubectl get hpa
lässt sich der Status überwachen. Es dauert etwa 1 bis 2 Minuten, bis der Server die Daten zuverlässig liefert. Jetzt starten wir den Lasttest in einem zweiten Terminal:
ab -n 500000 -c 100 http://mein-nginx.local/
Jetzt können wir im ersten Terminal mit Hilfe von
kubectl get hpa
sehen, dass der Prozentwert über 50 % steigt (unter TARGETS). Kurz darauf zeigt
kubectl get pods
an, dass Kubernetes neue Pods erstellt. Gleichzeitig kann man im Dashboard sehen, wie die Kurve nach oben geht. Nach dem Stoppen des ab-Befehls fällt die Last und die zusätzlichen Pods werden 5 Minuten später wieder gelöscht. Diese Wartezeit verhindert ein ständiges Hoch- und Runterfahren bei kurzen Lastspitzen.
Sichern und Wiederherstellen von Pod- und Deployment-Konfigurationen
Hat man ein Pod zur Zufriedenheit konfiguriert und alles läuft wie gewünscht, besteht übrigens die Möglichkeit, die Konfiguration des laufenden Pods mit dem Befehl
kubectl get pod {Name des Pods} -o yaml > datei.yaml
in einer yaml-Datei zu sichern. Wird das Pod gelöscht, so lässt es sich später problemlos mit dem Befehl
kubectl apply -f datei.yaml
wiederherstellen. Soll das ganze Deployment, das die Pods steuert, gesichert werden, so lässt sich das mit dem Befehl
kubectl get deployment {Name} -o yaml > datei.yaml
realisieren.

Eigene helm charts erstellen
Zum Schluss wollen wir noch demonstrieren, wie sich eigene helm charts erzeugen lassen. Dazu installieren wir den Klassiker „Nethack“ in unserem Cluster, für den in den Repositories kein chart vorhanden ist.
Als Basis verwenden wir das Docker-Image „jwodder/nethack“. Der Befehl
helm create nethack-chart
erstellt eine Grundstruktur für das neue chart. Im nächsten Schritt öffnen wir die Datei „nethack-chart/values.yaml“ und passen das Image an. Der Bereich „image“ der yaml-Datei muss folgendermaßen aussehen:
image:
repository: jwodder/nethack
pullPolicy: IfNotPresent
tag: „latest“
Jetzt müssen wir dafür sorgen, dass Kubernetes weiß, dass das Pod einen interaktiven Terminal-Stream öffnen muss, da sonst niemand mit dem Spiel interagieren kann. Das geht über „tty: true“ und „stdin: true“. Wir öffnen die Datei „nethack-chart/templates/deployment.yaml“ und fügen unter dem Abschnitt „containers:“ auf der gleichen Ebene wie „image:“ hinzu:
containers:
– name: {{ .Chart.Name }}
image: „{{ .Values.image.repository }}:{{ .Values.image.tag }}“
stdin: true # Entspricht -i
tty: true # Entspricht -t
Außerdem ergibt es auch Sinn, die Abschnitte „livenessProbe“ und „readinessProbe“ in der deployment.yaml auszukommentieren, sonst startet Kubernetes das Pod ständig neu, da es nicht auf HTTP-Anfragen reagiert (Nethack ist ja kein Webserver). Jetzt lässt sich das chart installieren:
helm install my-nethack ./nethack-chart
Um sich mit dem Spiel zu verbinden, müssen wir erst den Namen des Nethack-Pods herausfinden. Das geht mit
kubectl get pods
Danach wird die Verbindung mit
kubectl attach -it {Name des Pods}
hergestellt.
