Blog

Automatisiertes Kubernetes-Deployment mit Helm und Helmfile

11 Aug 2022

Baustellen Image

Von Tammo van Lessen: Für Kubernetes gibt es inzwischen für jeden Use Case ein passendes Werkzeug. Trotzdem ist es nach wie vor eine Herausforderung, reproduzierbare und gleichzeitig flexible Rollouts für Infrastrukturkomponenten und Anwendungen für verschiedene Stages zu automatisieren, ohne in der Scripting-Hölle zu landen. Helm wird zusammen mit Helmfile wirklich mächtig.

Es führen viele Wege zu einem automatisierten Deployment. Teilweise können religiöse Diskussionen über das „Wie“ und das „Womit“ geführt werden. Entscheidend ist, dass man mit möglichst einfachen, strukturgebenden Mitteln einen hohen Automatisierungsgrad beim Rollout der Infrastruktur- und Anwendungskomponenten erreicht. Eine CI/CD Pipeline, die unter Versionskontrolle stehende Provisionierungsskripte ausführt, ist ein guter Start. Mit der Zeit wachsen die Skripte aber an und es wird zunehmend schwer, sie wartbar und wiederholbar zu halten. Daher empfiehlt es sich, stattdessen Werkzeuge einzusetzen, die statt der sequenziellen Abarbeitung von Schritten ein Modell des Wunschzustands (desired state) verarbeiten und diesen im Zielsystem herzustellen versuchen. Helmfile ist ein solches Tool und gleicht so manche Unzulänglichkeit von Helm aus.

Was ist Helm?

Helm ist ein Paketmanager für Kubernetes. Obwohl nicht unumstritten, hat er sich als De-facto-Standard zur Paketierung von Anwendungen für Kubernetes durchgesetzt. Helm bildet eine logische Klammer um die zusammengehörenden Kubernetes-Manifeste, die eine Anwendung beschreibenund durch (Go-)Templates generiert werden. Die Werte, mit denen die Templateplatzhalter ersetzt werden, werden Helm von außen entweder als Values-Datei oder über Parameter mitgegeben. Templates und eine Default-Belegung der Werte werden in einem sogenannten Helm Chart zusammengefasst. So können die Kubernetes-Interna der Anwendung vor Usern versteckt werden. Die Werte sind die Schnittstelle zur Konfiguration des Rollouts, aber auch der Anwendung. Das gibt Autoren die Möglichkeit, ihre Charts anderen zur Verfügung zu stellen, ohne dass diese tiefere Kenntnis über die Ausführungsmechanik besitzen müssen. Helm Charts werden nach semver versioniert und in Helm Repositories (z. B. in S3 oder via OCI) publiziert. Mit dem Artifact Hub existiert ein Aggregator, der die Repositories anderer durchsuchbar macht. Dadurch ist ein sehr vitales Ökosystem mit unzähligen Komponenten entstanden.

Helm weist allerdings auch Unzulänglichkeiten auf, gerade wenn man es in Deployment Pipelines einsetzen möchte. Eine Herausforderung ist das Secrets Management. Secrets lassen sich nicht ohne Umwege aus Umgebungsvariablen oder aus anderen Quellen in die Templates einbringen. Möchte man mit Pull Requests arbeiten, wäre es hilfreich, die auszurollenden Änderungen vorher begutachten zu können. Kombiniert man mehrere Helm Charts zu einem sogenannten Umbrella Chart, müssen Werte wie Hostnames in der Regel mehrfach angegeben werden, denn eine Interpolation der Werte ist nicht ohne weiteres möglich.

Der typische Ausweg ist nun meist Shell-Scripting, z. B. um Secrets oder Umgebungsvariablen mittels gettext envsubst, jq, yq in die Values-Dateien zu schreiben und dann an Helm weiterzugeben. Ein fehleranfälliger Prozess.

Helmfile zur Rettung

Helmfile bietet sich als Alternative an. Es ist ein Wrapper für Helm und erlaubt eine deklarative Beschreibung der Releases, also der Information, welches Chart mit welchen Werten ausgerollt werden soll. Diese Beschreibung kann in einer Datei vorgenommen werden, aber auch auf mehrere Dateien modular verteilt werden.

Helmfile kann mehrere Umgebungen (Environments) verwalten. Dieses Feature ist sehr nützlich, um gleiche oder ähnliche Rollouts mit abweichenden Werten zu deployen. Naheliegend ist die Verwendung, um dev-, staging- und prod-Umgebungen aufzubauen, man kann es aber auch nutzen, um z. B. Umgebungen pro Mandant aufzubauen. Die Zielumgebung wird beim Aufruf als Parameter übergeben. Helmfile stellt dann die Werte, die in der Umgebung definiert wurden, den Templates als Variablen zur Verfügung. Der Aufruf helmfile -e development apply setzt die Umgebung auf development und führt apply aus. Das bedeutet, dass zunächst ein Diff zwischen den auf Basis der Werte generierten Manifestdateien und den im Kubernetes-Cluster vorhandenen Manifesten berechnet wird. Die Unterschiede werden ausgegeben und dann in den Cluster eingebracht.

Helmfile führt ein mehrstufiges Templating durch. Zunächst werden Templateausdrücke in den Helmfiles selbst evaluiert. Dadurch ist es möglich, ein Release innerhalb einer for-Schleife zu definieren und damit beispielsweise für jeden Mandanten eine Instanz einer Komponente auszurollen. So kann man auch komplexe Rollouts elegant ausdrücken.

Außerdem kann Helmfile die an Helm zu übergebenden Values-Dateien (so sie die Dateiendung .gotmpl verwenden) mittels Templating generieren. Dadurch ergeben sich vielfältige Möglichkeiten, auch weil sich die Werte mittels variantdev/vals aus externen Quellen wie S3, Vault oder Secret Managern in der Cloud beziehen lassen.

Listing 1 zeigt ein Minimalbeispiel zum Ausrollen einer Anwendung. Zunächst wird das Repository bekannt gemacht, aus dem das Chart bezogen werden soll. Dann werden die beiden Umgebungen development und production definiert. Für development wollen wir die Version 3.9 oder größer installieren, weil wir neue Versionen sofort integrieren möchten, um schnell zu merken, wenn etwas bricht. Das Caret drückt nach semver aus, dass neuere Minor- und Patch-Versionen herangezogen werden dürfen, nur eine neue Major-Version wollen wir nicht. Auf der Produktionsumgebung sind wir konservativ und geben eine genaue Versionsnummer an.

Danach wird das Release definiert. Die Anwendung wird in einen Namespace installiert, der sich dank Templating aus dem Namen der Umgebung und dem Suffix my-app zusammensetzt, also z. B. production-my-app. So können beide Umgebungen in einem Cluster existieren. Die auszurollende Version wird durch die gewählte Umgebung bestimmt. Wird die production-Umgebung ausgerollt, wird 3.8.0 installiert. Dem Release werden die Werte aus zwei Values-Dateien übergeben: eine, die für alle Umgebungen gilt, und eine, die umgebungsspezifisch ist. Das wird durch den Templateausdruck im Dateinamen erreicht. Die Anzahl der Replicas würde man z. B. in der globalen Values-Datei definieren, könnte sie aber explizit pro Umgebung überschreiben. Der Datenbank-Host wäre umgebungsspezifisch und würde in der zweiten Datei definiert. Helm mergt die beiden YAML-Dateien, sodass die zweite die Werte der ersten überschreibt.

Listing 1

repositories:
  - name: my-org
    url: https://my-org.example.com/my-app
 
environments:
  development:
    values:
      - my-app-version: ^3.9.0 
  production:
    values:
      - my-app-version: 3.8.0
 
releases:
  - name: my-app
    namespace: {{ .Environment.Name }}-my-app
    createNamespace: true
    labels:
      component: my-app
    chart: my-org/my-app
    version: '{{index .Values "my-app-version" }}'
    values:
      - my-app/values.yaml.gotmpl
        - my-app/{{ .Environment.Name }}-values.yaml.gotmpl
    secrets:
      - my-app/secrets.yaml
      - my-app/{{ .Environment.Name }}-secrets.yaml

Bleibt noch das Thema Secrets Management. Die Zugangsdaten für die Datenbank kann man sich entweder mittels der Templatefunktion requireEnv aus einer Umgebungsvariable besorgen, schöner geht das aber mit den eingebauten Mechanismen. Helmfile integriert das Helm-Plug-in helm-secrets, das wiederum Mozilla SOPS verwendet. Damit ist es möglich, Secrets verschlüsselt im Git-Repository abzulegen. Die Secrets können per GPG, aber auch mit Hilfe von in der Cloud gespeicherten Schlüsseln (z. B. Azure KeyVault, GCP KMS) ver- und entschlüsselt werden. So kann recht feingranular geregelt werden, wer die Secrets lesen oder schreiben darf. Unter Versionskontrolle liegen dann nur die verschlüsselten YAML-Dateien. Helmfile kümmert sich transparent um die Entschlüsselung, wenn die Dateien der Releasedefinition als Secret übergeben werden. Hier ist also ein guter Ort für umgebungsspezifische Zugangsdaten.

Fazit

Helm und Helmfile bilden ein wertvolles Duo, um automatisierte, reproduzierbare Rollouts auf Kubernetes zu realisieren. Das mehrstufige Templating und das Konzept der Umgebungen bietet alle Möglichkeiten, um die Gleichheit der Umgebungen effizient ausdrücken zu können und dennoch die nötige Flexibilität zu erhalten, um Updates oder Variationen einfach und ohne Duplikate umsetzen zu können. Das Secrets Management rundet das Featureset ab. Eine etwas ausführlichere Vorstellung mit Anwendungsszenarien kann hier angeschaut werden.