Helm глубже: шаблоны, хуки, зависимости, OCI

Рейтинг: 48.7% · 7 голосов
Kubernetes для разработчиков: поды, деплойменты, сервисы, ingress, конфиги и отладка. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
anton_k8s
Сообщения: 55
Зарегистрирован: 12 май 2026, 03:23

Helm глубже: шаблоны, хуки, зависимости, OCI

Сообщение anton_k8s »

Оглавление курса (48)
  1. Зачем нужен Kubernetes и из чего состоит кластер
  2. Поднимаем локальный кластер: minikube и kind
  3. Поды: базовая единица запуска
  4. Deployment и ReplicaSet: управляем репликами
  5. Service: сетевой доступ к подам
  6. ConfigMap и Secret: выносим конфигурацию
  7. Ingress: пускаем трафик снаружи
  8. Хранилище: Volumes и PersistentVolumeClaim
  9. Namespaces, requests и limits
  10. Health checks: liveness и readiness пробы
  11. Отладка: почему под не стартует
  12. Helm: пакетный менеджер для Kubernetes
  13. Базовая безопасность: RBAC и доступы
  14. Job и CronJob: разовые и периодические задачи
  15. StatefulSet и DaemonSet: stateful-нагрузки и системные агенты
  16. Стратегии обновления и планирование: rollout и rollback, graceful shutdown, nodeSelector, affinity, taints
  17. Автомасштабирование: HPA по метрикам, обзор VPA и Cluster Autoscaler
  18. Наблюдаемость: логи, метрики, events, обзор Prometheus и Grafana
  19. Безопасность глубже: securityContext, Pod Security Standards, NetworkPolicy, шифрование секретов
  20. Архитектура control plane: api-server, etcd, scheduler
  21. Узел кластера: kubelet, kube-proxy и container runtime
  22. Декларативная модель: reconciliation, контроллеры, CRD
  23. kubectl профессионально: get, describe, explain, jsonpath
  24. Под глубже: init, sidecar, lifecycle hooks и QoS
  25. Метки, селекторы и организация ресурсов
  26. Сервисы и kube-proxy глубже: типы, IPVS, EndpointSlices
  27. Ingress, ingress-контроллеры и Gateway API
  28. Секреты в кластере: шифрование, External Secrets, Vault
  29. Хранилище глубже: PV, StorageClass, CSI и StatefulSet
  30. Сеть кластера: CNI, NetworkPolicy и CoreDNS
  31. Планировщик: affinity, taints, topology spread
  32. Ресурсы, QoS и вытеснение: requests, limits, eviction
  33. Helm глубже: шаблоны, хуки, зависимости, OCI (вы здесь)
  34. Kustomize и управление конфигурацией без шаблонов
  35. RBAC и аутентификация глубже
  36. Admission и Pod Security: контроль на входе
  37. Policy as code: Kyverno и OPA Gatekeeper
  38. GitOps: Argo CD и Flux
  39. Операторы и CRD: расширяем Kubernetes
  40. Сервис-меш: Istio, Linkerd и когда он нужен
  41. Эксплуатация кластера: апгрейд, узлы, бэкап etcd
  42. Где запускать кластер: managed, self-hosted, k3s, Deckhouse
  43. Траблшутинг кластера: Pending, CrashLoop, узлы NotReady
  44. Стоимость и эффективность кластера: FinOps
  45. CI/CD в Kubernetes: сборка, деплой, прогрессивные релизы
  46. Лучшие практики и антипаттерны Kubernetes
  47. Сквозной проект и путь дальше: от манифеста до прода
  48. Деплой приложений через Argo CD: Application, sync и App-of-Apps
Зачем нужен Helm и какую боль он лечит

Представь, что у тебя 4 окружения - dev, stage, prod-eu, prod-ru - и для каждого почти одинаковый набор манифестов: Deployment, Service, Ingress (или Gateway), ConfigMap, HPA, ServiceAccount. Отличаются они на копейки: число реплик, имя образа, домен, лимиты, флаги. Если копировать YAML руками, ты получишь четыре расходящиеся версии правды, и через месяц никто не вспомнит, почему на prod-ru лимит памяти 512Mi, а на stage 256Mi. Это и есть боль, которую решает helm: один параметризованный пакет (chart) плюс набор значений на каждое окружение.

Helm - это менеджер пакетов для Kubernetes. По смыслу как apt или npm, только пакет здесь - это helm chart, архив с шаблонами манифестов. Установленный экземпляр чарта называется release. Команда helm install рендерит шаблоны в готовые манифесты и отправляет их в API-сервер; helm upgrade обновляет уже существующий релиз; helm rollback откатывает на прошлую ревизию. В этом уроке мы не повторяем "helm install nginx" из быстрого старта, а лезем под капот: как устроены Go-шаблоны, как работают хуки жизненного цикла, как подключать зависимости и почему чарты в 2026 живут в OCI-реестрах рядом с образами.

Важно сразу понять модель состояния. В Helm 3 нет Tiller (серверного компонента из Helm 2). Состояние релиза хранится в обычных Secret-ах в namespace релиза - по одному Secret на ревизию, с типом helm.sh/release.v1. Внутри лежит gzip+base64 снимок того, что было применено. Поэтому helm install kubernetes-кластеру не требует ничего, кроме твоих прав в namespace: вся логика на клиенте.

Изображение

Анатомия чарта: из чего собран helm chart

Минимальный чарт - это директория с предсказуемой структурой. Сгенерировать скелет:

Код: Выделить всё

helm create webapp
Внутри ты увидишь примерно такое дерево:

Код: Выделить всё

webapp/
  Chart.yaml          # метаданные: имя, версия, apiVersion, зависимости
  values.yaml         # значения по умолчанию
  values.schema.json  # (опционально) JSON Schema для валидации values
  charts/             # вендоренные субчарты (зависимости)
  crds/               # CRD, ставятся ДО рендера шаблонов
  templates/
    _helpers.tpl      # именованные шаблоны (helpers), не рендерятся сами по себе
    deployment.yaml
    service.yaml
    NOTES.txt         # текст, который печатается после install
  .helmignore
Chart.yaml для современного чарта обязан иметь apiVersion: v2 (v1 - это эпоха Helm 2, там зависимости жили в отдельном requirements.yaml). Поле type бывает application или library; library-чарт нельзя установить, он только отдаёт хелперы другим чартам. version - это версия самого чарта (по SemVer), appVersion - версия приложения внутри, они независимы.

Код: Выделить всё

apiVersion: v2
name: webapp
description: Демо веб-приложение
type: application
version: 1.4.2
appVersion: "2.7.0"
Файлы в templates/, имена которых начинаются с подчёркивания или это NOTES.txt, особенные: подчёркивание означает "это не объект Kubernetes, а partial". Всё остальное рендерится в манифесты и применяется в кластер.

Go-шаблоны: .Values, .Release, range, if, with

Helm рендерит шаблоны движком text/template из Go, расширенным функциями из библиотеки Sprig. В шаблон передаётся объект верхнего уровня, к полям которого ты обращаешься через точку. Главные ветки:
  • .Values - дерево из values.yaml, плюс то, что переопределили через --set и -f.
  • .Release - .Release.Name, .Release.Namespace, .Release.Revision, .Release.IsInstall, .Release.IsUpgrade.
  • .Chart - содержимое Chart.yaml (.Chart.Name, .Chart.Version, .Chart.AppVersion).
  • .Capabilities - что умеет кластер (.Capabilities.KubeVersion, доступные API).
  • .Files - доступ к произвольным файлам чарта (например, залить конфиг через .Files.Get).
Простейший Deployment с подстановкой:

Код: Выделить всё

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-webapp
  labels:
    app.kubernetes.io/name: {{ .Chart.Name }}
spec:
  replicas: {{ .Values.replicaCount }}
  template:
    spec:
      containers:
        - name: web
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          ports:
            - containerPort: {{ .Values.service.port }}
{{- with .Values.resources }}
          resources:
            {{- toYaml . | nindent 12 }}
{{- end }}
{{- if .Values.env }}
          env:
{{- range $k, $v := .Values.env }}
            - name: {{ $k }}
              value: {{ $v | quote }}
{{- end }}
{{- end }}
Разберём по косточкам. if вставляет блок только если условие истинно (пустая строка, 0, nil, пустой список - ложь). with переключает текущую область видимости: внутри with .Values.resources точка . указывает уже на resources, а не на корень - удобно, но помни, что .Release там недоступен напрямую. range итерирует список или словарь; форма $k, $v := даёт ключ и значение. Конструкция {{- ... }} с минусом съедает пробелы и перевод строки слева, что критично для чистого YAML.

Функции делают шаблоны живыми. default подставляет запасное значение, если поле пустое. quote оборачивает в кавычки (спасает от того, что "true" или "12345" YAML примет за bool/число). toYaml сериализует произвольное дерево обратно в YAML - незаменимо для блоков resources, nodeSelector, affinity. required валит рендер с понятной ошибкой, если значение не задано:

Код: Выделить всё

image: {{ required "Укажи image.repository в values" .Values.image.repository }}
nindent N - это indent с переводом строки в начале: вставляет \n и сдвигает каждую строку на N пробелов. Именно nindent в паре с toYaml - стандартный способ воткнуть многострочный блок на нужный уровень вложенности.

Named templates, include и tpl - переиспользование без копипасты

Чтобы не дублировать набор лейблов в каждом файле, их выносят в _helpers.tpl как именованный шаблон:

Код: Выделить всё

{{- define "webapp.labels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version }}
{{- end -}}
Подключают такой блок через include, а не через template. Разница принципиальна: template - это statement, его нельзя пропустить через пайп, а include - функция, возвращающая строку, поэтому работает {{ include "webapp.labels" . | nindent 4 }}. Второй аргумент . - это контекст, который ты передаёшь внутрь шаблона; забудешь его - и .Chart внутри окажется пустым.

Отдельная функция tpl рендерит строку как шаблон в рантайме. Это нужно, когда пользователь кладёт в values уже шаблонизированное значение, например ingress.host: "{{ .Release.Name }}.example.com". Без tpl это уедет в манифест дословно; с tpl - дорендерится:

Код: Выделить всё

host: {{ tpl .Values.ingress.host . | quote }}
Хуки: вмешиваемся в жизненный цикл релиза

Иногда нужно выполнить действие не "вместе" с релизом, а строго до или после. Классика - миграции БД перед раскаткой новой версии или прогрев кеша после. Для этого есть хуки: обычный манифест (чаще Job) с аннотацией helm.sh/hook. Helm вынимает его из основного потока и запускает в нужной фазе.

Код: Выделить всё

apiVersion: batch/v1
kind: Job
metadata:
  name: {{ .Release.Name }}-db-migrate
  annotations:
    "helm.sh/hook": pre-upgrade,pre-install
    "helm.sh/hook-weight": "-5"
    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
  backoffLimit: 2
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          command: ["./migrate", "up"]
Фазы, которые встречаются чаще всего: pre-install, post-install, pre-upgrade, post-upgrade, pre-delete, post-delete, plus test (запускается по helm test). hook-weight задаёт порядок: чем меньше число, тем раньше - то есть -5 отработает до 0. Веса сортируются как строки-числа в рамках одной фазы. hook-delete-policy решает судьбу объекта: before-hook-creation удаляет прошлый одноимённый объект перед новым запуском (без этого второй upgrade упадёт с "job already exists"), hook-succeeded чистит после успеха, hook-failed оставляет упавший Job для разбора.

Ключевой нюанс под капотом: Helm ждёт, пока хук-под дойдёт до Completed, и только потом продолжает. Если миграция упала - весь upgrade считается провалившимся, и основные манифесты не применяются. Это и хорошо (не выкатим код на несовместимую схему), и опасно (хук без таймаута может подвесить пайплайн - ставь activeDeadlineSeconds на Job).

Зависимости: dependencies, condition и tags

Реальное приложение редко одиноко: ему нужен PostgreSQL, Redis, очередь. Вместо того чтобы тащить их манифесты в свой чарт, объявляешь зависимости в Chart.yaml:

Код: Выделить всё

dependencies:
  - name: postgresql
    version: "15.5.x"
    repository: "https://charts.bitnami.com/bitnami"
    condition: postgresql.enabled
  - name: redis
    version: "19.x.x"
    repository: "oci://registry-1.docker.io/bitnamicharts"
    condition: redis.enabled
    tags:
      - cache
После правки запускаешь helm dependency update - Helm скачает субчарты в charts/ и зафиксирует точные версии в Chart.lock (аналог package-lock.json; коммить его в git). condition включает/выключает зависимость по булеву значению из values: если postgresql.enabled: false, субчарт не рендерится вовсе - удобно, когда на prod ты используешь внешнюю Managed-БД (например, Managed PostgreSQL в Yandex Cloud), а на dev поднимаешь контейнер. tags - групповой выключатель: можно одной строкой tags.cache: false погасить все компоненты с этим тегом. Значения для субчарта передаются через вложенную секцию его именем: всё под postgresql: в твоём values.yaml уезжает в субчарт postgresql.

OCI-репозитории: чарты живут рядом с образами

Старая модель Helm - это HTTP-репозиторий с index.yaml. С Helm 3.8 общедоступна (GA) поддержка OCI: чарт пакуется как OCI-артефакт и кладётся в тот же container registry, что и образы. Это убирает отдельную инфраструктуру для index.yaml и даёт единый ACL/скан на реестр. Поддерживают Docker Hub, GHCR, Yandex Container Registry, VK Cloud, Harbor.

Код: Выделить всё

helm package webapp                        # webapp-1.4.2.tgz
helm registry login cr.yandex -u <user>
helm push webapp-1.4.2.tgz oci://cr.yandex/<registry-id>
# установка прямо из реестра
helm install web oci://cr.yandex/<registry-id>/webapp --version 1.4.2
Обрати внимание: при oci:// имя чарта - это часть пути, а версия задаётся флагом --version (тег артефакта), а не как у HTTP-репозиториев. В dependencies для OCI repository указывают как oci://... без имени чарта в конце - Helm допишет name сам.

Проверяем до кластера: template, lint, diff

Никогда не применяй чарт вслепую. helm lint ловит структурные ошибки и плохие практики:

Код: Выделить всё

$ helm lint ./webapp
==> Linting ./webapp
[INFO] Chart.yaml: icon is recommended
[ERROR] templates/: parse error at (webapp/templates/deployment.yaml:14): unexpected "}" in operand
Error: 1 chart(s) linted, 1 chart(s) failed
helm template рендерит шаблоны локально, без обращения к кластеру, и печатает итоговые манифесты - так ты глазами проверяешь, что nindent не съел отступ и образ собрался верно:

Код: Выделить всё

$ helm template web ./webapp --set replicaCount=3 | head -n 8
---
# Source: webapp/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-webapp
spec:
  replicas: 3
Для апгрейдов незаменим плагин helm diff (ставится helm plugin install): он показывает, что именно изменится в кластере, ещё до применения - это спасает от случайного пересоздания PVC или смены selector у Service. А helm upgrade --install --dry-run=server прогонит рендер через admission-контроллеры кластера, не записывая ничего.

Много релизов: helmfile, Argo CD, Flux

Когда релизов десятки, императивный helm install по одному превращается в боль. Тут два пути. Первый - helmfile: декларативный helmfile.yaml описывает список релизов, их values и порядок, а helmfile apply приводит кластер к этому состоянию. Второй, более продакшен-зрелый, - GitOps: Argo CD или Flux держат желаемое состояние в git и сами синхронизируют его в кластер. В Argo CD ты заводишь Application, указываешь репозиторий с чартом и values - и контроллер сам делает рендер и применяет, давая drift-детекцию и автооткат. Helm здесь часто работает только как шаблонизатор (Argo рендерит через helm template и применяет сам), поэтому хуки helm в этом режиме могут вести себя иначе - проверяй документацию своего инструмента.

Типичные грабли и антипаттерны
  • Отступы в YAML. Самая частая ошибка. Путаница indent/nindent ломает структуру, и kubectl ругается на "did not find expected key". Всегда прогоняй helm template и смотри глазами; toYaml без nindent почти всегда баг.
  • --reuse-values на апгрейде. Флаг тащит значения прошлого релиза и накладывает поверх только новые --set. Если ты при этом ещё и поднял версию чарта с новыми ключами в values - получишь дыры: новые дефолты не подхватятся, потому что Helm возьмёт старый набор. Для предсказуемости держи values в git и ставь с -f values.yaml без --reuse-values, либо используй --reset-then-reuse-values осознанно.
  • Числа и булевы без quote. Тег образа "12345" или строка "true" без кавычек уедут как число/bool - и манифест либо упадёт, либо тихо изменит смысл. quote по умолчанию для строковых значений из values.
  • Незакрытый хук-Job. Хук без hook-delete-policy: before-hook-creation падает на втором апгрейде. Без activeDeadlineSeconds зависший хук вешает весь релиз.
  • CRD из templates/. CRD кладут в crds/ (ставятся один раз до шаблонов и не удаляются при helm uninstall) либо ставят отдельно; рендер CRD как обычного шаблона создаёт гонки.
  • Helm vs Kustomize. Это не "что лучше", а разные инструменты. Helm - шаблоны плюс пакетирование и версионирование с хуками и зависимостями; Kustomize - наложение патчей (overlays) на готовые манифесты без логики и без условий. Helm силён, когда чарт переиспользуют наружу и нужна параметризация; Kustomize - когда у тебя свои манифесты и хочется чистых diff-патчей без шаблонного синтаксиса. На практике их комбинируют: рендерят helm template и патчат Kustomize сверху.
Мини-лаба: собрать и обкатать свой чарт
  • helm create lab и удали лишнее из templates/, оставив deployment.yaml и service.yaml.
  • Вынеси набор лейблов в _helpers.tpl через define, подключи в обоих файлах через include ... . | nindent 4.
  • Добавь в values.yaml блок env как словарь и отрендери его через range; проверь helm template lab ./lab.
  • Сделай хук-Job pre-install с hook-weight "-1" и hook-delete-policy: before-hook-creation,hook-succeeded, который просто печатает echo.
  • Объяви зависимость redis с condition: redis.enabled, выполни helm dependency update, посмотри Chart.lock.
  • Запакуй helm package lab, залогинься в любой OCI-реестр и сделай helm push, затем helm install из oci://.
  • Поломай отступ в шаблоне намеренно и поймай ошибку через helm lint и helm template - так ты запомнишь, как читать parse error.
Контрольные вопросы
  • Чем include отличается от template и почему для пайпов через nindent годится только первый?
  • Что произойдёт при helm upgrade, если pre-upgrade хук-Job завершится с ошибкой, и в каком состоянии останется релиз?
  • Зачем нужны condition и tags в dependencies и как через них отключить субчарт PostgreSQL в пользу внешней Managed-БД?
  • Чем установка чарта из oci:// отличается по синтаксису от установки из классического HTTP-репозитория?
Итог

Helm - это не "ещё один способ применить YAML", а полноценный пакетный менеджер с шаблонами, версионированием, хуками жизненного цикла и зависимостями. Понимая, как Go-шаблоны видят .Values и .Release, как include и tpl убирают копипасту, как хуки и веса управляют порядком, а condition/tags - составом релиза, ты перестаёшь бояться чужих чартов и начинаешь писать свои. Добавь сюда OCI-реестры вместо отдельной инфраструктуры и связку с GitOps - и получишь воспроизводимую, проверяемую (helm template/lint/diff) доставку в Kubernetes от dev до prod.
👍7 ❤️2 🔥2 😄 🤔1
Аватара пользователя
markmar
Сообщения: 1
Зарегистрирован: 20 май 2026, 23:34

Re: Helm глубже: шаблоны, хуки, зависимости, OCI

Сообщение markmar »

Долго не доходило про include vs template, а тут наконец щёлкнуло - спасибо за пример с nindent 4. Раньше тупо копировал лейблы в каждый файл.
👍1 ❤️ 🔥 😄 🤔
Аватара пользователя
elixirguru
Сообщения: 1
Зарегистрирован: 20 май 2026, 22:21

Re: Helm глубже: шаблоны, хуки, зависимости, OCI

Сообщение elixirguru »

А правда что в Argo CD хуки helm не всегда отрабатывают как ожидаешь? Гонял pre-upgrade Job для миграций - в helm install работает, через ArgoCD ведёт себя иначе, теперь понял почему.
👍 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
Ресурсы, QoS и вытеснение: requests, limits, eviction
Следующая глава →
Kustomize и управление конфигурацией без шаблонов

Все главы курса «Kubernetes: оркестрация контейнеров от основ до продакшена»

Поделиться темой: ✈ Telegram VK
Похожие запросы: helm chart как установить приложение в kuberneteshelm глубже шаблоны хуки и зависимости чартов

Вернуться в «Kubernetes на практике»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость