Привет, Хабр! Меня зовут Мухин Сергей, я DevOps-инженер компании STM Labs. Мы предоставляем профессиональные сервисы в области системной интеграции и разработки ПО. В проектах нам нужно было получить подробные логи при обновлениях развёртываний и перейти на Helm 3. Эта статья о том, как мы пробовали решать эти задачи и почему в итоге выбрали werf — Open Source-утилиту, созданную компанией «Флант» и ставшую проектом CNCF.

Описание задачи
В качестве среды разработки и развёртывания мы используем на проектах контейнеры под управлением Kubernetes и Helm-чарты. Структура чарта стандартная, обычно мы создаём её с помощью helm create test
. В репозитории результат храним в следующем виде:
ubuntu $ helm create test
Creating test
ubuntu $ tree test/
test/
|-- Chart.yaml
|-- charts
|-- templates
| |-- NOTES.txt
| |-- _helpers.tpl
| |-- deployment.yaml
| |-- hpa.yaml
| |-- ingress.yaml
| |-- service.yaml
| |-- serviceaccount.yaml
| `-- tests
| `-- test-connection.yaml
`-- values.yaml
3 directories,10 files
ubuntu $
Файл Chart.yaml
активно используем для версионирования. Репозиторий с кодом хранится отдельно.
Нам нужно было решить две задачи:
сделать подробное логирование развёртывания;
перейти с Helm 2 на Helm 3.
Сначала подробно разберём первую задачу, а потом обратимся и ко второй.
Рассмотрим на примере, как проявляется недостаточность логирования оригинального Helm. Возьмём развёртывание helm create test
, которое я привёл выше, и переопределим тег образа на несуществующий docker.io/library/nginx:no
, чтобы в момент развёртывания nginx его не подкачал. Получаем следующую картину:
helm upgrade -i --atomic --set image.tag=no \
--timeout 20s test
Попробуем без ключа debug
понять, в чём проблема и почему развёртывание не удалось. Но никакого намёка в логах нет, причина ошибки может быть любой:

Добавим ключ debug
. Здесь видно удаление ресурсов и то, что в итоге под не поднимается:

Если бы это было развёртывание с несколькими подами, которые зависят друг от друга, мы бы также просто узнали, что они не развёртываются. Но понять, что конкретно упало, по таким логам невозможно. То есть и ключ debug
не помогает.
Варианты решения проблемы логирования
Для решения проблемы мы пробовали использовать разные «костыли».
Например, запускали сбор ивентов и логов всех сущностей до самогó развёртывания. Конечно, в итоге получался огромный вывод. Его было сложно анализировать: он содержал как важные, так и совершенно бесполезные данные.
Затем мы попробовали Shell- и Python-скрипты с собственной логикой. Этот подход частично прижился, но бывало, что скрипты отваливались или собирали ненужные логи. Это решение тоже нельзя было назвать отличным.
В итоге мы пришли к использованию werf.
Что такое werf
werf — это консольный Open Source-инструмент для упрощения организации жизненного цикла CI/CD в Kubernetes. Альтернатива Helm, созданная компанией «Флант» и ставшая проектом CNCF. Вот преимущества, заявленные разработчиками:
Управление жизненным циклом контейнеров приложения. Kubernetes отвечает за запуск, выполнение и остановку контейнеров, а werf — за сборку, тестирование, тегирование, публикацию и удаление неактуальных образов контейнеров из container registry.
Простота использования. Достаточно предоставить конфигурацию приложения (набор Dockerfile и Helm-чарт), а доставку werf возьмёт на себя.
Расширенные возможности, например автоматическое кэширование сборок и тегирование на основе контента, улучшенное отслеживание ресурсов и уникальный подход к очистке реестра контейнеров.
Объединение распространённых технологий, таких как Git, Buildah, Helm, Kubernetes, и вашей CI-системы.
Готовность к production.
Расскажу немного о технической части инструмента. Для развёртывания предлагают использовать четыре команды: converge
, plan
, dismiss
и bundle
. Есть и множество дополнительных команд, подробности о которых можно узнать в документации инструмента.

werf предлагает свою структуру репозитория. Helm-чарт рекомендуется держать в каталоге .helm
. Конфигурацию образов контейнеров предлагается описывать с помощью Dockerfile или собственного синтаксиса (Stapel). Например, так может выглядеть конфигурация для сборки образов backend и frontend с использованием Dockerfile:
# werf.yaml
project: app
configVersion: 1
---
image: backend
context: backend
dockerfile: Dockerfile
---
image: frontend
context: frontend
dockerfile: Dockerfile
В .helm
при этом нет Chart.yaml
. Его наличие необязательно, так как при отсутствии файла или отсутствии в нём имени или версии чарта будет использована следующая конфигурация:
apiVersion: v2
name: <имя проекта из werf.yaml>
version: 1.0.0
Больше информации о создании нового чарта можно найти в документации werf.
Для подробного отслеживания процесса выката в реальном времени werf использует собственную библиотеку kubedog. Вот какие возможности она предоставляет:
Периодический статус с информацией о текущем состоянии ресурсов.
Своевременная остановка проблемного выката (без ожидания таймаута) и предоставление пользователю необходимого контекста для решения.
Вывод логов и событий для выкатываемых ресурсов.
Отслеживание состояния дочерних ресурсов, а также кастомных ресурсов.
Дополнительные подробности можно прочитать в статье на Хабре.
Примеры использования werf
Вернёмся к нашему развёртыванию. Не меняя синтаксис, добавляем в начало werf:
werf helm upgrade -i --atomic --set image.tag=no \
--timeout 20s test test/
В результате в подробном логировании даже без ключа debug
видна ошибка, по которой развёртывание не завершается успешно: ErrImagePull
. Также видно конкретный образ, который не смог подкачаться. А если бы образ подкачался, но в дальнейшем упал, мы тоже увидели бы, какой лог ошибочный, и могли бы что-то с этим сделать.

К примеру, если переопределить образ nginx на postgres, получаем следующий вывод:

В использовании такого метода обновления на постоянной основе мы видим две потенциальные проблемы.
В отличие от Helm, werf, если три раза видит критическую ошибку, сразу заканчивает развёртывание, а не ждёт по timeout. Где именно будет ошибка — в ивентах или логах — не имеет значения. Чтобы исправить это поведение, нужно указывать в шаблонах аннотацию fail mode для игнорируемого ресурса. В принципе, сделать это несложно.
Второй повод для беспокойства заключается в том, что разработчики werf постоянно поддерживают собственный форк Helm с интегрированным kubedog. Это достаточно трудоёмкий процесс, и мы не уверены, что у команды однажды не закончится терпение.
Примечание от команды «Фланта»: ответ на эти опасения коллег — появление проекта Nelm. В ближайшем будущем мы не будем просто поддерживать форк Helm, а сделаем из него самостоятельный проект. Его можно будет использовать и отдельно, без werf.
Репозиторий Nelm уже есть на GitHub, а отдельную утилитуnelm
ожидаем в первой половине 2025 года. Когда это случится, мы широко анонсируем проект на всё сообщество. Следите за нашим блогом.
В случае, если форк будет заморожен, можно использовать kubedog напрямую. Для этого нужно в определённом формате передать библиотеке ресурсы, за которыми нужно следить:
cat << EOF | kubedog multitrack
{
"StatefulSets": [
{
"ResourceName": "mysts 1",
"Namespace": "myns"
}
],
"Deployments": [
{
"ResourceName": "mydeploy22",
"Namespace": "myns"
}
]
}
EOF
Формат тоже достаточно трудоёмкий, но в итоге kubedog делает своё дело:

Про подробное логирование развёртывания мы поговорили, самое время вернуться ко второй задаче, которая перед нами стояла: переходу с Helm 2 на Helm 3. Есть официальная инструкция для таких случаев, но она достаточно непоследовательная. От ситуации к ситуации всё может меняться, и намного проще для решения задачи использовать werf.
Для подобной миграции в werf используется команда werf helm migrate2to3 --release RELEASE_NAME --target-namespace NAMESPACE
, где:
RELEASE_NAME
— имя Helm 2-релиза;NAMESPACE
— пространство имён, в котором он находится.
После выполнения команды Helm 2-релиз удалится и полностью перенесётся в Helm 3 с тем же именем в том же пространстве имён. По нашему опыту, всё получается даже в сложных ситуациях, при этом подробное логирование будет всё так же доступно. Если же использовать werf не хочется, можно установить сам плагин из репозитория на GitHub.

Для других задач werf мы не использовали, но приведу ещё несколько возможных сценариев:
объединение Dockerfile и Helm-шаблонов в рамках одного репозитория;
автоматический push в registry на уровне команды;
использование конфига werf compose, который работает как Docker Compose, но позволяет переопределять параметры;
werf plan как аналог terraform plan.

Заключение
C помощью werf мы решили задачи подробного логирования при обновлении и миграции с Helm 2 на Helm 3. При этом не пришлось делать радикальных изменений в структуре шаблонов и подходе к обновлению, поскольку мы использовали только вложенный инструмент werf helm.
P. S.
Читайте также в блогах STM Labs и «Фланта»: