Три рычага безопасного релиза: трафик, когорты и функциональные флаги

У вас есть новая версия приложения. Тесты проходят на стейджинге. Команда уверена в результате. Но перед тем как нажать «деплой» в продакшн, вы всё равно колеблетесь. Это нормально. Каждый деплой несёт риск, и самый безопасный способ снизить этот риск — не отправлять изменения всем сразу.

Идея проста: выпускайте релиз постепенно. Но «постепенно» означает разное в зависимости от того, чем вы управляете. Когда вы решаете не выкатывать изменение всем пользователям одновременно, у вас есть три независимых рычага. Каждый контролирует свой аспект релиза. Понимание того, когда использовать каждый из них и как их комбинировать, — это то, что отличает контролируемый rollout от игры в рулетку.

Трафик: контроль нагрузки на новую версию

Самый прямой способ постепенного релиза — контролировать долю запросов, попадающих на новую версию. Вместо того чтобы направлять весь трафик на обновлённое приложение, вы отправляете только его часть. Пять процентов всех запросов идут на новую версию. Остальные девяносто пять процентов продолжают обрабатываться старой версией.

Этот подход называется разделением трафика (traffic splitting). Вы настраиваете его на уровне инфраструктуры: балансировщик нагрузки, service mesh или API-шлюз. Конфигурация проста. Вы указываете процент, и инфраструктура распределяет запросы соответствующим образом. Никакой привязки к пользователям. Никакой логики таргетинга. Только грубая пропорция сетевого трафика.

Разделение трафика идеально подходит для самого раннего этапа валидации. Вы хотите узнать, не падает ли новая версия при старте, не выдаёт ли ошибки под реальной нагрузкой или не потребляет ли больше памяти, чем ожидалось. Поскольку выборка случайна, вы быстро получаете сигнал о базовом состоянии. Если уровень ошибок взлетает, вы останавливаете rollout до того, как проблема затронет большинство пользователей.

Вот реалистичный пример с использованием Istio VirtualService для разделения трафика: 5% на новую версию и 95% на старую.

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: myapp
spec:
  hosts:
  - myapp.example.com
  http:
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        host: myapp
        subset: v2
      weight: 5
    - destination:
        host: myapp
        subset: v1
      weight: 95

Ограничение очевидно: вы не можете контролировать, кто получит новую версию. Один и тот же пользователь может попасть на новую версию в одном запросе и на старую — в следующем. Такая неконсистентность допустима для валидации инфраструктуры, но становится проблемой, когда нужно наблюдать за поведением пользователя в динамике или собирать обратную связь от конкретных людей.

Когорты: контроль того, кто получает новую версию

Когда вам нужно таргетировать конкретных пользователей, разделения трафика недостаточно. Вы хотите, чтобы сначала новую версию увидели внутренние тестировщики. Или бета-пользователи. Или пользователи из определённого региона. Или аккаунты с конкретным тарифным планом. Здесь в игру вступают когорты.

Когорта — это группа пользователей, определённая по критерию: диапазон ID пользователей, географическое местоположение, тип аккаунта, бизнес-сегмент или любой другой атрибут, известный вашей системе. Вместо маршрутизации случайного процента трафика вы направляете весь трафик от конкретной когорты на новую версию. Это называется staged rollout (поэтапное развёртывание).

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

Когорты также позволяют постепенное расширение. Вы начинаете с внутренних пользователей, затем расширяете на бета-пользователей, затем на пять процентов продакшн-пользователей в определённом регионе, затем на двадцать пять процентов и так далее. На каждом этапе у вас есть известная группа пользователей, дающая сигнал. Rollout превращается в серию осознанных расширений, а не в единое бинарное решение.

Оборотная сторона — сложность. Вам нужна инфраструктура для идентификации и маршрутизации пользователей на основе атрибутов. Ваш балансировщик или шлюз должен понимать идентификатор пользователя, а не только пути запросов. Это часто требует интеграции с системами аутентификации, базами пользователей или управлением сессиями. Настройка сложнее, чем разделение трафика, но даёт гораздо более богатый контроль.

Функции: контроль того, какая функциональность активна

Трафик и когорты контролируют, какую версию приложения запускает пользователь. Но иногда вы хотите развернуть новую версию везде и выборочно активировать конкретные функции внутри неё. Возможно, новая версия уже работает на всех серверах, но конкретная функция не должна быть видна всем. Или вы хотите включить функцию для подмножества пользователей без повторного деплоя.

Здесь в игру вступают функциональные флаги (feature flags). Функциональный флаг — это условный переключатель в вашем коде, который определяет, активна ли функция для данного пользователя. Флаг можно переключать во время выполнения без нового деплоя. Вы можете включить функцию для десяти процентов пользователей, для внутренних аккаунтов, для пользователей с чётными ID или на основе любой другой логики, которую вы определите.

Функциональные флаги разделяют деплой и активацию. Вы можете развернуть код, содержащий новую функцию, за дни или недели до того, как функция будет фактически включена. Это снижает риск самого деплоя, потому что вы не меняете поведение одновременно с изменением кода. Если с функцией что-то пошло не так, вы отключаете флаг. Никакого отката. Никакого повторного деплоя. Просто изменение конфигурации.

Сила функциональных флагов — в гранулярности. Вы можете таргетировать отдельных пользователей, запускать A/B-тесты, постепенно увеличивать охват или мгновенно отключать функцию, вызывающую проблемы. Но эта сила накладывает ответственность. Флаги добавляют условную логику в кодовую базу. Слишком много флагов или флаги, которые никогда не удаляются, создают технический долг. Каждый флаг, оставшийся в коде после полного внедрения функции, становится мёртвым грузом.

Комбинирование трёх рычагов

Эти три компонента не исключают друг друга. На практике зрелая стратегия progressive delivery использует их все вместе.

Диаграмма ниже показывает, как три рычага работают вместе в типичной последовательности progressive delivery.

flowchart TD A[Начало релиза] --> B{Валидировать инфраструктуру?} B -->|Да| C[Разделение трафика] C --> D[Проверка уровня ошибок] D -->|OK| E{Нужна обратная связь от пользователей?} D -->|Высокий| F[Остановить rollout] E -->|Да| G[Когортный rollout] E -->|Нет| H{Развернуть код сейчас, активировать позже?} G --> H H -->|Да| I[Функциональные флаги] H -->|Нет| J[Полный rollout] I --> K[Постепенно включить флаг] K --> L[Мониторинг и настройка] L -->|Безопасно| J L -->|Проблема| M[Отключить флаг]

Типичная последовательность может выглядеть так:

  1. Разверните новую версию на небольшой процент трафика, используя разделение трафика. Проверьте, что приложение не падает и не утекает память.
  2. Расширьте на когорту внутренних пользователей с помощью staged rollout. Соберите обратную связь и наблюдайте за паттернами поведения.
  3. Когда уверенность появится, разверните новую версию на всех серверах, но оставьте новую функцию за функциональным флагом. Включите флаг для десяти процентов пользователей.
  4. Постепенно увеличивайте процент включения флага, отслеживая метрики. Если что-то пошло не так, мгновенно отключите флаг.

Каждый рычаг даёт вам свой тип контроля. Трафик контролирует, насколько широко распространяется новая версия. Когорты контролируют, кто затронут. Функции контролируют, какая функциональность активна. Когда вы понимаете все три, вы можете спроектировать стратегию релиза, соответствующую вашему уровню риска и потребностям валидации.

Практический чек-лист

Перед следующим релизом задайте себе эти вопросы:

  • Нужно ли сначала проверить базовое состояние инфраструктуры? Используйте разделение трафика.
  • Нужна ли обратная связь от конкретных пользователей? Используйте когорты и staged rollout.
  • Хотите ли вы развернуть код сейчас, но активировать функции позже? Используйте функциональные флаги.
  • Есть ли у вас способ измерить, безопасна ли новая версия или функция? Если нет, не начинайте rollout.

Вывод

Постепенный релиз — это не одна техника. Это три независимых рычага, решающие разные проблемы. Разделение трафика валидирует инфраструктуру. Когорты валидируют пользовательский опыт. Функциональные флаги разделяют деплой и активацию. Команды, которые выпускают релизы безопасно, — это те, кто знает, какой рычаг дёрнуть и когда.