Blog

  • 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.

  • Hallo Welt!

    Willkommen bei WordPress. Dies ist dein erster Beitrag. Bearbeite oder lösche ihn und beginne mit dem Schreiben!