Organisering av deployment i flera k8s-miljöer med hjÀlp av helmfile

Helmfile — en wrapper för helm, som gör det möjligt att beskriva flera helm-releaser pĂ„ ett stĂ€lle, parametrera deras diagram för flera miljöer samt ange ordningen för deras distribution.

Man kan lÀsa om helmfile och exempel pÄ dess anvÀndning i readme och best practices guide.

Vi kommer att bekanta oss med icke-uppenbara sÀtt att beskriva releaser i helmfile.

Anta att vi har en grupp helm-diagram (till exempel postgres och en viss backend-applikation) och flera miljöer (flera kubernetes-kluster, flera namespace, eller bÄde och). Vi tar helmfile, lÀser dokumentationen och börjar beskriva vÄra miljöer och releaser:

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

Vi har fĂ„tt 2 miljöer: devel, production — i varje finns sina egna vĂ€rden för helm-diagram av releaser. Vi kommer att distribuera i dem sĂ„ hĂ€r:

helmfile -n  -e  apply

Olika versioner av helm-diagram i olika miljöer.

Vad gör vi om vi behöver rulla ut olika versioner av backend i olika miljöer? Hur parametriserar vi versionen av releasen? HÀr kommer vÀrden frÄn miljön till hjÀlp, som Àr tillgÀngliga genom {{ .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 }}
...

Olika uppsÀttningar av applikationer i olika miljöer.

Bra, men vad hÀnder om vi inte behöver rulla ut postgres, eftersom vi vet att vi inte ska ha en databas i k8s och för produktion har vi ett utmÀrkt separat postgres-kluster? För att lösa detta problem har vi etiketter (labels). production 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

Det Àr bra, men personligen skulle jag föredra att beskriva vilka applikationer som ska dras upp i miljön, inte med hjÀlp av startargument, utan i beskrivningen av sjÀlva miljöerna. Vad ska man göra? Man kan placera releasebeskrivningarna i en separat mapp, i miljöbeskrivningen skapa en lista över nödvÀndiga releaser och "koppla" bara de relevanta releaserna genom att ignorera de andra.

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

Anteckning

NÀr du anvÀnder bases: det Àr nödvÀndigt att anvÀnda yaml-separatorn ---, sÄ att man kan mallifiera releases (och andra delar, som helmDefaults) med vÀrden frÄn environments

I sÄ fall kommer release postgres inte ens att ingÄ i beskrivningen för produktion. Mycket praktiskt!

Överskrivbara globala vĂ€rden för releaser

SjÀlvklart Àr det bra att man kan stÀlla in vÀrden för helm-charts för varje miljö, men vad hÀnder om vi har beskrivningar för flera miljöer och vi vill, till exempel, sÀtta samma vÀrde för alla affinity, men inte vill konfigurera det som standard i sjÀlva chartsen som förvaras i repos.

I sÄ fall skulle vi kunna stÀlla in tvÄ filer med values för varje release: en med standardvÀrden som definierar vÀrdena för sjÀlva chartet, och den andra med vÀrden för miljön, som i sin tur kommer att ÄsidosÀtta standardvÀrdena.

    .
    ├── 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 av globala vÀrden för helm-diagram för alla versioner pÄ miljönivÄ

Anta att vi har flera ingÄngar i flera versioner - vi skulle kunna definiera dem manuellt för varje diagram hosts:, men i vÄrt fall Àr domÀnen densamma, sÄ varför inte extrahera den till en global variabel och bara sÀtta in dess vÀrde i diagrammen? För detta mÄste de vÀrdefiler vi vill parametriseras ha tillÀgget .gotmpl, sÄ att helmfile vet att den ska köra den genom mallmotorn.

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

Anteckning

Det Àr uppenbart att ingress i Postgres-diagrammet Àr nÄgot mycket tvivelaktigt, sÄ det tas med i artikeln bara som ett sfÀriskt exempel i vakuum, för att undvika att introducera en ny release bara för att beskriva ingress.

InsÀttning av hemligheter (secrets) frÄn miljövariabler.

PÄ liknande sÀtt som ovan kan man ocksÄ införa krypterade med hjÀlp av helm secrets vÀrden. IstÀllet för att skapa en egen fil för varje release dÀr man definierar krypterade vÀrden för diagrammet, kan vi helt enkelt definiera vÀrden i den release-specifika default.yaml.gotmpl som hÀmtas frÄn variabler som anges pÄ miljönivÄ. VÀrden som vi inte behöver dölja för nÄgon kan redan överstyra i release-vÀrdena för specifik miljö.

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

Anteckning

Förresten, getOrNil — en sĂ€rskild funktion för Go-mallar i helmfile, som, Ă€ven om .Values.secrets inte finns, inte genererar ett fel, utan tillĂ„ter att anvĂ€nda funktionen default för att införa ett standardvĂ€rde.

Slutsats

De beskrivna sakerna verkar ganska uppenbara, men information om att enkelt beskriva deployment i flera miljöer med hjÀlp av helmfile Àr mycket begrÀnsad, och jag Àlskar IaC (Infrastructure-as-Code) och vill ha en tydlig beskrivning av deploymentens tillstÄnd.

Sammanfattningsvis vill jag lÀgga till att variabler för standardmiljön i sin tur kan parameteriseras med variabler frÄn operativsystemets miljö för en viss runner, frÄn vilken deployment kommer att startas, och pÄ sÄ sÀtt fÄ dynamiska miljöer.

helmfile.yaml

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

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster