Organizzazione della distribuzione in più ambienti K8 utilizzando helmfile

Helmfile - involucro per timone, che consente di descrivere molte versioni del timone in un unico posto, parametrizzare i relativi grafici per diversi ambienti e anche impostare l'ordine della loro distribuzione.

Puoi leggere informazioni sull'helmfile stesso ed esempi del suo utilizzo in readme и guida alle migliori pratiche.

Faremo conoscenza con modi non ovvi per descrivere le versioni in helmfile

Supponiamo di avere un pacchetto di grafici helm (ad esempio, postgres e alcune applicazioni backend) e diversi ambienti (diversi cluster Kubernetes, diversi spazi dei nomi o diversi entrambi). Prendiamo l'helmfile, leggiamo la documentazione e iniziamo a descrivere i nostri ambienti e versioni:

    .
    ├── 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

Alla fine ci siamo ritrovati con 2 ambienti: devel, produzione - ciascuno contiene i propri valori per i grafici di rilascio del timone. Li distribuiremo in questo modo:

helmfile -n <namespace> -e <env> apply

Diverse versioni di carte timone in diversi ambienti

Cosa succede se dobbiamo implementare diverse versioni del backend in ambienti diversi? Come parametrizzare la versione di rilascio? I valori ambientali disponibili attraverso {{ .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 }}
...

Diversi set di applicazioni in diversi ambienti

Ottimo, ma cosa succede se non ne abbiamo bisogno? production implementare Postgres, perché sappiamo che non è necessario inserire il database in K8 e in vendita abbiamo un meraviglioso cluster Postgres separato? Per risolvere questo problema abbiamo le etichette

helmfile -n <namespace> -e devel apply
helmfile -n <namespace> -e production -l app=backend apply

Questo è fantastico, ma personalmente preferisco descrivere quali applicazioni distribuire nell'ambiente non utilizzando argomenti di lancio, ma nella descrizione degli ambienti stessi. Cosa fare? Puoi inserire le descrizioni delle versioni in una cartella separata, creare un elenco delle versioni necessarie nella descrizione dell'ambiente e “prelevare” solo le versioni necessarie, ignorando il resto

    .
    ├── 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

Заметка

Quando si utilizza la bases: è necessario utilizzare il separatore yaml ---, in modo da poter creare modelli di release (e altre parti, come helmDefaults) con valori provenienti dagli ambienti

In questo caso la versione postgres non verrà nemmeno inclusa nella descrizione per la produzione. Molto comodamente!

Valori globali sovrascrivibili per le versioni

Naturalmente, è fantastico poter impostare valori per i grafici timone per ciascun ambiente, ma cosa succede se abbiamo descritti diversi ambienti e vogliamo, ad esempio, impostare lo stesso per tutti? affinity, ma non vogliamo configurarlo di default nei grafici stessi, che sono memorizzati in rape.

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

    .
    ├── 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 grafici timone di tutte le versioni a livello di ambiente

Supponiamo di creare diversi ingressi in diverse versioni: potremmo definirli manualmente per ciascun grafico hosts:, ma nel nostro caso il dominio è lo stesso, quindi perché non inserirlo in qualche variabile globale e sostituirne semplicemente il valore nei grafici? Per fare ciò, quei file con valori che vogliamo parametrizzare dovranno avere l'estensione .gotmpl, in modo che helmfile sappia che deve essere eseguito tramite il motore del modello.

    .
    ├── 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 }}

Заметка

Ovviamente, l'ingresso nel grafico Postgres è qualcosa di estremamente dubbio, quindi questo articolo è fornito semplicemente come esempio sferico nel vuoto e per non introdurre qualche nuova versione nell'articolo solo per il gusto di descrivere l'ingresso

Sostituzione dei segreti con i valori dell'ambiente

Per analogia con l'esempio precedente, puoi sostituire quelli crittografati utilizzando segreti del timone significati. Invece di creare il nostro file segreto per ogni versione, in cui possiamo definire valori crittografati per il grafico, possiamo semplicemente definire nella versione default.yaml.gotmpl i valori che verranno presi dalle variabili definite in livello ambientale. E i valori che non dobbiamo nascondere a nessuno possono essere facilmente ridefiniti nei valori di rilascio 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

Заметка

Tra l'altro, getOrNil - una funzione speciale per i modelli Go in helmfile, che, anche se .Values.secrets non esisterà, non genererà un errore, ma consentirà il risultato utilizzando la funzione default sostituire il valore predefinito

conclusione

Le cose descritte sembrano abbastanza ovvie, ma le informazioni su una comoda descrizione della distribuzione in diversi ambienti utilizzando helmfile sono molto scarse e adoro IaC (Infrastructure-as-Code) e desidero avere una descrizione chiara dello stato di distribuzione.

In conclusione vorrei aggiungere che le variabili per l'ambiente di default potranno a loro volta essere parametrizzate con le variabili d'ambiente dell'OS di un determinato runner da cui verrà lanciato il deploy, ed ottenere così ambienti dinamici

helmfile.yaml

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

Fonte: habr.com

Aggiungi un commento