Organizzazione del deployment in molteplici ambienti k8s utilizzando helmfile

Helmfile β€” un wrapper per helm, che consente di descrivere in un luogo centrale molteplici rilasci di helm, parametrizzare i loro chart per diversi ambienti e stabilire l’ordine del loro deployment.

Puoi leggere di helmfile e degli esempi del suo utilizzo nella readme e guida alle best practices.

Noi ci concentreremo su modi non ovvi per descrivere i rilasci in helmfile.

Supponiamo di avere un insieme di helm chart (per esempio, postgres e una certa applicazione backend) e diversi ambienti (diversi cluster Kubernetes, diversi namespace o entrambi). Prendiamo helmfile, leggiamo la documentazione e iniziamo a descrivere i nostri ambienti e i nostri rilasci:

    .
    β”œβ”€β”€ 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

Abbiamo creato 2 ambienti: devel, production β€” ciascuno contiene i propri valori per i rilasci dei chart helm. Li deployeremo in questo modo:

helmfile -n  -e  apply

Diverse versioni dei chart helm in diversi ambienti

Cosa fare se dobbiamo rilasciare diverse versioni del backend in diversi ambienti? Come parametrizzare la versione del rilascio? In nostro aiuto vengono i valori dell'ambiente, accessibili tramite {{ .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 }}
...

Diverso set di applicazioni in diversi ambienti

Ottimo, ma e se non ci serve rilasciare production postgres, perchΓ© sappiamo che non dobbiamo inserire un database in k8s e per la produzione abbiamo un meraviglioso cluster postgres separato? Per risolvere questo problema abbiamo etichette (labels)

helmfile -n  -e devel apply
helmfile -n  -e production -l app=backend apply

È fantastico, ma personalmente preferisco descrivere quali applicazioni distribuire in un ambiente non attraverso gli argomenti di avvio, ma nella descrizione dei singoli ambienti. Cosa fare? Si può inserire la descrizione delle release in una cartella separata, nel descrizione dell'ambiente si può creare un elenco delle release necessarie e "collegare" solo quelle necessarie, ignorando le altre.

    .
    β”œβ”€β”€ 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

Nota

Quando utilizzi bases: Γ¨ necessario utilizzare obbligatoriamente il delimitatore yaml ---, in modo da poter template unc releases (e altre parti, come helmDefaults) con i valori provenienti dagli environments

In questo caso, la release postgres non verrΓ  nemmeno inclusa nella descrizione per la produzione. Molto comodo!

Valori globali sovrascrivibili per le release

Certo, Γ¨ fantastico che si possano impostare valori diversi per i chart helm di ogni ambiente, ma cosa succede se abbiamo diversi ambienti e vogliamo, ad esempio, impostare lo stesso valore per tutti affinity, ma non vogliamo configurarlo per impostazione predefinita nei chart stessi, che sono archiviati nei repository.

In questo caso, potremmo per ogni release specificare 2 file con valori: il primo con i valori di default, che determineranno i valori del chart stesso, e il secondo con i valori per l'ambiente, che a sua volta sovrascriverΓ  i default.

    .
    β”œβ”€β”€ 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"

Definizione dei valori globali per i chart helm di tutti i rilasci a livello di ambiente

Supponiamo di avere piΓΉ ingress in diversi rilasci β€” potremmo definire manualmente per ciascun chart hosts:, ma nel nostro caso il dominio Γ¨ lo stesso e quindi perchΓ© non estrarlo in una variabile globale e semplicemente usare il suo valore nei chart? A tal fine, i file con i valori che vogliamo parametrizzare dovranno avere estensione .gotmpl, in modo che helmfile sappia che deve passarli attraverso il templater.

    .
    β”œβ”€β”€ 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 }}

Nota

È evidente che l'ingress nel chart di postgres sia qualcosa di estremamente discutibile, quindi nell'articolo è riportato semplicemente come esempio sferico nel vuoto e per non introdurre una nuova release solo per descrivere l'ingress.

Sostituzione dei segreti (secrets) dai valori di ambiente.

Analogamente all'esempio sopra, Γ¨ possibile sostituire anche i valori cifrati attraverso il helm secrets . Invece di creare un file secrets per ogni release in cui definire i valori cifrati per il chart, possiamo semplicemente definire nel file default.yaml.gotmpl della release i valori che verranno presi dalle variabili definite a livello di ambiente. E i valori che non dobbiamo tenere nascosti possono essere sovrascritti tranquillamente nei valori della release in un ambiente specifico.

    .
    β”œβ”€β”€ 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

Nota

A proposito, getOrNil β€” una funzione speciale per i modelli go in helmfile, che, anche se .Values.secrets non esiste, non genera un errore, ma consente alla fine di utilizzare la funzione default per inserire il valore di default.

Conclusione

Le cose descritte sembrano piuttosto ovvie, ma le informazioni su come descrivere comodamente il deployment in diversi ambienti usando helmfile sono piuttosto scarse, e io amo IaC (Infrastructure-as-Code) e voglio avere una chiara descrizione dello stato del deployment.

In conclusione, voglio aggiungere che le variabili per l'ambiente predefinito possono a loro volta essere parametrizzate tramite le variabili di ambiente del sistema operativo di un runner da cui verrΓ  eseguito il deployment, ottenendo in questo modo ambienti dinamici.

helmfile.yaml

environments:
  default:
    values:
    - global:
        clusterDomain: {{ env "CLUSTER_DOMAIN" | default "cluster.local" }}
        ingressDomain: {{ env "INGRESS_DOMAIN" }}

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server πŸ”₯ Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster