Паттерн «Job-миграция перед Deployment»

Уровень: advanced ~50 мин Практика: Umami (продолжение M5) (MIT)

Что нужно знать перед уроком

  • M6.03 — Два обязательных модуля одновременно — нужен опыт с зависимостями от Module перед переходом к паттернам Nelm внутри самого релиза
  • M1.10 — Jobs/CronJobs — нужно понимать неизменяемость полей Job из Kubernetes

Что нужно знать перед уроком

M1 урок 10Job и его неизменяемые после создания поля. M4 урок 4 — стадии Nelm (Render/Plan/Apply). Здесь — первый из ключевых паттернов Nelm-аннотаций для управления порядком внутри одного релиза (в отличие от M6 уроков 1–3, которые были про зависимости от внешних cluster-wide Module).

Теория

Проблема: обычный порядок манифестов в чарте недостаточен

Если у вашего Application есть Go-бэкенд (Umami), которому перед каждым запуском новой версии нужно применить миграцию схемы БД, интуитивно кажется, что достаточно просто разместить Job миграции раньше Deployment в файлах чарта. Но Nelm (как и Helm) не гарантирует порядок применения ресурсов по порядку файлов в templates/ — на стадии Apply (M4 урок 4) операции без явно указанных зависимостей могут выполняться параллельно. Без явной аннотации Deployment может запуститься до завершения миграции — новый код бэкенда обратится к ещё не мигрированной схеме БД.

Решение — связка двух аннотаций

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ .Application.Instance.Name }}-db-migrate
  annotations:
    werf.io/delete-policy: before-creation
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: {{ .Application.Package.Images "backend" }}
          command: ["./migrate", "up"]
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Application.Instance.Name }}-app
  annotations:
    werf.io/deploy-dependency-migrate: state=ready,kind=Job,name=db-migrate
spec:
  template:
    spec:
      containers:
        - name: backend
          image: {{ .Application.Package.Images "backend" }}

werf.io/delete-policy: before-creation на самом Job решает первую проблему: поля Job (в частности, spec.template) неизменяемы после создания (M1 урок 10) — при каждом новом деплое с потенциально изменившейся командой/образом миграции обычный kubectl apply/Server-Side Apply завершился бы ошибкой попытки изменить неизменяемое поле. delete-policy: before-creation указывает Nelm удалить предыдущий Job перед созданием нового при каждом деплое — эффективно «пересоздавая» его заново, а не пытаясь обновить.

werf.io/deploy-dependency-migrate: state=ready,kind=Job,name=db-migrate на Deployment решает вторую проблему: Deployment ждёт, пока указанный Job (по имени db-migrate, без префикса инстанса — Nelm сопоставляет по факту имени ресурса в релизе) не достигнет состояния ready (для Job это означает успешное завершение — Succeeded), и только затем начинает создание/обновление самого Deployment. Формат аннотации: state=ready|present[,name=][,namespace=][,kind=][,group=][,version=]id в имени аннотации (migrate в deploy-dependency-migrate) произвольный, используется только для того, чтобы можно было объявить несколько независимых deploy-dependency-* аннотаций на одном ресурсе с разными id.

Почему нельзя обойтись обычным порядком манифестов

Это не просто «Nelm не заботится о порядке файлов» — сама модель DAG на стадии Plan (M4 урок 4) строится из явных связей между ресурсами (через такие аннотации), а не из текстового порядка YAML-документов в файлах чарта. Параллелизм на стадии Apply — намеренная оптимизация производительности деплоя (независимые ресурсы применяются одновременно, ускоряя общее время nelm release install); без явных deploy-dependency-* аннотаций Nelm не имеет оснований считать Job и Deployment зависимыми друг от друга, даже если по смыслу приложения зависимость очевидна человеку, читающему код.

Частые ошибки и подводные камни

  • Полагаться на порядок файлов в templates/ вместо явных аннотаций. Nelm (и Helm) не гарантирует порядок применения по порядку файлов — только явные аннотации создают реальную зависимость в DAG.
  • Забыть werf.io/delete-policy: before-creation на Job миграции. Без него второй и последующий деплои завершатся ошибкой из-за попытки изменить неизменяемые поля уже существующего Job.
  • Указывать в deploy-dependency-migrate неверный kind/name, не совпадающий с реальным именем ресурса Job в манифесте. Nelm сопоставляет зависимость по этим полям буквально — опечатка в имени ресурса сделает аннотацию бесполезной без явной ошибки на этапе Render (проблема проявится только на Apply, когда Nelm не найдёт ресурс для ожидания).

Практика в кластере

🧩 Практика урока: Umami (продолжение M5) · лицензия MIT
  1. Добавьте Job-миграцию и связывающую аннотацию в шаблоны Umami:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
mkdir -p templates
cat > templates/job-migrate.yaml <<'EOF'
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ .Application.Instance.Name }}-db-migrate
  annotations:
    werf.io/delete-policy: before-creation
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: {{ .Application.Package.Images "backend" }}
          command: ["node", "migrate.js"]
EOF
  1. Добавьте werf.io/deploy-dependency-migrate в существующий Deployment бэкенда:
1
2
3
metadata:
  annotations:
    werf.io/deploy-dependency-migrate: state=ready,kind=Job,name={{ .Application.Instance.Name }}-db-migrate
  1. Задеплойте и понаблюдайте за порядком выполнения:
1
2
nelm release install -n course-m6-umami -r umami-demo . --auto-rollback
d8 k get jobs,deployments -n course-m6-umami -w

Убедитесь, что Job переходит в Completed до появления Pod’ов нового Deployment.

  1. Повторите деплой второй раз и убедитесь, что старый Job был корректно удалён и пересоздан, а не привёл к ошибке неизменяемых полей:
1
nelm release install -n course-m6-umami -r umami-demo . --auto-rollback

Практика разработки

Практики разработки в этом уроке нет — весь код относится к шаблонам, применённым в практике выше.

Шпаргалка команд урока

1
2
werf.io/delete-policy: before-creation
werf.io/deploy-dependency-<id>: state=ready|present[,name=][,namespace=][,kind=][,group=][,version=]

Полная таблица всех аннотаций порядка/зависимостей — M7 урок 2.

Вопросы для самопроверки

Почему одного лишь размещения Job раньше Deployment в файлах чарта недостаточно для гарантии порядка?

Параллелизм на стадии Apply — намеренная оптимизация, работающая без явных deploy-dependency аннотаций.

Источник: Deckhouse — Nelm-аннотации

Зачем нужен werf.io/delete-policy: before-creation на Job миграции?

Без этой аннотации второй деплой завершился бы ошибкой попытки изменить неизменяемые поля существующего Job.

Источник: Deckhouse — Nelm-аннотации

Что означает state=ready в werf.io/deploy-dependency-migrate для ресурса типа Job?

В отличие от state=present (просто существование), ready для Job подразумевает успешное завершение.

Источник: Deckhouse — Nelm-аннотации

Что означает произвольная часть <id> в имени аннотации werf.io/deploy-dependency-<id>?

id используется только для различения нескольких аннотаций зависимости на одном и том же ресурсе.

Источник: Deckhouse — Nelm-аннотации

Что произойдёт, если в deploy-dependency указать неверное имя ресурса Job?

Render — чистая шаблонизация без проверки существования ресурсов; несоответствие имени станет очевидно только при построении/выполнении DAG.

Источник: Deckhouse — Nelm-аннотации

Рекомендуемая литература

Официальная документация

Статьи и блоги

Книги

  • Michael Hausenblas, Stefan Schimanski. Programming Kubernetes. 2019. ISBN 978-1492047107.

Связанные материалы