При деплое в 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
В этом примере реализован такой порядок:
развертывание БД;
инициализация или миграции БД;
развертывание приложения.
Здесь есть особенность: перед тем, как начинать деплой, нам необходимо убедиться, что динамически создающийся (например, оператором на основе секретов из 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.
Читайте также в нашем блоге:
«werf vs. Helm: корректно ли их вообще сравнивать?»;
«Первые шаги с werf: собираем и деплоим простое приложение в Kubernetes»;
«Новые возможности werf: CI/CD на основе werf и Argo CD».