Представь: у тебя интернет-магазин, пятница вечер, трафик на пике. Надо выкатить обновление — новый алгоритм расчёта скидок. Обычный деплой означает несколько минут даунтайма. Несколько минут в пятницу вечером — это потерянные заказы, злые пользователи и нервный менеджер. Сине-зелёный деплой решает именно эту проблему.

Как это работает

Идея проще, чем кажется. Ты держишь две идентичные production-среды: одну называешь «синей», другую — «зелёной». В любой момент времени только одна из них принимает реальный трафик, вторая стоит в резерве.

Последовательность такая:

  1. Сейчас работает синяя среда — на ней живёт версия 1.0.
  2. Ты разворачиваешь версию 1.1 на зелёной среде.
  3. Прогоняешь тесты на зелёной — убеждаешься, что всё работает.
  4. Переключаешь балансировщик нагрузки с синей на зелёную. Это занимает секунды.
  5. Зелёная теперь принимает трафик. Синяя стоит живой, на ней 1.0.
  6. Если что-то пошло не так — переключаешь обратно за секунды.
  7. Когда убедился, что 1.1 работает штатно — синюю обновляешь до 1.1 и она становится следующим резервом.

Переключение трафика — это буквально изменение одного параметра в конфиге балансировщика или смена DNS-записи. Даунтайм при этом нулевой.

Минимальная реализация через Nginx

Не нужен Kubernetes, чтобы попробовать. Вот как это делается на двух обычных серверах с Nginx в качестве балансировщика.

Структура такая:

  • lb.example.com — балансировщик (Nginx)
  • blue.internal — синяя среда, порт 8080
  • green.internal — зелёная среда, порт 8080

Конфиг балансировщика:

upstream active_backend {
    server blue.internal:8080;
    # server green.internal:8080;  # закомментировано — не активно
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://active_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Чтобы переключиться на зелёную среду, раскомментируешь строку с green, комментируешь строку с blue и делаешь nginx -s reload. Reload в Nginx — это graceful: текущие соединения доживают свой век, новые идут уже на новый upstream. Даунтайма нет.

Можно автоматизировать это через простой скрипт:

#!/bin/bash
CURRENT=$(grep -v '#' /etc/nginx/conf.d/backend.conf | grep 'server' | awk '{print $2}')

if [[ $CURRENT == *"blue"* ]]; then
    sed -i 's/server blue.internal/# server blue.internal/' /etc/nginx/conf.d/backend.conf
    sed -i 's/# server green.internal/server green.internal/' /etc/nginx/conf.d/backend.conf
    echo "Switched to GREEN"
else
    sed -i 's/server green.internal/# server green.internal/' /etc/nginx/conf.d/backend.conf
    sed -i 's/# server blue.internal/server blue.internal/' /etc/nginx/conf.d/backend.conf
    echo "Switched to BLUE"
fi

nginx -s reload

Просто, предсказуемо, легко дебажить.

Реализация в Kubernetes

В Kubernetes сине-зелёный деплой делается через манипуляцию с labels у Service.

Есть два Deployment:

# blue deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-blue
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      version: blue
  template:
    metadata:
      labels:
        app: myapp
        version: blue
    spec:
      containers:
      - name: app
        image: myapp:1.0
# green deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-green
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      version: green
  template:
    metadata:
      labels:
        app: myapp
        version: green
    spec:
      containers:
      - name: app
        image: myapp:1.1

Service при этом смотрит на конкретную версию:

apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  selector:
    app: myapp
    version: blue  # ← вот здесь переключаем
  ports:
  - port: 80
    targetPort: 8080

Чтобы переключиться на зелёную:

kubectl patch service myapp-service -p '{"spec":{"selector":{"version":"green"}}}'

Эта команда выполняется мгновенно. Kubernetes перестаёт направлять трафик на синие поды и начинает на зелёные. Существующие соединения завершаются корректно благодаря graceful termination.

Что делать с базой данных

База данных — самое больное место в сине-зелёном деплое. Если новая версия приложения меняет схему БД, надо думать аккуратно.

Плохой сценарий: версия 1.1 добавляет колонку discount_type в таблицу orders с NOT NULL. Ты переключаешься на зелёную среду, но потом что-то идёт не так и надо откатиться на синюю. Версия 1.0 не умеет работать с колонкой discount_type и падает.

Правильный подход — backward-compatible миграции. Делается в три этапа:

Деплой 1.1: добавляем колонку discount_type как nullable, без дефолта. Обе версии — старая и новая — могут работать с такой схемой.

Деплой 1.2: версия приложения начинает заполнять discount_type. Старая версия её игнорирует — ничего не ломается.

Деплой 1.3: когда убедились, что откат больше не нужен — делаем колонку NOT NULL, добавляем дефолт, убираем обратную совместимость.

Да, это дольше и сложнее, чем просто «добавил колонку и забыл». Но это цена нулевого даунтайма.

Альтернатива — использовать отдельную базу для каждой среды и синхронизировать их. Это работает для read-heavy приложений, но для write-heavy становится кошмаром. Лучше не надо.

Мониторинг после переключения

Переключил трафик — не расслабляйся. Первые 10-15 минут критичны. Что смотришь:

Error rate: если на синей среде было 0.1% ошибок, а на зелёной стало 2% — что-то не так. Порог ставь исходя из baseline, не из головы.

Latency: медианное время ответа и p99. Если медиана не изменилась, а p99 вырос с 200мс до 2 секунд — у части пользователей проблемы.

Бизнес-метрики: конверсия, количество оформленных заказов, количество регистраций. Технические метрики могут быть в норме, а пользователи при этом не могут пройти онбординг из-за бага в новой форме.

Хорошая практика — автоматический откат. Если error rate превышает порог в течение 5 минут после переключения — скрипт автоматически откатывается на предыдущую среду и пишет в Slack. Без участия человека.

#!/bin/bash
ERROR_RATE=$(curl -s prometheus:9090/api/v1/query \
  --data-urlencode 'query=rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m])' \
  | jq '.data.result[0].value[1]' | tr -d '"')

if (( $(echo "$ERROR_RATE > 0.05" | bc -l) )); then
    echo "Error rate $ERROR_RATE exceeds threshold, rolling back"
    ./switch-to-blue.sh
    curl -X POST $SLACK_WEBHOOK -d '{"text":"Auto-rollback triggered: error rate exceeded 5%"}'
fi

Когда это реально нужно

Сине-зелёный деплой — не серебряная пуля. Есть смысл использовать, если:

  • Приложение обрабатывает деньги, заказы, медицинские данные — то есть даунтайм стоит реальных денег или репутации.
  • Деплои происходят часто: несколько раз в неделю или чаще.
  • Есть возможность держать две среды — это дополнительные расходы на инфраструктуру.
  • Команда готова поддерживать усложнённый пайплайн деплоя.

Когда это излишество:

  • Сайт-визитка или лендинг с ночным трафиком в 10 человек. Даунтайм в 2 минуты раз в месяц — не проблема.
  • Стартап на ранней стадии, где скорость итераций важнее стабильности.
  • Бюджет не позволяет держать двойную инфраструктуру.

В этих случаях хватает rolling deployment или просто деплоя в нерабочее время.

Сравнение с канареечным деплоем

Часто путают blue-green с canary deployment. Разница принципиальная.

При blue-green переключение бинарное: либо 100% трафика на синей, либо 100% на зелёной. Промежуточного состояния нет.

При canary сначала на новую версию идёт 1-5% трафика, потом 10%, потом 50%, потом 100%. Это позволяет поймать баги на малом проценте пользователей, но усложняет инфраструктуру и требует умного роутинга.

Blue-green проще настроить и понять. Canary лучше для критически важных изменений, где нужно валидировать на реальных пользователях постепенно.

Можно комбинировать: делать canary внутри зелёной среды — сначала 10% трафика на зелёную, потом 100%.

Инструменты, которые упрощают жизнь

Argo Rollouts — расширение для Kubernetes, которое добавляет blue-green и canary из коробки. Не надо писать скрипты руками, всё через CRD-ресурсы.

Spinnaker — тяжёлая платформа для CI/CD от Netflix. Умеет blue-green, canary, автоматический rollback по метрикам. Избыточно для маленьких команд, незаменимо для крупных.

AWS CodeDeploy — если ты на AWS, то blue-green деплой для ECS и Lambda настраивается через пару кликов в консоли. С Lambda вообще переключение происходит на уровне aliases — это элегантно.

GitHub Actions + Terraform — для тех, кто не хочет отдельный инструмент. Пишешь workflow, который поднимает новую среду через Terraform, прогоняет тесты и переключает DNS или балансировщик.

Чек-лист перед первым blue-green деплоем

  • Есть мониторинг с историческим baseline для error rate и latency
  • Определены пороги для автоматического отката
  • База данных мигрируется в backward-compatible режиме
  • Оба окружения идентичны (версии зависимостей, конфиги, переменные окружения)
  • Есть runbook: кто что делает при проблемах после переключения
  • Команда знает, как вручную откатиться за 60 секунд
  • Переключение протестировано в staging, прежде чем делать в production

Последний пункт особенно важен. Первый раз переключать среды лучше не в пятницу вечером на production, а в спокойной обстановке на staging, когда никто не смотрит и можно спокойно разобраться с нюансами.

Если ты запускаешь новый проект и думаешь о том, как выстроить деплой с самого начала — это вопрос, который стоит поднять ещё на этапе проектирования инфраструктуры. Студия REEXY, например, при разработке корпоративных сайтов и интернет-магазинов закладывает архитектуру под такие сценарии изначально, чтобы потом не переделывать.

Blue-green деплой — это про уверенность. Когда знаешь, что в любой момент можешь откатиться за 30 секунд, деплоить становится менее страшно. А когда деплоить не страшно — делаешь это чаще. А когда делаешь чаще — изменения меньше, риски ниже. Вот такая петля обратной связи.