Organizarea desfășurării în mai multe medii k8s cu ajutorul helmfile

Helmfile — un wrapper pentru helm, care permite descrierea într-un singur loc a mai multor lansări helm, parametrizarea diagramelor pentru diferite medii și stabilirea ordinii de desfășurare a acestora.

Despre helmfile și exemplele sale de utilizare puteți citi în readme și ghidul celor mai bune practici.

Ne vom familiariza cu metodele mai puțin evidente de a descrie lansările în helmfile.

Să presupunem că avem o serie de diagrame helm (în acest exemplu, fie postgres și o aplicație backend) și mai multe medii (mai multe clustere Kubernetes, mai multe spații de nume sau atât unul, cât și altul). Luăm helmfile, citim documentația și începem să descriem mediile și lansările noastre:

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

Am obținut 2 medii: devel, producție — în fiecare se află valorile proprii pentru diagramele helm ale lansărilor. Le vom desfășura astfel:

helmfile -n  -e  apply

Versiuni diferite ale diagramelor helm în diferite medii

Ce facem dacă trebuie să desfășurăm versiuni diferite ale backendului în medii diferite? Cum parametrizăm versiunea lansării? Valorile mediului, accesibile prin {{ .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 }}
...

Un set diferit de aplicații în diferite medii

Excelent, dar ce facem dacă nu trebuie să producție desfășurăm postgres, pentru că știm că nu trebuie să punem baza de date în k8s și pentru produs avem un cluster postgres separat minunat? Pentru a rezolva această problemă avem etichete (labels)

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

Este minunat, dar personal prefer să descriu ce aplicații să desfășor în mediu nu prin argumente de lansare, ci în descrierea în sine a mediilor. Ce să fac? Pot plasa descrierile versiunilor într-un folder separat, iar în descrierea mediului să am o listă cu versiunile necesare și să "atașez" doar versiunile dorite, ignorând pe celelalte.

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

Notă

Când folosești bases: este necesar să se utilizeze separatorul yaml ---, pentru a putea șabloniza versiunile (și celelalte părți, cum ar fi helmDefaults) cu valori din medii

În acest caz, versiunea postgres nici măcar nu va ajunge în descrierea pentru producție. Foarte convenabil!

Valorile globale care pot fi suprascrise pentru versiuni

Desigur, e minunat că pentru fiecare mediu putem stabili valori pentru chart-uri helm, dar ce facem dacă avem descrise mai multe medii și vrem, de exemplu, să stabilim aceeași valoare pentru toate affinity, dar nu dorim să o configurăm implicit în chart-urile în sine, care sunt stocate în repouri.

În acest caz, am putea stabili pentru fiecare versiune 2 fișiere cu valori: primul cu valori implicite, care vor determina valorile chart-ului, iar al doilea cu valori pentru mediu, care la rândul său va suprascrie valorile implicite.

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

Definirea valorilor globale pentru graficele helm pentru toate lansările la nivelul mediului

Să presupunem că avem mai multe ingress-uri create în mai multe lansări — am putea defini manual pentru fiecare grafic în parte hosts:, dar în cazul nostru domeniul este același, așa că de ce nu l-am scoate într-o variabilă globală și am folosi doar valoarea sa în grafice? Pentru aceasta, fișierele cu valori pe care dorim să le parametrizăm trebuie să aibă extensia .gotmpl, astfel încât helmfile să știe că trebuie să fie procesat prin motorul de șabloane.

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

Notă

Este evident că ingress în chartul postgres este un lucru extrem de discutabil, de aceea în articol este adus doar ca un exemplu sferic în vid și pentru a nu introduce în articol o nouă versiune doar pentru a descrie ingress.

Înlocuirea secretelor (secrets) din valorile de mediu.

În mod similar cu exemplul de mai sus, putem adăuga și valorile criptate folosind helm secrets. În loc să creăm un fișier secrets pentru fiecare versiune, în care să definim valorile criptate pentru chart, putem doar să definim în default.yaml.gotmpl valorile care vor proveni din variabilele specificate la nivelul mediu. Iar valorile pe care nu trebuie să le ascundem de nimeni pot fi deja redefinite în valorile versiunii din mediu specific.

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

Notă

Apropo, getOrNil — o funcție specială pentru template-urile Go în helmfile, care, chiar dacă .Values.secrets nu va exista, nu va genera o eroare, ci va permite în rezultat, folosind funcția default să înlocuim cu o valoare implicită.

Concluzie

Lucrurile descrise par destul de evidente, însă informațiile despre modul convenabil de a descrie desfășurarea în mai multe medii folosind helmfile sunt foarte rare, iar eu iubesc IaC (Infrastructure-as-Code) și doresc să am o descriere clară a stării desfășurării.

În concluzie, vreau să adaug că variabilele pentru mediu default pot fi, la rândul lor, parametrizate cu variabile de mediu ale sistemului de operare al unui anumit runner, de pe care va fi lansată desfășurarea, obținând astfel medii dinamice.

helmfile.yaml

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

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster