Skip to main content

Як реалізувати blue-green deployment з Docker?

Blue-green deployment це deploy-стратегія, де два ідентичні середовища коротко співіснують під час release. Старе (blue) обслуговує трафік, поки нове (green) прогрівається; як green healthy, load-balancer флипається, blue стає rollback-target. З Docker це straightforward.

Теорія

TL;DR

  • Два повні середовища: blue (поточне) і green (нове). Обидва full-running, лише одне обслуговує трафік.
  • Reverse proxy / load-balancer route'ить трафік на те, що «live».
  • Cutover атомарний, флипни routing-config і уся система перемикається на нову версію.
  • Rollback атомарний, флипни routing назад, якщо нова версія misbehaving.
  • Ціна: 2x ресурсів під час deploy-вікна.
  • Найкраще для stateless-app. State (DB) потребує спеціального handling, бо обидві версії можуть коротко крутитися проти тих самих даних.

Візуальний flow

До deploy: Під час deploy (обидва running): Трафік Трафік | | v v +---------+ +---------+ | router | | blue | ← трафік +---------+ | v1.0 | | | +---------+ v v (лише blue приймає до cutover) +-----+ +------+ |blue | |green | |v1.0 | |v1.1 | (warmup, healthcheck running) +-----+ +------+ Після cutover: Після drain і cleanup: Трафік | Трафік v | +---------+ v | router | ──→ green +---------+ +---------+ | green | ← трафік | | | v1.1 | v v +---------+ +-----+ +------+ (blue видалено) |blue | |green | |v1.0 | |v1.1 | ← трафік +-----+ +------+

Реалізація з Docker + reverse-proxy

Setup

bash
# Shared-мережа для proxy і app docker network create proxy

Крок 1: blue running

bash
docker run -d --name api-blue \ --network proxy \ --restart unless-stopped \ myorg/api:1.0

Reverse proxy (Traefik, nginx, Caddy) route'ить трафік на api-blue.

# nginx upstream upstream api_backend { server api-blue:3000; }

Крок 2: підняти green

bash
docker run -d --name api-green \ --network proxy \ --restart unless-stopped \ --health-cmd='curl -f http://localhost:3000/health' \ --health-interval=5s \ myorg/api:1.1

Green up, але без трафіку. Чекай healthcheck:

bash
docker inspect api-green --format '{{.State.Health.Status}}' # чекай до: healthy

Крок 3: smoke-test green out-of-band

До флипу трафіку, верифікуй, що green працює напряму:

bash
docker run --rm --network proxy curlimages/curl \ curl -f http://api-green:3000/health docker run --rm --network proxy curlimages/curl \ curl -f http://api-green:3000/api/v1/test

Якщо green misbehaving, ти ще не зачепив прод. Фіксь або abandon green.

Крок 4: cutover

Онови reverse-proxy на route до green:

upstream api_backend { server api-green:3000; }

Reload nginx (nginx -s reload) або тригер Traefik оновитися через labels. Cutover near-instant; будь-які in-flight запити на blue продовжуються (graceful), нові йдуть на green.

Крок 5: monitor

Дивись метрики, error-rates, app-логи. Якщо trouble:

upstream api_backend { server api-blue:3000; # ← rollback }

Reload. Трафік знов на blue. Загальний rollback-час: секунди.

Крок 6: drain і cleanup

Якщо green хороший після monitoring-вікна:

bash
docker stop api-blue && docker rm api-blue

Перейменуй для наступного deploy:

bash
docker rename api-green api-blue

Або, частіше, наступний release стає новим «green», і цикл продовжується.

З Traefik (auto-routing через labels)

yaml
# compose.yaml — initial state services: traefik: image: traefik:v3 command: - --providers.docker - --entrypoints.web.address=:80 ports: ["80:80"] volumes: ["/var/run/docker.sock:/var/run/docker.sock:ro"] api-blue: image: myorg/api:1.0 labels: - "traefik.enable=true" - "traefik.http.routers.api.rule=Host(`api.example.com`)" - "traefik.http.services.api.loadbalancer.server.port=3000"

Коли deploy'їш green:

bash
# Підняти green БЕЗ traefik-labels (без трафіку) docker run -d --name api-green myorg/api:1.1 # Або з labels, але на іншому host-правилі # Як green healthy, поміняй labels: # Прибери labels з blue # Додай labels на green # Traefik підхоплює зміну за секунди

Label-driven Traefik робить cutover декларативним.

State-management — складна частина

Blue-green легкий для stateless-сервісів. State додає складність:

Schema бази:

  • Під час cutover-вікна обидва blue і green можуть query DB.
  • Schema має бути backward compatible з обома версіями.
  • Патерн: expand-then-contract.
    1. Deploy expand-migration (нова колонка, нова таблиця). Старий код ще працює.
    2. Deploy green-коду, що використовує новий schema. Обидві версії коротко співіснують під час cutover.
    3. Після cutover і підтвердження, deploy contract-migration (drop стару колонку).
  • Ніколи не breaking-change schema під час blue-green deploy.

Сесії:

  • Якщо сесії в memory, blue-сесії зникають при cutover.
  • Бери external-сесії (Redis, JWT, signed-cookies). Тоді обидві версії можуть обслуговувати будь-яку сесію.

File-uploads:

  • Той самий volume змонтовано у blue і green; обидва можуть r/w.
  • Або бери object-storage (S3), обидві версії показують на той самий bucket.

Кеші:

  • Deserializer'и нової версії мають розуміти cache-entries старої версії (або namespace by version).
  • Або інвалідуй cache як частину cutover (короткочасне сповільнення, не breakage).

Варіанти traffic-shifting

Blue-green бінарний: 100% blue або 100% green. Варіанти:

  • Canary: route малий % (5%) на green; якщо метрики хороші, підніми до 50%, 100%. Поступово; дозволяє ловити slow-burn регресії.
  • A/B-testing: route за user-атрибутом (cookie, header) замість відсотка. Корисно для feature-comparison.
  • Rolling: замінюй task по одному (Swarm/K8s дефолт). Менший resource-cost, менш атомарний.

Blue-green найпростіший і найбільш атомарний; canary ловить більше issue; rolling найдешевший. Більшість команд використовують комбінацію.

Типові помилки

Забути тестувати rollback

Тижні минули; ніхто реально не верифікував, що флип routing назад працює. Тестуй: deploy no-op green, флип, флип назад, підтверди.

Schema-breaking-зміни під час cutover

sql
-- Migration, що крутиться під час cutover ALTER TABLE users DROP COLUMN old_field;

Старий blue ще query'їть old_field до flip-у трафіку. Результат: помилки під час короткого overlap. Фікс: expand-then-contract патерн.

State, що не переживає cutover

In-memory кеші, in-memory сесії, in-flight WebSocket. Плануй, як кожен переживає. Сесії external'ізуй; WebSocket gracefully drain (connection migration це складна проблема).

Недостатнє monitoring під час cutover

Якщо флипнув і відвернувся, не знаєш, чи green healthy під реальним load. Дивись error-rates, latency, throughput у real time протягом перших 5-10 хвилин.

Без auto-cutover

Ручне reverse-proxy редагування error-prone. Бери traefik labels, consul-template або CI-скрипт, що робить routing-зміну атомарно.

Реальне застосування

  • Stateless web/API сервіси: ідеальний use case. Більшість команд, що роблять blue-green, роблять тут.
  • Single-host Compose-деплої: swap через Traefik-labels або nginx-upstream.
  • Swarm-based-деплої: комбінуй blue-green зі Swarm-сервісами, labeled by color, плюс Traefik або HAProxy routing.
  • Kubernetes: Service selector swap; або бери спеціалізований tooling (Argo Rollouts, Flagger) для blue-green і canary.

Питання для поглиблення

Q: Як довго чекати між підняттям green і cutover?


A: Достатньо для healthcheck + smoke-test + warmup. App з cold-cache можуть потребувати хвилин синтетичного трафіку перед тим, як performance прод-рівня. «Healthy» необхідно, але недостатньо.

Q: Що з long-running з'єднаннями (WebSocket, gRPC streams)?


A: Вони живуть на blue до reconnect. Нові-відкриті йдуть на green. Більшість app drain natural'но за хвилини. Для критичних persistent з'єднань плануй maintenance-вікно або реалізуй reconnect-логіку.

Q: Чи blue-green кращий за canary?


A: Різні цілі. Blue-green: атомарний cutover, легше reason, миттєвий rollback. Canary: поступовий exposure, ловить slow-burn регресії, більш nuanced. Більшість зрілих команд роблять canary для великих release, blue-green для швидких iteration.

Q: Як зробити blue-green з DB-schema змінами?


A: Expand-then-contract. (1) Deploy schema-migration, що додає нову структуру без видалення старої (expand). (2) Deploy app-коду (green), що використовує і стару, і нову. (3) Як green стабільний, deploy schema-migration, що видаляє стару (contract). Три release для однієї логічної зміни, але кожен безпечний.

Q: (Senior) Як обробляти partial blue-green, де DB розділена між двома deploy?


A: Бери feature-flags + acutate schema-management. Code-path, що використовує новий schema, gated by flag; обидва blue і green мають новий код, але лише green має flag enabled. Deploy schema-migration спочатку (expand). Deploy обидва blue і green з новим кодом, flag off. Cutover на green. Enable flag (можливо gradually через percentage). Якщо issue, disable flag (rollback без re-deploy). Eventually прибери старий schema і code-path. Decouples deploy від rollout.

Приклади

Single-host з nginx

bash
# Стан: api-blue running, nginx route'ить на нього # Підняти green docker run -d --name api-green \ --network proxy \ --restart unless-stopped \ --health-cmd='wget -q --spider http://localhost:3000/health' \ myorg/api:1.1 # Чекай healthy while [[ "$(docker inspect api-green --format '{{.State.Health.Status}}')" != "healthy" ]]; do sleep 2 done # Smoke-test docker run --rm --network proxy curlimages/curl curl -f http://api-green:3000/health # Onovi nginx-config sed -i 's/api-blue:3000/api-green:3000/' /etc/nginx/conf.d/default.conf nginx -t && nginx -s reload # Monitor 10 хвилин sleep 600 # Якщо error-rate нормальний, cleanup blue docker stop api-blue && docker rm api-blue docker rename api-green api-blue

Traefik label-swap патерн

yaml
# compose-blue.yaml — currently active services: api: image: myorg/api:1.0 labels: - "traefik.enable=true" - "traefik.http.routers.api.rule=Host(`api.example.com`)" - "traefik.http.services.api.loadbalancer.server.port=3000" container_name: api-blue
bash
# Підняти green БЕЗ traefik-labels docker run -d --name api-green --network proxy myorg/api:1.1 # (без labels, тож Traefik не route'ить трафік до нього) # Smoke-test green docker run --rm --network proxy curlimages/curl curl -f http://api-green:3000/health # Cutover: видали labels з blue, додай green-labels docker container update --label-add 'traefik.enable=false' api-blue docker container update --label-add 'traefik.enable=true' api-green docker container update --label-add 'traefik.http.routers.api.rule=Host(`api.example.com`)' api-green docker container update --label-add 'traefik.http.services.api.loadbalancer.server.port=3000' api-green # Traefik підхоплює зміну за секунди.

Нота: docker container update для labels працює лише на Swarm-mode сервісах; для звичайного docker run можна перестворити container з оновленими labels. Багато команд через це беруть Swarm-сервіси або K8s.

Rollback-playbook

bash
#!/bin/bash # rollback.sh — викликай, коли post-cutover monitoring показує trouble set -e # Відновити blue-labels (або revert nginx-config) sed -i 's/api-green:3000/api-blue:3000/' /etc/nginx/conf.d/default.conf nginx -t && nginx -s reload # Опційно, зупини green (або лиши running для forensics) # docker stop api-green echo "Rolled back to blue. Investigate green container: docker logs api-green"

Швидкий rollback. Підготовлено заздалегідь, крутиться за секунди. Якщо не можеш запустити це з muscle-memory, не маєш реально blue-green.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Коментарі

Ще немає коментарів