Organizowanie wdrażania w wielu środowiskach K8s przy użyciu pliku Helmfile

Plik Helm - opakowanie na hełm, co pozwala na opisanie wielu wydań sterów w jednym miejscu, parametryzację ich wykresów dla kilku środowisk, a także ustalenie kolejności ich wdrażania.

Możesz przeczytać o samym pliku Helmfile i przykładach jego użycia w readme и przewodnik po najlepszych praktykach.

Zapoznamy się z nieoczywistymi sposobami opisywania wydań w pliku sterowym

Załóżmy, że mamy pakiet wykresów Helm (na przykład postgres i jakąś aplikację zaplecza) i kilka środowisk (kilka klastrów Kubernetes, kilka przestrzeni nazw lub kilka obu). Bierzemy plik sterowy, czytamy dokumentację i zaczynamy opisywać nasze środowiska i wydania:

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

Skończyło się na 2 środowiskach: devel, produkcja — każdy zawiera własne wartości dla wykresów zwolnienia steru. Wdrożymy je w następujący sposób:

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

Różne wersje wykresów steru w różnych środowiskach

Co się stanie, jeśli będziemy musieli wdrożyć różne wersje backendu w różnych środowiskach? Jak sparametryzować wersję wydania? Wartości środowiskowe dostępne poprzez {{ .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 }}
...

Różne zestawy aplikacji w różnych środowiskach

Świetnie, ale co, jeśli nie musimy production wdrożyć postgres, bo wiemy, że nie musimy wpychać bazy danych do k8s i na sprzedaż mamy wspaniały, osobny klaster Postgres? Aby rozwiązać ten problem, mamy etykiety

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

To świetnie, ale osobiście wolę opisywać, które aplikacje należy wdrożyć w środowisku, nie używając argumentów uruchamiania, ale opisując same środowiska. Co robić? Możesz umieścić opisy wydań w osobnym folderze, stworzyć listę potrzebnych wydań w opisie środowiska i „wybrać” tylko te niezbędne wydania, resztę ignorując

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

Notatka

Podczas korzystania z bases: konieczne jest użycie separatora yaml ---, dzięki czemu możesz szablonować wydania (i inne części, takie jak helmDefaults) z wartościami ze środowisk

W takim przypadku wersja Postgres nie zostanie nawet uwzględniona w opisie wersji produkcyjnej. Bardzo wygodnie!

Nadpisywane wartości globalne dla wydań

Oczywiście super, że można ustawić wartości dla wykresów steru dla każdego środowiska, ale co jeśli mamy opisanych kilka środowisk i chcemy np. ustawić to samo dla wszystkich affinity, ale nie chcemy tego domyślnie konfigurować w samych wykresach, które są przechowywane w rzepie.

W tym przypadku dla każdego wydania moglibyśmy określić 2 pliki z wartościami: pierwszy z wartościami domyślnymi, które określą wartości samego wykresu, a drugi z wartościami dla środowiska, co z kolei nadpisze domyślne.

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

Definiowanie globalnych wartości dla wykresów steru wszystkich wydań na poziomie środowiska

Załóżmy, że tworzymy kilka wejść w kilku wersjach – możemy ręcznie zdefiniować dla każdego wykresu hosts:, ale w naszym przypadku domena jest taka sama, więc dlaczego nie umieścić jej w jakiejś zmiennej globalnej i po prostu zastąpić jej wartość na wykresach? Aby to zrobić, pliki z wartościami, które chcemy sparametryzować, będą musiały mieć rozszerzenie .gotmpl, aby plik sterowy wiedział, że należy go uruchomić przez silnik szablonów.

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

Notatka

Oczywiście ingres na wykresie postgres jest czymś niezwykle wątpliwym, więc ten artykuł podano po prostu jako sferyczny przykład w próżni i po to, aby nie wprowadzać do artykułu jakiejś nowej wersji tylko po to, by opisać ingres

Zastępowanie wpisów tajnych z wartości środowiska

Analogicznie do powyższego przykładu, możesz zastąpić zaszyfrowane za pomocą tajemnice steru znaczenia. Zamiast tworzyć dla każdego wydania własny plik sekretów, w którym możemy zdefiniować zaszyfrowane wartości dla wykresu, możemy po prostu zdefiniować w wydaniu default.yaml.gotmpl wartości, które będą pobierane ze zmiennych zdefiniowanych przy poziom środowiska. A wartości, których nie musimy przed nikim ukrywać, można łatwo przedefiniować w wartościach wydania w konkretnym środowisku.

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

Notatka

By the way, getOrNil - specjalna funkcja dla szablonów go w pliku sterowym, które nawet jeśli .Values.secrets nie będzie istnieć, nie zgłosi błędu, ale pozwoli na wynik za pomocą funkcji default zastąpić wartość domyślną

wniosek

Opisane rzeczy wydają się dość oczywiste, ale informacje na temat wygodnego opisu wdrożenia w kilku środowiskach przy użyciu pliku Helmfile są bardzo skąpe, a ja uwielbiam IaC (Infrastructure-as-Code) i chcę mieć jasny opis stanu wdrożenia.

Na zakończenie dodam, że zmienne dla środowiska domyślnego można z kolei sparametryzować zmiennymi środowiskowymi systemu operacyjnego danego runnera, z którego zostanie uruchomione wdrożenie, i w ten sposób uzyskać środowiska dynamiczne

helmfile.yaml

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

Źródło: www.habr.com

Dodaj komentarz