Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Як реалізувати blue-green deployment з Docker?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Blue-green** крутить два повні середовища. Blue обслуговує прод-трафік; green це нова версія, idle. Після того, як health-checks пройдуть, флипни load-balancer blue → green. Старий blue лишається як instant rollback-target. ```bash # Blue running docker run -d --name api-blue --network proxy myorg/api:1.0 # Підняти green docker run -d --name api-green --network proxy myorg/api:1.1 # Верифікуй green-health, потім онови reverse-proxy на route до api-green # Drain blue, потім docker stop api-blue ``` **Головне:** миттєвий cutover, миттєвий rollback (флипни назад на blue). Ціна: 2x ресурсів під час deploy. Найкраще для stateless-сервісів. State (DB) потребує окремого handling, schema має бути backward-compatible під час swap-вікна.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**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.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.