Organisation des Deployments in mehrere k8s-Umgebungen mit helmfile

Helmfile — eine Wrapper fĂŒr helm, 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 readme und best practices guide.

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

helmfile.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.yaml

Wir 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  apply

Unterschiedliche 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  -e devel apply
helmfile -n  -e production -l app=backend apply

Das 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.yaml

releases/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.yaml

Notiz

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

releases/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.yaml

envs/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.yaml

helmfile.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. helm secrets 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.yaml

helmfile.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.domain

envs/production/values/backend.yaml

elasticsearch:
  host: elastic-0.production.domain

Notiz

Ü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

ZuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz kaufen, VPS VDS Server đŸ”„ ZuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz kaufen, VPS VDS Server - ProHoster