Helm Charts erstellen: Struktur, Templates und der Weg zum Release

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:

  • include ruft benannte Templates aus _helpers.tpl auf. Namen, Labels und Selektoren sollten dort zentral stehen, damit Deployment und Service dieselben Selektoren verwenden.
  • nindent rü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 lint prüft Struktur, Pflichtfelder und übliche Chart-Fehler.
  • helm template rendert die Manifeste lokal, ganz ohne Cluster. Die Ausgabe gehört in ein Review oder in die CI.
  • helm install --dry-run geht 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 lint und helm template fangen 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.

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert