сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · CI/CD

Стратегии деплоя: rolling, blue-green, канарейка

Стратегии выката

Поставил стенд: балансировщик (посредник, который раздаёт запросы между одинаковыми копиями приложения) и три таких копии, а рядом клиент, который непрерывно шлёт запросы. Погасил все три разом и поднял новую версию: из 404 запросов 320 получили ошибку, это 79%. Заменил по одному, дожидаясь готовности каждого нового и давая балансировщику перечитать конфигурацию: 362 запроса, ошибок ноль.

Стержень: выкат оценивают не тем, доехала ли новая версия, а тем, сколько запросов при этом пострадало и можно ли вернуться назад.

// Формулировки: «чем blue-green отличается от канарейки?», «как выкатывать без простоя?», «как откатиться, если уже поехала миграция?»

Постепенная замена и почему её мало

Постепенная замена (rolling) означает: гасим один экземпляр, поднимаем новый, ждём, переходим к следующему. Мой замер показал, что дьявол в слове «ждём», и я намерил три разных исхода на одном стенде.

Разом: 79% запросов с ошибкой. По одному, дожидаясь, пока новый экземпляр начнёт отвечать, но подряд, без пауз: 49% ошибок - неожиданно много. Причина оказалась не в приложении: балансировщик помнит, кто из экземпляров недавно не ответил, и какое-то время его не трогает. При быстрой замене живых кандидатов в его картине мира просто не остаётся. Проверил гипотезу: тот же выкат с паузой дольше этого окна памяти дал 914 запросов и НОЛЬ ошибок. Столько же дал вариант, где после каждого шага балансировщику велели перечитать конфигурацию.

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

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

Две площадки и канарейка

Схема с двумя площадками (blue-green) устроена иначе: рядом с работающей версией поднимают полную копию новой, прогревают её, проверяют, а потом одним движением переводят трафик. Плюс - откат тоже одним движением и почти мгновенный. Минус - на время выката нужен двойной запас железа, и общая база данных всё равно одна на обе площадки.

Канарейка - это когда новую версию сначала показывают маленькой доле пользователей. Настроил веса 9 к 1 и отправил 200 запросов: 180 ушли на старую версию, 20 на новую, ровно 10%. Дальше долю поднимают, если метрики не испортились.

// Полезная арифметика, которую редко проговаривают. Если новая версия ломается на каждом запросе, а трафика на неё 10%, то вероятность заметить это после 5 запросов - 41%, после 10 - 65%, после 20 - 88%, после 50 - 99%. Считается как единица минус 0,9 в степени числа запросов. Отсюда прямой вывод: канарейку держат не «пять минут для галочки», а до того числа запросов, при котором поломку видно почти наверняка. И смотрят при этом на метрики ошибок и задержек по каждой версии отдельно, иначе 10% плохих ответов утонут в общем графике.

две площадки
полная копия рядом и переключение трафика одним движением
канарейка
маленькая доля трафика на новую версию, чтобы поймать поломку рано

Откат: почему он не всегда возможен

Откат кода - дело секунд: вернули предыдущий образ, и всё. Откат данных - совсем другая история. Если новая версия успела применить изменение схемы (схема - это описание таблиц и колонок базы) или переписать данные, то старая версия на новую схему уже не встанет: она не знает про новую колонку, или наоборот, ищет ту, что удалили.

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

// Отсюда же правило про необратимые шаги. Удаление колонки, переименование, изменение типа - это точки невозврата, и планировать их надо отдельно, со свежей резервной копией и отведённым окном. И проверять откат заранее, а не в момент аварии: выкат, который никто ни разу не откатывал, скорее всего не откатывается.

совместимое изменение схемы
такое, при котором и старый, и новый код работают
точка невозврата
шаг, после которого откат кода уже не вернёт работоспособность

Как отвечать: «Как выкатить новую версию без простоя?»

Смотрю на две вещи: сколько запросов пострадает и как быстро я смогу вернуться. Постепенная замена по одному экземпляру - база, но её мало: я мерил на стенде, замена разом дала 79% ошибок, а замена по одному подряд, без пауз, всё равно 49%, потому что балансировщик какое-то время не трогает экземпляр, который только что не отвечал. Ноль ошибок получился, когда после каждого шага балансировщик узнавал о новом экземпляре. Поэтому мне нужны проверка готовности, запрет гасить следующий до подтверждения предыдущего и корректное завершение старого процесса. Если версия рискованная, добавляю канарейку: держу её не по часам, а до числа запросов, при котором поломку видно почти наверняка - при 10% трафика это порядка полусотни запросов. И отдельно проверяю, что откат возможен: изменения схемы делаю совместимыми в обе стороны, иначе откатывать будет некуда.

Ответ переводит разговор с названий стратегий на измеримый результат и заканчивается откатом. Про откат забывают чаще всего, а именно он отличает выкат от лотереи.

На чём валятся

  • Гасят все экземпляры разом и называют это выкатом без простоя.
  • Считают, что достаточно поднять новый экземпляр: балансировщик узнаёт об этом не сразу.
  • Держат канарейку по часам, а не по числу запросов, и выпускают поломку в прод.
  • Смотрят общие метрики вместо разбивки по версиям и не видят 10% плохих ответов.
  • Делают несовместимое изменение схемы и обнаруживают, что откат кода уже ничего не чинит.

Проверьте себя

Пять вопросов из банка по этой подтеме. Всего их 7, остальные разбираются в тренажёре.

  1. #dvo_deploy_strategies1 / 5
    Чем blue-green отличается от канареечного выката?
    A)Blue-green выкатывает по нодам, канарейка — по namespace
    B)Blue-green переключает целиком, канарейка — долю
    C)Blue-green применяется к базам, канарейка — к приложениям без состояния
    D)Blue-green не требует отката, канарейка откатывается только вручную
    показать ответ и разбор
    +B)Blue-green переключает целиком, канарейка — долю

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

  2. #dvo_deploy_strategies2 / 5
    Выкатка идёт rolling update, в релизе есть миграция с переименованием колонки. Что сломается?
    A)Миграция не применится: схему во время выкатки заблокирует оркестратор
    B)Ничего: старые поды переживут смену схемы без обращений к колонке
    C)Старые поды продолжают работать со старой схемой и начнут падать
    D)Новые поды не стартуют, пока все старые не завершатся
    показать ответ и разбор
    +C)Старые поды продолжают работать со старой схемой и начнут падать

    // разбор: При постепенной выкатке обе версии кода какое-то время работают с одной базой. Переименование ломает старые поды сразу. Потому миграции ведут по схеме expand-contract: сначала добавляем новую колонку и пишем в обе, выкатываем код, который читает новую, и только следующим релизом убираем старую. Каждый шаг совместим с соседней версией кода.

  3. #dvo_deploy_strategies3 / 5
    Релиз откатили командой отката, поды вернулись к прошлой версии, а ошибки остались. Почему так бывает?
    A)Откат вернул образ, но не убрал старые поды из балансировки
    B)Кэш образов на нодах отдал новую версию под старым тегом
    C)Прошлая ревизия удалена из истории, откатились на позапрошлую
    D)Данные уже записаны в новом формате: код откатился, состояние — нет
    показать ответ и разбор
    +D)Данные уже записаны в новом формате: код откатился, состояние — нет

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

  4. #dvo_deploy_strategies4 / 5
    Как выкатить код на прод заранее, но включить фичу для пользователей позже и без нового деплоя?
    A)Держать фичу в отдельной ветке до самого запуска
    B)Собрать отдельный образ на день запуска и выкатить его
    C)Запланировать rolling update ровно на час запуска
    D)Спрятать её за feature-флагом: деплой и релиз разведены
    показать ответ и разбор
    +D)Спрятать её за feature-флагом: деплой и релиз разведены

    // разбор: Feature flag разводит деплой (код на проде) и релиз (фича доступна пользователям): код катится заранее, спрятанный за флагом, а включается переключением флага — без пересборки и передеплоя. Это же даёт постепенный раскат (процент пользователей), быстрый выключатель при проблеме (kill switch) и A/B. Ветки и отдельные образы такой развязки не дают.

  5. #dvo_deploy_strategies5 / 5
    Нужно переименовать колонку в БД без простоя, при rolling update, где старые и новые поды работают вместе. Как?
    A)Переименовать колонку одной миграцией прямо в релизе
    B)Expand-contract: расширить, потом сузить схему
    C)Остановить все поды на время миграции, потом поднять
    D)Откатывать миграцию, если старые поды начнут падать
    показать ответ и разбор
    +B)Expand-contract: расширить, потом сузить схему

    // разбор: Переименование в лоб ломает старые поды, которые ещё работают при rolling update. Делают expand-contract (расширить-сузить): 1) добавить новую колонку (expand), 2) писать в обе (двойная запись) и бэкфилл старых данных, 3) переключить чтение на новую, 4) в следующем релизе удалить старую (contract). Каждый шаг обратно совместим, поэтому старые и новые поды сосуществуют без простоя.

дальше

Теорию прочитали. Навык ставится повторением

В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.