Четыре известных подводных камня Nelm
Что нужно знать перед уроком
- M7.08 — Семь готовых use-case'ов — нужен полный набор аннотаций/функций как контекст перед разбором того, что может пойти не так
Что нужно знать перед уроком
M7 уроки 1–8 — полный справочник аннотаций и функций. Этот заключительный урок модуля разбирает четыре официально задокументированных подводных камня Nelm — не гипотетические, а реально зафиксированные в официальной документации Nelm-аннотаций проблемы, с которыми сталкиваются разработчики Application-пакетов.
Теория
Подводный камень 1 — Server-Side Apply и null-значения
Server-Side Apply может провалиться на полях со значением null. Если значение из .Values равно nil (например, необязательный параметр не задан в config-values.yaml), итоговый манифест будет содержать foo: null — и в зависимости от типа поля Kubernetes API может либо корректно интерпретировать это как «убрать поле», либо выдать ошибку валидации, в зависимости от специфики конкретного API-ресурса и его OpenAPI-схемы.
Решение: оборачивать потенциально пустые значения в {{ if .Values.foo }} (полностью исключая поле из манифеста, если значение отсутствует) или использовать функцию default:
| |
Подводный камень 2 — lookup и рассинхронизация Plan/Apply
Функция lookup (стандартная Helm-функция для чтения текущего состояния объектов кластера прямо во время рендера) может привести к устаревшему («stale») отрендеренному плану, если ресурсы кластера изменяются в промежутке между стадиями Plan и Apply (M4 урок 4) — Plan увидел одно состояние, а к моменту Apply состояние кластера уже другое, и итоговое поведение становится непредсказуемым.
Решение: не использовать lookup для критичной логики (условного изменения манифеста на основе текущего состояния кластера); вместо этого передавать нужные данные явно через .Values, полученные заранее (например, через --set в CI-пайплайне) или через собственный механизм получения состояния вне цикла Render/Plan/Apply одного релиза.
Подводный камень 3 — недетерминированные функции и «мерцающий» diff
Функции вроде now (текущее время) и randAlphaNum (случайная строка), а также итерация по map с переменным порядком ключей (в Go map не гарантирует порядок итерации без явной сортировки) могут производить разный рендер при каждом вызове. Это приводит к ложным diff’ам при включённом «замораживании» плана (сравнении Plan с предыдущим состоянием) — Nelm видит «изменение», хотя пользователь не менял ничего осмысленного, просто рендер каждый раз генерирует новое случайное значение или другой порядок ключей.
Решение: избегать now/randAlphaNum в местах, где важна стабильность рендера между запусками (например, для меток или полей, которые не должны меняться при каждом деплое); для генерации одноразовых секретов при первой установке — see также use case 6 из M7 урока 8 (deploy-on: install + ownership: anyone), фиксирующий значение один раз и не пересоздающий его при обновлениях; для итерации по map — использовать явную сортировку ключей (keys | sortAlpha) перед range.
Подводный камень 4 — deploy-dependency не работает между разными lifecycle-фазами
deploy-dependency, разобранный в M7 уроке 2, строит DAG в рамках одной операции (install/upgrade/rollback) — если ресурс А имеет deploy-on: pre-install, а ресурс Б (с deploy-dependency на ресурс А) имеет deploy-on: install, зависимость не сработает предсказуемо в том смысле, в котором ожидается: pre-install и install — это, по сути, разные проходы рендера/применения, и DAG для каждой фазы строится независимо, а не как единое целое между фазами.
Решение: держать взаимозависимые ресурсы в одной lifecycle-фазе (deploy-on), используя deploy-dependency/weight только для управления порядком внутри этой фазы; если необходима строгая последовательность между разными фазами (pre-install → install), полагаться на естественную гарантию порядка самих фаз (Kubernetes/Nelm выполняет pre-install целиком до перехода к install), а не пытаться связать конкретные ресурсы разных фаз через deploy-dependency.
Частые ошибки и подводные камни
(Этот раздел урока целиком посвящён подводным камням — см. теорию выше, где каждый камень уже сопровождён решением.)
Практика в кластере
Проверьте пакет Umami на все четыре подводных камня:
| |
Если найдены потенциально проблемные конструкции — примените решения из теории (обёртки if/default, замена lookup на явные .Values, стабилизация случайных значений через deploy-on: install + ownership: anyone).
Практика разработки
Проведите финальный аудит всего пакета Umami (накопленного за M5–M7) на соответствие всем разобранным в курсе паттернам и подводным камням — это готовит пакет к следующему этапу курса, посвящённому CI/CD-публикации в M8.
Шпаргалка команд урока
Полный список известных pitfalls — 📋 nelm .
Вопросы для самопроверки
Решение — обёртка {{ if }} или функция default.
Источник: Deckhouse — Nelm-аннотации
Между Plan и Apply может пройти время, за которое состояние кластера изменится.
Источник: Deckhouse — Nelm-аннотации
Недетерминированность рендера — источник ложных срабатываний diff.
Источник: Deckhouse — Nelm-аннотации
Между фазами гарантируется только последовательность самих фаз, а не связь конкретных ресурсов через deploy-dependency.
Источник: Deckhouse — Nelm-аннотации
Это полный список известных подводных камней из [официальной документации Nelm-аннотаций](https://deckhouse.ru/products/kubernetes-platform/documentation/latest/architecture/marketplace/nelm-annotations.html).
Источник: Deckhouse — Nelm-аннотации
Рекомендуемая литература
Официальная документация
Статьи и блоги
- Вышла werf 2.0: новый движок развёртывания Nelm — Flant на Habr.
- Представляем werf 1.0 stable: GitOps и Helm — Flant на Habr.
- GitLab + K8s + Werf — Habr.
Книги
- Matt Butcher, Matt Farina, Josh Dolitsky. Learning Helm. 2019. ISBN 978-1492036781.
Связанные материалы
- Предыдущий урок: M7.08 — Семь готовых use-case’ов.
- Следующий модуль: M8 — Продвинутый Werf, урок 1.
✓ Урок пройден — все вопросы самопроверки отвечены верно