Learning Platform
Глоссарий Troubleshooting
Урок 16.03 · 22 мин
Средний
Blue/GreenZero-downtimeDeploymentOperator 1.14Rollback

Blue/Green deployment в Flink K8s Operator

В уроке про upgrade modes мы видели, что любой upgrade (даже savepoint mode) подразумевает downtime: пока новый кластер поднимается из savepoint, старого уже нет, ничего не работает. Для большинства пайплайнов 30-60 секунд приемлемо, но для критичных бизнес-сценариев (биллинг, антифрод, real-time bidding) это слишком долго.

Решение из мира классических веб-сервисов — Blue/Green deployment. Поднимаем новую версию рядом со старой, прогреваем её, переключаем трафик одним щелчком. Flink Kubernetes Operator 1.14 поддерживает Blue/Green встроенно, и в этом уроке разберём, как именно это работает для стримового джоба со стейтом.


Классический Blue/Green:

  • Blue — текущая prod-версия. Принимает весь трафик.
  • Green — новая версия. Поднимается рядом, прогревается.
  • В нужный момент трафик переключается с Blue на Green одним движением (изменение selector в Service, например).
  • Blue остаётся работать на случай отката.

Для stateful Flink-джоба это сложнее, чем для stateless HTTP-сервиса. Потому что:

  1. Оба кластера не могут одновременно потреблять из Kafka с одним и тем же consumer group ID — будет split brain (часть партиций уходит к Blue, часть к Green).
Kafka consumer groups: почему split brain возникает при Blue/Green
  1. Оба кластера не могут одновременно писать в sink (Kafka, Iceberg) с EXACTLY_ONCE — конфликт transaction id.
  2. Стейт обоих должен быть идентичен в момент переключения, иначе будут потери или дубли.

Flink Operator 1.14 решает это через хитрый flow с savepoint и атомарным cutover.

NOTE

Blue/Green в Operator 1.14 — это spec.job.deploymentMode: blue-green (флаг feature gate). До этого был только rolling-replace (которому посвящён прошлый урок про upgrade modes). Blue/Green снижает downtime до ~2-5 секунд.


Как работает Blue/Green в операторе

Идея: оператор поднимает второй кластер (Green) с новым образом, восстанавливает его из того же savepoint, что и Blue. Подгоняет к realtime через прогрев. Дальше — координированный cutover: Blue делает финальный savepoint, Green подцепляет его, начинает потреблять с тех же offset-ов. Между cutover-ами — секунды простоя на джоб, а не минуты.

Blue/Green flow в Operator 1.14
Шаг 1: applykubectl apply с новым образом. Оператор видит изменение spec, при deploymentMode: blue-green начинает blue-green flow.
Шаг 2: trigger savepointОператор просит Blue (текущий джоб) сделать savepoint (НЕ stop-with-savepoint - джоб продолжает работать). Savepoint сохраняется в S3.
Шаг 3: deploy GreenОператор создаёт новый набор подов (JM + TM) с тегом green в labels. Stays alongside Blue. Restore from savepoint.
Шаг 4: catch upGreen догоняет realtime. Читает Kafka с offset-ов savepoint-а до текущего LEO. Может занять минуты в зависимости от lag.
Шаг 5: cutoverBlue делает финальный savepoint. Green восстанавливается из него. Cutover: Blue cancel, Green становится active. Atomic - оператор гарантирует, что между Blue cancel и Green start не более 2-5 секунд.
Шаг 6: cleanupBlue pods удаляются. Green становится новым Blue для следующего upgrade. Savepoint от предыдущей версии остаётся в S3 для возможного rollback.

Детали: как избежать конфликта sink-ов

Самая хитрая часть — что делать с sink-ами в момент, когда оба кластера работают параллельно. Несколько вариантов, как Operator 1.14 это решает.

Kafka sink с транзакциями

При EXACTLY_ONCE Kafka sink использует transactional producer с уникальным transactional.id per parallelism slot. Если Blue и Green будут писать в один топик с одинаковыми transactional.id — Kafka брокер обнаружит конфликт (fencing through producer epoch) и отклонит транзакции от одного из них.

Operator 1.14 присваивает Blue и Green разные transactional.id префиксы (например, flink-blue-orders- и flink-green-orders-). Оба могут писать параллельно, без конфликта. Когда Blue cancel-ится, его транзакции аккуратно завершены, Green продолжает свои.

WARNING

Если ваш downstream consumer читает с isolation.level = read_committed, в период параллельной работы Blue и Green он увидит дублирование записей: и Blue, и Green обработают одни и те же входные события (Blue из старого offset-а, Green — из catch-up). Это не баг — это правда того, что было прочитано. После cutover Blue остановится, дубли прекратятся.

Если для вас дубли в краткий период критичны (например, биллинг) — Blue/Green вам не подходит. Используйте обычный savepoint upgrade с 60-секундным downtime.

Iceberg sink

Apache Iceberg использует optimistic concurrency control: commits are file-based, и при коллизии commits Iceberg выкидывает retry. Blue и Green могут параллельно писать файлы данных, но commits координируются через snapshot ID. В Operator 1.14 для Iceberg sink Green явно ждёт, пока Blue прекратит commits, прежде чем начать свои — это small atomic gap.

Internal/custom sink-и

Если ваш sink не идемпотентен и не транзакционен — Blue/Green не безопасен. Сначала сделайте sink либо idempotent, либо переключите на savepoint upgrade.


Манифест FlinkDeployment с Blue/Green

apiVersion: flink.apache.org/v1beta1
kind: FlinkDeployment
metadata:
  name: orders-to-iceberg
  namespace: flink-jobs
spec:
  image: my-registry/orders-to-iceberg:v0.4.2
  flinkVersion: v2_2

  flinkConfiguration:
    state.backend.type: rocksdb
    state.checkpoints.dir: s3://flink-state/orders/checkpoints
    state.savepoints.dir: s3://flink-state/orders/savepoints
    high-availability.type: kubernetes
    high-availability.storageDir: s3://flink-state/orders/ha
    execution.checkpointing.interval: "30s"
    execution.checkpointing.unaligned: "true"

  jobManager:
    replicas: 2
    resource:
      memory: "2048m"
      cpu: 1

  taskManager:
    replicas: 4
    resource:
      memory: "4096m"
      cpu: 2

  job:
    jarURI: local:///opt/flink/usrlib/orders-to-iceberg.jar
    entryClass: com.acme.flink.OrdersToIceberg
    parallelism: 8
    upgradeMode: savepoint

    # Blue/Green specific
    deploymentMode: blue-green
    blueGreen:
      maxCatchUpTime: 5m
      cutoverStrategy: atomic

Что важно:

  • deploymentMode: blue-green включает Blue/Green flow вместо обычного replace.
  • blueGreen.maxCatchUpTime: 5m — максимум, который Green может догонять. Если за 5 минут не сравнялся с Blue по lag — оператор abort upgrade.
  • blueGreen.cutoverStrategy: atomic — координированный final savepoint + handover. Альтернатива — progressive, где cutover делается партициям-по-партиции (это для оооочень больших джобов, обычно не нужно).

Также при Blue/Green оператор требует двойного запаса ресурсов в кластере: на момент catch-up в namespace должны быть слоты и для Blue, и для Green. Проверьте ResourceQuota.


Сам процесс cutover: что вы увидите

При kubectl apply -f с новым образом и deploymentMode: blue-green:

# Перед apply
kubectl -n flink-jobs get pods -l app=orders-to-iceberg
# orders-to-iceberg-jobmanager-blue-0          1/1   Running
# orders-to-iceberg-taskmanager-blue-0         1/1   Running
# orders-to-iceberg-taskmanager-blue-1         1/1   Running

# После apply
kubectl apply -f orders-to-iceberg.yaml

# Через 30 секунд (savepoint и pull нового образа)
kubectl -n flink-jobs get pods -l app=orders-to-iceberg
# orders-to-iceberg-jobmanager-blue-0          1/1   Running
# orders-to-iceberg-taskmanager-blue-0         1/1   Running
# orders-to-iceberg-taskmanager-blue-1         1/1   Running
# orders-to-iceberg-jobmanager-green-0         0/1   ContainerCreating
# orders-to-iceberg-taskmanager-green-0        0/1   Pending

# Через 2 минуты (catch-up)
kubectl get flinkdeployment orders-to-iceberg -o jsonpath='{.status.phase}'
# CATCHING_UP

# Через 5 минут (cutover завершён)
kubectl -n flink-jobs get pods -l app=orders-to-iceberg
# orders-to-iceberg-jobmanager-green-0         1/1   Running
# orders-to-iceberg-taskmanager-green-0        1/1   Running
# orders-to-iceberg-taskmanager-green-1        1/1   Running
# (Blue pods удалены)

Метки blue и green — это flink.apache.org/deployment-color label, которые оператор добавляет автоматически. По ним можно фильтровать в Prometheus, чтобы графики Blue и Green не сливались.


Откат при провале upgrade

Если Green не догоняет за maxCatchUpTime, или падает на старте — оператор сам отменяет upgrade. Blue остаётся работать, Green удаляется. Вы видите в events:

Type    Reason            Message
----    ------            -------
Warning UpgradeAborted    Green failed to catch up within 5m (lag still 10000 records)
Normal  RollbackToBlue    Reverted to blue deployment v0.4.1

Если Green догнался и cutover успешно прошёл, а потом обнаружилась проблема в новом коде — стандартный откат:

spec:
  image: my-registry/orders-to-iceberg:v0.4.1  # старый
  job:
    initialSavepointPath: s3://flink-state/orders/savepoints/savepoint-pre-upgrade-XXX
    deploymentMode: blue-green

Оператор увидит изменение, запустит Blue/Green flow в обратную сторону — теперь “Green” будет старая версия, а текущая (новая) — “Blue”, и через 2-5 секунд активной станет старая.

TIP

Каждый Blue/Green upgrade оставляет в S3 несколько savepoint-ов: pre-upgrade, final-cutover. В CI/CD регистрируйте эти точки в external metadata store с тегом версии — для быстрого rollback не придётся лезть в kubectl get -o yaml.


Когда Blue/Green НЕ подходит

  1. Не идемпотентные sink-и: дубли в downstream неприемлемы — используйте обычный upgrade.
  2. Очень дорогой стейт: 100+ ГБ state с долгим savepoint и долгим catch-up — может быть дешевле жить с минутой downtime, чем держать удвоенные ресурсы.
  3. Limited cluster capacity: если в namespace нет слотов на удвоенный TM-пул — оператор не сможет deploy Green.
  4. Жёсткий single-writer requirement: некоторые системы (банковские) требуют гарантию одного writer-а — Blue/Green с временной двойной записью не подходит.

Ключевые выводы

  1. Blue/Green deployment — паттерн для zero-downtime upgrade: новый кластер поднимается рядом, прогревается, потом атомарный cutover.

  2. Operator 1.14 поддерживает встроенно через spec.job.deploymentMode: blue-green. До этого был только rolling-replace.

  3. Flow в 6 шагов: apply -> savepoint Blue -> deploy Green -> catch-up -> atomic cutover -> cleanup Blue.

  4. Конфликт sink-ов решается через разные transactional.id для Kafka, optimistic concurrency для Iceberg. Custom non-idempotent sink-и несовместимы.

  5. Downtime ~2-5 секунд вместо 30-60 секунд при обычном upgrade. Цена — удвоенные ресурсы на период catch-up.

  6. Откат через initialSavepointPath работает так же, как при обычном upgrade — Blue/Green просто запускается в обратную сторону.

  7. Не для всех случаев: дубли downstream, очень большой стейт, ограниченные ресурсы кластера — лучше обычный savepoint upgrade.

Проверка знанийKnowledge check
Команда хочет использовать Blue/Green deployment для критичного джоба антифрода (parallelism 32, state 80 ГБ, EXACTLY_ONCE Kafka sink, downstream consumer с isolation_level = read_committed читает алерты по фроду). Каждый алерт триггерит блокировку транзакции через REST API во внешний платёжный шлюз. Это безопасно? Какие нюансы?
ОтветAnswer
Технически Blue/Green deployable, но есть серьёзный risk с downstream side effect. Что безопасно: - Kafka sink с EXACTLY_ONCE и разными transactional.id для Blue и Green - Kafka брокер правильно сериализует commits, read_committed consumer не увидит uncommitted данных. - Stat 80 ГБ - savepoint в native format должен укладываться в minutes, что приемлемо. - Atomic cutover дает downtime 2-5 секунд - для антифрода терпимо. Что НЕ безопасно: - Downstream consumer триггерит REST API во внешний платёжный шлюз. Это side effect, который НЕ управляется Kafka транзакциями. В период catch-up Green переигрывает события из savepoint до текущего LEO. Если downstream consumer не дедуплицирует алерты - он отправит дублирующие блокировки в платёжный шлюз. Платёжный шлюз может: (а) идемпотентно отбросить (если есть idempotency-key) - безопасно; (б) применить дважды - кризис: повторная блокировка транзакций. Рекомендации: 1. Убедиться, что downstream consumer идемпотентный (deduplicate by alert_id на стороне consumer или платёжный шлюз идемпотентный по request_id). 2. Если идемпотентности нет - НЕ использовать Blue/Green. Делать обычный savepoint upgrade с минутным downtime. 3. Resource quota в namespace должна позволять удвоенные TM ресурсы на период catch-up (parallelism 32 = 32 slots × 2 = 64 slots на ~5 минут). 4. maxCatchUpTime поднять до 15 минут (lag за время savepoint может быть большим при 80 ГБ state). Вывод: Blue/Green требует не только идемпотентного sink-а, но и идемпотентного всего downstream. В сценариях с side effects на внешние системы это часто нарушается.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. Команда настраивает Blue/Green deployment для критичного джоба с parallelism=8. ResourceQuota в namespace позволяет максимум 16 TaskManager-подов суммарно. Какие риски при первом Blue/Green upgrade?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5