Das passende Training Kubernetes Helm Training – Charts, Deployments & Praxis (2 Tage)
Helm: Values, ConfigMap oder Secret? Die eine Regel, mit der niemand mehr suchen muss

Das passende Training Kubernetes Helm Training – Charts, Deployments & Praxis (2 Tage)

Helm: Values, ConfigMap oder Secret? Die eine Regel, mit der niemand mehr suchen muss

Ganz ehrlich: Die Frage kam im Helm-Training nicht als Frage. Sie kam als Geständnis.

“Wir suchen einfach durch”

Ein Teilnehmer beschreibt, wie es bei ihren Helm-Charts aussieht: Manches steht in der values.yaml, manches in ConfigMaps, manches in Secrets. Historisch gewachsen, jeder hat mal was dazugebaut. Und wenn heute jemand einen Wert ändern will, dann sucht er. Durch alle drei Stellen. Bis er ihn findet.

Ich wollte gerade antworten, da nickten drei andere. Dasselbe Bild bei ihnen. Das Thema stand nicht auf der Agenda, aber es war das, was alle wissen wollten.

Meine Antwort passt in einen Satz, und sie ist seitdem meine Regel für jedes Helm-Chart: Alles, was sich pro Umgebung ändert, gehört in die Values. ConfigMap und Secret sind Templates im Chart, keine Parallelwelt.

Und der Nachsatz, den kaum ein Chart befolgt: Was in jeder Umgebung gleich ist, steht direkt im Template. Nicht in den Values. Dann muss es dort auch niemand suchen.

Warum die Parallelwelt entsteht

Klar kannst du an einer ConfigMap von Hand rumschrauben, mit kubectl edit geht das in zehn Sekunden. Kubernetes nickt das ab, dem ist alles egal. Helm nicht. Helm 3 kommt beim nächsten upgrade vorbei, sieht deine Änderung nicht einmal an und bügelt drüber, was im Template steht. Gemerkt hat es keiner, bis es brennt. Helm 4 ist bei neuen Releases nachtragender und bricht gleich mit einem Konflikt ab. Besser, aber auch kein Spaß um 17 Uhr.

Das Ergebnis ist eine zweite Ablage neben dem Chart. Zwei Orte, an denen derselbe Wert stehen kann, und keiner weiß, welcher gerade gilt. Genau das ist die Parallelwelt.

Bei mir im eigenen Cluster gibt es deshalb nur Charts. Auch für eine reine Konfigurationsänderung baue ich lieber eine neue Chart-Version, statt irgendwo ein Objekt anzupassen. Dann weiß ich bei jedem Problem, welche Version lief, und kann mich mit anderen über genau diese Version unterhalten. Dazu passt der Satz aus meinem Artikel über den Schreibfehler, den Helm vier Stunden lang installiert hat: Was jemand von Hand in eine ConfigMap tippt, prüft niemand.

So sieht die Regel in YAML aus

Die Values-Datei einer Umgebung enthält nur das, was in dieser Umgebung anders ist. Nichts, was überall gleich ist, und nichts, was geheim ist:

# values-prod.yaml - nur das, was in Prod anders ist
nfs:
  server: nfs-prod.firma.intern
  path: /export/app
db:
  host: db-prod.firma.intern
  vaultPath: shop/prod      # Pfad im Vault, relativ zum Mount

Die ConfigMap ist ein Template im Chart. Sie pflegt keine Werte, sie setzt die Values ein:

# templates/configmap.yaml - Template, keine zweite Ablage
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "app.fullname" . }}-config
data:
  NFS_SERVER: {{ .Values.nfs.server | quote }}
  NFS_PATH: {{ .Values.nfs.path | quote }}
  DB_HOST: {{ .Values.db.host | quote }}

Und das Secret? Steht gar nicht im Chart. Dazu gleich.

Für Secrets gilt die Regel mit einem Zusatz

Der Zusatz ist hart: Ein Credential liegt nie im Klartext im Git-Repo. Nicht in den Values, nicht in einem Template, nicht in einer Datei, die “nur für Test” heißt.

Wie kommt der Wert dann in den Cluster? Erster Weg: Der Git-Server bringt ihn mit. In GitLab hinterlegst du ihn als CI/CD-Variable. Nicht in der .gitlab-ci.yml, sonst steht er doch wieder im Repo. Die Pipeline reicht ihn beim helm upgrade durch, und dein Repo hat das Passwort nie gesehen.

Zweiter Weg, und das ist der, den ich nehme: Ein Operator holt den Wert zur Laufzeit aus einem Secrets-Manager. Bei HashiCorp Vault ist das der Vault Secrets Operator. Bei OpenBao nehme ich den External Secrets Operator, der kann auch die Cloud-Dienste. Verschlüsselt im Repo per SOPS geht ebenfalls. Warum das alles nötig ist, steht in meinem Artikel über die Postkarte namens Kubernetes-Secret.

Im Chart steht dann kein Secret mehr, sondern der Auftrag an den Operator:

# templates/db-secret.yaml - der Auftrag an den Vault Secrets Operator
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
  name: {{ include "app.fullname" . }}-db
spec:
  vaultAuthRef: vault-auth   # VaultAuth-Ressource im Namespace
  mount: apps                # KV-v2-Mount im Vault
  type: kv-v2
  path: {{ .Values.db.vaultPath }}
  refreshAfter: 1h
  destination:
    name: {{ include "app.fullname" . }}-db
    create: true

Der Pfad im Vault ändert sich pro Umgebung, deshalb steht er in den Values. Das Passwort selbst taucht im Repo nie auf. Deine Anwendung merkt von dem Ganzen nichts, für sie liegt da ein ganz normales Secret. Nur dass der Operator jede Stunde im Vault nachschaut und es austauscht, wenn sich dort etwas geändert hat. Du musst dafür keinen Finger krumm machen.

Und wo ich sofort den Stecker ziehe: Secrets, die ein Chart selbst würfelt. Klingt bequem, aber dieses Passwort kennen nur Helm und das gerenderte Manifest. In Git taucht es nie auf. Und beim nächsten Upgrade? Je nach Chart ist es weg, und deine Datenbank kennt das neue nicht. Also: Generierung aus, bei Bitnami heißt die Option existingSecret, Secret von außen. Fertig.

Meine klare Ansage

Du brauchst keine Richtlinie mit zwölf Punkten. Du brauchst eine Frage, die jeder im Team in zwei Sekunden beantwortet: Ändert sich der Wert pro Umgebung? Dann Values. Ist er geheim? Dann kommt er von außen, nie aus dem Repo. Alles andere ist ein Template im Chart.

Wer diese Regel einmal durchzieht, hat pro Umgebung genau eine Values-Datei und sucht nie wieder durch drei Stellen. Genau das bauen wir im Kubernetes-Helm-Training an einem echten Chart durch, inklusive der Frage, was man mit der gewachsenen Buntmischung macht, die schon da ist.