Кратко о Helmfile
Helmfile - декларативная обёртка над Helm для управления множеством релизов, окружений и секретов из единого репозитория.
Вместо ручного вызова helm install/upgrade для каждого чарта, helmfile позволяет описать весь стек в одном конфиге и деплоить его одной командой. Это Infrastructure as Code для Kubernetes-приложений.
Зачем нужен Helmfile
Helm сам по себе управляет одним чартом. Когда в кластере десятки приложений с разными окружениями, начинаются проблемы:
- Для каждого приложения нужно помнить команды, флаги и порядок установки
- Values-файлы разбросаны по разным местам
- Секреты хранятся вне git, легко рассинхронизируются
- Нет единого обзора того, что задеплоено в кластер
- Зависимости между приложениями приходится отслеживать вручную
Helmfile решает всё это, предоставляя единую точку входа для описания, параметризации и деплоя всей инфраструктуры.
Установка
# brew (macOS/Linux)
brew install helmfile
# вручную
wget -O ~/bin/helmfile https://github.com/helmfile/helmfile/releases/download/v1.1.7/helmfile_1.1.7_linux_amd64.tar.gz
chmod +x ~/bin/helmfileНеобходимые helm-плагины
helm plugin install https://github.com/databus23/helm-diff
helm plugin install https://github.com/jkroepke/helm-secretsИли автоматическая инициализация
helmfile init --forceВерсии
helm 3.19.0+, helmfile 1.1.7+, helm-diff 3.13.0+, helm-secrets 4.4.2+
Основные команды
helmfile list # список всех релизов
helmfile apply # установка/обновление (идемпотентно)
helmfile sync # полная синхронизация
helmfile diff # различия между текущим и желаемым состоянием
helmfile destroy # удаление всех релизов
helmfile template # рендер манифестов без деплоя
helmfile -e prod apply # деплой конкретного окружения
helmfile -l name=redis sync # обновить один релиз по лейблуПример простой конфигурации
helmfile.yaml
releases:
- name: webapp
namespace: default
chart: ./charts/webapp
version: "0.1.0"
wait: true
installed: true
- name: backend
namespace: default
chart: ./charts/backend
wait: true
- name: database
namespace: default
chart: ./charts/database
wait: trueЧерез set можно установить свои значения values
- name: backend
namespace: default
chart: ./backend
wait: true
set:
- name: replicaCount
value: 2Репозитории
Чарты из внешних репозиториев можно подключать через OCI-ссылку напрямую
releases:
- name: nginx
namespace: default
chart: oci://registry-1.docker.io/bitnamicharts/nginx
version: "15.4.4"
wait: true
set:
- name: service.type
value: ClusterIPИли через объявление репозитория
repositories:
- name: prometheus-community
url: https://prometheus-community.github.io/helm-charts
releases:
- name: prom-norbac-ubuntu
namespace: prometheus
chart: prometheus-community/prometheus
set:
- name: rbac.create
value: falseОпределение OCI-репозитория
repositories:
- name: ocirepo
url: registry-1.docker.io/bitnamicharts
oci: true
releases:
- name: nginx
namespace: default
chart: ocirepo/nginx
version: 15.4.4
wait: true
set:
- name: service.type
value: ClusterIPПродвинутая структура проекта
Простой helmfile.yaml с перечислением релизов быстро перерастает в неуправляемый файл. Для production-окружений используется модульная структура с разделением ответственности.
Ниже - разбор реальной продакшен-конфигурации на базе репозитория zzamzam-k8s, где helmfile управляет 20+ приложениями (ingress-nginx, cert-manager, postgresql, gitea, velero, wikijs, oauth2-proxy и т.д.).
Файловая структура
.
├── helmfile.yaml # точка входа
├── .sops.yaml # конфигурация шифрования
├── .helmfile/
│ ├── environments.yaml.gotmpl # шаблон окружений
│ ├── releases.yaml.gotmpl # генерация релизов из apps
│ └── repositories.yaml # helm-репозитории
├── apps/
│ └── _others.yaml # реестр всех приложений
├── releases/
│ ├── _override.yaml.gotmpl # механизм переопределения values
│ ├── ingress-nginx.yaml.gotmpl # общие values для ingress-nginx
│ ├── cert-manager.yaml.gotmpl # общие values для cert-manager
│ ├── velero.yaml.gotmpl # общие values для velero
│ └── ... # по файлу на каждый релиз
├── envs/
│ └── k8s/ # окружение "k8s"
│ ├── env.yaml # installed/needs для каждого app
│ ├── values/
│ │ └── _all.yaml.gotmpl # переопределения values
│ └── secrets/
│ └── _all.yaml # зашифрованные секреты (SOPS)
└── charts/
└── remark42/ # собственный helm-чарт
├── Chart.yaml
├── templates/
└── values.yaml
Точка входа - helmfile.yaml
bases:
- .helmfile/environments.yaml.gotmpl
---
bases:
- .helmfile/repositories.yaml
- .helmfile/releases.yaml.gotmpl
helmDefaults:
wait: true
atomic: true
devel: true
createNamespace: trueФайл разделён на два YAML-документа (через ---). Это требование helmfile - окружения загружаются первым проходом, а затем подключаются репозитории и релизы.
helmDefaults задаёт поведение по умолчанию для всех релизов:
wait: true- ждать, пока все ресурсы перейдут в Readyatomic: true- при ошибке автоматический откатcreateNamespace: true- автоматическое создание namespace
Окружения - .helmfile/environments.yaml.gotmpl
templates:
.default: &default
missingFileHandler: Info
values:
# Реестр приложений
- apps/*.*
# Настройки окружения
- envs/{{ .Environment.Name }}/*.*
# Values для helm-релизов
- envs/{{ .Environment.Name }}/values/*.*
secrets:
# Зашифрованные секреты
- envs/{{ .Environment.Name }}/secrets/*.*
environments:
k8s:
<<: *defaultЗдесь задана цепочка загрузки переменных. Каждое окружение наследует шаблон &default через YAML-якорь. Helmfile загружает values и secrets по слоям, и более поздние слои переопределяют ранние:
apps/*.*- описания всех приложений (какой чарт, версия, репозиторий)envs/k8s/*.*- какие приложения установлены и их зависимостиenvs/k8s/values/*.*- переопределения values для этого окруженияenvs/k8s/secrets/*.*- зашифрованные секреты
missingFileHandler: Info означает, что если файл не найден - helmfile просто пропустит его, а не упадёт с ошибкой. Это позволяет не создавать пустые файлы для каждого слоя.
Добавить новое окружение (например, staging) - одна строка:
environments:
k8s:
<<: *default
staging:
<<: *defaultИ создать папку envs/staging/ с env.yaml, values/, secrets/.
Реестр приложений - apps/
apps:
ingress-nginx:
repo: ingress-nginx
chart: ingress-nginx
version: 4.0.16
namespace: ingress-nginx
cert-manager:
repo: jetstack
chart: cert-manager
version: v1.7.0
namespace: cert-manager
postgresql:
repo: bitnami
chart: postgresql
version: 11.0.1
namespace: postgresql
gitea:
repo: gitea-charts
chart: gitea
version: 5.0.1
namespace: gitea
velero:
repo: vmware-tanzu
chart: velero
version: 6.0.0
namespace: velero
remark42:
repo: charts
chart: remark42
namespace: remark42
pairdrop:
repo: zzamtools
version: 1.2.0
chart: base-deployment
namespace: pairdropЭто словарь apps - единое место, где описаны все приложения: имя, helm-репозиторий, чарт, версия и namespace. Здесь не указывается, установлено ли приложение - это решает конкретное окружение.
Разделение между “что может быть установлено” (apps) и “что установлено” (env.yaml) позволяет переиспользовать один реестр для множества окружений.
Динамическая генерация релизов - .helmfile/releases.yaml.gotmpl
releases:
{{- range $release, $v := .Values.apps }}
- name: {{ $release }}
labels:
app: {{ $release }}
chart: {{ $v.repo }}/{{ $v.chart }}
{{- if $v | getOrNil "version" }}
version: {{ $v.version }}
{{- end }}
{{- if $v | getOrNil "namespace" }}
namespace: {{ $v.namespace }}
{{- end }}
missingFileHandler: Info
values:
{{- if $v | getOrNil "valueFiles" }}
{{- range $valueFile := $v.valueFiles }}
- releases/{{ $valueFile }}
{{- end }}
{{- else }}
- releases/{{ $release }}.yaml.gotmpl
{{- end }}
- releases/_override.yaml.gotmpl
{{- if ($v | getOrNil "installed") }}
installed: true
{{- else }}
installed: false
{{- end }}
{{- if ($v | getOrNil "needs") }}
needs:
{{- toYaml $v.needs | trim | nindent 4 }}
{{- end }}
{{- end }}Это ключевой элемент архитектуры. Вместо ручного перечисления каждого release, шаблон итерируется по словарю .Values.apps и генерирует helmfile-releases автоматически.
Для каждого приложения шаблон:
- Формирует
chartизrepo/chart - Подключает values из
releases/<имя>.yaml.gotmpl - Добавляет
_override.yaml.gotmplдля переопределений из окружения - Устанавливает
installed: true/falseна основе env.yaml - Прописывает
needsдля управления порядком деплоя
Например, запись из apps
apps:
gitea:
repo: gitea-charts
chart: gitea
version: 5.0.1
namespace: giteaвместе с env.yaml
apps:
gitea:
installed: true
needs:
- postgresql/postgresql
- gitea/gitea-secretsпревратится в release
releases:
- name: gitea
labels:
app: gitea
chart: gitea-charts/gitea
version: 5.0.1
namespace: gitea
missingFileHandler: Info
values:
- releases/gitea.yaml.gotmpl
- releases/_override.yaml.gotmpl
installed: true
needs:
- postgresql/postgresql
- gitea/gitea-secretsКонфигурация окружения - envs/k8s/env.yaml
apps:
ingress-nginx:
installed: true
cert-manager:
installed: true
cert-manager-issuers:
installed: true
needs:
- cert-manager/cert-manager
postgresql-configmaps:
installed: true
postgresql:
installed: true
needs:
- postgresql/postgresql-configmaps
gitea:
installed: true
needs:
- postgresql/postgresql
- gitea/gitea-secrets
keycloak:
installed: false
needs:
- postgresql/postgresql
velero:
installed: true
pairdrop:
installed: true
kavita:
installed: trueЭтот файл определяет, какие приложения установлены в данном окружении и в каком порядке. Values из apps и env.yaml мержатся - apps задаёт “что” (чарт, версия), а env.yaml - “где” (installed, зависимости).
Формат needs использует синтаксис namespace/release-name для определения зависимостей. Helmfile деплоит зависимости раньше зависимых релизов.
Общие values для релиза - releases/
Каждый файл releases/<имя>.yaml.gotmpl содержит values, общие для всех окружений.
releases/ingress-nginx.yaml.gotmpl
controller:
resources:
requests:
cpu: 10m
memory: 256Mi
kind: DaemonSet
hostNetwork: true
service:
type: ""
metrics:
enabled: false
config:
enable-real-ip: "true"
use-forwarded-headers: "false"releases/velero.yaml.gotmpl
initContainers:
- name: velero-plugin-for-aws
image: velero/velero-plugin-for-aws:v1.9.2
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /target
name: plugins
backupsEnabled: true
snapshotsEnabled: false
deployNodeAgent: true
resources:
requests:
cpu: 10m
memory: 128Mireleases/cert-manager.yaml.gotmpl
installCRDs: true
crds:
enabled: true
prometheus:
enabled: falseМинимальные releases могут содержать только fullnameOverride
# releases/kavita.yaml.gotmpl
fullnameOverride: kavitaМеханизм переопределения - releases/_override.yaml.gotmpl
{{- if (.Values | getOrNil .Release.Name) }}
{{ .Values | getOrNil .Release.Name | toYaml }}
{{- end }}Этот шаблон проверяет, есть ли в values текущего окружения ключ с именем текущего релиза, и если есть - подставляет его содержимое как дополнительные values.
Это позволяет переопределять параметры любого приложения в одном файле envs/<env>/values/_all.yaml.gotmpl:
# envs/k8s/values/_all.yaml.gotmpl
gitea:
gitea:
config:
database:
DB_TYPE: postgres
HOST: postgresql.postgresql.svc.cluster.local:5432
ingress-nginx:
controller:
metrics:
enabled: true
velero:
configuration:
backupStorageLocation:
- name: default
provider: aws
bucket: my-backupsВместо создания отдельного values-файла для каждого приложения, все переопределения для окружения собраны в одном месте. Когда в окружении десяток приложений, это значительно удобнее.
Helm-репозитории - .helmfile/repositories.yaml
repositories:
- name: ingress-nginx
url: https://kubernetes.github.io/ingress-nginx
- name: jetstack
url: https://charts.jetstack.io
- name: gitea-charts
url: https://dl.gitea.io/charts/
- name: bitnami
url: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami
- name: oauth2-proxy
url: https://oauth2-proxy.github.io/manifests
- name: vmware-tanzu
url: https://vmware-tanzu.github.io/helm-charts
- name: zzamtools
url: https://zzamtools.github.io/helm-chartsИмена репозиториев используются в apps/ при указании repo.
Собственные чарты - charts/
Если для приложения нет готового helm-чарта, можно разместить свой в папке charts/. В примере выше для remark42 используется собственный чарт:
# apps/_others.yaml
apps:
remark42:
repo: charts # папка charts/ в корне проекта
chart: remark42
namespace: remark42Helmfile автоматически подхватит чарт из charts/remark42/.
Шифрование секретов через SOPS
Helmfile интегрируется с helm-secrets и SOPS для безопасного хранения секретов в git.
.sops.yaml
creation_rules:
- age: age1q2f4expgz8f2cfrk56cmaeset3f0flwggzhnmrx2yr9pkhasq9qs6ftltyСекреты хранятся зашифрованными в envs/<env>/secrets/*.yaml и автоматически расшифровываются при деплое.
Редактирование секретов
helm secrets edit envs/k8s/secrets/_all.yamlДля генерации ключей используется утилита age:
age-keygen -o ~/.config/sops/age/keys.txtПубличный ключ прописывается в .sops.yaml, приватный хранится на машинах, с которых происходит деплой.
Мультитенантная конфигурация
Та же архитектура масштабируется для деплоя одних и тех же приложений в разные окружения с разными параметрами. Пример из helmfile-examples:
envs/
├── clusters/
│ └── k0s/ # кластерные аддоны
│ └── env.yaml
├── client-a/
│ └── prod/
│ ├── env.yaml
│ ├── values/
│ │ └── _all.yaml.gotmpl
│ └── secrets/
│ └── _all.yaml
├── client-b/
│ └── prod/
│ ├── env.yaml
│ ├── values/
│ └── secrets/
└── client-c/
└── prod/
├── env.yaml
├── values/
└── secrets/
.helmfile/environments.yaml
environments:
clusters/k0s:
<<: *default
client-a/prod:
<<: *default
client-b/prod:
<<: *default
client-c/prod:
<<: *defaultКаждый клиент получает свою конфигурацию
envs/client-a/prod/env.yaml
global:
ingressDomain: client-a-prod.example.com
apps:
simple-python-web-app:
installed: trueenvs/client-a/prod/values/_all.yaml.gotmpl
simple-python-web-app:
env:
CLIENT_ID: "Client A"Деплой каждого окружения отдельно
helmfile -e client-a/prod -n client-a-prod apply
helmfile -e client-b/prod -n client-b-prod apply
helmfile -e client-c/prod -n client-c-prod applyПриложение наследует общие values из releases/simple-python-web-app.yaml.gotmpl, а клиентские переопределения подтягиваются через _override.yaml.gotmpl.
Как работает цепочка values
Порядок приоритета (от низшего к высшему):
releases/<app>.yaml.gotmpl- общие defaults для всех окруженийreleases/_override.yaml.gotmpl- подтягивает переопределения из values окруженияenvs/<env>/values/_all.yaml.gotmpl- values конкретного окружения (источник для _override)envs/<env>/secrets/_all.yaml- зашифрованные секреты (высший приоритет)
Принцип наследования
Общие значения задаются один раз в
releases/, а каждое окружение переопределяет только то, что отличается. Это DRY-подход - не нужно дублировать полную конфигурацию для каждого окружения.
Как добавить новое приложение
- Добавить описание в
apps/
apps:
my-app:
repo: bitnami
chart: my-app
version: 1.0.0
namespace: my-app- Создать файл общих values
# releases/my-app.yaml.gotmpl
replicas: 1
resources:
requests:
cpu: 10m
memory: 128Mi- Включить в нужном окружении
# envs/k8s/env.yaml
apps:
my-app:
installed: true
needs:
- postgresql/postgresql- При необходимости - переопределить values для окружения
# envs/k8s/values/_all.yaml.gotmpl
my-app:
replicas: 3
ingress:
enabled: true
host: my-app.example.comКак добавить новое окружение
- Добавить окружение в
.helmfile/environments.yaml.gotmpl
environments:
k8s:
<<: *default
staging:
<<: *default- Создать структуру папок
envs/staging/
├── env.yaml # какие apps установлены
├── values/
│ └── _all.yaml.gotmpl
└── secrets/
└── _all.yaml
-
Описать нужные приложения и их параметры
-
Деплоить
helmfile -e staging applyCI/CD интеграция
Пример GitLab CI для helmfile
deploy:
stage: deploy
image: alpine/helmfile
script:
- helmfile init --force
- helmfile -e ${CI_ENVIRONMENT_NAME} applyTL;DR
Helmfile описывает всю Kubernetes-инфраструктуру как код. Приложения, параметры, секреты, зависимости, окружения - всё управляется одной командой из единого git-репозитория.
Samples
- https://github.com/zam-zam/zzamzam-k8s - production-конфигурация с 20+ приложениями
- https://github.com/zam-zam/helmfile-examples - обучающий пример с мультитенантным деплоем