— un envoltorio para , que permite describir en un solo lugar múltiples lanzamientos de helm, parametrizar sus gráficos para varios entornos y también establecer el orden de su implementación.
Sobre helmfile y ejemplos de su uso se puede leer en y .
Nos familiarizaremos con formas no obvias de describir lanzamientos en helmfile
Supongamos que tenemos un conjunto de gráficos de helm (por ejemplo, postgres y alguna aplicación de backend) y varios entornos (varios clústeres de kubernetes, varios namespaces o ambos). Tomamos 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.yamlhelmfile.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.yamlHemos creado 2 entornos: devel, producción — en cada uno hay sus propios valores para los gráficos de helm de los lanzamientos. Los implementaremos así:
helmfile -n -e applyDiferentes versiones de gráficos de helm en diferentes entornos
¿Qué hacer si necesitamos desplegar diferentes versiones del backend en diferentes entornos? ¿Cómo parametrizar la versión del lanzamiento? Aquí entran en juego los valores del entorno, 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 }}
...Diferente conjunto de aplicaciones en diferentes entornos
Genial, pero ¿qué pasa si no necesitamos producción desplegar postgres, porque sabemos que no debemos meter una base de datos en k8s y para producción tenemos un magnífico clúster de postgres aparte? Para resolver este problema tenemos etiquetas (labels)
helmfile -n -e devel apply
helmfile -n -e production -l app=backend applyEs genial, pero personalmente prefiero describir qué aplicaciones desplegar en el entorno no mediante argumentos de lanzamiento, sino en la descripción de los propios entornos. ¿Qué hacer? Se puede colocar la descripción de los lanzamientos en una carpeta separada, en la descripción del entorno incluir una lista de los lanzamientos necesarios y "conectar" solo los lanzamientos necesarios, ignorando los demás.
.
├── envs
│ ├── devel
│ │ └── valores
│ │ ├── backend.yaml
│ │ └── postgres.yaml
│ └── producción
│ └── valores
│ ├── backend.yaml
│ └── postgres.yaml
+ ├── lanzamientos
+ │ ├── backend.yaml
+ │ └── postgres.yaml
└── helmfile.yaml
helmfile.yaml
entornos:
devel:
valores:
- gráficos:
versiones:
backend: 1.1.0
- aplicaciones:
- postgres
- backend
producción:
valores:
- gráficos:
versiones:
backend: 1.0.5
- aplicaciones:
- backend
- lanzamientos:
- - nombre: postgres
- etiquetas:
- app: postgres
- espera: true
- gráfico: estable/postgresql
- versión: 8.4.0
- valores:
- - envs/{{ .Environment.Name }}/valores/postgres.yaml
- - nombre: backend
- etiquetas:
- app: backend
- espera: true
- gráfico: private-helm-repo/backend
- versión: {{ .Values.charts.versions.backend }}
- necesita:
- - postgres
- valores:
- - envs/{{ .Environment.Name }}/valores/backend.yaml
+ ---
+ bases:
+ {{- range .Values.aplicaciones }}
+ - lanzamientos/{{ . }}.yaml
+ {{- end }}lanzamientos/postgres.yaml
lanzamientos:
- nombre: postgres
etiquetas:
app: postgres
espera: true
gráfico: estable/postgresql
versión: 8.4.0
valores:
- envs/{{ .Environment.Name }}/valores/postgres.yamllanzamientos/backend.yaml
lanzamientos:
- nombre: backend
etiquetas:
app: backend
espera: true
gráfico: private-helm-repo/backend
versión: {{ .Values.charts.versions.backend }}
necesita:
- postgres
valores:
- envs/{{ .Environment.Name }}/valores/backend.yamlNota
Al usar bases: es necesario utilizar el delimitador yaml ---, para que se puedan templatear los lanzamientos (y otras partes, como helmDefaults) con valores de los entornos
En ese caso, el lanzamiento de postgres ni siquiera aparecería en la descripción para producción. ¡Es muy conveniente!
Valores globales anularábles para los lanzamientos
Por supuesto, es genial que se puedan definir valores para los gráficos de helm en cada entorno, pero ¿qué pasa si tenemos varios entornos descritos y queremos, por ejemplo, establecer uno igual para todos? afinidad, pero no queremos configurarlo por defecto en los propios gráficos, que están almacenados en los repos.
En ese caso, podríamos establecer para cada lanzamiento 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 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.yamllanzamientos/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.yamlenvs/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 los charts de helm de todos los lanzamientos a nivel de entorno
Supongamos que tenemos varios ingress en varios lanzamientos — podríamos definir manualmente para cada chart hosts:, pero en nuestro caso el dominio es el mismo, así que ¿por qué no sacarlo a una variable global y simplemente usar su valor en los charts? Para eso, los archivos de valores que queremos parametrizar deben tener la extensión .gotmpl, para que helmfile sepa que debe pasarlos por el 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.yamlhelmfile.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 }}Nota
Es obvio que el ingress en el chart de Postgres es algo bastante cuestionable, por lo que en el artículo se presenta simplemente como un ejemplo esférico en un vacío y para no introducir una nueva versión en el artículo solo por describir el ingress.
Interpolación de secretos (secrets) desde variables de entorno.
De manera similar al ejemplo anterior, se pueden interpolar valores cifrados utilizando . En lugar de crear un archivo de secretos para cada versión, donde definiríamos los valores cifrados para el chart, podemos simplemente definir en el archivo default.yaml.gotmpl valores que se obtendrán de variables asignadas a nivel de entorno. Y los valores que no necesitamos ocultar de nadie se pueden redefinir en los valores de la versión 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.yamlhelmfile.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.domainenvs/production/values/backend.yaml
elasticsearch:
host: elastic-0.production.domainNota
Por cierto, getOrNil — una función especial para las plantillas de Go en helmfile, que, incluso si .Values.secrets no existe, no lanza un error, sino que permite finalmente usar la función default para establecer un valor por defecto
Conclusión
Las cosas descritas parecen bastante obvias, pero la información sobre cómo realizar un despliegue conveniente en varios entornos utilizando helmfile es muy escasa, y a mí me gusta IaC (Infrastructure-as-Code) y quiero tener una descripción clara del estado del despliegue.
En conclusión, quiero añadir que las variables para el entorno por defecto se pueden parametrizar con variables de entorno del sistema operativo de algún runner desde el cual se iniciará el despliegue, lo que permitirá obtener entornos dinámicos.
helmfile.yaml
environments:
default:
values:
- global:
clusterDomain: {{ env "CLUSTER_DOMAIN" | default "cluster.local" }}
ingressDomain: {{ env "INGRESS_DOMAIN" }}Fuente: habr.com
