Образ собран, тесты прошли — остался последний шаг: новый код встречается с реальными пользователями. Именно здесь и прячется риск. Стратегии релиза отвечают на два вопроса: сколько пользователей заметят проблему и как быстро вы вернётесь назад.
Раньше деплоили «всё или ничего»
Исторически деплой означал: остановить сервер, залить новую версию, запустить. Пока идёт замена — сервис недоступен. Если что-то пошло не так — откат занимает столько же времени, сколько сам деплой. Все пользователи видят проблему одновременно.
Современные стратегии решают это по-разному. Разберём каждую с нуля.
Rolling update — менять по одной реплике
Проблема: при полной замене сервис на время недоступен, а на большом парке серверов это неприемлемо.
Решение: реплики заменяются по очереди, а не все сразу. Пока одна реплика перезапускается с новой версией, остальные продолжают обслуживать запросы. Ёмкость сервиса почти не проседает.
Kubernetes делает это из коробки: он не пустит трафик на новую реплику, пока та не пройдёт readiness-проверку. Если новая реплика стартует с ошибками — rolling останавливается сам.
Что rolling не умеет:
- мгновенный откат (откат — это такой же rolling в обратную сторону, занимает минуты);
- защита от ошибок, которые видны только под полным трафиком;
- контроль «кто именно увидит новую версию».
Важное условие: во время rolling update работают две версии одновременно. Это значит, что схема базы данных и формат сообщений должны понимать обе версии. Если новая версия добавила колонку, а старая её не знает — это не проблема; если старая версия сломается при виде новых данных — это проблема.
Rolling update — разумный выбор по умолчанию для большинства изменений.
Blue-green — мгновенный переключатель
Проблема: откат занимает минуты, а некоторые ошибки критичны и каждая секунда на счету.
Решение: держать два полных окружения. Одно (green) несёт боевой трафик, в другом (blue) разворачивается новая версия. Когда blue готово и проверено — трафик переключается целиком. Откат: переключить трафик обратно на green. Занимает секунды.
Что blue-green даёт:
- новую версию можно проверить в продакшн-окружении до того, как её увидят пользователи;
- откат — переключение, а не новый деплой.
За что платите:
- двойная ёмкость на время выкатки;
- база данных общая для обоих окружений, поэтому миграции схемы должны понимать обе версии;
- фоновые обработчики (воркеры, потребители очередей) нельзя запускать в двух версиях одновременно — их переключают, не дублируют.
Blue-green оправдан, когда цена ошибки высока, а релизы редкие: платёжные системы, критичные API, системы с SLA.
Canary — выкатка на процент
Проблема: даже хорошо протестированный код может вести себя иначе под реальным трафиком и реальными данными.
Решение: новая версия сначала получает маленькую долю трафика — например, 1%. Если метрики (ошибки, время ответа, бизнес-показатели) не ухудшаются, доля растёт: 1% → 5% → 25% → 100%. При деградации — автоматический откат. Проблему увидела сотая часть пользователей, а не все.
Название — от шахтёрской практики брать канарейку в шахту: если птица гибнет, шахтёры уходят. Новая версия — канарейка; если она «гибнет» на малом проценте, остальные пользователи в безопасности.
Что нужно для canary:
- инфраструктура для маршрутизации с весами (service mesh, Argo Rollouts, Flagger);
- надёжные метрики — без них canary превращается в rolling с лишними шагами; продвижение по ступеням должно быть по формальным критериям, не «посмотрим глазами»;
- время — выкатка растягивается на часы.
Canary оправдан при большом трафике (на 100 запросов в секунду 1% — это один запрос; статистики нет) и зрелой наблюдаемости.
Feature flags — деплой и релиз как разные события
Проблема: иногда код готов, но включать его для всех страшно. Или нужно проверить фичу на 10% пользователей без изменений в инфраструктуре.
Решение: code travels separately from its activation. Код едет в продакшн выключенным, а включается позже — без деплоя. Флаг можно включить для всех, для процента, для конкретного сегмента (например, только бета-пользователи).
Логика одна во всех языках: взять идентификатор (пользователя, заказа), спросить сервис флагов, выбрать путь:
// Java — Unleash
UnleashContext ctx = UnleashContext.builder()
.userId(customerId)
.build();
if (unleash.isEnabled("new-pricing", ctx)) {
return newPricingPolicy.calculate(order);
}
return currentPricingPolicy.calculate(order);
// Go — Unleash
ctx := unleash.Context{UserId: customerId}
if unleash.IsEnabled("new-pricing", unleash.WithContext(ctx)) {
return newPricingPolicy.Calculate(order)
}
return currentPricingPolicy.Calculate(order)
// Node/TypeScript — Unleash
const enabled = isEnabled("new-pricing", { userId: customerId });
if (enabled) {
return newPricingPolicy.calculate(order);
}
return currentPricingPolicy.calculate(order);
# Python — Unleash
if client.is_enabled("new-pricing", {"userId": customer_id}):
return new_pricing_policy.calculate(order)
return current_pricing_policy.calculate(order)
Что флаги меняют в процессе:
- деплой становится скучным — выключенный код не несёт риска;
- включение обратимо за секунды;
- постепенная раскатка по пользователям без инфраструктуры весов.
Главная ловушка — вечные флаги. Флаг без даты удаления через год становится частью архитектуры, которую никто не понимает. Заведите правило: у каждого флага есть владелец и дата, когда он должен исчезнуть. После полной раскатки — удалить.
Для начала не нужен специальный сервис: таблица в PostgreSQL с кешем вполне достаточна. Unleash, LaunchDarkly и другие решения нужны, когда таблицы начинает не хватать.
Сравнение стратегий
| Rolling | Blue-green | Canary | + Feature flags | |
|---|---|---|---|---|
| Радиус ошибки | Все, постепенно | Все, мгновенно | Малый процент | Управляемый сегмент |
| Скорость отката | Минуты | Секунды | Автоматика | Секунды |
| Дополнительные затраты | Нет | Двойная ёмкость | Маршрутизация + метрики | Дисциплина флагов |
| Когда применять | По умолчанию | Дорогая ошибка | Большой трафик, зрелые метрики | Всегда полезны |
Практичная связка для большинства команд: rolling + feature flags. Canary — когда трафик и метрики дозрели. Blue-green — точечно для самых критичных систем.
Типичные ошибки
Canary без формальных критериев. «Посмотрим в Grafana» — это не canary, это надежда. Если нет порогов для автоматического продвижения или отката, canary не работает как защита.
Blue-green с несовместимой миграцией схемы. Переключили на blue, мигрировали схему — старый green больше не работает с новой схемой. Откат за секунды превращается в восстановление из резервной копии.
Вечные флаги. Сотня флагов, переплетённых условиями, сложнее любого монолита. Флаги — временный инструмент.
Воркеры под canary. Потребитель очереди в двух версиях, по-разному обрабатывающих одни данные — рассинхрон. Для фоновых обработчиков используют Recreate или переключение, не распределение по процентам.
Коротко
- Стратегии релиза отличаются радиусом ошибки и скоростью отката.
- Rolling — реплики меняются по одной; откат занимает минуты; выбор по умолчанию.
- Blue-green — два окружения, трафик переключается целиком; откат за секунды; цена — двойная ёмкость.
- Canary — новая версия получает малый процент трафика и растёт по метрикам; требует маршрутизации и надёжных метрик.
- Feature flags — деплой и включение функции разделены; флаги включают/выключают за секунды без деплоя.
- При rolling и canary две версии работают одновременно — схема БД и контракты должны понимать обе.
- Флаги — временный инструмент: заводить с датой удаления и убирать после раскатки.
Что почитать дальше
- Деплой в Kubernetes — механика rolling update и совместимые миграции.
- Принципы конвейера — деплой и релиз как разные события.
- Ветки и релизный цикл — как код доезжает до этих стратегий.