Helm ist der Paketmanager für Kubernetes. Ein Chart bündelt Manifeste, Standardwerte und Metadaten zu einer installierbaren Einheit. Wer eigene Anwendungen ausrollt, kommt an eigenen Charts kaum vorbei: Sie machen Deployments wiederholbar, umgebungsabhängig konfigurierbar und versionierbar.
Was ein Chart eigentlich ist
Ein Helm Chart ist ein Verzeichnis mit einer festen Struktur. Helm rendert daraus Kubernetes-Manifeste und wendet sie auf ein Cluster an. Die Trennung ist bewusst: Templates beschreiben die Form der Ressourcen, Values die veränderlichen Werte. Dieselbe Chart-Version lässt sich so in Entwicklung, Staging und Produktion installieren, ohne die Manifeste zu kopieren.
Voraussetzungen
- Helm 3, lokal installiert (
helm version) - Ein Kubernetes-Cluster zum Testen, zum Beispiel kind oder minikube
kubectl, konfiguriert auf dieses Cluster
Zum reinen Rendern der Manifeste reicht Helm allein. Ein Cluster brauchst du erst, wenn du installieren oder upgraden willst.
Ein Chart anlegen
Der schnellste Einstieg ist das Gerüst von Helm selbst:
helm create meine-app
Daraus entsteht diese Struktur:
meine-app/
Chart.yaml # Name, Version, Beschreibung
values.yaml # Standardwerte
charts/ # Abhängigkeiten
templates/ # Kubernetes-Manifeste als Templates
deployment.yaml
service.yaml
ingress.yaml
_helpers.tpl # wiederverwendbare Template-Funktionen
NOTES.txt # Hinweis nach der Installation
helm create legt ein vollständiges Beispiel für einen Webdienst an, inklusive Ingress, ServiceAccount und Tests. Für ein erstes eigenes Chart ist das hilfreich. Was du nicht brauchst, löschst du, statt um leere Dateien herumzubauen.
Chart.yaml
Die Metadaten stehen in Chart.yaml. Für Helm 3 ist apiVersion: v2 Pflicht. version ist die Version des Charts nach SemVer, appVersion die Version der Anwendung, die das Chart ausrollt. Beides ist unabhängig: Ein Chart kann Version 1.4.0 haben und trotzdem Anwendung 2.3.1 deployen.
apiVersion: v2
name: meine-app
description: Webdienst mit Deployment, Service und Ingress
type: application
version: 0.1.0
appVersion: "1.0.0"
type: application markiert ein installierbares Chart. type: library ist für Charts gedacht, die nur Hilfs-Templates bereitstellen und nicht selbst installiert werden.
Werte und Templates
Alles, was sich zwischen Umgebungen unterscheidet, gehört nach values.yaml: Image, Replikas, Ressourcen, Ingress-Host, Feature-Flags. Templates lesen diese Werte über .Values.
# values.yaml
replicaCount: 2
image:
repository: ghcr.io/example/meine-app
tag: "1.0.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
Im Deployment ziehst du die Werte so hinein:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "meine-app.fullname" . }}
labels:
{{- include "meine-app.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "meine-app.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "meine-app.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 8080
resources:
{{- toYaml .Values.resources | nindent 12 }}
Drei Stellen lohnen einen zweiten Blick:
includeruft benannte Templates aus_helpers.tplauf. Namen, Labels und Selektoren sollten dort zentral stehen, damit Deployment und Service dieselben Selektoren verwenden.nindentrückt mehrzeilige Blöcke korrekt ein. Ohne Einrückung wird das gerenderte YAML ungültig.toYamlübernimmt verschachtelte Werte wie Resources oder Annotations, ohne sie Feld für Feld nachzubauen.
Blöcke optional machen
Nicht jede Installation braucht einen Ingress oder ein PersistentVolume. Optionale Ressourcen steuerst du mit if:
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "meine-app.fullname" . }}
spec:
rules:
- host: {{ .Values.ingress.host | quote }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ include "meine-app.fullname" . }}
port:
number: {{ .Values.service.port }}
{{- end }}
Das führende Minus in {{- entfernt die Leerzeile davor. Sonst sammeln sich im gerenderten Manifest leere Zeilen, die später schwer zu lesen sind.
Prüfen, bevor etwas im Cluster landet
Drei Befehle decken die meisten Fehler ab, bevor ein Release entsteht:
helm lint meine-app
helm template meine-app ./meine-app
helm install meine-app ./meine-app --dry-run --debug
helm lintprüft Struktur, Pflichtfelder und übliche Chart-Fehler.helm templaterendert die Manifeste lokal, ganz ohne Cluster. Die Ausgabe gehört in ein Review oder in die CI.helm install --dry-rungeht denselben Weg wie eine echte Installation, wendet aber nichts an. Fehlende Werte und ungültiges YAML fallen hier auf.
Werte für eine Umgebung übergibst du per Datei oder einzeln:
helm template meine-app ./meine-app -f values-prod.yaml --set replicaCount=3
--set überschreibt Werte aus Dateien. Für alles, was länger als ein einzelner Wert ist, ist eine eigene Values-Datei lesbarer.
Installieren und aktualisieren
helm install meine-app ./meine-app --namespace meine-app --create-namespace
helm upgrade meine-app ./meine-app --namespace meine-app
helm rollback meine-app 1 --namespace meine-app
upgrade ist auch der richtige Befehl für die erste Installation, wenn das Release vielleicht schon existiert:
helm upgrade --install meine-app ./meine-app --namespace meine-app --create-namespace
Helm speichert jedes Release als Secret im Ziel-Namespace. Deshalb funktionieren Rollback und History, ohne dass das Chart selbst Zustand hält.
Abhängigkeiten
Charts können andere Charts einbinden, etwa eine Datenbank oder einen Ingress-Controller. Die Deklaration steht in Chart.yaml:
dependencies:
- name: postgresql
version: "16.x.x"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
helm dependency update ./meine-app
Helm lädt die Abhängigkeit als Archiv nach charts/. Werte des Untercharts adressierst du unter dem Chart-Namen, also postgresql.auth.password in der eigenen values.yaml. condition schaltet die Abhängigkeit ab, wenn du in Produktion eine externe Datenbank nutzt.
Packen und veröffentlichen
Ein Chart wird als .tgz verteilt:
helm package ./meine-app
Es entsteht meine-app-0.1.0.tgz. Der Dateiname setzt sich aus name und version zusammen. Für ein eigenes Repository genügt ein Verzeichnis mit den Paketen und einem Index:
mkdir -p repo
mv meine-app-0.1.0.tgz repo/
helm repo index repo --url https://example.com/charts
index.yaml ist der Katalog, den helm repo add später liest. In der Praxis landet dieses Verzeichnis auf einem Webserver, in einer OCI-Registry oder in einem Chart-Museum. OCI ist der Weg, den aktuelle Helm-Versionen bevorzugen:
helm push meine-app-0.1.0.tgz oci://ghcr.io/example/charts
Worauf es in der Praxis ankommt
- Keine Umgebungsdaten ins Chart. Hostnamen, Replikas und Ressourcengrenzen bleiben in Values-Dateien außerhalb des Charts oder in einem Überschreibungs-Mechanismus wie Helmfile oder Argo CD.
- Ressourcen immer setzen. Requests und Limits gehören in die Standardwerte, sonst plant der Scheduler blind.
- Labels zentral halten. Selektoren von Deployment und Service müssen übereinstimmen. Ein Helper-Template verhindert, dass sie auseinanderlaufen.
- Version bei jeder Änderung erhöhen. Helm behandelt ein Chart mit gleicher Version als dasselbe Paket. Ohne Versionsbump überschreibst du ein bereits veröffentlichtes Archiv.
- Secrets nicht in values.yaml committen. Passwörter kommen über
--set, eine externe Secrets-Datei außerhalb von Git oder über einen Operator wie External Secrets. - Lint und Template in der CI.
helm lintundhelm templatefangen YAML- und Strukturfehler, bevor ein Release gebaut wird.
Kurzfassung
Ein Helm Chart ist ein versioniertes Verzeichnis: Chart.yaml beschreibt es, values.yaml konfiguriert es, templates/ erzeugt die Manifeste. helm create liefert das Gerüst, helm lint und helm template prüfen es, helm upgrade --install rollt es aus. Wer Werte, Labels und Versionen sauber trennt, kann dasselbe Chart über alle Umgebungen hinweg benutzen.
Schreibe einen Kommentar