Организация на деплоя в множество k8s среди с помощта на helmfile

Helmfile — обвивка для helm, която позволява в едно място да описвате множество helm релизи, да параметризирайте техните чарти за няколко среди и също да зададете реда на тяхната деплойка.

За самото helmfile и примери за неговото използване можете да прочетете в readme и ръководство за добри практики.

Ние ще разгледаме неочевидни начини да опишем релизите в helmfile

Да предположим, че имаме пакет helm-чартове (например postgres и едно backend приложение) и няколко среди (няколко Kubernetes клъстера, няколко namespace'а или и двете). Вземаме helmfile, четем документацията и започваме да описваме нашите среди и релизи:

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

helmfile.yaml

среди:
  devel:
  production:

релизи:
  - име: postgres
    етикети:
      app: postgres
    изчакай: true
    chart: stable/postgresql
    версия: 8.4.0
    values:
      - envs/{{ .Environment.Name }}/values/postgres.yaml
  - име: backend
    етикети:
      app: backend
    изчакай: true
    chart: private-helm-repo/backend
    версия: 1.0.5
    нуждае се:
      - postgres
    values:
      - envs/{{ .Environment.Name }}/values/backend.yaml

Получихме 2 среди: devel, производството — в тях се намират свої стойности за helm чартовете на релизите. Ние ще деплойваме в тях по следния начин:

helmfile -n  -e  apply

Различни версии на helm чартовете в различни среди

Какво да правим, ако трябва да разпространяваме различни версии на бекенда в различни среди? Как да параметризираме версията на релиза? На помощ идват стойностите на средата, достъпни чрез {{ .Values }}

helmfile.yaml

среди:
  devel:
+   values:
+   - charts:
+       versions:
+         backend: 1.1.0
  production:
+   values:
+   - charts:
+       versions:
+         backend: 1.0.5
...
  - име: backend
    етикети:
      app: backend
    изчакай: true
    chart: private-helm-repo/backend
-   версия: 1.0.5
+   версия: {{ .Values.charts.versions.backend }}
...

Различен набор от приложения в различни среди

Отлично, но какво, ако не искаме да производството разпространяваме postgres, защото знаем, че не трябва да слагаме база данни в k8s и за продакшъна имаме страхотен отделен клъстер postgres? За решаването на този проблем имаме етикети (labels)

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

Това е страхотно, но аз лично предпочитам да описвам какви приложения да развърна в средата не с помощта на аргументи за стартиране, а в описанието на самите среди. Какво да правим? Може да се постави описанието на версиите в отделна папка, в описанието на средата да се заведе списък с нужните версии и да "подключим" само нужните версии, игнорирайки другите.

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

Бележка

При използване на bases: необходимо да се използва yaml разделител ---, за да може да се шаблонизират releases (и другите части, като helmDefaults) със стойности от environments

В такъв случай версиите postgres дори няма да попаднат в описанието на production. Много удобно!

Глобални стойности, които могат да се пренастройват за версиите

Разбира се, добре е, че можем да задаваме стойности за helm чартовете за всяка среда, но какво ако имаме описани няколко среди и искаме, да речем, да зададем идентични стойности за всички affinity, но не искаме да конфигурираме по подразбиране в самите чартове, които се съхраняват в репозиториите.

В такъв случай бихме могли да зададем за всяка версия 2 файла с values: първият с подразбиращи се стойности, които ще определят самите стойности на чарта, а вторият със стойности за средата, който от своя страна ще пренастройва подразбиращите се.

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

Определение на глобални стойности за helm графики на всички версии на етапа.

Да предположим, че в няколко версии създаваме няколко ingress — можехме ръчно да определим за всяка графика. hosts:, но в нашия случай домейнът е един и същ, така че защо да не го изнесем в глобална променлива и просто да подставим стойността му в графиките? За целта файловете с values, които искаме да параметризирани, трябва да имат разширение .gotmpl, за да може helmfile да знае, че трябва да ги премине през шаблонизатор.

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

Бележка

Очевидно, че ingress в чарта postgres е нещо изключително спорно, затова в статията е представено просто като сферален пример в вакуум и за да не се въвежда нов релиз само за описването на ingress.

Подмяна на секрети (secrets) от стойности на околната среда.

По аналогия с горепосочения пример, може да се подмятат и данни, криптирани с помощта на helm secrets. предложения. Вместо да създаваме файл с секрети за всеки релиз, в който да определяме криптирани стойности за чарта, можем просто да зададем стойности в default.yaml.gotmpl, които ще се взимат от променливи, зададени на ниво околни среди. А стойностите, които не трябва да крием от никого, могат да бъдат спокойно презаписани в стойностите на релиза за конкретната среда.

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

Бележка

Между другото, getOrNil — специална функция за go шаблони в helmfile, която, дори ако .Values.secrets не съществува, няма да хвърли грешка, а ще позволи с помощта на функция по подразбиране да се подмени стойността по подразбиране.

Заключение

Описаните неща изглеждат доста очевидни, но информацията за удобно описване на деплой в multiple среди с помощта на helmfile е доста оскъдна, а аз обичам IaC (Infrastructure-as-Code) и искам да имам ясно описание на състоянието на деплоя.

В заключение искам да добавя, че променливите за средата по подразбиране могат да се параметризира с променливи на ОС на конкретен ранър, от който ще се стартира деплой, и по този начин да се получат динамични среди.

helmfile.yaml

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

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster