Docker: Blue/green на одному хості — найпростіший підхід, що працює

Було корисно?

Проблема: у вас один Linux-сервер, одна публічна IP-адреса й один додаток, який не може лягти. «Просто деплойнути» перетворюється на п’ятихвилинну деградацію, «відкат, який не працює», і Slack-потік керівництва, що нагадує протокол з місця події.

Blue/green на одному хості — без блиску. Це не Kubernetes. Це не service mesh. Це невелика, дисциплінована послідовність кроків, яка дає вам дві версії поруч і переключення, яке можна відмінити без молитв.

Що означає blue/green на одному хості (і чого це не стосується)

Розгортання blue/green — це одночасний запуск двох повних версій вашого сервісу: поточної («blue») та кандидата («green»). Ви перевіряєте green, поки blue продовжує обробляти трафік. Потім перемикаєте трафік на green. Якщо щось пішло не так, повертаєтеся на blue.

На одному хості обмеження грубі:

  • У вас одне ядро, один мережевий інтерфейс, одна дискова підсистема. Ваша «надлишковість» переважно процедурна.
  • Не можна припускати, що невдалий деплой не пошкодить хост (безконтрольні логи, витоки пам’яті, заповнення диска).
  • Переключення має відбуватись на рівні L7 (зворотний проксі) або через місцеву переназначення портів. DNS занадто повільний, а «просто змініть порт у клієнта» — це жарт.

Отже, мета не «висока доступність» в класичному розумінні. Мета — безпечна зміна: коротші вікна простою, передбачуваний відкат і менше «не знаємо, що сталося» післяінцидентних звітів.

Ось суб’єктивна порада: якщо ви на одному хості, робіть blue/green із контейнером-зворотним проксі (Nginx чи HAProxy), тримайте прикладні контейнери простими, а зміни стану — явними. Якщо ви спробуєте хитрувати з iptables і ad-hoc скриптами — ви досягнете успіху аж до того моменту, коли не досягнете.

Жарт наприкінці: розгортання на одному хості — як дві паски безпеки в одному авто. Допомагає, але не перетворює авто на літак.

Цікаві факти та коротка історія (щоб ви перестали повторювати старі помилки)

  1. Blue/green з’явилось до контейнерів. Шаблон походить із релізної інженерії задовго до Docker — два ідентичні середовища й перемикання роутера були найпростішою історією «нульового простою».
  2. «Атомарний деплой» раніше означав фліп симлінків. Багато систем розгорталися у версіоновані каталоги й миттєво перемикали симлінк. Переключення upstream у зворотних проксі — духовний наступник.
  3. Ранні мережеві рішення Docker були гіршими, ніж пам’ять каже. Міст за замовчуванням еволюціонував; практики типу «просто опублікуйте порт і досить» виникли в часи, коли менше людей робили серйозну маршрутизацію багатьох сервісів на одному хості.
  4. Перевірки здоров’я не завжди були першим класом. Docker Compose поступово нормалізував явні healthcheck; старі стеки використовували крихкі «sleep 10» скрипти й малювали надію, що додаток готовий.
  5. Nginx вже два десятиліття — робоча конячка для релізів. Його механіка reload (грейсфулове перезавантаження конфігурації без обриву з’єднань) зробила його природним інструментом для переключення трафіку ще до появи «cloud-native».
  6. HAProxy популяризував явне здоров’я бекендів і поведінку ланцюжків непрацездатності. Багато SRE-команд дізналися, що «upstream слухає» — не те саме, що «сервіс здоровий».
  7. Розгортання на одному хості досі нормальні. Безліч внутрішніх додатків і edge-сервісів працюють на одиничних машинах, бо бізнес цінує простоту понад теоретичну стійкість.
  8. Метафорі «тварини vs худоба» ніколи не стосувалася вашої фінансової бази даних. Навіть у контейнерному світі stateful-сервіси залишаються особливими. Стратегія розгортання має це визнавати.

Найпростіший дизайн, який реально працює

Ми будуємо чотири рухомі частини:

  • proxy: контейнер з зворотним проксі, який володіє публічними портами (80/443). Він маршрутизовує на upstream «blue» або «green».
  • app-blue: контейнер поточної продакшен-версії.
  • app-green: контейнер-кандидат.
  • опціональні сайдкари: ранери міграцій, одноразові smoketests або маленький endpoint «whoami» для валідації маршрутизації.

Правила, що тримають це в порядку:

  1. Лише proxy прив’язує публічні порти. Blue і green залишаються в внутрішній Docker-мережі без опублікованих портів. Це уникне випадкового відкриття й конфліктів портів.
  2. Тримайте конфіг проксі переключуваним і перезавантажуваним. Один файл із вибором «активного upstream» простіший, ніж шаблонізувати п’ятдесят рядків під час інциденту.
  3. Закривайте cutover на справжній перевірці здоров’я. Якщо у вашого додатку немає /health, додайте його. Фічі можна доставити пізніше; без знання, чи воно живе — не можна нікого пускати.
  4. Зробіть відкат першокласною командою. Якщо для відкату потрібно «пригадати, що ми змінили», у вас немає відкату. У вас є театральне імпровізаційне шоу.
  5. Зміни стану — найскладніше. Blue/green чудово працює для безстанового коду. Для змін схеми БД потрібна стратегія сумісності або окремий планований крок обслуговування.

Парафразована думка від John Allspaw: «У операціях відмова — нормальна; стійкість приходить від підготовки до неї, а не від заперечення її існування.»

Розклад хоста: порти, мережі, томи та одна річ, якої не можна ділити

Порти

Публічні:

  • proxy:80 і proxy:443 опубліковані на хості.

Внутрішні:

  • app-blue слухає на 8080 всередині контейнера.
  • app-green слухає на 8080 всередині контейнера.

Nginx маршрутизує до app-blue:8080 або app-green:8080 у спільній внутрішній мережі.

Мережі

Створіть виділену мережу, наприклад bg-net. Не використовуйте дефолтну мережу, якщо ви цінуєте свого майбутнього себе.

Томи

Ось підступ одноручного хоста: спільне доступне записуване сховище між blue і green може зіпсувати вам день.

  • Добре ділити: тільки для читання — assets, довірена конфігурація, TLS-сертифікати, і, можливо, кеш, якщо втратити його безпечно.
  • Обережно ділити: директорії для завантажень. Дві версії можуть писати різні формати, права або шляхи.
  • Не діліть бездумно: файли SQLite, вбудовані бази даних або будь-що, де два процеси можуть одночасно писати без координації.

Якщо ваш додаток пише на локальний диск, віддайте перевагу одному з варіантів:

  1. Екстерналізуйте стан (object storage, база даних тощо).
  2. Версіоновані каталоги стану для кожного кольору, потім контрольована міграція і контрольоване переключення.
  3. «Спільна, але сумісна» схема, де обидві версії можуть працювати проти тієї самої БД і витримувати змішаний трафік під час cutover.

Логування

На одному хості ріст логів — вбивця розгортань. Використовуйте опції ротації логів Docker. Інакше ваш «деплой без простою» перетвориться на «немає диска, немає сервісу».

Робочий шаблон Docker Compose

Це навмисно нудно. Нудне — добре. Потім ви зможете налаштувати, коли воно стане надійним.

cr0x@server:~$ cat docker-compose.yml
version: "3.9"

services:
  proxy:
    image: nginx:1.25-alpine
    container_name: bg-proxy
    ports:
      - "80:80"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./nginx/snippets:/etc/nginx/snippets:ro
    depends_on:
      app-blue:
        condition: service_healthy
      app-green:
        condition: service_healthy
    networks:
      - bg-net

  app-blue:
    image: myapp:blue
    container_name: app-blue
    environment:
      - APP_COLOR=blue
    expose:
      - "8080"
    healthcheck:
      test: ["CMD-SHELL", "wget -qO- http://localhost:8080/health | grep -q ok"]
      interval: 5s
      timeout: 2s
      retries: 10
      start_period: 10s
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "5"
    networks:
      - bg-net

  app-green:
    image: myapp:green
    container_name: app-green
    environment:
      - APP_COLOR=green
    expose:
      - "8080"
    healthcheck:
      test: ["CMD-SHELL", "wget -qO- http://localhost:8080/health | grep -q ok"]
      interval: 5s
      timeout: 2s
      retries: 10
      start_period: 10s
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "5"
    networks:
      - bg-net

networks:
  bg-net:
    name: bg-net

Проксі-конфіг визначає, який колір отримує трафік. Тримайте це рішення в одному маленькому файлі.

cr0x@server:~$ mkdir -p nginx/conf.d nginx/snippets
...output...
cr0x@server:~$ cat nginx/snippets/upstream-active.conf
set $upstream app-blue;
...output...
cr0x@server:~$ cat nginx/conf.d/default.conf
server {
  listen 80;

  location /healthz {
    return 200 "proxy ok\n";
  }

  location / {
    include /etc/nginx/snippets/upstream-active.conf;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Request-Id $request_id;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_pass http://$upstream:8080;
  }
}
...output...

Цей підхід використовує змінну upstream. Її легко змінювати, легко відстежувати зміни й вона чисто перезавантажується.

Механіка переключення: атомарно, спостережувано, оборотно

Розгорніть green, не торкаючись трафіку

Зберіть/завантажте нове зображення як myapp:green. Запустіть його поруч із blue. Перевірте здоров’я й поведінку через проксі (або напряму в внутрішній мережі через exec curl).

Переключіть трафік, змінивши один файл і перезавантаживши Nginx

Переключення має бути:

  • Достатньо атомарним: одна зміна, один reload.
  • Спостережуваним: ви можете бачити, який upstream обробляє трафік.
  • Оборотним: той самий механізм у зворотному напрямку.

Перезавантаження Nginx — грейсфул: воно завантажує нову конфігурацію й дозволяє старим воркерам завершити активні з’єднання. Це не вирішує всіх краєвих випадків (довготривалі стріми), але це найменш поганий варіант на одному хості.

Відкат, повернення на blue шляхом зворотних дій

Процедура відкату має бути механічно ідентична до cutover. Якщо відкат «відрізняється», він зламається о 2-й ночі, коли ваш мозок — це в основному кава.

Другий і останній жарт: найнадійніший моніторинг — це все ще «клієнт», але у нього жахлива маршрутизація алертів і немає вікон обслуговування.

Практичні задачі (команди, виводи, рішення)

Це кроки, які ви справді виконуватимете. Кожне завдання містить: команду, приклад виводу, що це означає і яке рішення ви приймаєте на основі цього.

Завдання 1: Підтвердити версії Docker і Compose (перевірка спроможності)

cr0x@server:~$ docker version --format '{{.Server.Version}}'
26.1.3

Що це означає: у вас сучасне ядро Docker. Це важливо для стабільності мережі й поведінки health check.

Рішення: якщо це древнє (думаємо про епоху 19.x), виділіть час на оновлення перед тим, як звинувачувати додаток у дивній поведінці.

cr0x@server:~$ docker compose version
Docker Compose version v2.29.1

Що це означає: доступний Compose v2; можна покладатися на сучасну поведінку CLI.

Рішення: стандартизувати на docker compose (плагін), а не на старий бінарний docker-compose, щоб зменшити розриви «працює на моєму ноуті».

Завдання 2: Створити мережу один раз (щоб уникнути сюрпризів)

cr0x@server:~$ docker network create bg-net
8d4b7b7f0d3e5e2e0f6f5f5a8d1c0d3a9c2b1f2e3a4b5c6d7e8f9a0b1c2d3e4

Що це означає: мережа існує; контейнери можуть вирішувати імена сервісів.

Рішення: якщо в організації кілька стеків на одному хості — явна іменна мережа уникне колізій і «чому цей контейнер бачить ту базу» моментів.

Завдання 3: Підняти blue і green (але ще не переключати)

cr0x@server:~$ docker compose up -d
[+] Running 3/3
 ✔ Container app-blue   Started
 ✔ Container app-green  Started
 ✔ Container bg-proxy   Started

Що це означає: все стартувало. Це не те саме, що «все здорове».

Рішення: негайно перевірити статус здоров’я; не святкуйте, базуючись лише на «Started».

Завдання 4: Перевірити здоров’я контейнерів (гейт для cutover)

cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Status}}'
NAMES      STATUS
bg-proxy   Up 25 seconds
app-blue   Up 25 seconds (healthy)
app-green  Up 25 seconds (healthy)

Що це означає: обидві версії проходять health check. Проксі запущене.

Рішення: якщо green (unhealthy), зупиніться. Перегляньте логи й виправте перед тим, як переключити хоч один пакет.

Завдання 5: Підтвердити, який колір обслуговує через проксі (реальна істина)

cr0x@server:~$ curl -sS localhost/ | head -n 1
hello from blue

Що це означає: ваш поточний Nginx snippet вказує на app-blue.

Рішення: якщо це не те, що ви очікували — виправте конфіг проксі перед тим, як рухатися далі. Не дебажте «деплой», коли це просто маршрутизація.

Завдання 6: Валідовати green безпосередньо з контейнера проксі (щоб обійти зовнішній шум)

cr0x@server:~$ docker exec -it bg-proxy sh -lc "wget -qO- http://app-green:8080/ | head -n 1"
hello from green

Що це означає: Nginx може резолвити і дістатися до green у внутрішній мережі.

Рішення: якщо резолвинг не вдається — у вас невідповідність мережі (не та мережа, невірне ім’я сервісу або контейнери не приєднані).

Завдання 7: Прогнати тест конфігурації Nginx перед reload (щоб уникнути самонанесених відмов)

cr0x@server:~$ docker exec -it bg-proxy nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Що це означає: перезавантаження не зламає проксі синтаксичною помилкою.

Рішення: якщо тест не пройдено — не перезавантажуйте. Виправте конфіг. Зламане проксі — миттєвий простій.

Завдання 8: Переключитись на green (заміна одного файлу + reload)

cr0x@server:~$ sed -i 's/set \$upstream app-blue;/set $upstream app-green;/' nginx/snippets/upstream-active.conf
...output...
cr0x@server:~$ docker exec -it bg-proxy nginx -s reload
2026/01/03 09:22:48 [notice] 1#1: signal process started

Що це означає: Nginx прийняв сигнал reload і запустив нові воркери з новим таргетом маршрутизації.

Рішення: негайно перевірте, чи трафік тепер потрапляє на green, і слідкуйте за рівнем помилок кілька хвилин.

Завдання 9: Підтвердити, що cutover спрацював (і не наполовину)

cr0x@server:~$ curl -sS localhost/ | head -n 1
hello from green

Що це означає: запити тепер йдуть до green.

Рішення: якщо бачите змішані результати — можливо, у вас кілька екземплярів проксі, зовнішній балансувальник або кеш, що маскує реальність. Перевірте end-to-end.

Завдання 10: Спостерігайте логи 60 секунд після cutover (дешевий канар)

cr0x@server:~$ docker logs -f --since=60s app-green
INFO listening on :8080
INFO GET / 200 3ms
INFO GET /api/orders 200 21ms

Що це означає: green обслуговує реальний трафік і повертає 200.

Рішення: якщо бачите 500, таймаути або помилки БД: відкотіться зараз, а потім розбирайтесь спокійно.

Завдання 11: Відкат на blue (той самий механізм)

cr0x@server:~$ sed -i 's/set \$upstream app-green;/set $upstream app-blue;/' nginx/snippets/upstream-active.conf
...output...
cr0x@server:~$ docker exec -it bg-proxy nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
cr0x@server:~$ docker exec -it bg-proxy nginx -s reload
2026/01/03 09:24:10 [notice] 1#1: signal process started

Що це означає: відкат завершено з точки зору маршрутизації.

Рішення: якщо ви не можете відкотитися за хвилину — ваш процес не blue/green, а «blue/green-щось». Виправляйте процес, а не робіть геройства.

Завдання 12: Перевірити навантаження ресурсів (реалії одного хоста)

cr0x@server:~$ docker stats --no-stream
CONTAINER   CPU %   MEM USAGE / LIMIT     MEM %   NET I/O         BLOCK I/O     PIDS
app-blue    0.10%   120MiB / 8GiB         1.46%   15MB / 18MB     5MB / 1MB     23
app-green   2.40%   620MiB / 8GiB         7.56%   120MB / 95MB    60MB / 4MB    45
bg-proxy    0.05%   20MiB / 8GiB          0.24%   135MB / 130MB   2MB / 1MB     5

Що це означає: green важчий. Це може бути нормально або початок проблеми.

Рішення: якщо пам’ять зростає драматично — перевірте на витоки, зміни кешування або відсутні ліміти. На одному хості «додати репліки» — не план.

Завдання 13: Перевірити прив’язки портів (щоб уникнути випадкового відкриття і колізій)

cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Ports}}'
NAMES      PORTS
bg-proxy   0.0.0.0:80->80/tcp
app-blue   8080/tcp
app-green  8080/tcp

Що це означає: лише proxy прив’язаний до хост-порту 80. Blue/green — тільки внутрішні.

Рішення: якщо бачите 0.0.0.0:8080->8080/tcp на контейнері додатку — виправте. Ви створили другий шлях входу, що обходить переключення.

Завдання 14: Дебаг DNS і приєднання до мережі (коли проксі «не може дістатися upstream»)

cr0x@server:~$ docker network inspect bg-net --format '{{json .Containers}}'
{"a1b2c3d4":{"Name":"app-blue","IPv4Address":"172.20.0.2/16"},"b2c3d4e5":{"Name":"app-green","IPv4Address":"172.20.0.3/16"},"c3d4e5f6":{"Name":"bg-proxy","IPv4Address":"172.20.0.4/16"}}

Що це означає: усі три контейнери в одній мережі. Імена мають резолвитись.

Рішення: якщо проксі не в списку — підключіть його до мережі або виправте секцію мережі в Compose.

Завдання 15: Підтвердити дайджести образів (переконатися, що ви запускаєте те, що думаєте)

cr0x@server:~$ docker image inspect myapp:green --format '{{.Id}}'
sha256:7f0c1e9a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8

Що це означає: тег green вказує на конкретний ID образу.

Рішення: якщо CI ребейдить теги, покладайтеся на дайджести в реліз-нотах. «Green» не повинен мовчки змінюватися під час інциденту.

Завдання 16: Швидко виявити тиск на диск від Docker (перед тим, як це стане простоєм)

cr0x@server:~$ docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          42        8         18.4GB    9.1GB (49%)
Containers      16        3         1.2GB     900MB (75%)
Local Volumes   12        7         6.8GB     1.1GB (16%)
Build Cache     31        0         3.5GB     3.5GB

Що це означає: у вас є місце для звільнення, особливо build cache.

Рішення: якщо диск підтискає — очищуйте build cache у контрольованому вікні. Якщо чистити сліпо під час інциденту, можете видалити образ для відкату, який ще потрібен.

Три корпоративні міні-історії з практики

Інцидент через хибне припущення: «слухає = здоровий»

Середня компанія вела внутрішній API для інтеграцій біллінгу на одному хості. Додаток контейнеризували, хост був стабільний, а релізи — «швидкі». Вони вирішили зробити blue/green, стартуючи green на іншому порту і переключаючи upstream в Nginx.

Health check був TCP-підключенням. Якщо порт приймав з’єднання, green вважався добрим. Це здавалося логічним: процес запущений, так?

Потім в релізі з’явилась міграція старту, яка прогрівала кеш, підвантажуючи порцію даних з бази. Процес відкривав порт рано (дефолт фреймворку), але він ще не був готовий обробляти реальні запити. Під навантаженням він повертав 503 і таймаути, хоча залишався «healthy». Nginx охоче посилав продакшен-трафік у напівпробуджений сервіс.

Аварія не тривала довго, бо відкат був швидким. Шкода була в тому, що всі втратили довіру до процесу розгортання і почали вишукувати зміни лише в робочий час. Це не надійність — це страх перед розкладом.

Вони виправили це, змінивши health endpoint так, щоб він перевіряв залежності: підключення до БД, завершені міграції й простий запит. Ендпоінт залишився швидким і легким. Переключення знову стало нудним.

Оптимізація, яка відбилася боком: «поділимо все, щоб зекономити диск»

Інша команда вела контент-сервіс із завантаженнями користувачів на локальному диску. Щоб «оптимізувати», вони підмонтували той самий том uploads у blue і green, щоб нічого не синхронізувати і мати миттєвий відкат.

Це працювало місяцями, поки реліз не змінив спосіб генерації мініатюр. Green почав писати нові файли з іншим найменуванням і трохи іншою маскою прав. Blue, що продовжував працювати, обслуговував старі сторінки, які посилались на мініатюри, що перезаписувалися або замінювалися в процесі.

Симптом був дивний: не повний простій, а переривчасті биті зображення і іноді 403. Першими почали надходити звернення до підтримки, потім інженери. Команда ганялася за заголовками кешу, поведінкою CDN і навіть багами в браузерах. Корінь проблеми був побутовий: дві версії писали в одну директорію без контракту сумісності.

Виправлення теж було приземленим: версіоновані каталоги для згенерованих артефактів і фонове завдання для бекфіла. Shared uploads залишилися, але похідні файли стали версіоновані й потім мігрували. «Оптимізація диска» заощадила кілька гігабайт і коштувала тижня дебагу.

Нудна, але правильна практика, що врятувала день: «зберігайте старий образ і шлях відкату»

Регульований ентерпрайз тримав один хост у контрольованому сегменті для легасі gateway інтеграцій. Жодного автоскейлінгу, ніякої магії — лише зміни через керування й Pager.

Вони впровадили blue/green з проксі-контейнером і двома app-контейнерами, але також дотримувалися нудного правила: ніколи не очищати образи в робочий час і завжди зберігати останній відомо-робочий образ за дайджестом у локальному mirror-реєстрі. Нікому це правило не подобалося. Здавалося мотлохом.

Одного дня зовнішня бібліотека опублікувала поламаний мінорний реліз, який проходив юніт-тести, але викликав runtime-падіння лише під певним TLS-handshake. Green запустився, коротко вважався здоровим, а потім падав під реальним клієнтським трафіком.

Відкат спрацював миттєво. Жодної драми. Але справжнє спасіння було в тому, що команда могла розгорнути відомо-робочий образ навіть після того, як CI просунув теги далі, бо дайджест зберігся локально. Не довелося «перебудовувати минулотижневий коміт» під тиском.

Нічого героїчного не сталося, і саме в цьому сенс. Постмортем був коротким, технічним і без емоцій.

План швидкої діагностики

Коли blue/green деплой на одному хості йде не за планом, вузьке місце зазвичай одне з: маршрутизація, здоров’я, ресурси або стан. Перевіряйте в цьому порядку; це оптимізовано для «зупинити кровотечу» перш за все.

1) Маршрутизація: трафік йде туди, куди ви думаєте?

  • Перевірте, що проксі живе і відповідає на /healthz.
  • Перевірте, який upstream активний у файлі snippet.
  • Перевірте, що тест конфігурації Nginx пройшов і reload був виконаний.
cr0x@server:~$ curl -sS -i localhost/healthz | head -n 1
HTTP/1.1 200 OK

Рішення: якщо health проксі провалюється — припиніть дебаг додатку. Спочатку виправте проксі/контейнер/фаєрвол хоста.

2) Здоров’я: green насправді здоровий, чи просто «запущений»?

cr0x@server:~$ docker inspect -f '{{.State.Health.Status}}' app-green
healthy

Рішення: якщо unhealthy, не переключайтеся. Якщо вже переключилися — відкотіться і дебагайте green офлайн.

3) Ресурси: чи ви виснажуєте хост?

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:           7.7Gi       6.9Gi       210Mi       120Mi       610Mi       430Mi
Swap:          0B          0B          0B

Рішення: якщо доступної пам’яті мало і green важчий, ви на шляху до OOM killer. Відкат або додайте ліміти й ємність.

4) Стан: чи ви зробили щось невідворотне?

Якщо додаток використовує БД — перевірте міграції та сумісність. Якщо ви змінили формати даних, відкат коду може не відновити поведінку.

cr0x@server:~$ docker logs --since=10m app-green | tail -n 20
INFO migration complete
INFO connected to db

Рішення: якщо міграції виконано і вони несумісні назад — відкат коду може не виправити проблему. Ваш «деплой» перетворився на інцидент з даними.

Типові помилки: симптом → причина → виправлення

1) Симптом: Nginx показує «502 Bad Gateway» після переходу

  • Причина: проксі не може дістатися до green (не та мережа, невідповідність імені контейнера, green впав або додаток слухає на іншому порту).
  • Виправлення: перевірте приєднання до мережі й ім’я сервісу; підтвердіть, що green здоровий; підтвердіть, що додаток слухає на 8080.
cr0x@server:~$ docker exec -it bg-proxy sh -lc "wget -S -qO- http://app-green:8080/health"
  HTTP/1.1 200 OK
  ...
ok

2) Симптом: Green «healthy», але користувачі бачать помилки

  • Причина: health check занадто поверхневий (порт відкрито, але залежності не готові), або він не відображає реальні шляхи запитів.
  • Виправлення: зробіть health check таким, щоб він перевіряв критичні залежності і репрезентативний легкий запит. Тримайте його швидким.

3) Симптом: Після переключення продуктивність падає через 5–15 хвилин

  • Причина: витік пам’яті, ріст кешу, зміни пулу з’єднань або посилення логів, що викликає навантаження диску.
  • Виправлення: перевірте docker stats, пам’ять хоста й обсяг логів; встановіть ліміти пам’яті; ротуйте логи; досліджуйте пули з’єднань.

4) Симптом: Відкат не вирішує проблему

  • Причина: green виконав невідворотну зміну стану (міграція схеми, перепис даних), або проксі все ще маршрутизує на green через застарілу конфіг чи кілька проксі.
  • Виправлення: підтвердіть маршрутизацію; якщо стан змінився — використовуйте фікс уперед або відновлення з бекапу/снапшоту. Неможливо «відкотити час» через YAML.

5) Симптом: Одночасно обслуговують і blue, і green

  • Причина: контейнери додатку опублікували хост-порти, обходячи проксі, або зовнішній балансувальник спрямовує трафік на обидва.
  • Виправлення: приберіть публікацію портів з контейнерів додатку; переконайтесь, що відкритий лише проксі; перевірте зовнішню маршрутизацію.

6) Симптом: Розгортання інколи падає з помилкою «bind: address already in use»

  • Причина: ви намагалися прив’язати контейнери додатку безпосередньо до хост-портів для обох кольорів.
  • Виправлення: припиніть прив’язку портів додатків до хоста. Прив’язуйте тільки проксі до хост-портів і маршрутизуйтесь внутрішньо.

7) Симптом: Reload Nginx викликає коротке обривання довготривалих з’єднань

  • Причина: WebSocket/стріми і налаштування проксі не підігнані; reload грейсфул, але не чарівний.
  • Виправлення: перевірте keepalive-настройки; для дійсно довготривалих з’єднань розгляньте HAProxy з явною поведінкою бекендів або прийміть коротке вікно обслуговування.

8) Симптом: Диск заповнюється під час деплою

  • Причина: ріст логів, накопичення образів, build cache.
  • Виправлення: встановіть ротацію логів Docker; прорідіть з наміром; зберігайте образи для відкату; моніторьте диск.

Чеклісти / покроковий план

Перевірка перед польотом (перед тим, як торкатися продакшен-трафіку)

  1. Proxy — єдиний сервіс, що публікує 80/443 на хості.
  2. Blue і green працюють у тій самій внутрішній Docker-мережі.
  3. Існують health check і вони перевіряють залежності (не тільки «порт відкритий»).
  4. Логи мають ротацію (max-size, max-file).
  5. Шлях відкату протестований: перемкнення назад на blue і перезавантаження проксі працюють.
  6. Зміни стану сплановані: міграції сумісні назад або виділені окремо.
  7. Ви вмієте перевірити «який колір живий» за реальним запитом.

Покроковий план деплою (single-host blue/green)

  1. Зібрати/завантажити образ green і чітко його маркувати.
  2. Запустити green поруч із blue (без публічних портів на green).
  3. Чекати на здоров’я: green має бути healthy.
  4. Smoke-тест green зсередини контейнера проксі (мережа і відповідь).
  5. Прогнати тест конфігурації Nginx перед reload.
  6. Переключити файл upstream на green.
  7. Перезавантажити проксі і підтвердити, що живий трафік іде на green.
  8. Спостерігати логи, рівень помилок, латентність і ресурси хоста кілька хвилин.
  9. Тримати blue запущеним доти, доки ви впевнені (ваше вікно для відкату).
  10. Після прийняття, зупинити blue і зберегти його образ принаймні на один релізний цикл.

Чекліст відкату (коли green підводить)

  1. Переключити файл upstream назад на blue.
  2. Прогнати тест Nginx.
  3. Перезавантажити Nginx.
  4. Підтвердити, що живий трафік йде на blue.
  5. Забрати логи і метрики green для дебагу.
  6. Вирішити, чи тримати green запущеним для розслідування, чи зупинити його, щоб звільнити ресурси.

Питання й відповіді

1) Чи можна робити blue/green без зворотного проксі?

Можна, але ви винайдете з нуля зворотний проксі, використовуючи хост-порти, iptables або DNS-хаки. На одному хості явний проксі — найменш поганий варіант переключення.

2) Чому не просто використовувати публікацію портів Docker і поміняти порти?

Бо ви не зможете прив’язати два контейнери до одного хост-порту, а сама заміна не атомарна без додаткових механізмів. До того ж ви в якийсь момент відкриєте обидві версії.

3) Чи дійсно перезавантаження Nginx — це нульовий простій?

Для типових HTTP-запитів — близько до нульового. Reload грейсфул, але довготривалі з’єднання і дивні клієнти можуть відчути перебої. Проєктуйте з цим у розумі.

4) Чи повинні blue і green ділити ту саму базу даних?

Зазвичай так, але тільки якщо ваша стратегія міграцій підтримує сумісність під час перекриття. Якщо green вимагає руйнівної міграції — потрібен запланований крок по даних або вікно простою.

5) Який найдорожчий безпечний спосіб обробки міграцій схеми?

Віддавайте перевагу зворотньо-сумісним міграціям: додавайте стовпці, уникайте руйнівних змін і розгортайте код, який може працювати з обома форматами. Деструктивний клін ап виконуйте пізніше.

6) Як довго тримати blue після cutover?

Достатньо, щоб впіймати ті відмови, які реально трапляються у вашому середовищі: зазвичай від хвилин до годин. Якщо збої часто проявляються «на наступний ранок», тримайте blue доступним довше — ресурси дозволяють.

7) Чи можна канарити невеликий відсоток трафіку на одному хості?

Так, але це складніше. HAProxy робить вагові бекенди простими. В Nginx це теж можливо, але вимагатиме складнішої конфігурації і кращої спостережуваності.

8) Що з TLS на проксі?

Термінуйте TLS на проксі. Тримайте сертифікати на хості як readonly mounts. Не термінуйте TLS і в додатках одночасно, якщо вам подобається дебаг.

9) Чи потрібні ліміти ресурсів для контейнерів у blue/green?

На одному хості — так. Без лімітів дві версії, що працюють одночасно, можуть виснажити хост і зробити відкат марним, бо все буде повільним.

10) Як довести, яка версія обслужила запит?

Поверніть рядок версії в заголовку відповіді або в debug-endpoint. Логуйте це. Коли хтось каже «green поламаний», вам потрібні докази, а не відчуття.

Висновок: практичні наступні кроки

Blue/green на одному хості — не про досконалість. Це про виключення найдурніших відмов: зламані деплои, повільні відкати і «не знаємо, що живе». Якщо зробите лише три речі — зробіть ці:

  1. Поставте зворотний проксі попереду й зробіть його єдиною публічною точкою входу.
  2. Зробіть перевірки здоров’я реальними й закривайте cutover на них.
  3. Зробіть cutover і відкат однією і тією ж операцією в протилежні боки.

Потім дисципліновано займіться нецікавою, але важливою роботою: ротацією логів, збереженням образів для відкату і розгляданням змін стану як окремих подій. Ваші майбутні інциденти все одно трапляться. Вони просто будуть коротші, чистіші й менш театральні.

← Попередня
Набори даних ZFS за робочим навантаженням: прийом управління, що запобігає хаосу
Наступна →
DNS SERVFAIL від постачальника: як довести, що проблема на їхньому боці

Залишити коментар