Organizando a implantação em vários ambientes K8s usando helmfile

Helmfile - invólucro para leme, que permite descrever muitas versões do helm em um só lugar, parametrizar seus gráficos para vários ambientes e também definir a ordem de sua implantação.

Você pode ler sobre o próprio helmfile e exemplos de seu uso em readme и guia de melhores práticas.

Conheceremos maneiras não óbvias de descrever lançamentos no helmfile

Digamos que temos um pacote de gráficos de leme (por exemplo, postgres e algum aplicativo de back-end) e vários ambientes (vários clusters Kubernetes, vários namespaces ou vários de ambos). Pegamos o helmfile, lemos a documentação e começamos a descrever nossos ambientes e releases:

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

Acabamos com 2 ambientes: d, produção — cada um contém seus próprios valores para os gráficos de liberação do leme. Iremos implantar para eles assim:

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

Diferentes versões de gráficos de leme em diferentes ambientes

E se precisarmos implementar versões diferentes do back-end em ambientes diferentes? Como parametrizar a versão de lançamento? Os valores ambientais disponíveis atravé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 }}
...

Conjunto diferente de aplicativos em ambientes diferentes

Ótimo, mas e se não precisarmos production implementar o postgres, porque sabemos que não precisamos enviar o banco de dados para o k8s e temos um maravilhoso cluster postgres separado para venda? Para resolver este problema temos rótulos

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

Isso é ótimo, mas pessoalmente prefiro descrever quais aplicativos implantar no ambiente não usando argumentos de inicialização, mas na descrição dos próprios ambientes. O que fazer? Você pode colocar as descrições dos lançamentos em uma pasta separada, criar uma lista dos lançamentos necessários na descrição do ambiente e “pegar” apenas os lançamentos necessários, 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

Заметка

Quando se utiliza o bases: é necessário usar o separador yaml ---, para que você possa modelar versões (e outras partes, como helmDefaults) com valores de ambientes

Neste caso, o release do postgres nem será incluído na descrição para produção. Muito confortavelmente!

Valores globais substituíveis para lançamentos

Claro, é ótimo que você possa definir valores para gráficos de leme para cada ambiente, mas e se tivermos vários ambientes descritos e quisermos, por exemplo, definir o mesmo para todos? affinity, mas não queremos configurá-lo por padrão nos próprios gráficos, que são armazenados nos nabos.

Neste caso, para cada versão poderíamos especificar 2 arquivos com valores: o primeiro com valores padrão, que determinará os valores do próprio gráfico, e o segundo com valores para o ambiente, que por sua vez substituirá o os padrão.

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

Definindo valores globais para gráficos de leme de todas as versões no nível do ambiente

Digamos que criamos vários ingressos em vários lançamentos - poderíamos definir manualmente para cada gráfico hosts:, mas no nosso caso o domínio é o mesmo, então por que não colocá-lo em alguma variável global e simplesmente substituir seu valor nos gráficos? Para isso, os arquivos com valores que queremos parametrizar deverão ter a extensão .gotmpl, para que o helmfile saiba que precisa ser executado por meio do mecanismo de modelo.

    .
    ├── 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, o ingresso no gráfico do postgres é algo extremamente duvidoso, então este artigo é dado simplesmente como um exemplo esférico no vácuo e para não introduzir alguma nova versão no artigo apenas para descrever o ingresso

Substituindo segredos de valores ambientais

Por analogia com o exemplo acima, você pode substituir os criptografados usando segredos do leme significados. Em vez de criarmos nosso próprio arquivo de segredos para cada release, no qual podemos definir valores criptografados para o gráfico, podemos simplesmente definir no release default.yaml.gotmpl os valores que serão retirados das variáveis ​​definidas no nível ambiental. E os valores que não precisamos esconder de ninguém podem ser facilmente redefinidos nos valores de lançamento em um 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

Заметка

By the way, getOrNil - uma função especial para modelos go no helmfile, que, mesmo que .Values.secrets não existirá, não gerará erro, mas permitirá o resultado usando a função default substituir valor padrão

Conclusão

As coisas descritas parecem bastante óbvias, mas as informações sobre uma descrição conveniente da implantação em vários ambientes usando helmfile são muito escassas, e eu adoro IaC (Infraestrutura como Código) e quero ter uma descrição clara do estado de implantação.

Concluindo, gostaria de acrescentar que as variáveis ​​​​do ambiente padrão podem, por sua vez, ser parametrizadas com as variáveis ​​​​de ambiente do SO de um determinado executor a partir do qual será lançada a implantação, e assim obter ambientes dinâmicos

helmfile.yaml

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

Fonte: habr.com

Adicionar um comentário