Типичные ошибки и их диагностика
Собираем в одном месте грабли, на которые наступают чаще всего, и приёмы их быстрой диагностики.
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 --allvskubectl getдля релизов. - Helm детерминирован — почти всё воспроизводится и чинится локально.