Анатомия чарта: из чего состоит пакет

Создаём первый чарт командой helm create и разбираем каждый файл и папку внутри.

Чарт — это директория с предсказуемой структурой. Helm узнаёт чарт по наличию Chart.yaml в корне; всё остальное — соглашения, которым он следует.

Полезная аналогия: чарт — это как deb- или rpm-пакет в мире Linux, только для Kubernetes. У него есть метаданные (что за пакет, какая версия), есть «настройки по умолчанию» и есть содержимое, которое разворачивается в систему. Разница в том, что чарт не хранит готовые манифесты, а генерирует их при установке — поэтому один и тот же чарт ставит и dev-стенд с одной репликой, и прод-кластер с десятью, просто на разных значениях. Эта способность подстраиваться и делает чарт переиспользуемой единицей, а не одноразовым YAML-файлом.

Почему структура именно такая и почему её важно соблюдать? Helm не сканирует директорию «как получится» — у него жёсткие ожидания: дефолты он ищет в values.yaml, шаблоны — только в templates/, зависимости — только в charts/. Положите шаблон не в ту папку, и он либо не отрендерится, либо отрендерится не вовремя. Поэтому новичку выгоднее принять конвенцию как данность, а не выдумывать свою раскладку: вся экосистема (линтеры, IDE-плагины, CI-проверки, тысячи чужих чартов) рассчитана ровно на эту структуру.

Самый быстрый способ увидеть структуру — сгенерировать заготовку:

helm create webapp
tree webapp

Вывод:

webapp/
├── Chart.yaml
├── values.yaml
├── charts/
├── templates/
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── serviceaccount.yaml
│   ├── hpa.yaml
│   ├── NOTES.txt
│   └── tests/
│       └── test-connection.yaml
└── .helmignore

Chart.yaml — паспорт чарта

Обязательный файл с метаданными. Минимальный набор:

apiVersion: v2          # v2 = Helm 3
name: webapp
description: Веб-приложение
type: application       # или library
version: 0.1.0          # версия ЧАРТА (SemVer)
appVersion: "1.0.0"     # версия приложения внутри (строка!)

Запомните различие: version — версия упаковки (растёт при любой правке чарта), appVersion — версия вашего приложения (растёт при релизе кода). apiVersion: v2 отличает чарты Helm 3 от старых v1.

Почему два разных поля версии, а не одно? Потому что чарт и приложение живут своими жизнями. Вы можете поправить отступ в шаблоне, добавить новый параметр или исправить опечатку в комментарии — код приложения при этом ни на байт не изменился, но упаковка стала другой, и потребители должны это заметить. Тогда вы поднимаете version, оставив appVersion прежним. И наоборот: вышел новый образ приложения 2.5.0 — вы меняете appVersion и, как правило, тоже бампаете version чарта (раз изменился дефолтный тег). Эта пара версий — то, на что смотрят системы вроде ArgoCD и репозитории чартов, чтобы понять, что именно поменялось и нужно ли катить обновление.

Поле description и необязательные keywords, home, icon кажутся косметикой, но именно они формируют карточку чарта в репозитории и в выводе helm search. Если вы публикуете чарт для команды или сообщества, осмысленное описание экономит коллегам минуты разбирательств «а что это вообще ставит».

values.yaml — значения по умолчанию

Здесь живут все настраиваемые параметры с дефолтными значениями. Шаблоны обращаются к ним через .Values. Это «панель управления» чарта:

replicaCount: 1
image:
  repository: nginx
  tag: "1.25.3"
  pullPolicy: IfNotPresent
service:
  type: ClusterIP
  port: 80

Ключевая мысль: values.yaml — это не «конфиг для разработчика чарта», а интерфейс для того, кто чарт устанавливает. Автор шаблонов и установщик — часто разные люди. Установщик не обязан читать ваши шаблоны; он смотрит в values.yaml, видит доступные ручки и крутит их под свой стенд через -f my-values.yaml или --set. Поэтому к нему относятся как к документированному API: понятные имена, разумная вложенность, дефолты, при которых чарт хотя бы запустится без единой правки.

templates/ — шаблоны манифестов

Сердце чарта. Каждый .yaml здесь — шаблон Kubernetes-манифеста с вставками на языке Go templates. При install Helm рендерит их, подставляя значения, и применяет результат. Файлы, имена которых начинаются с подчёркивания (_helpers.tpl), — особые: они не дают манифестов сами по себе, а хранят переиспользуемые куски.

Стоит сразу принять важную вещь: имя файла в templates/ для Helm почти ничего не значит. Helm не смотрит, что файл называется deployment.yaml — он просто рендерит его содержимое и склеивает все результаты вместе, разделяя их по YAML-разделителю ---. Можно описать три ресурса в одном файле или раскидать по трём — итог в кластере будет одинаковым. Разбиение по файлам — это удобство для человека, а не требование движка. На практике придерживаются правила «один файл — один тип ресурса» (deployment.yaml, service.yaml, ingress.yaml): так чарт легче читать и ревьюить.

Подпапка templates/tests/ — отдельная история. Манифесты в ней помечены аннотацией helm.sh/hook: test и не разворачиваются при обычной установке. Они запускаются командой helm test уже после релиза и проверяют, что приложение действительно живо (например, что сервис отвечает на запрос). Это встроенный способ smoke-теста релиза, и сгенерированный test-connection.yaml — его минимальный пример.

charts/ — вложенные чарты (зависимости)

Сюда складываются subcharts — другие чарты, от которых зависит ваш (например, ваш webapp зависит от postgresql). Helm устанавливает их вместе с родителем. Подробно — в разделе про зависимости.

_helpers.tpl и NOTES.txt

_helpers.tpl — библиотека именованных шаблонов (например, генерация полного имени релиза), чтобы не повторять логику в каждом манифесте. NOTES.txt — текст, который Helm печатает после установки (как достучаться до приложения); тоже шаблонизируется.

.helmignore

Как .gitignore, но для упаковки чарта: перечисляет, что не класть в .tgz (тесты, README-черновики, мусор IDE).

Как Helm читает чарт под капотом

При любой операции Helm загружает чарт в память как дерево: метаданные из Chart.yaml, дефолты из values.yaml, все файлы templates/ и рекурсивно все subchart-ы из charts/. Затем он формирует объект контекста (.Values, .Release, .Chart, .Capabilities) и прогоняет каждый шаблон из templates/ через движок Go template. Файлы _*.tpl и NOTES.txt в финальные манифесты не превращаются. Всё, что отрендерилось в непустой YAML, отправляется в кластер.

Тонкость, которая экономит часы отладки: рендеринг и применение — это две раздельные фазы. Сначала Helm целиком собирает текст всех манифестов на своей стороне, ничего ещё не отправляя в кластер. И только если весь набор собрался без ошибок шаблонизатора, он передаёт результат в Kubernetes. Поэтому ошибку в шаблоне можно поймать заранее, без риска что-либо сломать, командой helm template — она проходит ровно первую фазу и печатает готовый YAML в терминал. Это ваш главный инструмент при разработке чарта: правите шаблон, гоняете helm template, смотрите вывод, повторяете.

Ещё одна деталь про пустоту. Если шаблон после рендера дал пустую строку или только комментарии — Helm не создаст из него ресурс, а просто пропустит. Это используют намеренно: оборачивают весь манифест в if, и при выключенном параметре файл «исчезает». Так один чарт умеет включать и выключать целые ресурсы (ingress, hpa, serviceaccount) без удаления файлов — достаточно флага в values.

Частые ошибки

  • Файл в templates/, который не должен рендериться. Любой не-_ файл там Helm попытается отрендерить как манифест. Вспомогательные куски называйте с подчёркиванием.
  • Число вместо строки в appVersion. Версии вроде 1.0 в YAML станут числом 1; берите в кавычки.
  • Путать version и appVersion. При правке шаблона поднимать нужно version чарта, даже если код приложения не менялся.

Итог

  • helm create даёт каноническую структуру; чарт опознаётся по Chart.yaml.
  • Chart.yaml — метаданные, values.yaml — дефолты, templates/ — шаблоны, charts/ — зависимости.
  • Файлы _*.tpl и NOTES.txt не становятся манифестами — это вспомогательные.
Проверьте себя
1. По какому файлу Helm опознаёт директорию как чарт?
Avalues.yaml
BChart.yaml в корне
Ctemplates/deployment.yaml
D_helpers.tpl
2. Чем отличаются version и appVersion в Chart.yaml?
AНичем
Bversion — версия упаковки чарта, appVersion — версия приложения внутри
CappVersion для Kubernetes
Dversion всегда равна appVersion
3. Почему файл templates/_helpers.tpl не превращается в манифест?
AОн пустой
BФайлы с префиксом _ Helm трактует как вспомогательные, а не как манифесты
CОн в .helmignore
DЭто баг Helm