— eine Wrapper für , die es ermöglicht, an einem Ort viele Helm-Releases zu beschreiben, deren Charts für verschiedene Umgebungen zu parametrisieren und die Reihenfolge ihres Deployments festzulegen.
Über Helmfile und Beispiele für seine Nutzung kann man im und .
lesen. Wir werden uns mit weniger offensichtlichen Wegen beschäftigen, Releases in Helmfile zu beschreiben.
Angenommen, wir haben eine Sammlung von Helm-Charts (zum Beispiel PostgreSQL und eine Backend-Anwendung) und mehrere Umgebungen (einige Kubernetes-Cluster, mehrere Namespaces oder beides). Wir nehmen Helmfile, lesen die Dokumentation, und beginnen, unsere Umgebungen und Releases zu beschreiben:
.
├── envs
│ ├── devel
│ │ └── values
│ │ ├── backend.yaml
│ │ └── postgres.yaml
│ └── production
│ └── values
│ ├── backend.yaml
│ └── postgres.yaml
└── helmfile.yamlhelmfile.yaml
environments:
devel:
production:
releases:
- name: postgres
labels:
app: postgres
wait: true
chart: stable/postgresql
version: 8.4.0
values:
- envs/{{ .Environment.Name }}/values/postgres.yaml
- name: backend
labels:
app: backend
wait: true
chart: private-helm-repo/backend
version: 1.0.5
needs:
- postgres
values:
- envs/{{ .Environment.Name }}/values/backend.yamlWir haben 2 Umgebungen erhalten: devel, Produktion — in jeder befinden sich ihre eigenen Werte für die Helm-Chart-Releases. Wir werden sie so deployen:
helmfile -n -e applyUnterschiedliche Versionen von Helm-Charts in unterschiedlichen Umgebungen
Was tun, wenn wir verschiedene Versionen des Backends in unterschiedlichen Umgebungen ausrollen müssen? Wie parametrisieren wir die Version des Releases? Dazu kommen Umgebungswerte ins Spiel, die über {{ .Values }}
helmfile.yaml
environments:
devel:
+ values:
+ - charts:
+ versions:
+ backend: 1.1.0
production:
+ values:
+ - charts:
+ versions:
+ backend: 1.0.5
...
- name: backend
labels:
app: backend
wait: true
chart: private-helm-repo/backend
- version: 1.0.5
+ version: {{ .Values.charts.versions.backend }}
...Verschiedene Anwendungssets in unterschiedlichen Umgebungen
Das ist gut, aber was ist, wenn wir PostgreSQL nicht ausrollen wollen, weil wir wissen, dass wir keine Datenbank in k8s stecken müssen und für die Produktion einen hervorragenden separaten PostgreSQL-Cluster haben? Um dieses Problem zu lösen, haben wir Labels (labels) Produktion helmfile -n -e devel apply helmfile -n -e production -l app=backend apply
helmfile -n <namespace> -e devel apply
helmfile -n <namespace> -e production -l app=backend applyDas ist großartig, aber persönlich bevorzuge ich es, zu beschreiben, welche Anwendungen in der Umgebung bereitgestellt werden sollen, nicht durch Startargumente, sondern durch die Beschreibung der Umgebungen selbst. Was kann man tun? Man könnte die Versionsbeschreibungen in einen separaten Ordner legen, in der Umgebungsliste die benötigten Versionen aufführen und nur die benötigten Versionen "anschließen", während die anderen ignoriert werden.
.
├── envs
│ ├── devel
│ │ └── values
│ │ ├── backend.yaml
│ │ └── postgres.yaml
│ └── production
│ └── values
│ ├── backend.yaml
│ └── postgres.yaml
+ ├── releases
+ │ ├── backend.yaml
+ │ └── postgres.yaml
└── helmfile.yaml
helmfile.yaml
environments:
devel:
values:
- charts:
versions:
backend: 1.1.0
- apps:
- postgres
- backend
production:
values:
- charts:
versions:
backend: 1.0.5
- apps:
- backend
- releases:
- - name: postgres
- labels:
- app: postgres
- wait: true
- chart: stable/postgresql
- version: 8.4.0
- values:
- - envs/{{ .Environment.Name }}/values/postgres.yaml
- - name: backend
- labels:
- app: backend
- wait: true
- chart: private-helm-repo/backend
- version: {{ .Values.charts.versions.backend }}
- needs:
- - postgres
- values:
- - envs/{{ .Environment.Name }}/values/backend.yaml
+ ---
+ bases:
+ {{- range .Values.apps }}
+ - releases/{{ . }}.yaml
+ {{- end }}releases/postgres.yaml
releases:
- name: postgres
labels:
app: postgres
wait: true
chart: stable/postgresql
version: 8.4.0
values:
- envs/{{ .Environment.Name }}/values/postgres.yamlreleases/backend.yaml
releases:
- name: backend
labels:
app: backend
wait: true
chart: private-helm-repo/backend
version: {{ .Values.charts.versions.backend }}
needs:
- postgres
values:
- envs/{{ .Environment.Name }}/values/backend.yamlNotiz
Bei der Nutzung eines bases: es ist zwingend erforderlich, den YAML-Trenner zu verwenden ---, um die Releases (und andere Teile wie helmDefaults) mit Werten aus environments zu templatisieren.
In diesem Fall kommt das Release postgres nicht einmal in die Beschreibung für production. Sehr praktisch!
Globale überschreibbare Werte für Releases
Natürlich ist es großartig, dass man für jede Umgebung Werte für Helm-Charts festlegen kann, aber was ist, wenn wir mehrere Umgebungen beschrieben haben und wir beispielsweise für alle eine identische festlegen möchten Affinity, aber wir nicht möchten, dass sie standardmäßig in den Charts, die in Repos gespeichert sind, konfiguriert ist.
In diesem Fall könnten wir für jedes Release zwei Dateien mit Werten festlegen: eine mit Standardwerten, die die Werte des Charts definieren, und eine zweite mit Werten für die Umgebung, die wiederum die Standardwerte überschreiben wird.
.
├── envs
+ │ ├── default
+ │ │ └── values
+ │ │ ├── backend.yaml
+ │ │ └── postgres.yaml
│ ├── devel
│ │ └── values
│ │ ├── backend.yaml
│ │ └── postgres.yaml
│ └── production
│ └── values
│ ├── backend.yaml
│ └── postgres.yaml
├── releases
│ ├── backend.yaml
│ └── postgres.yaml
└── helmfile.yamlreleases/backend.yaml
releases:
- name: backend
labels:
app: backend
wait: true
chart: private-helm-repo/backend
version: {{ .Values.charts.versions.backend }}
needs:
- postgres
values:
+ - envs/default/values/backend.yaml
- envs/{{ .Environment.Name }}/values/backend.yamlenvs/default/values/backend.yaml
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- backend
topologyKey: "kubernetes.io/hostname"Definition globaler Werte für Helm-Charts aller Releases auf Umgebungslevel
Angenommen, wir erstellen in mehreren Releases mehrere Ingress, könnten wir für jedes Chart manuell festlegen hosts:, aber in unserem Fall ist die Domain die gleiche, warum sollten wir sie nicht in eine globale Variable auslagern und einfach ihren Wert in die Charts einsetzen? Damit müssen die Werte-Dateien, die wir parametrisieren wollen, die Erweiterung .gotmpl, damit Helmfile weiß, dass sie durch den Template-Processor geleitet werden müssen.
.
├── envs
│ ├── default
│ │ └── values
- │ │ ├── backend.yaml
- │ │ ├── postgres.yaml
+ │ │ ├── backend.yaml.gotmpl
+ │ │ └── postgres.yaml.gotmpl
│ ├── devel
│ │ └── values
│ │ ├── backend.yaml
│ │ └── postgres.yaml
│ └── production
│ └── values
│ ├── backend.yaml
│ └── postgres.yaml
├── releases
│ ├── backend.yaml
│ └── postgres.yaml
└── helmfile.yamlhelmfile.yaml
environments:
devel:
values:
- charts:
versions:
backend: 1.1.0
- apps:
- postgres
- backend
+ - global:
+ ingressDomain: k8s.devel.domain
production:
values:
- charts:
versions:
backend: 1.0.5
- apps:
- backend
+ - global:
+ ingressDomain: production.domain
---
bases:
{{- range .Values.apps }}
- releases/{{ . }}.yaml
{{- end }}envs/default/values/backend.yaml.gotmpl
ingress:
enabled: true
paths:
- /api
hosts:
- {{ .Values.global.ingressDomain }}envs/default/values/postgres.yaml.gotmpl
ingress:
enabled: true
paths:
- /
hosts:
- postgres.{{ .Values.global.ingressDomain }}Notiz
Offensichtlich ist der Ingress im Postgres-Chart äußerst fragwürdig, weshalb er in diesem Artikel nur als theoretisches Beispiel im Vakuum angeführt wird, damit kein neuer Release nur zur Beschreibung des Ingress erstellt wird.
Das Einfügen von Geheimnissen (secrets) aus Umgebungsvariablen
Analog zu dem oben genannten Beispiel können auch verschlüsselte Werte eingefügt werden. Anstatt für jede Version eine eigene Sekretdatei zu erstellen, in der die verschlüsselten Werte für das Chart definiert werden, können wir einfach im Release-Standard-default.yaml.gotmpl Werte festlegen, die aus Variablen auf Ebene der Umgebungen entnommen werden. Werte, die wir nicht geheim halten müssen, können dann problemlos in den Release-Werten für die jeweilige Umgebung überschrieben werden.
.
├── envs
│ ├── default
│ │ └── values
│ │ ├── backend.yaml
│ │ └── postgres.yaml
│ ├── devel
│ │ ├── values
│ │ │ ├── backend.yaml
│ │ │ └── postgres.yaml
+ │ │ └── secrets.yaml
│ └── production
│ ├── values
│ │ ├── backend.yaml
│ │ └── postgres.yaml
+ │ └── secrets.yaml
├── releases
│ ├── backend.yaml
│ └── postgres.yaml
└── helmfile.yamlhelmfile.yaml
environments:
devel:
values:
- charts:
versions:
backend: 1.1.0
- apps:
- postgres
- backend
- global:
ingressDomain: k8s.devel.domain
+ secrets:
+ - envs/devel/secrets.yaml
production:
values:
- charts:
versions:
backend: 1.0.5
- apps:
- backend
- global:
ingressDomain: production.domain
+ secrets:
+ - envs/production/secrets.yaml
---
bases:
{{- range .Values.apps }}
- releases/{{ . }}.yaml
{{- end }}envs/devel/secrets.yaml
secrets:
elastic:
password: ENC[AES256_GCM,data:hjCB,iv:Z1P6/6xBJgJoKLJ0UUVfqZ80o4L84jvZfM+uH9gBelc=,tag:dGqQlCZnLdRAGoJSj63rBQ==,type:int]
...envs/production/secrets.yaml
secrets:
elastic:
password: ENC[AES256_GCM,data:ZB/VpTFk8f0=,iv:EA//oT1Cb5wNFigTDOz3nA80qD9UwTjK5cpUwLnEXjs=,tag:hMdIUaqLRA8zuFBd82bz6A==,type:str]
...envs/default/values/backend.yaml.gotmpl
elasticsearch:
host: elasticsearch
port: 9200
password: {{ .Values | getOrNil "secrets.elastic.password" | default "password" }}envs/devel/values/backend.yaml
elasticsearch:
host: elastic-0.devel.domainenvs/production/values/backend.yaml
elasticsearch:
host: elastic-0.production.domainNotiz
Übrigens, getOrNil — eine spezielle Funktion für Go-Templates in helmfile, die selbst wenn .Values.secrets nicht existiert, keine Fehler auslöst, sondern es ermöglicht, mithilfe der Funktion default einen Standardwert einzufügen.
Fazit
Die beschriebenen Dinge scheinen recht offensichtlich zu sein, aber die Informationen über eine benutzerfreundliche Beschreibung des Deployments in mehreren Umgebungen mit helmfile sind sehr spärlich, und ich liebe IaC (Infrastructure-as-Code) und möchte eine klare Beschreibung des Deployment-Status haben.
Zusammenfassend möchte ich hinzufügen, dass die Variablen für die Standardumgebung wiederum durch Umgebungsvariablen des Betriebssystems eines bestimmten Runners, von dem das Deployment gestartet wird, parametriert werden können, um so dynamische Umgebungen zu erhalten.
helmfile.yaml
environments:
default:
values:
- global:
clusterDomain: {{ env "CLUSTER_DOMAIN" | default "cluster.local" }}
ingressDomain: {{ env "INGRESS_DOMAIN" }}Quelle: habr.com
