Organizar la implementación en múltiples entornos k8 usando helmfile

Archivo de timón - envoltorio para timón, que le permite describir muchas versiones de timón en un solo lugar, parametrizar sus gráficos para varios entornos y también establecer el orden de su implementación.

Puede leer sobre el propio helmfile y ejemplos de su uso en readme и guía de mejores prácticas.

Nos familiarizaremos con formas no obvias de describir lanzamientos en helmfile.

Digamos que tenemos un paquete de gráficos de helm (por ejemplo, postgres y alguna aplicación de backend) y varios entornos (varios clústeres de Kubernetes, varios espacios de nombres o varios de ambos). Tomamos el helmfile, leemos la documentación y comenzamos a describir nuestros entornos y lanzamientos:

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

Terminamos con 2 ambientes: desarrollo, Production — cada uno contiene sus propios valores para las tablas de liberación del timón. Les implementaremos así:

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

Diferentes versiones de cartas de timón en diferentes entornos

¿Qué pasa si necesitamos implementar diferentes versiones del backend en diferentes entornos? ¿Cómo parametrizar la versión de lanzamiento? Los valores ambientales disponibles a través de {{ .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 }}
...

Diferentes conjuntos de aplicaciones en diferentes entornos.

Genial, pero ¿qué pasa si no es necesario? production ¿Implementar Postgres, porque sabemos que no necesitamos insertar la base de datos en K8 y tenemos a la venta un maravilloso clúster de Postgres separado? Para solucionar este problema tenemos etiquetas.

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

Esto es genial, pero personalmente prefiero describir qué aplicaciones implementar en el entorno no usando argumentos de inicio, sino en la descripción de los propios entornos. ¿Qué hacer? Puede colocar las descripciones de las versiones en una carpeta separada, crear una lista de las versiones necesarias en la descripción del entorno y "recoger" sólo las versiones necesarias, ignorando el 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

Заметка

Cuando se utiliza el bases: es necesario usar el separador yaml ---, para que pueda crear plantillas de lanzamientos (y otras partes, como helmDefaults) con valores de entornos

En este caso, la versión de Postgres ni siquiera se incluirá en la descripción de producción. ¡Muy cómodamente!

Valores globales anulables para lanzamientos

Por supuesto, es fantástico poder establecer valores para los gráficos de timón para cada entorno, pero ¿qué pasa si tenemos varios entornos descritos y queremos, por ejemplo, establecer los mismos para todos? affinity, pero no queremos configurarlo por defecto en los propios gráficos, que se almacenan en los nabos.

En este caso, para cada versión podríamos especificar 2 archivos con valores: el primero con valores predeterminados, que determinarán los valores del propio gráfico, y el segundo con valores para el entorno, que a su vez anulará los los 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 globales para gráficos de timón de todas las versiones a nivel ambiental

Digamos que creamos varios ingresos en varias versiones; podríamos definir manualmente para cada gráfico hosts:, pero en nuestro caso el dominio es el mismo, entonces ¿por qué no ponerlo en alguna variable global y simplemente sustituir su valor en los gráficos? Para ello, aquellos archivos con valores que queramos parametrizar tendrán que tener la extensión .gotmpl, para que helmfile sepa que debe ejecutarse a través del motor de plantillas.

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

Заметка

Obviamente, el ingreso en el gráfico de Postgres es algo extremadamente dudoso, por lo que este artículo se presenta simplemente como un ejemplo esférico en el vacío y para no introducir ninguna nueva versión en el artículo solo por describir el ingreso.

Sustituir secretos por valores ambientales.

Por analogía con el ejemplo anterior, puede sustituir los cifrados usando secretos del timón significados. En lugar de crear nuestro propio archivo secreto para cada versión, en el que podemos definir valores cifrados para el gráfico, podemos simplemente definir en la versión default.yaml.gotmpl los valores que se tomarán de las variables definidas en el nivel ambiental. Y los valores que no necesitamos ocultar a nadie se pueden redefinir fácilmente en los valores de lanzamiento en un entorno 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

Заметка

Por cierto, getOrNil - una función especial para las plantillas go en helmfile, que, incluso si .Values.secrets no existirá, no arrojará un error, pero permitirá el resultado usando la función default sustituir el valor predeterminado

Conclusión

Las cosas descritas parecen bastante obvias, pero la información sobre una descripción conveniente de la implementación en varios entornos usando helmfile es muy escasa, y me encanta IaC (Infraestructura como código) y quiero tener una descripción clara del estado de implementación.

Para concluir, me gustaría agregar que las variables para el entorno predeterminado pueden, a su vez, parametrizarse con las variables de entorno del SO de un determinado corredor desde el cual se lanzará el despliegue, y así obtener entornos dinámicos.

helmfile.yaml

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

Fuente: habr.com

Añadir un comentario