Зачем Helm
Развернуть приложение в k8s, это десяток YAML: Deployment, Service, Ingress, ConfigMap, Secret, ServiceAccount, RBAC, HPA, PDB. Развернуть то же самое в dev / staging / prod с разными значениями, копипаст и поломки.
Helm решает три задачи:
- Шаблонизация, values.yaml + Go-templates → готовые YAML
- Релизы, install/upgrade/rollback с версионированной историей
- Дистрибуция, chart как артефакт (tar.gz в OCI registry или старом chartmuseum)
В 2026, фактический стандарт для пакетирования open-source приложений в k8s. nginx, postgres, redis, prometheus, grafana, cert-manager, у всех есть official chart.
Структура chart
mychart/
├── Chart.yaml # metadata: name, version, appVersion
├── values.yaml # default-значения переменных
├── values.schema.json # опционально: JSON-schema для values
├── templates/
│ ├── _helpers.tpl # переиспользуемые шаблоны
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── configmap.yaml
│ └── NOTES.txt # печатается после install
├── charts/ # вложенные dependency-charts
└── crds/ # CRD'ы устанавливаются ДО templates
Chart.yaml, metadata
apiVersion: v2 # v2 = Helm 3
name: my-app
description: My production app
type: application # application | library
version: 1.4.2 # version самого chart (SemVer)
appVersion: "2.6.0" # version приложения внутри
dependencies:
- name: postgresql
version: "12.x.x"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
Различай: version меняется при изменениях chart (даже
если код приложения тот же), appVersion, версия приложения.
Templates, Go templates над YAML
Файлы в templates/ рендерятся через Go-template engine. Доступны:
.Values, содержимое values.yaml (плюс overrides из CLI).Release,.Release.Name,.Release.Namespace,.Release.Revision.Chart, содержимое Chart.yaml.Capabilities, версия k8s, доступные API.Files, статичные файлы chart'а (для configMap)
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-app.fullname" . }} labels: {{- include "my-app.labels" . | nindent 4 }}spec:
replicas: {{ .Values.replicaCount }}template:
spec:
containers:
- name: app
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" {{- with .Values.resources }} resources: {{- toYaml . | nindent 12 }} {{- end }}env:
{{- range $k, $v := .Values.env }} - name: {{ $k }} value: {{ $v | quote }} {{- end }}Стандартные правила:
{{- ... -}}, убрать whitespace до/послеnindent N, отступ N пробелов с newline'ом передtoYaml, серилизовать map/list в YAMLdefault X .Values.foo, fallback если значение пустоеrequired "foo нужен" .Values.foo, ошибка если не задано
Helpers (_helpers.tpl)
Переиспользуемые шаблоны:
{{/* labels стандартные k8s */}}{{- define "my-app.labels" -}}app.kubernetes.io/name: {{ .Chart.Name }}app.kubernetes.io/instance: {{ .Release.Name }}app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}app.kubernetes.io/managed-by: {{ .Release.Service }}helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}{{- end }}Файлы начинающиеся с _ не рендерятся как k8s-объекты, только
определяют helper'ы.
Lifecycle команд
# Локальная отладка - что выйдет, не применяя
helm template my-release ./mychart -f values-prod.yaml
# Установка (создаёт release в namespace'е)
helm install my-release ./mychart -n production --create-namespace
# Обновление (с историей версий)
helm upgrade my-release ./mychart -f values-prod.yaml
# Откат на предыдущую ревизию
helm rollback my-release # на N-1
helm rollback my-release 3 # на ревизию 3
# История релизов
helm history my-release
# Список всех релизов
helm list -A
# Удалить
helm uninstall my-release
Каждый release хранится как Secret типа helm.sh/release.v1
в namespace'е. Содержит сжатый JSON со всеми манифестами и
values. Это база для rollback'а.
values.yaml, overrides
# values.yaml (default)
replicaCount: 1
image:
repository: nginx
tag: "" # пусто → fallback на Chart.AppVersion
pullPolicy: IfNotPresent
resources:
requests: { cpu: 100m, memory: 128Mi }ingress:
enabled: false
# Переопределить из CLI или из второго values-файла
helm install my-release ./mychart \
-f values.yaml -f values-prod.yaml \
--set replicaCount=3 \
--set image.tag=v2.6.0 \
--set-string env.DEBUG=false # принудительно строкой
Порядок: default values.yaml → -f files (по порядку) → --set. Поздние overridят ранние.
Helm vs kustomize, когда что
| Признак | Helm | Kustomize |
|---|---|---|
| Подход | templating (Go-templates) | overlays (патчи) |
| Версионирование релиза | да, в Secret | нет (нативно) |
| Установка | helm install | kubectl apply -k ./ |
| Кривая обучения | средняя (templating язык) | пологая (только YAML + патчи) |
| Дистрибуция | chart-репо, OCI registry | git-репо |
| Conditional logic | сильная (if/else/range) | слабая (только overlays) |
| Hooks (pre/post-install) | да | нет |
| Когда выбирать | дистрибуция чужих приложений, сложная логика | свои деплои с парой окружений |
Часто комбинируют: helm для общедоступных chart'ов (postgres-operator, ingress-nginx), kustomize для своих app-deployments. Или Helmfile / ArgoCD как orchestrator поверх обоих.
Hooks
Special-аннотации в манифестах для запуска до/после lifecycle:
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration
annotations:
"helm.sh/hook": pre-upgrade,pre-install
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
Помогает запустить миграции БД до апдейта приложения. Минус: hook-Jobs не учитываются в release-истории, при rollback не откатятся.
Dependencies
Chart может зависеть от других chart'ов:
# Chart.yaml
dependencies:
- name: postgresql
version: "12.x.x"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled # включается через values
alias: db # обращаться через .Values.db
helm dependency update # скачивает в charts/
helm dependency build # из Chart.lock
Subchart values переопределяются через корневой values.yaml:
postgresql: # имя subchart'а
auth:
database: myapp
username: myapp
OCI registries, Helm 3.8+
Chart можно публиковать в OCI registry (Docker Hub, ECR, GHCR):
helm package ./mychart
▸mychart-1.4.2.tgz
helm push mychart-1.4.2.tgz oci://ghcr.io/me/charts
helm install my-rel oci://ghcr.io/me/charts/mychart --version 1.4.2
Бонус: можно подписывать через [[image-signing-cosign|cosign]] как обычные образы.
Когда что-то пошло не так
Error: UPGRADE FAILED: another operation (install/upgrade/rollback) is in progress, упал предыдущий upgrade.helm history, потомhelm rollbackили удалить «pending» release-secret вручную.unable to recognize "": no matches for kind "Foo", CRD не установлен. Положи вcrds/или установи отдельно--skip-crds.- values рендерятся не так как ожидаешь,
helm template my-rel ./chart -f values.yamlлокально, посмотри YAML до apply. {{ .Values.foo.bar }}падает с nil pointer, нет defaults в values.yaml. Используй(.Values.foo).barилиdefault.- Hook job висит, нет
hook-delete-policy, остаётся как pod после успеха. Добавитьhook-succeeded. - release есть, объекты исчезли, кто-то прошёлся
kubectl deleteмимо Helm.helm upgrade --forceпересоздаст. Воспитательная беседа с командой о mutability через Helm. - Secrets bloat, много upgrade'ов, много Secret'ов в namespace'е.
--history-max 10ограничивает количество. Error: rendered manifests contain a resource that already exists, был создан вручную тот же объект. Удалить старый илиhelm install --take-ownership(Helm 3.14+).