Организация на деплоя в множество 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
    етикети:
      приложение: postgres
    чакай: true
    чарт: stable/postgresql
    версия: 8.4.0
    стойности:
      - envs/{{ .Environment.Name }}/values/postgres.yaml
  - име: backend
    етикети:
      приложение: backend
    чакай: true
    чарт: private-helm-repo/backend
    версия: 1.0.5
    нужда:
      - postgres
    стойности:
      - envs/{{ .Environment.Name }}/values/backend.yaml

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

helmfile -n  -e  apply

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

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

helmfile.yaml

среди:
  devel:
+   стойности:
+   - чартове:
+       версии:
+         backend: 1.1.0
  production:
+   стойности:
+   - чартове:
+       версии:
+         backend: 1.0.5
...
  - име: backend
    етикети:
      приложение: backend
    чакай: true
    чарт: private-helm-repo/backend
-   версия: 1.0.5
+   версия: {{ .Values.charts.versions.backend }}
...

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

Чудесно, но какво ако не ни трябва да production внедряваме 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 файла с стойности: един с дефолтни стойности, които ще определят стойностите на самия чарт, и втори със стойности за средата, който от своя страна вече ще препокрива дефолтните.

    .
    ├── 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 не съществуват, не ще предизвика грешка, а ще позволи в резултат с помощта на функция default да подменим стойност по подразбиране.

Заключение

Описаните неща изглеждат доста очевидни, но информацията за удобно описание на деплоя в няколко среди с помощта на 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