Organizando a implantación en varios ambientes k8s usando helmfile

Helmfile - envoltorio para leme, que permite describir moitas versións de timón nun só lugar, parametrizar os seus gráficos para varios ambientes e tamén establecer a orde da súa implantación.

Podes ler sobre o propio helmfile e exemplos do seu uso README и guía de boas prácticas.

Coñeceremos formas non obvias de describir as versións en helmfile

Digamos que temos un paquete de gráficos helm (por exemplo, digamos Postgres e algunha aplicación de backend) e varios ambientes (varios clústeres de kubernetes, varios espazos de nomes ou varios dos dous). Collemos o ficheiro helm, lemos a documentación e comezamos a describir os nosos ambientes e versións:

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

Rematamos con 2 ambientes: desenvolver, produción - cada un contén os seus propios valores para os gráficos de lanzamento do timón. Implementarémoslles así:

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

Diferentes versións de gráficos de timón en diferentes ambientes

E se necesitamos lanzar diferentes versións do backend a diferentes ambientes? Como parametrizar a versión do lanzamento? Os valores ambientais dispoñibles a través {{ .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 }}
...

Diferente conxunto de aplicacións en diferentes ambientes

Xenial, pero que pasa se non o necesitamos production lanzar postgres, porque sabemos que non necesitamos empurrar a base de datos a k8s e que temos á venda un marabilloso clúster de postgres separado? Para solucionar este problema temos etiquetas

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

Isto é xenial, pero persoalmente prefiro describir que aplicacións implementar no entorno non usando argumentos de lanzamento, senón na descrición dos propios entornos. Que facer? Pode colocar as descricións de versións nun cartafol separado, crear unha lista das versións necesarias na descrición do ambiente e "recoller" só as versións necesarias, ignorando o resto

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

A nota

Ao usar bases: é necesario utilizar o separador de yaml ---, para que poida modelo lanzamentos (e outras partes, como helmDefaults) con valores de ambientes

Neste caso, a versión de postgres nin sequera se incluirá na descrición para a produción. Moi cómodo!

Valores globais anulables para versións

Por suposto, é xenial que poidas establecer valores para os gráficos de timón para cada ambiente, pero que pasa se temos varios ambientes descritos e queremos, por exemplo, establecer o mesmo para todos. affinity, pero non queremos configuralo por defecto nos propios gráficos, que se almacenan en grelos.

Neste caso, para cada versión poderiamos especificar 2 ficheiros con valores: o primeiro con valores predeterminados, que determinará os valores do propio gráfico, e o segundo con valores para o entorno, que á súa vez anulará o os predeterminados.

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

Definición de valores globais para gráficos de timón de todos os lanzamentos a nivel ambiental

Digamos que creamos varias entradas en varias versións; poderiamos definir manualmente para cada gráfico hosts:, pero no noso caso o dominio é o mesmo, entón por que non poñelo nalgunha variable global e simplemente substituír o seu valor nos gráficos? Para iso, aqueles ficheiros con valores que queiramos parametrizar terán que ter a extensión .gotmpl, para que helmfile saiba que hai que executalo a través do motor de modelos.

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

A nota

Obviamente, a entrada no gráfico de postgres é algo moi dubidoso, polo que este artigo dáse simplemente como un exemplo esférico no baleiro e para non introducir algunha nova versión no artigo só para describir a entrada.

Substituíndo segredos dos valores ambientais

Por analoxía co exemplo anterior, pode substituír os cifrados usando segredos de timón significados. En lugar de crear o noso propio ficheiro de segredos para cada versión, no que podemos definir valores cifrados para o gráfico, simplemente podemos definir na versión default.yaml.gotmpl os valores que se tomarán das variables definidas no nivel ambiental. E os valores que non necesitamos ocultar a ninguén pódense redefinir facilmente nos valores de lanzamento nun ambiente específico.

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

A nota

By the way, getOrNil - unha función especial para os modelos go en helmfile, que, aínda que .Values.secrets non existirá, non lanzará un erro, pero permitirá o resultado usando a función default substituír o valor predeterminado

Conclusión

As cousas descritas parecen bastante obvias, pero a información sobre unha descrición cómoda da implementación en varios ambientes que usan helmfile é moi escasa e encántame IaC (Infraestrutura-como-Code) e quero ter unha descrición clara do estado de implementación.

Como conclusión, gustaríame engadir que as variables para o entorno predeterminado pódense, á súa vez, parametrizarse coas variables de entorno do SO dun determinado corredor desde o que se iniciará o despregamento, e así obter contornas dinámicas.

helmfile.yaml

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

Fonte: www.habr.com

Engadir un comentario