Введение в Go templates: первые вставки

Превращаем статичный YAML в шаблон: первые подстановки значений через двойные фигурные скобки.

Helm использует движок Go templates. Всё, что заключено в {{ ... }}, — это инструкция движку: подставить значение, вызвать функцию или выполнить условие. Остальной текст копируется как есть.

Прежде чем писать первую вставку, полезно понять, что мы вообще делаем. У нас есть статичный YAML-манифест, который работает только для одного конкретного случая: одно имя, один образ, одно число реплик. Шаблонизация — это операция «вынеси изменчивое наружу»: всё, что от установки к установке меняется, мы заменяем на вставку, которая будет подставлять значение из values.yaml. В итоге один файл-шаблон обслуживает бесконечное число конфигураций. Это ровно тот же приём, что параметр функции вместо захардкоженной константы — только применённый к тексту манифеста.

Важно сразу зафиксировать: Go templates — это не «язык Helm», а стандартный шаблонизатор из языка Go, который Helm взял и дополнил своими функциями и объектами. Поэтому базовый синтаксис (скобки, if, range, конвейеры |) одинаков с другими инструментами на Go. Знание переносимо, и при поиске в документации стоит держать в голове это разделение: часть конструкций — общие для Go, часть — добавки Helm и библиотеки Sprig.

Возьмём кусок Deployment и параметризуем его. Слева — то, что вы пишете в templates/, справа — что получится после рендера.

Подстановка значения

Шаблон (язык — смесь YAML и Go template, поэтому подсветка без запуска):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-web
spec:
  replicas: {{ .Values.replicaCount }}
  template:
    spec:
      containers:
        - name: web
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"

При values.yaml с replicaCount: 3, image.repository: nginx, image.tag: "1.25.3" и релизе с именем my получится:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-web
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: web
          image: "nginx:1.25.3"

Откуда берутся точки: контекст

Ведущая точка . — это корневой контекст, в котором живут встроенные объекты. .Values — ваши значения, .Release — данные о релизе (имя, namespace), .Chart — поля Chart.yaml. Точка-разделитель проходит по вложенности: .Values.image.tag = поле tag внутри image внутри values.

Стоит привыкнуть читать точку как «здесь и сейчас» — это указатель на текущий контекст, и поначалу он всегда корневой. Запись .Values.image.tag разбирается слева направо именно как навигация по дереву: «от текущего контекста возьми Values, в нём image, в нём tag». Если на любом шаге элемента нет, движок по умолчанию подставит пустое значение, а не упадёт — и это коварно: опечатка .Values.imgae.tag не вызовет ошибку, а просто даст пустоту, и манифест соберётся «успешно», но неправильно. Поэтому проверять вывод helm template глазами на ранней стадии — не паранойя, а норма.

Зачем вообще нужна эта пустота-по-умолчанию? Она удобна для необязательных полей: написали annotations: {{ .Values.podAnnotations }}, пользователь ничего не задал — получили пусто, и ничего не сломалось. Но та же черта прячет опечатки. Баланс между «не падать на отсутствующем» и «ловить ошибки» решается позже функциями вроде required и default — пока достаточно знать, что молчаливая пустота существует и за ней нужен глаз.

Обрезка пробелов: дефис в скобках

Шаблонные конструкции оставляют после себя пустые строки и пробелы, что ломает отступы YAML. Дефис - у скобки убирает пробелы рядом: {{- срезает пробелы слева, -}} — справа.

spec:
  {{- if .Values.ingress.enabled }}
  rules:
    - host: {{ .Values.ingress.host }}
  {{- end }}

Без {{- на месте if и end остались бы пустые строки, и YAML мог бы стать невалидным. Управление пробелами — едва ли не главная «боль новичка» в Helm.

Корень боли в том, что управляющие конструкции (if, range, end) сами занимают строку. После рендера выражение исчезает, но перенос строки и отступ перед ним остаются — и в YAML, где значимы именно отступы и переносы, этот «мусор» ломает структуру. В большинстве языков лишняя пустая строка безвредна, в YAML — нет, и в этом вся ловушка. Дефис у скобки — это явная команда «съешь соседние пробельные символы, включая перенос строки». Запомнить направление просто: дефис «смотрит» в ту сторону, где режет пробелы, — {{- режет слева от себя, -}} справа.

Практический совет: не сыпьте дефисами наугад «чтобы наверняка». Перебор тоже вреден — можно случайно склеить две строки, которые должны были остаться раздельными. Рабочий цикл такой: написали конструкцию, прогнали helm template, посмотрели на реальные отступы в выводе и точечно добавили дефис там, где зияет лишняя пустая строка. Глаз на отступы тренируется за пару дней, и потом эта «боль» становится механической привычкой.

Комментарии в шаблонах

{{/* Это комментарий шаблона: в вывод не попадёт */}}
# А это обычный YAML-комментарий: попадёт в манифест

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

Движок проходит шаблон как текст, ищет {{ }}, вычисляет выражения в контексте (где . = корневой объект) и подставляет результат как строку. Важно: Go template ничего не знает про YAML — для него это просто текст. Поэтому корректность отступов и кавычек целиком на вас. Сначала текст полностью собирается, и лишь потом готовая строка парсится как YAML и уходит в кластер. Если на этапе YAML-парсинга отступ поехал — Helm выдаст ошибку вроде error converting YAML to JSON.

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

  • Забыть, что . может меняться. Внутри range/with точка переопределяется на текущий элемент — об этом отдельный урок.
  • Неэкранированные спецсимволы YAML. Значение с двоеточием или # без кавычек сломает YAML; оборачивайте через quote.
  • Лишние пробелы от конструкций. Без {{-/-}} отступы съезжают — частая причина невалидного манифеста.

Итог

  • Всё в {{ }} вычисляется движком; остальной текст копируется как есть.
  • . — корневой контекст: .Values, .Release, .Chart.
  • Дефис {{-/-}} обрезает пробелы; движок не знает про YAML — отступы на вас.
Проверьте себя
1. Что делает движок с текстом внутри двойных фигурных скобок?
AКопирует как есть
BВычисляет выражение в контексте и подставляет результат как строку
CИгнорирует
DПревращает в комментарий
2. Зачем нужен дефис в конструкции {{- ... }}?
AДелает текст жирным
BОбрезает пробелы/пустые строки слева, чтобы не ломать отступы YAML
CОтключает шаблон
DКомментирует строку
3. Что Go template знает про структуру YAML?
AПолностью её понимает
BНичего — для него это просто текст, корректность отступов на авторе
CПроверяет схему
DАвтоматически выравнивает