Задаём порядок деплоя ресурсов в Kubernetes с помощью werf/Helm

от автора

При деплое в Kubernetes часто требуется выкатывать ресурсы в определённом порядке, а иногда и дожидаться готовности сторонних ресурсов. Например, сначала нужно запустить БД, дождаться создания динамического Secret’а сторонним оператором, потом выполнить инициализацию/миграции БД, а уже затем запустить само приложение. 

Рассмотрим, как решать такие задачи с помощью Helm, а также сравним с более быстрым и удобным вариантом, который предлагает Open Source-утилита werf.

Развертывание с помощью Helm

Когда возникает необходимость указать порядок, в котором должны быть запущены приложения в кластере, встает вопрос: как это правильно сделать? В Helm задать последовательность выката ресурсов довольно сложно: в основном это делается либо через самодельные проверки готовности требуемых ресурсов в initContainers/containers, либо через разделение одного Helm-релиза на несколько частей и их последовательный выкат. Оба способа неудобны и требуют лишних действий. 

Рассмотрим развертывание ресурсов в произвольном порядке. Для этого возьмем простой пример с базой данных и реализуем ожидание БД с помощью initContainers:

kind: StatefulSet metadata:   name: postgres --- kind: Deployment metadata:   name: redis {{ if $.Release.IsInstall }} --- kind: Job metadata:   name: init-db spec:   template:     spec:       initContainers:       - name: wait-db         image: postgres         command:         - sh         - -ec         - |           until pg_isready -h postgres -p 5432 -U postgres; do             sleep 1           done             containers:       - name: init-db         image: backend         command: ["my-backend", "init-db"] {{ else }} --- kind: Job metadata:   name: migrate-db   annotations:     helm.sh/hook: pre-upgrade spec:   template:     spec:       initContainers:       - name: wait-db         image: postgres         command:         - sh         - -ec         - |           until pg_isready -h postgres -p 5432 -U postgres; do             sleep 1           done       containers:       - name: migrate-db         image: backend         command: ["my-backend", "migrate-db"] {{ end }} --- kind: Deployment metadata:   name: backend spec:   template:     spec:       initContainers:       - name: wait-db-init-and-ready         image: backend         command:         - sh         - -ec         - |           until <db-initialized-and-ready>; do             sleep 1           done       - name: wait-redis         image: redis         command:           - sh         - -ec         - |            until redis-cli -h redis -p 6379 get hello; do              sleep 1            done       containers:       - name: backend         image: backend

В этом примере реализован такой порядок: 

  1. развертывание БД;

  2. инициализация или миграции БД;

  3. развертывание приложения.

Здесь есть особенность: перед тем, как начинать деплой, нам необходимо убедиться, что динамически создающийся (например, оператором на основе секретов из Vault) Secret my-dynamic-secret присутствует в кластере. Поэтому сначала дождемся его создания, а затем начнем деплой приложения:

kubectl wait ... secret/my-dynamic-secret helm install app .

Теперь посмотрим, как можно решить эту задачу с werf.

Развертывание в произвольном порядке с помощью werf

Более удобно реализовать упорядоченный выкат можно с утилитой werf. Недавно у нее появилась возможность задать порядок выката ресурсов с помощью аннотаций, через которые указывается «вес» ресурса. По умолчанию (если ничего не указано) все ресурсы имеют вес 0, поэтому разворачиваются и отслеживаются одновременно. Но если задать им разные веса, то при выкате werf сгруппирует ресурсы в соответствии с их весом и будет разворачивать их от группы с меньшим весом к группе с большим весом, ожидая, пока каждая группа не придет в состоянии полной готовности.

Задать вес ресурсов можно через аннотацию werf.io/weight (по аналогии с helm.sh/hook-weight для Helm-хуков). Решим предыдущую задачу уже с использованием весов:

kind: StatefulSet metadata:   name: postgres   annotations:     werf.io/weight: "0" --- kind: Deployment metadata:   name: redis   annotations:     werf.io/weight: "0" {{ if $.Release.IsInstall }} --- kind: Job metadata:   name: init-db   annotations:     werf.io/weight: "10" spec:   template:     spec:       containers:       - name: init-db         image: backend         command: ["my-backend", "init-db"] {{ end }} --- kind: Job metadata:   name: migrate-db   annotations:     werf.io/weight: "20" spec:   template:     spec:       containers:       - name: init-db         image: backend         command: ["my-backend", "migrate-db"] --- kind: Deployment metadata:   name: backend   annotations:     secret.external-dependency.werf.io/resource: "secret/my-dynamic-secret"     werf.io/weight: "30" spec:   template:     spec:       containers:       - name: backend         image: backend

Запустить развертывание теперь можно одной командой:

werf converge

Первыми задеплоятся база данных и Redis, затем произойдет инициализация или миграции БД, и только потом запустится приложение. Обратите внимание на аннотацию secret.external-dependency.werf.io/resource: "secret/my-dynamic-secret". Она добавлена к Deployment’у приложения и указывает, что перед созданием Deployment’а надо также дождаться готовности внешней зависимости — Secret’а my-dynamic-secret. При этом Secret может разворачиваться либо как часть другого релиза werf, либо вообще создаваться без werf (например, сторонним оператором). 

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

Заключение

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

P.S.

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


ссылка на оригинал статьи https://habr.com/ru/company/flant/blog/682804/