Откат: когда вернуться назад не так просто, как кажется
Вы только что развернули новую версию приложения. Через пять минут в дашборде мониторинга появляются ошибки. Пользователи сообщают о проблемах. Первая мысль очевидна: вернуть старую версию. Это и есть откат в его простейшей форме — возврат к последнему известному стабильному состоянию, чтобы выиграть время и разобраться, что пошло не так.
Для многих команд откат — это стратегия восстановления по умолчанию. Это интуитивно понятно. Если новая версия сломала что-то, старая работала нормально. Просто поменяйте их местами. Но реальность сложнее. Откат работает по-разному в зависимости от того, что именно вы откатываете: код приложения, схему базы данных или конфигурацию инфраструктуры. У каждого свои механики, риски и ограничения.
Откат приложения: относительно простой случай
Откат кода приложения — самый простой сценарий. У вас есть работающий экземпляр приложения, и вы заменяете новую версию старой. Способ зависит от стратегии развертывания.
Если вы используете blue-green развертывание, откат означает переключение трафика обратно на среду, где всё ещё работает старая версия. Зеленая среда снова становится активной, а синяя выводится из ротации. Это переключение может занять секунды, потому что обе среды уже запущены и готовы.
Если вы используете canary-релизы, откат означает остановку подачи трафика на новую версию и перенаправление всего обратно на предыдущую. Canary «убивается», и стабильная версия снова обрабатывает все запросы.
Если вы используете обычные rolling updates, откат означает повторное развертывание предыдущего артефакта на тех же серверах или контейнерах. Это занимает больше времени, потому что каждый экземпляр нужно обновлять по одному, но процесс всё равно предсказуем.
Например, если вы используете Kubernetes, одна команда может откатить развертывание до предыдущей ревизии:
kubectl rollout undo deployment/my-app -n production
Эта команда говорит Kubernetes уменьшить количество новых подов и увеличить количество старых, фактически обращая rolling update вспять. Вы также можете указать конкретную ревизию, если нужно откатиться более чем на один шаг:
kubectl rollout undo deployment/my-app -n production --to-revision=3
Чтобы увидеть историю ревизий перед откатом:
kubectl rollout history deployment/my-app -n production
Ключевое преимущество отката приложения в том, что он не меняет данные. Вы меняете только код, который обрабатывает входящие запросы. База данных остаётся нетронутой, пользовательские данные не теряются и не преобразуются. Это делает откат приложения относительно безопасным и быстрым.
Следующая блок-схема отображает пути принятия решений для каждого типа отката, обсуждаемого в этом разделе.
Откат базы данных: где начинается хаос
Откат базы данных — это совсем другая история. Базы данных хранят состояние, которое постоянно меняется. Когда новая версия приложения изменяет схему базы данных — добавляет колонку, переименовывает таблицу, меняет тип данных — простого отката кода приложения недостаточно. Нужно также вернуть структуру базы данных в предыдущее состояние.
Здесь сложность многократно возрастает. Рассмотрим простой сценарий: ваша новая версия добавляет колонку phone_number в таблицу users. Приложение начинает записывать номера телефонов в эту колонку. Через час вы обнаруживаете критическую ошибку и решаете откатиться. Вы разворачиваете старый код приложения, но старый код ничего не знает о колонке phone_number. Более того, нужно решить, что делать с данными, уже записанными в эту колонку. Удалить их? Перенести куда-то ещё? Оставить и надеяться, что старый код их проигнорирует?
Самый безопасный подход — с самого начала делать каждую миграцию базы данных обратимой. Это означает, что каждый скрипт миграции включает шаг up, применяющий изменение, и шаг down, откатывающий его. При откате вы запускаете down-миграцию, чтобы восстановить предыдущую схему.
Но не все изменения действительно обратимы без потери данных. Если ваша новая версия удалила колонку, которая всё ещё использовалась, откат означает воссоздание этой колонки и восстановление её данных из бэкапа. Если ваша новая версия объединила две таблицы в одну, откат означает их разделение и выяснение, какие строки принадлежали какой таблице. Эти операции рискованны, трудоёмки и часто требуют ручного вмешательства.
Многие команды принимают эту реальность и предпочитают вообще избегать отката базы данных. Вместо этого они вкладывают значительные усилия в тестирование миграций перед развертыванием, прогоняя их на стейджинг-окружениях, максимально приближенных к продуктивным данным. Когда что-то идёт не так, они предпочитают написать прямой фикс, а не пытаться откатиться назад.
Откат инфраструктуры: скрытая паутина зависимостей
Откат инфраструктуры означает отмену изменений на серверах, в правилах сети, балансировщиках нагрузки или вспомогательных сервисах. Если вы управляете инфраструктурой с помощью таких инструментов, как Terraform, Ansible или Pulumi, откат обычно включает применение предыдущей версии ваших конфигурационных файлов.
Проблема в том, что изменения инфраструктуры редко затрагивают только одну вещь. Изменение правила файрвола может нарушить подключение к базе данных. Изменение конфигурации балансировщика нагрузки может повлиять на маршрутизацию трафика для нескольких сервисов. Откат файла состояния Terraform может удалить ресурсы, созданные новой версией, что может вызвать каскад других проблем.
Откат инфраструктуры также требует времени. Применение предыдущей конфигурации требует запуска тех же процессов подготовки, которые создавали инфраструктуру изначально. Если ваша инфраструктура большая или сложная, это может занять минуты или даже часы — время, в течение которого ваши пользователи видят ошибки.
Ограничения отката как стратегии
Откат — это не универсальная страховочная сетка. Он работает только при соблюдении трёх условий.
Во-первых, старая версия должна всё ещё быть стабильной и совместимой с текущим состоянием системы. Если ваша новая версия работала несколько часов и пользователи ввели данные, которые старая версия не может прочитать, откат приведёт к потере или повреждению данных.
Во-вторых, проблема должна быть в коде или конфигурации, а не в данных. Если проблема в том, что пользователи неправильно используют функцию или качество данных ухудшилось, откат кода ничего не исправит.
В-третьих, откат должен быть быстрее, чем время, необходимое для прямого исправления. Если откат занимает тридцать минут, а написание хотфикса — десять, откат — более медленный вариант.
Существует также поведенческий риск. Команды, которые слишком полагаются на откат, могут стать небрежными в предварительном тестировании. Появляется мышление: «Если сломается, просто откатим». Это опасно, потому что откат имеет реальную стоимость. Пользователей, которые видели ошибки или потеряли данные, не волнует, что вы восстановились за пять минут. Их доверие уже подорвано.
Практический чек-лист перед решением об откате
Прежде чем выполнять откат, задайте себе эти вопросы:
- Старая версия всё ещё работает и готова принимать трафик?
- Изменилась ли схема базы данных таким образом, что старый код стал несовместим?
- Как долго работала новая версия и сколько пользовательских данных было введено?
- Можно ли откатить миграцию базы данных без потери данных?
- Будет ли откат быстрее, чем написание прямого исправления?
- Сообщили ли вы план отката команде и заинтересованным сторонам?
Если ответ на любой из этих вопросов вызывает тревогу, рассмотрите вместо этого прямой фикс.
Вывод
Откат — это легитимная стратегия восстановления, но это не бесплатная кнопка «отмена». Откат приложения относительно безопасен и быстр. Откат базы данных рискован и часто необратим. Откат инфраструктуры медленный и может иметь каскадные эффекты. Прежде чем сделать откат своим ответом по умолчанию, поймите, что именно вы откатываете и какова будет реальная цена. Иногда лучший ход — исправить проблему напрямую. И именно это мы рассмотрим дальше.