Learning Platform
Глоссарий Troubleshooting
Урок 18.03 · 25 мин
Продвинутый
Go templatesSprigvalues_helpers.tplHelm hooksincludenamed templates

Helm templating: values, helpers, hooks

helm install под капотом — это два этапа: рендеринг templates в plain YAML и применение результата к кластеру. Этот урок — про первый этап. Здесь мы разбираем, как Go templates превращаются в манифесты, какие тонкости есть с indentation и type coercion, как делать переиспользуемый код через _helpers.tpl, и как использовать hooks для координации lifecycle событий (DB migrations перед app upgrade).


awk: модель pattern-action и переменные

Built-in objects: четыре источника данных

Внутри template доступны pre-injected объекты:

  • .Values — данные из values.yaml + любые -f overrides + --set. Это user-controlled источник.
  • .Release — метаданные текущего release:
    • .Release.Name — имя релиза (то, что в helm install <name>).
    • .Release.Namespace — namespace.
    • .Release.Revision — номер ревизии (1 при install, увеличивается при upgrade).
    • .Release.IsInstall / .Release.IsUpgrade — boolean, для conditional logic.
    • .Release.Service — всегда “Helm”.
  • .Chart — содержимое Chart.yaml:
    • .Chart.Name, .Chart.Version (SemVer chart-а), .Chart.AppVersion (версия app).
  • .Capabilities — feature detection:
    • .Capabilities.KubeVersion.Major, .Capabilities.KubeVersion.Minor.
    • .Capabilities.APIVersions.Has "networking.k8s.io/v1/Ingress" — проверка наличия API.
  • .Files — доступ к файлам внутри chart:
    • .Files.Get "config/nginx.conf" — прочитать как string.
    • .Files.Glob "config/**.conf" — glob по нескольким файлам.
  • .Template — метаданные текущего template:
    • .Template.Name, .Template.BasePath.

Пример использования всех:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-{{ .Chart.Name }}
  namespace: {{ .Release.Namespace }}
  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 }}
spec:
  replicas: {{ .Values.replicaCount }}

Pipe и функции

| — это pipeline (как в shell). Значение слева передаётся как последний аргумент функции справа. Это самая частая конструкция в чартах.

# Default value, если values.yaml не указан
image: {{ .Values.image.tag | default "latest" }}

# Quote string (важно для tags типа "1.25" чтобы не стать float)
image: nginx:{{ .Values.image.tag | quote }}

# Upper/lower case
env:
  - name: APP_ENV
    value: {{ .Values.environment | upper }}

# Base64 encoding (для Secret data)
data:
  password: {{ .Values.password | b64enc }}

# Множественные функции в pipeline
name: {{ .Release.Name | trunc 63 | trimSuffix "-" }}

default — отдельно отметить: если values.yaml не задал ключ, или задал nil/empty string — берётся default. Удобно для optional полей.


Conditional logic: if / else / with

{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ .Release.Name }}
spec:
  rules:
    - host: {{ .Values.ingress.host }}
      {{- if .Values.ingress.tls }}
      tls:
        - secretName: {{ .Values.ingress.tls.secretName }}
      {{- end }}
{{- end }}
  • {{- if ... }}- слева убирает whitespace (включая newline) перед действием. Это критично для читаемого output без лишних пустых строк.
  • {{- end }} — закрывает блок. Без end template не валиден.
  • with — задаёт scope:
{{- with .Values.resources }}
resources:
  {{- toYaml . | nindent 12 }}
{{- end }}

Внутри with . — это .Values.resources (если непустое). Если пусто — блок целиком пропускается. Удобно для optional блоков.

WARNING

{{- if .Values.foo }} истинно если .Values.foo — non-empty (не nil, не "", не 0, не false, не empty slice). Если .Values.foo: 0if будет false (0 интерпретируется как falsy). Для явной проверки на existence используй hasKey .Values "foo" или (.Values.foo | default 0) | int.


range: итерация

# values.yaml
envVars:
  - name: DB_HOST
    value: postgres
  - name: DB_PORT
    value: "5432"
  - name: LOG_LEVEL
    value: info
# template
env:
  {{- range .Values.envVars }}
  - name: {{ .name }}
    value: {{ .value | quote }}
  {{- end }}

Внутри range . — текущий элемент. Если нужен индекс:

{{- range $index, $env := .Values.envVars }}
- name: {{ $env.name }}
  value: {{ $env.value | quote }}
{{- end }}

Для map:

{{- range $key, $value := .Values.labels }}
{{ $key }}: {{ $value | quote }}
{{- end }}

toYaml + nindent: сериализация map в YAML

Самый частый паттерн — взять секцию из values.yaml и вставить её as-is в template:

# values.yaml
resources:
  limits:
    cpu: 500m
    memory: 512Mi
  requests:
    cpu: 100m
    memory: 128Mi
# template
containers:
  - name: app
    resources:
      {{- toYaml .Values.resources | nindent 6 }}

Что делает:

  • toYaml берёт Go map и сериализует в YAML string.
  • nindent 6 добавляет newline + indent 6 пробелов в начало каждой линии.

Результат:

containers:
  - name: app
    resources:
      limits:
        cpu: 500m
        memory: 512Mi
      requests:
        cpu: 100m
        memory: 128Mi

Без nindent indentation поломалась бы. Это главный source багов в helm charts: неправильный nindent ломает YAML структуру, и helm template рендерит invalid YAML.

DANGER

Helm не валидирует YAML структуру до отправки на apiserver. helm template покажет тебе результат — всегда смотри, как реально отрендерилось. Тип ошибок: mapping values are not allowed in this context — это значит индентация поехала.


_helpers.tpl: переиспользуемые шаблоны

Файл templates/_helpers.tpl — это define-блоки, которые можно include в других templates. Имя начинается с _ — Helm не пытается рендерить его как K8s манифест.

# templates/_helpers.tpl
{{/*
Полное имя release-а (truncated to 63 chars for k8s label limit).
*/}}
{{- define "mychart.fullname" -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}

{{/*
Standard labels.
*/}}
{{- define "mychart.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 -}}

{{/*
Selector labels (subset of labels, used in matchLabels).
*/}}
{{- define "mychart.selectorLabels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}

Использование:

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "mychart.fullname" . }}
  labels:
    {{- include "mychart.labels" . | nindent 4 }}
spec:
  selector:
    matchLabels:
      {{- include "mychart.selectorLabels" . | nindent 6 }}

Ключевые моменты:

  • include (НЕ template) — потому что include возвращает строку, которую можно pipe-нуть в nindent. template действие выводит сразу в output без возможности pipe.
  • {{- define "name" -}} ... {{- end -}}- режут whitespace по краям, иначе define содержит ведущий/висячий newline.
  • Передаём . — это весь scope (с .Values, .Release, .Chart). Без этого helper не имеет доступа к built-in objects.

Sprig: stdlib функции

Helm включает Sprig — большую stdlib функций для templates. Частые:

  • String: upper, lower, title, trim, quote, squote, replace, substr, trunc, contains, hasPrefix.
  • Default & nil: default, empty, coalesce (первое non-empty).
  • Encoding: b64enc, b64dec, sha256sum, sha1sum.
  • Lists: list, first, last, len, has (contains), concat, uniq.
  • Math: add, sub, mul, div, mod, max, min.
  • Date: now, date "2006-01-02".
  • Generation: uuidv4, randAlphaNum, genCA, genSignedCert.

Particularly useful:

# SHA256 над rendered ConfigMap data — для rollout на изменение config
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}

Этот pattern заставляет Deployment рестарт-нуться при каждом изменении ConfigMap (потому что pod template hash меняется — Deployment делает rolling update). Stand-art для helm charts.


Helm hooks: координация lifecycle

Hooks — это обычные K8s resources (обычно Jobs) с annotation helm.sh/hook. Они выполняются на конкретных моментах lifecycle:

  • pre-install — до создания main resources в install.
  • post-install — после.
  • pre-upgrade / post-upgrade — аналогично для upgrade.
  • pre-delete / post-delete — для uninstall.
  • pre-rollback / post-rollback — для rollback.
  • test — запускается через helm test <release>.

Пример: DB migration перед каждым app upgrade.

# templates/migration-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ include "mychart.fullname" . }}-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:
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          command: ["./migrate", "up"]

Аннотации:

  • helm.sh/hook — на каком event выполняется (comma-separated).
  • helm.sh/hook-weight — порядок выполнения hooks одного типа. Меньше — раньше. По умолчанию 0.
  • helm.sh/hook-delete-policy:
    • before-hook-creation — delete предыдущую hook resource перед созданием новой (нужно, потому что Job immutable — нельзя update).
    • hook-succeeded — удалить hook resource после успешного выполнения.
    • hook-failed — удалить hook resource после failure (по дефолту НЕ удаляется, чтобы можно было разобраться).
Helm hooks: lifecycle install/upgrade
helm upgradeПользователь запускает helm upgrade web ./chart. Helm читает Chart, рендерит templates с values, сепарирует resources на main (Deployments, Services) и hooks (Jobs с annotation).
pre-upgradeСоздаются hook resources с annotation pre-upgrade. По hook-weight определяется порядок. Helm ждёт Job в Status: Completed. Если Failed — весь upgrade abort-ится, main resources не trogаются.
apply mainПосле успеха всех pre-upgrade hooks Helm применяет основные manifests (Deployment, Service, ConfigMap). Rolling update идёт по обычным K8s правилам Deployment-а.
post-upgradeПосле завершения apply (но без проверки Pod readiness без --wait) Helm запускает post-upgrade hooks: smoke tests, cache warmup, notification webhooks. Параллельно с rolling update.
hook-weightКонтроль порядка. Например: migrate (weight -5), seed (weight 0), warmup (weight 5). Helm создаёт по возрастанию weight, ждёт completion каждого перед следующим. Resources одного weight параллельно.
delete-policyhook-succeeded — удалить Job после успеха (чистый namespace). hook-failed — удалить после failure (обычно не используется — оставить для диагностики). before-hook-creation — удалить старый Job перед созданием нового (обязательно — Jobs immutable).
NOT trackedHook resources НЕ tracked как часть release. Не появляются в helm get manifest. Не удаляются при helm uninstall (только их by-product через delete-policy). helm rollback не запускает обратные hooks — migrations down скрипт надо вызывать отдельно.

Hook idempotency: главный pitfall

Helm hooks не tracked как часть release. Это значит:

  • helm get manifest не покажет hooks (если они не оставлены через delete-policy).
  • helm rollback НЕ запускает обратные действия. Откат на старую ревизию — БЕЗ rollback migrations.

Это означает: DB migration через pre-upgrade hook должна быть idempotent и backward-compatible. Иначе после rollback приложение старой версии встретит схему БД новой версии и сломается.

Pattern для миграций:

  1. Expand: добавить колонку как nullable. Старый код игнорирует, новый — заполняет.
  2. Code change: deploy новый код, который читает/пишет колонку.
  3. Migrate data: backfill старых записей.
  4. Contract: через несколько релизов сделать колонку non-null.
DANGER

Никогда не делай DROP COLUMN или RENAME через helm hook — это сломает rollback. Pattern expand-then-contract — единственный safe способ эволюционировать схему вместе с relations.


NOTES.txt: пост-инсталляционная инструкция

templates/NOTES.txt — это шаблонизированный текст, который выводится после helm install / helm upgrade. Полезно для показа пользователю, как получить доступ к новому release:

{{- if eq .Values.service.type "NodePort" }}
Get the URL by running:
  export NODE_PORT=$(kubectl get svc {{ include "mychart.fullname" . }} -o jsonpath='{.spec.ports[0].nodePort}')
  echo "http://$(minikube ip):$NODE_PORT"
{{- else if eq .Values.service.type "LoadBalancer" }}
Wait for external IP:
  kubectl get svc {{ include "mychart.fullname" . }} -w
{{- end }}

helm get notes <release> — повторно показать notes уже установленного release-а.


Killer-моменты

  • toYaml | nindent N — главный pattern для embed values секции в template. Неправильный nindent — invalid YAML.
  • {{- ... -}} режут whitespace. Без них в output появляются пустые строки. С обеих сторон — режут с обеих.
  • include vs template: include возвращает string (можно pipe), template сразу выводит. Всегда используй include в practice.
  • Hooks не tracked. helm uninstall не удалит created-by-hook resources автоматически (DB rows и т.д.). helm rollback не делает hook rollback.
  • checksum/config annotation — стандартный pattern для рестарта Deployment при изменении ConfigMap.
  • DB migrations через pre-upgrade hook должны быть idempotent + backward-compatible. Иначе rollback ломает app.
  • if .Values.foo с foo: 0 — false (0 is falsy). Используй hasKey для distinguishing missing vs zero.

Проверка знанийKnowledge check
Что делает выражение со связкой toYaml и nindent 12 (типа toYaml .Values.resources pipe nindent 12) в template, и что произойдёт если убрать nindent?
ОтветAnswer
toYaml сериализует map .Values.resources в YAML-string. nindent 12 ставит newline перед результатом и indent-ит каждую line на 12 пробелов. Это нужно, чтобы YAML структура правильно вложилась в template — например, если этот блок находится под spec.template.spec.containers[].resources, нужны 12 пробелов отступа. Без nindent toYaml вернёт string с indent 0 — она попадёт в template без отступов, YAML станет invalid. helm template покажет broken indentation: 'mapping values are not allowed in this context'. Альтернатива nindent — indent (без leading newline) — но nindent чаще нужен, потому что toYaml идёт на новой строке.
Проверка знанийKnowledge check
Helm chart с pre-upgrade hook, который делает ALTER TABLE users DROP COLUMN email. После upgrade обнаружили баг, делаем helm rollback. Что произойдёт?
ОтветAnswer
Catastrophic failure. helm rollback восстанавливает rendered manifests из target revision — старый Deployment с старой версией кода. Но hook НЕ tracked в release: helm rollback НЕ запускает inverse migration (CREATE COLUMN). Старый код запустится, попытается прочитать email — column не существует — SQL error на каждом запросе. Pattern fix: использовать expand-then-contract миграций. Сначала добавить новую колонку nullable, deploy код, который читает обе, через несколько релизов — удалить старую. Никогда DROP/RENAME в hooks. Helm hooks — для idempotent, backward-compatible изменений.
Проверка знанийKnowledge check
В чём разница между include 'foo' . и template 'foo' . в Helm template?
ОтветAnswer
template — это action, которое выводит результат defined template сразу в output. Нельзя pipe-нуть. include — это функция, которая возвращает string. Можно pipe в nindent, sha256sum, b64enc. Practical: вызов include с pipe в nindent — конструкция типа 'labels:' и далее include mychart.labels с pipe в nindent 4 — работает, потому что include результат проходит через nindent. С template такой синтаксис не работает — template не возвращает значение. Правило: всегда используй include в helpers — он более flexible.
Проверка знанийKnowledge check
Pod не перестартовал после helm upgrade с изменённым ConfigMap (поменяли value в values.yaml). Почему и как чинить?
ОтветAnswer
Deployment пересоздаёт Pod-ы только когда меняется pod template (image, env, volumes — то, что в spec.template). Просто обновление ConfigMap не trigger-ит rolling update — Pods продолжают работать со старым (cached) ConfigMap в memory или mount-нутым старым snapshot. Fix: добавить annotation checksum/config с SHA256-hash от ConfigMap content в pod template (spec.template.metadata.annotations). Hash вычисляется через include на configmap.yaml template + sha256sum функцию sprig. Когда ConfigMap меняется — hash меняется — pod template hash меняется — Deployment делает rolling update. Стандартный pattern для всех helm charts.
Проверка знанийKnowledge check
Почему _helpers.tpl начинается с _, и что произойдёт без подчёркивания?
ОтветAnswer
Helm рассматривает все файлы в templates/ как K8s манифесты для рендера. Если файл начинается с _ — Helm пропускает его при render-форе output (не пытается apply как resource). _helpers.tpl содержит только define-блоки, которые сами по себе не дают YAML — они вызываются через include из других templates. Если переименовать в helpers.tpl — Helm попытается отрендерить файл и применить как resource. Результат — пустой output (define не выводит ничего), но helm может выдать warning 'manifest is empty'. Convention: подчёркивание = utility file.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 5. Что делает выражение {{ toYaml .Values.resources | nindent 12 }} в Helm template, и что произойдёт, если убрать nindent?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5