Як реалізувати 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
# Shared-мережа для proxy і app
docker network create proxyКрок 1: blue running
docker run -d --name api-blue \
--network proxy \
--restart unless-stopped \
myorg/api:1.0Reverse proxy (Traefik, nginx, Caddy) route'ить трафік на api-blue.
# nginx upstream
upstream api_backend {
server api-blue:3000;
}Крок 2: підняти green
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.1Green up, але без трафіку. Чекай healthcheck:
docker inspect api-green --format '{{.State.Health.Status}}'
# чекай до: healthyКрок 3: smoke-test green out-of-band
До флипу трафіку, верифікуй, що green працює напряму:
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-вікна:
docker stop api-blue && docker rm api-blueПерейменуй для наступного deploy:
docker rename api-green api-blueАбо, частіше, наступний release стає новим «green», і цикл продовжується.
З Traefik (auto-routing через labels)
# 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:
# Підняти 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.
- Deploy expand-migration (нова колонка, нова таблиця). Старий код ще працює.
- Deploy green-коду, що використовує новий schema. Обидві версії коротко співіснують під час cutover.
- Після 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
-- 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
selectorswap; або бери спеціалізований 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
# Стан: 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-blueTraefik label-swap патерн
# 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# Підняти 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
#!/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.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.
Коментарі
Ще немає коментарів