Встроенные объекты: Release, Chart, Values, Capabilities

Helm передаёт в каждый шаблон набор готовых объектов — разбираем, что в них и зачем.

Встроенные объекты — это данные, которые Helm кладёт в корневой контекст шаблона: информация о релизе, чарте, значениях и возможностях кластера. Доступ через ведущую точку: .Release.Name, .Chart.Version и т.д.

Откуда вообще берутся эти объекты и почему их не нужно объявлять? Перед тем как рендерить шаблоны, Helm формирует словарь с заранее известными ключами и передаёт его движку как корневой контекст. Часть данных он берёт из ваших файлов (.Values, .Chart), часть — из контекста самой операции (.Release), часть — спрашивает у кластера (.Capabilities). Для автора шаблона это выглядит так, будто данные «уже есть» — их остаётся только прочитать. Понимать происхождение полезно: оно объясняет, почему одни объекты доступны всегда, а другие (например, реальная версия кластера) — только при работе с живым кластером.

Объекты удобно делить на две группы по тому, кто их заполняет. .Values — единственный, чьё содержимое определяете вы: это ваши настройки. Остальные (.Release, .Chart, .Capabilities, .Files, .Template) заполняет сам Helm, и вы их только читаете. Это разделение объясняет и соглашение об именах: встроенные объекты всегда с заглавной буквы, чтобы их нельзя было спутать с вашими ключами внутри .Values, которые вы вольны называть как угодно.

.Release — данные текущей установки

ПолеЗначение
.Release.Nameимя релиза (что вы дали в install)
.Release.Namespacenamespace установки
.Release.Revisionномер ревизии (1, 2, ...)
.Release.IsInstall / .IsUpgradetrue на install / на upgrade

.Release.Name — самый используемый объект: им префиксуют имена ресурсов, чтобы два релиза одного чарта не конфликтовали.

metadata:
  name: {{ .Release.Name }}-config
  namespace: {{ .Release.Namespace }}

.Chart — поля Chart.yaml

Зеркало Chart.yaml: .Chart.Name, .Chart.Version, .Chart.AppVersion. Удобно для лейблов, чтобы по объекту в кластере было видно, каким чартом и какой версией он создан.

Зачем тащить версию чарта в лейблы каждого ресурса? Чтобы кластер был самодокументируемым. Через полгода кто-то найдёт в namespace непонятный Deployment, посмотрит его лейблы — и сразу увидит helm.sh/chart: webapp-1.2.0, то есть «это поставил чарт webapp версии 1.2.0». Без таких меток связь между объектом в кластере и его источником теряется, и разбираться приходится археологией. Поэтому стандартный набор лейблов app.kubernetes.io/* в сгенерированных Helm чартах — не украшение, а инструмент эксплуатации: по ним фильтруют, группируют и отслеживают ресурсы.

Обратите внимание на разделение ролей версий и здесь: в лейбл version кладут .Chart.AppVersion (версию приложения — что важно для дежурного инженера), а в helm.sh/chart.Chart.Version (версию упаковки — что важно для того, кто катит обновления). Это прямое продолжение различия из урока про Chart.yaml, доведённое до видимых меток в кластере.

labels:
  app.kubernetes.io/name: {{ .Chart.Name }}
  app.kubernetes.io/version: {{ .Chart.AppVersion }}
  helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"

.Values — ваши значения

Уже знакомый объект: слитый результат values.yaml + переопределений. Это единственный объект, содержимое которого определяете вы.

.Capabilities — что умеет кластер

Позволяет писать чарты, адаптирующиеся к версии Kubernetes и доступным API. Например, выбрать правильный apiVersion для Ingress в зависимости от версии кластера:

{{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }}
apiVersion: networking.k8s.io/v1
{{- else }}
apiVersion: networking.k8s.io/v1beta1
{{- end }}

.Capabilities.KubeVersion.Minor даёт минорную версию кластера. Это делает чарт переносимым между кластерами разных версий.

Почему это вообще проблема, которую нужно решать? Kubernetes активно меняет свои API между версиями: ресурс, живший под networking.k8s.io/v1beta1, в новых версиях переехал на v1, а в самых старых его не было вовсе. Захардкодьте один apiVersion — и чарт, отлично работающий на свежем кластере, упадёт на старом (или наоборот). .Capabilities разрывает эту жёсткую привязку: чарт спрашивает у кластера «а есть ли у тебя такой API?» и сам выбирает подходящий вариант. Так один и тот же чарт переживает обновления Kubernetes и работает в разнородном парке кластеров без форков под каждую версию.

Важная оговорка про честность данных. .Capabilities правдив ровно настолько, насколько Helm видит кластер. При реальной установке он опрашивает живой API и отвечает точно. А вот при helm template без подключения к кластеру Helm подставляет стабы — заглушки с дефолтной версией, которая может не совпасть с боевой. Если логика чарта завязана на версию, при локальном рендере честнее задавать её явно через --kube-version, иначе можно отлаживать ветку if, которая на проде никогда не выполнится.

.Files — доступ к файлам чарта

Позволяет вшить в манифест содержимое файла из чарта (например, конфиг приложения) в ConfigMap:

data:
  app.conf: |-
{{ .Files.Get "configs/app.conf" | indent 4 }}

Есть и .Files.Glob для набора файлов по маске. Важное ограничение: нельзя читать файлы из templates/ и из родительского чарта — только из своего.

.Template — о текущем шаблоне

.Template.Name и .Template.BasePath — путь текущего рендерящегося шаблона; используется редко, в основном в сложных хелперах.

Как объекты попадают в контекст под капотом

Перед рендером Helm собирает корневой объект-словарь и кладёт в него эти поля. .Release и .Capabilities формируются из текущей операции и опроса кластера (или из стабов при helm template без кластера). .Chart читается из Chart.yaml, .Values — из слияния. Все имена объектов начинаются с заглавной буквы — это соглашение Helm, отличающее встроенные объекты от ваших ключей в .Values (которые вы пишете как угодно). Внутри range/with корневой контекст подменяется, и чтобы достать встроенные объекты, используют $ — ссылку на исходный корень.

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

  • Потерять доступ к .Release внутри range. Точка там — элемент цикла; пишите $.Release.Name.
  • Полагаться на .Capabilities при helm template без кластера. Там подставляются стабы, реальная версия может отличаться; используйте --kube-version.
  • Читать .Files из templates/. Запрещено; кладите вспомогательные файлы вне templates/.

Итог

  • .Release — имя/namespace/ревизия установки; .Chart — поля Chart.yaml; .Values — ваши значения.
  • .Capabilities делает чарт переносимым между версиями кластера; .Files вшивает файлы чарта.
  • Встроенные объекты пишутся с заглавной; внутри циклов корень доступен через $.
Проверьте себя
1. Зачем имена ресурсов часто префиксуют .Release.Name?
AДля красоты
BЧтобы два релиза одного чарта не конфликтовали именами в кластере
CЭтого требует YAML
DДля ускорения
2. Для чего нужен объект .Capabilities?
AХранит секреты
BСообщает версию кластера и доступные API, чтобы чарт адаптировался
CЛогирует ошибки
DЗадаёт values
3. Как достать .Release.Name внутри блока range, где точка — элемент цикла?
AНикак
BЧерез $.Release.Name — ссылку на исходный корневой контекст
CЧерез .Release всё равно работает
DЧерез --set