Арганізацыя дэплою ў мноства k8s асяродкаў з дапамогай helmfile

Helmfile - Абгортка для рулявое кола, якая дазваляе ў адным месцы апісваць мноства helm рэлізаў, параметрызаваць іх чарты для некалькіх акружэнняў, а таксама задаваць парадак іх дэплою.

Аб самім helmfile і прыкладах яго выкарыстання можна пачытаць у ридми и кіраўніцтва па перадавым вопыце.

Мы ж пазнаёмімся з невідавочным спосабам апісаць рэлізы ў helmfile

Дапушчальны, у нас есць пачак helm-чартаў (для прыкладу хай будзе postgres і нейкае backend прыкладанне) і некалькі акружэнняў (некалькі kubernetes кластараў, некалькі namespace'аў ці некалькі і таго, і іншага). Бярэм helmfile, чытаем дакументацыю і пачынаем апісваць нашы асяроддзі і рэлізы:

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

У нас атрымалася 2 асяроддзі: развіваць, вытворчасць - У кожным знаходзяцца свае значэнні для helm чартаў рэлізаў. Мы будзем дэплоіць у іх так:

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

Розныя версіі helm чартаў у розных асяродках

Што рабіць, калі нам трэба выкочваць розныя версіі бэкенда ў розныя асяроддзі? Як параметрызаваць версію рэлізу? На дапамогу прыходзяць значэнні асяроддзя, даступныя праз {{ .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 }}
...

Розны набор прыкладанняў у розных асяродках

Выдатна, але што калі нам не трэба ў production выкочваць postgres, таму што мы ведаем, што не трэба базу дадзеных штурхаць ў k8s і для прода ў нас есць выдатны асобны кластар postgres? Для вырашэння гэтай праблемы ў нас ёсць лэйблы (labels)

helmfile -n <namespace> -e devel apply
helmfile -n <namespace> -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 значэння. Замест таго, каб для кожнага рэлізу ствараць свой файл 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) і жадаю мець выразнае апісанне стейта дэплою.

У заключэнне хачу дадаць, што зменныя для асяроддзя default можна ў сваю чаргу параметрызаваць зменнымі асяроддзі АС нейкага раннера, з якога будзе запускацца дэплой, і такім чынам атрымаць дынамічныя асяроддзі

helmfile.yaml

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

Крыніца: habr.com

Дадаць каментар