Типичные ошибки и их диагностика

Собираем в одном месте грабли, на которые наступают чаще всего, и приёмы их быстрой диагностики.

90% проблем Helm — это либо отступы YAML после рендера, либо потеря контекста в шаблонах, либо неявное поведение слияния и хуков. Диагностика почти всегда начинается с helm template --debug.

Ошибка 1: отступы и nindent

Симптом: error converting YAML to JSON: yaml: line N: .... Причина — съехавший отступ во вставленном блоке. Лечение:

helm template web ./chart -f values.yaml --debug | sed -n '1,80p'

Найдите строку N в отрендеренном выводе. Частые виновники: indent вместо nindent (блок прилип к ключу), неверное число отступа, забытый {{-. Помните: число в nindent = глубина ключа + 2.

Почему эта ошибка так распространена? Helm работает с шаблоном как с обычным текстом и не понимает структуру YAML — он подставляет строки, а правильность отступов проверяет уже YAML-парсер на готовом тексте. Поэтому шаблон может выглядеть аккуратно, но после подстановки многострочного значения (например, блока через toYaml) вложенные строки окажутся прижаты к нулевой колонке. nindent и решает ровно это: сначала переносит строку, затем добавляет нужный отступ каждой строке вставляемого блока. Когда сомневаетесь в числе — не считайте в уме, а посмотрите глубину ключа в реально отрендеренном манифесте: значение должно стоять на два пробела правее своего ключа.

Ошибка 2: потеря контекста в range/with

Симптом: nil pointer evaluating interface {}.Name или пустое значение внутри цикла. Причина — точка подменена элементом цикла, а вы обращаетесь к .Release/.Values. Лечение — $:

# было (не работает): внутри range точка — элемент
name: {{ .Release.Name }}-{{ .name }}
# стало: корень через $
name: {{ $.Release.Name }}-{{ .name }}

Ошибка 3: число/строка в YAML

Симптом: образ не находится (nginx:1.1 вместо 1.10) или env-переменная отвергнута («expected string»). Причина — YAML привёл значение к числу/булеву. Лечение — quote или --set-string.

Корень проблемы — в правилах нетипизированного YAML (так называемые «нормские булевы» и числовые литералы). Значение 1.10 без кавычек YAML честно прочитает как число, у которого хвостовой ноль незначим, и превратит в 1.1. То же бывает с версиями вроде 1.20, с ведущими нулями в портах или PIN-кодах (0755 уйдёт в восьмеричное прочтение), со значениями yes/no/on/off, которые станут булевыми. Правило простое: всё, что концептуально является строкой — теги образов, версии, идентификаторы, значения env, — оборачивайте в quote прямо в шаблоне, а при передаче через флаг используйте --set-string, чтобы Helm не пытался угадать тип.

Ошибка 4: гонка и зависание хуков

Симптом: релиз застрял в pending-upgrade; следующий деплой не идёт. Причина — хук-Job не завершается (упал, ждёт недоступный ресурс) или конфликт имён без before-hook-creation. Диагностика:

helm status web -n prod                 # увидеть pending
kubectl get jobs -n prod                # найти зависший Job-хук
kubectl logs job/web-migrate -n prod    # логи (если хук не удалён)

Лечение: исправить хук; для разбора оставлять упавший Job (не ставить hook-failed в delete-policy). Зависший pending иногда снимают helm rollback на прошлую ревизию.

Состояние pending-upgrade особенно коварно, потому что блокирует следующие деплои: Helm считает, что предыдущая операция ещё идёт, и отказывается стартовать новую. Так бывает не только из-за упавшего хука, но и если процесс helm убили в середине апгрейда (отменили джобу CI, упал раннер). В этом случае релиз остаётся «полузавершённым», и нужно либо откатить его на заведомо рабочую ревизию через helm rollback, либо, если откатывать некуда, аккуратно снять флаг ожидания. Профилактика — давать хук-Job явный разумный activeDeadlineSeconds, чтобы зависшая миграция не висела вечно, и проектировать миграции идемпотентными: повторный запуск после сбоя не должен ломаться на «уже применённой» миграции.

Ошибка 5: «значение не применилось»

Симптом: --set foo.bar=x не повлиял. Причина — опечатка в пути (создалась мёртвая ветка) или забытый --reuse-values при upgrade сбросил кастом. Лечение:

helm get values web -n prod --all       # что реально в релизе
helm template web ./chart --set foo.bar=x --show-only templates/deployment.yaml

Ошибка 6: список не «дополнился»

Симптом: переопределил один элемент массива — пропали остальные. Причина — списки при слиянии заменяются целиком. Лечение — задавать массив полностью или проектировать values без «дополнения» списков.

Эта грабля логично вытекает из того, как устроено глубокое слияние values. Слияние «вглубь» определено только для карт (объектов): ключи из пользовательских values накладываются на ключи дефолтов, недостающие остаются. Но у списка нет ключей, по которым их можно сопоставить, — Helm не знает, дополняете вы массив или хотите заменить, — поэтому он выбирает однозначное правило: список замещается целиком. Отсюда вывод для проектирования values: если параметр предполагает частичную настройку (например, набор переменных окружения, который пользователь захочет расширить), моделируйте его картой, а не списком. Тогда добавление одного ключа не сотрёт остальные. А если список всё же необходим, документируйте это явно: «переопределяете — задавайте целиком».

Ошибка 7: дрейф между Helm и кластером

Симптом: приложение ведёт себя не так, как описано в чарте, хотя релиз «успешен». Причина — кто-то поправил ресурс руками (kubectl edit, kubectl scale) или сторонний контроллер изменил поле. Helm про эту правку не знает: его представление о релизе хранится в секрете-снимке ревизии и расходится с реальностью. При следующем апгрейде трёхстороннее слияние может как затереть ручную правку, так и неожиданно её сохранить — в зависимости от того, было ли поле в прошлом манифесте Helm. Лечение — не править управляемые Helm ресурсы вручную: любое изменение должно идти через values и новый релиз, иначе кластер и история чарта рассинхронизируются.

Таблица «симптом → причина»

СимптомВероятная причина
converting YAML to JSONотступы / indent vs nindent
nil pointer в циклеконтекст: нужен $
образ не найден (1.1)нет quote на теге
застрял pending-upgradeзависший хук
значение не применилосьопечатка пути / нет --reuse-values

Как диагностировать под капотом

Единый метод: материализуй и читай. Поскольку Helm детерминирован, любая ошибка воспроизводится локально через helm template с теми же values — кластер не нужен для большинства проблем шаблонизации. Для проблем уже установленного релиза источник истины — снимок ревизии: helm get manifest и helm get values --all показывают, что Helm думает о релизе, а kubectl get -o yaml — что на самом деле в кластере. Расхождение между ними — это либо ручной дрейф, либо хук вне жизненного цикла. Привычка сравнивать «намерение Helm» с «реальностью kubectl» закрывает большинство загадок.

Итог

  • Главные грабли: отступы (indent/nindent), потеря контекста (нужен $), число вместо строки (quote), зависшие хуки, опечатки в путях values, замена списков.
  • Универсальный метод: helm template --debug для шаблонов, helm get manifest/values --all vs kubectl get для релизов.
  • Helm детерминирован — почти всё воспроизводится и чинится локально.
Проверьте себя
1. Какой первый шаг при ошибке «error converting YAML to JSON»?
AПерезапустить кластер
Bhelm template ... --debug и прочитать съехавшую строку в отрендеренном манифесте
CУдалить релиз
DСменить версию Helm
2. Как исправить nil pointer при обращении к .Release.Name внутри range?
AУбрать range
BИспользовать $.Release.Name — корневой контекст через $
CДобавить quote
DСменить namespace
3. Почему переопределение одного элемента массива через --set убирает остальные?
AБаг Helm
BСписки при слиянии заменяются целиком, а не дополняются
CНужен --reuse-values
DМассивы не поддерживаются