Хабрахабр

Трезвый взгляд на Helm 2: «Вот такой, какой есть…»

Как и любое другое решение, Helm — пакетный менеджер для Kubernetes — имеет плюсы, минусы и область применения, поэтому при его использовании стоит правильно оценивать свои ожидания…

Мы используем Helm в своём арсенале инструментов непрерывного выката. На момент написания статьи в наших кластерах более тысячи приложений и примерно 4000 инсталляций этих приложений в различных вариациях. Периодически мы сталкиваемся с проблемами, но в целом довольны решением, не имеем простоев и потерь данных.

Основной мотив написания статьи — предоставить пользователю объективную оценку основных проблем Helm 2 без категорических заключений, а также желание поделиться опытом и нашими решениями.

[BUG] После выката состояние ресурсов релиза в кластере не соответствуют описанному Helm-чарту

При работе Helm не учитывает состояние ресурсов релиза в кластере. При повторной инсталляции результат определяется только текущей и сохранённой конфигурациями. Таким образом, состояние ресурса в кластере и реестре Helm различается, а Helm это никак не учитывает.

Рассмотрим, как данная проблема проявляется:

  1. Шаблон ресурса в чарте соответствует состоянию X.
  2. Пользователь выполняет инсталляцию чарта (Tiller сохраняет состояние ресурса X).
  3. Далее пользователь вручную меняет ресурс в кластере (состояние изменяется с X на Y).
  4. Не делая никаких изменений, он выполняет helm upgrade… И ресурс по-прежнему в состоянии Y, хотя пользователь ожидает X.

И это ещё не всё. В какой-то момент пользователь меняет шаблон ресурса в чарте (новое состояние W) — тогда мы имеем два сценария после выполнения helm upgrade:

  • Падает применение патча X-W.
  • После применение патча ресурс переходит в состояние Z, которое не соответствует желаемому.

Для избежания подобной проблемы предлагается организовать работу с релизами следующим образом: никто не должен изменять ресурсы вручную, Helm — единственный инструмент для работы с ресурсами релиза. В идеале, изменения чарта версионируются в Git-репозитории и применяются исключительно в рамках CD.

Вручную синхронизация может выглядеть следующим образом: Если такой вариант не подходит, то можно следить за синхронизацией состояний ресурсов релиза.

  1. Узнаём состояния ресурсов релиза через helm get.
  2. Узнаём состояния ресурсов в Kubernetes через kubectl get.
  3. Если ресурсы отличаются, то проводим синхронизацию Helm с Kubernetes:
    1. Создаём отдельную ветку.
    2. Обновляем манифесты чарта. Шаблоны должны соответствовать состояниям ресурсов в Kubernetes.
    3. Выполняем деплой. Синхронизируем состояние в реестре Helm и кластере.
    4. После этого ветку можно удалить и продолжить штатную работу.

При применении патчей с использованием команды kubectl apply выполняется так называемый 3-way-merge, т.е. учитывается реальное состояние обновляемого ресурса. Посмотреть код алгоритма можно здесь, а почитать про него — здесь.

С Helm 2 не всё так радужно: 3-way-merge внедрять не планируется, но есть PR для исправления способа создания ресурсов — узнать подробности или даже поучаствовать можно в рамках соответствующего issue. На момент написания статьи разработчики Helm ищут пути внедрения 3-way-merge в Helm 3.

[BUG] Error: no RESOURCE with the name NAME found

Проблема проявляется, если при повторном выкате успешно создаются новые ресурсы, а сам выкат в итоге завершается с ошибкой. Под новыми ресурсами подразумеваются те, которых не было в последней инсталляции чарта.

В результате, Helm пытается повторно создать эти ресурсы и завершается с ошибкой «no RESOURCE with the name NAME found» (ошибка говорит об обратном, но проблема именно в этом). При неудавшемся выкате релиз сохраняется в реестре с пометкой FAILED, а при инсталляции Helm опирается на состояние последнего релиза DEPLOYED, который в данном случае ничего не знает о новых ресурсах. Отчасти проблема связана с тем, что Helm не учитывает состояние ресурсов релиза в кластере при создании патча, о чём написано в предыдущем разделе.

В настоящий момент единственным решением будет удаление новых ресурсов вручную.

После долгого обсуждения с разработчиками в Helm для команд upgrade/rollback добавили опцию --cleanup-on-fail, которая активирует автоматическую очистку при неудачном выкате. Для того, чтобы избежать подобного состояния, можно автоматически удалять новые ресурсы, созданные в текущем upgrade/rollback, если команда в итоге завершается с ошибкой. Наш PR находится на стадии обсуждения, поиска наилучшего решения.

13 в командах helm install/upgrade появляется опция --atomic, которая активирует очистку и rollback при провальной инсталляции (подробнее см. Начиная с версии Helm 2. в PR).

[BUG] Error: watch closed before Until timeout

Проблема может возникать, когда Helm hook выполняется слишком долго (например, при миграциях) — даже несмотря на то, что не превышаются указанные timeout'ы у helm install/upgrade, а также spec.activeDeadlineSeconds у соответствующего Job.

Helm не обрабатывает эту ошибку и сразу падает — вместо того, чтобы повторить запрос на ожидание. Такую ошибку выдаёт API-сервер Kubernetes во время ожидания выполнения hook job.

В качестве решения можно увеличить таймаут в api-server: --min-request-timeout=xxx в файле /etc/kubernetes/manifests/kube-apiserver.yaml.

[BUG] Error: UPGRADE FAILED: «foo» has no deployed releases

Если первый релиз через helm install завершился с ошибкой, то последующий helm upgrade вернёт подобную ошибку.

Чтобы не прерываться на выполнение ручных команд, можно использовать возможности werf для выката. Казалось бы, решение довольно простое: нужно вручную выполнить helm delete --purge после провальной первой инсталляции, но это ручное действие ломает автоматику CI/CD. При использовании werf проблемный релиз будет автоматически пересоздан при повторной инсталляции.

13 в командах helm install и helm upgrade --install достаточно указать опцию --atomic и после провальной инсталяции релиз автоматически удалится (подробности см. Кроме того, начиная с версии Helm 2. в PR).

Autorollback

В Helm не хватает опции --autorollback, которая при выкате запомнит текущую успешную ревизию (упадёт, если последняя ревизия не успешна) и после неуспешной попытки деплоя выполнит rollback до сохранённой ревизии.

Для того, чтобы минимизировать вероятность простоя продуктива, часто используется подход с несколькими контурами (к примеру, staging, qa и production), который заключается в последовательном выкате на контуры. Так как для продуктива критично, чтобы всё работало без перебоев, необходимо искать решения, выкат должен быть предсказуемым. При таком подходе большинство проблем фиксируется до выката в продуктив и в связке с autorolback’ом позволяет достигать неплохих результатов.

Неплохая статья с описанием этого подхода доступна здесь. Для организации autorollback можно использовать плагин helm-monitor, который позволяет завязать rollback на метрики из Prometheus.

Для некоторых наших проектов используется достаточно простой подход:

  1. Перед деплоем запоминаем текущую ревизию (считаем, что в нормальной ситуации, если релиз существует, то он обязательно в состоянии DEPLOYED):

    export _RELEASE_NAME=myrelease
    export _LAST_DEPLOYED_RELEASE=$(helm list -adr | \ grep $_RELEASE_NAME | grep DEPLOYED | head -n2 | awk '')

  2. Запускаем install или upgrade:

    helm install/upgrade ... || export _DEPLOY_FAILED=1

  3. Проверяем статус деплоя и делаем rollback до сохранённого состояния:

    if [ "$_DEPLOY_FAILED" == "1" ] && [ "x$_LAST_DEPLOYED_RELEASE" != "x" ] ; then helm rollback $_RELEASE_NAME $_LAST_DEPLOYED_RELEASE
    fi

  4. Завершаем pipeline с ошибкой, если деплой оказался неуспешным:

    if [ "$_DEPLOY_FAILED" == "1" ] ; then exit 1 ; fi

Опять же, начиная с версии Helm 2.13, при вызове helm upgrade достаточно указать опцию --atomic и после провальной инсталяции будет автоматически выполнен rollback (подробности см. в PR).

Ожидание готовности ресурсов релиза и обратная связь в момент выката

По задумке, Helm должен следить за выполнением соответствующих liveness- и readiness-проб при использовании опции --wait:

--wait if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout

Должным образом сейчас эта функция не работает: поддерживаются не все ресурсы и не все версии API. Да и сам заявленный процесс ожидания не удовлетворяет нашим потребностям.

Если выкат завершается с ошибкой, то мы об этом узнаём только по истечении timeout. Как и в случае с использованием kubectl wait, нет быстрой обратной связи и нет возможности регулировать это поведение. При проблемной инсталляции необходимо как можно раньше завершить процесс выката, зафейлить CI/CD pipeline, откатить релиз до рабочей версии и перейти к отладке.

В случае с kubectl wait можно организовать отдельный процесс для показа логов, для которого потребуются имена ресурсов релиза. Если проблемный релиз откатили, а Helm не возвращает никакой информации в процессе выката, то к чему сводится отладка?! А помимо логов pod'ов полезная информация может содержаться и в процессе выката, событиях ресурсов… Как организовать простое и рабочее решение — сходу неясно.

Все данные объединяются в единый поток и выдаются в лог. Наша CI/CD-утилита werf может деплоить Helm-чарт и следить за готовностью ресурсов, а также выводить сопутствующую выкату информацию.

С помощью утилиты можно подписаться на ресурс и получать события и логи, а также своевременно узнать о провалившимся выкате. Эта логика вынесена в отдельное решение kubedog. в качестве решения после вызова helm install/upgrade без опции --wait можно вызвать kubedog для каждого ресурса релиза. Т.е.

Подробнее об утилите можно почитать в нашей недавней статье. Мы стремились сделать инструмент, который предоставит всю необходимую информацию для отладки в выводе CI/CD pipeline.

Возможно, в Helm 3 когда-нибудь появится подобное решение, но пока наш issue находится в подвисшем состоянии.

Безопасность при использовании helm init по умолчанию

По умолчанию, при выполнении команды helm init, серверный компонент устанавливается в кластер с правами, аналогичными суперпользователю, что может приводить к нежелательным последствиям при доступе третьих лиц.

Чтобы обеспечить безопасность кластера, необходимо ограничить возможности Tiller, а также позаботиться о соединении — безопасности сети, по которой происходит общение между компонентами Helm.

Подробнее можно почитать в документации Helm: Securing your Helm Installation. Первое может быть достигнуто за счёт использования стандартного механизма Kubernetes RBAC, который позволит ограничить действия tiller, а второе — настройкой SSL.

Отчасти мы согласны: реализация неидеальная, — но взглянем на это и с другой стороны. Бытует мнение, что наличие серверного компонента — Tiller — серьёзная архитектурная ошибка, буквально инородный ресурс с правами суперпользователя в экосистеме Kubernetes. Tiller доведёт состояние релиза до валидного. Если прервать процесс деплоя и убить Helm client, то система не останется в неопределенном состоянии, т.е. Также необходимо понимать, что несмотря на то, что в Helm 3 отказываются от Tiller, эти функции так или иначе будут выполняться контроллером CRD.

Марсианские Go-шаблоны

Go-шаблоны имеют большой порог вхождения, однако никаких ограничений по возможностям и проблем с DRY у технологии нет. Основные принципы, синтаксис, функции и операторы разбираются в нашей предыдущей статье из серии про Helm.

Отсутствие секретов из коробки

Хранить и сопровождать код приложения, инфраструктуры, а также шаблонов для выката удобно, когда они расположены в одном месте. И секреты не исключение.

Helm не поддерживает секреты из коробки, однако доступен плагин helm-secrets, который по сути является прослойкой между sops, менеджером секретов от Mozilla, и Helm.

Из особенностей: При работе с секретами мы используем собственное решение, реализованное в werf (документация по секретам).

  • Простота реализации.
  • Хранение секрета в файле, а не только в YAML. Удобно при хранении сертификатов, ключей.
  • Перегенерация секретов с новым ключом.
  • Выкат без секретного ключа (при использовании werf). Может пригодиться для тех случаев, когда у разработчика нет этого секретного ключа, но есть необходимость запустить деплой на тестовый или локальный контур.

Заключение

Helm 2 позиционируется как стабильный продукт, но при этом есть множество багов, которые висят в подвешенном состоянии (часть из них — по несколько лет!). Вместо решений или хотя бы заплаток все силы брошены на разработку Helm 3.

Каждый четверг проходит получасовой митинг разработчиков Helm, на котором можно узнать о приоритетах и текущих направлениях команды, задать вопросы и форсировать свои наработки. Несмотря на то, что MR и issue могут висеть месяцами (вот пример того, как мы добавляли before-hook-creation policy для хуков в течение нескольких месяцев), участвовать в развитии проекта всё же можно. О мите и других каналах связи подробно написано здесь.

Сами мы на сегодня придерживаемся такой позиции, что, несмотря на недостатки, Helm является приемлемым решением для деплоя и участвовать в его развитии полезно для всего сообщества. Использовать Helm или нет — решать, конечно, вам.

P.S.

Читайте также в нашем блоге:

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть