Organizacija implementacije v več k8s okolij s pomočjo helmfile

Helmfile — ovitka za helm, ki omogoča opisovanje številnih helm izdaj na enem mestu, parametrize njihove karte za več okolij ter določanje reda njihovega prenosa.

O helmfile in primerih njegove uporabe lahko preberete v readme in najboljših praksah.

Seznanili se bomo z manj očitnimi načini za opisovanje izdaj v helmfile

Recimo, da imamo paket helm-kart (vzemimo za primer postgres in neko aplikacijo za backend) ter več okolij (več kubernetes grozdov, več namespacingov ali oboje). Vzamemo helmfile, preberemo dokumentacijo in začnemo opisovati naša okolja in izdaj:

    .
    ├── envs
    │   ├── devel
    │   │   └── values
    │   │       ├── backend.yaml
    │   │       └── postgres.yaml
    │   └── production
    │       └── values
    │           ├── backend.yaml
    │           └── postgres.yaml
    └── helmfile.yaml

helmfile.yaml

okolja:
  devel:
  production:

izdaje:
  - ime: postgres
    oznake:
      app: postgres
    počakaj: true
    chart: stable/postgresql
    različica: 8.4.0
    vrednosti:
      - envs/{{ .Environment.Name }}/values/postgres.yaml
  - ime: backend
    oznake:
      app: backend
    počakaj: true
    chart: private-helm-repo/backend
    različica: 1.0.5
    potrebuje:
      - postgres
    vrednosti:
      - envs/{{ .Environment.Name }}/values/backend.yaml

Imamo 2 okolji: devel, production — v vsakem so svoje vrednosti za helm karte izdaj. Uporabljali bomo naslednji postopek za prenos:

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

Različne različice helm kart v različnih okoljih

Kaj storiti, če moramo zagotoviti različne različice backend-a v različnih okoljih? Kako parametrizirati različico izdaje? Na pomoč pridejo vrednosti okolja, dostopne preko {{ .Values }}

helmfile.yaml

okolja:
  devel:
+   vrednosti:
+   - karte:
+       različice:
+         backend: 1.1.0
  production:
+   vrednosti:
+   - karte:
+       različice:
+         backend: 1.0.5
...
  - ime: backend
    oznake:
      app: backend
    počakaj: true
    chart: private-helm-repo/backend
-   različica: 1.0.5
+   različica: {{ .Values.charts.versions.backend }}
...

Različni nabori aplikacij v različnih okoljih

Odlično, vendar kaj, če nam ni treba production zagotoviti postgres, ker vemo, da podatkovne baze ne smemo stlačiti v k8s in da imamo za produkcijo čudovit ločen grozd postgres? Za rešitev te težave imamo oznake (labels)

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

To je odlično, vendar osebno raje opisujem, katere aplikacije naj se postavijo v okolju, ne z uporabo zagonskih argumentov, ampak v opisu samih okolij. Kaj storiti? Opis izdaj lahko postavimo v posebno mapo, v opisu okolja pa ustvarimo seznam potrebnih izdaj in 'priključimo' samo potrebne izdaje, vse ostale pa ignoriramo.

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

Opomba

Pri uporabi bases: je nujno treba uporabiti yaml ločilo ---, da bi lahko šablonizirali releases (in druge dele, kot so helmDefaults) z vrednostmi iz environments

V tem primeru izdajanje postgres ne bo prišlo niti v opis za produkcijo. To je zelo priročno!

Preseženi globalni vrednosti za izdaje

Seveda je dobro, da lahko za vsako okolje nastavite vrednosti za helm karte, vendar kaj, če imamo opisana več okolij in želimo na primer nastaviti enake vrednosti za vse affinity, ne želimo pa jih nastaviti privzeto v samih kartah, ki so shranjene v repih.

V tem primeru bi lahko za vsako izdajo nastavili 2 datoteki z vrednostmi: prva z privzetimi vrednostmi, ki bi določile vrednosti same karte, in druga z vrednostmi za okolje, ki bi nato prepisala privzete.

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

Določitev globalnih vrednosti za helm sheme vseh izdaj na ravni okolja

Recimo, da imamo v več izdajah ustvarjene več ingress — za vsak chart bi lahko ročno določili hosts:, vendar imamo v našem primeru isti domen, zakaj ga ne bi spravili v neko globalno spremenljivko in preprosto vstavili njeno vrednost v sheme? Za to morajo imeti tisti datoteki z vrednostmi, ki jih želimo parametrizirati, končnico .gotmpl, da bo helmfile vedel, da jih je treba podati skozi predlogo.

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

Opomba

Očitno je, da je ingress v chartu postgres nekaj zelo dvomljivega, zato je v članku navedeno le kot sferični primer v vakuumu in za to, da ne bi uvajali v članek kakšne nove različice samo zaradi opisovanja ingress.

Vstavljanje skrivnosti (secrets) iz vrednosti okolja.

Po analogy s prej omenjenim primerom lahko vstavimo tudi šifrirane z uporabo. helm secrets vrednosti. Namesto da za vsak izdajo ustvarimo svoj datoteko secrets, v kateri določimo za chart šifrirane vrednosti, lahko preprosto določimo v izdajnem default.yaml.gotmpl vrednosti, ki bodo prevzete iz spremenljivk, določenih na ravni okolij. Vrednosti, ki jih ne potrebujemo skrbeti pred nikomer, lahko že mirno prekoračimo v vrednostih izdaje v konkretnem okolju.

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

Opomba

Mimogrede, getOrNil — posebna funkcija za go predloge v helmfile, ki, tudi če .Values.secrets ne bo obstajala, ne bo povzročila napake, ampak bo omogočila z uporabo funkcije default vstaviti privzeto vrednost.

Zaključek

Opišane stvari se zdijo precej očitne, a informacije o enostavnem opisu namestitve v več okoljih z uporabo helmfile so zelo skope, jaz pa imam rad IaC (Infrastructure-as-Code) in želim imeti jasno opisano stanje namestitve.

Na koncu želim dodati, da lahko spremenljivke za okolje default parameteriziramo s spremenljivkami okolja operacijskega sistema nekega runnerja, s katerega se bo izvajala namestitev, in tako pridobimo dinamična okolja.

helmfile.yaml

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

Izvor: habr.com

Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki 🔥 Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki | ProHoster