При деплое в 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.

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

Комментарии (0)