Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Откат релиза: как вернуть предыдущую версию

Откат релиза: почему код возвращается легко, а схема — нет

Откат релиза: redeploy и rollout undo, обратимые миграции, схема expand-contract, feature flags и проверка отката до инцидента.

Прямой ответ

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

Возврат кода в Kubernetes:

kubectl rollout undo deployment/api -n production
kubectl rollout status deployment/api -n production
kubectl rollout history deployment/api -n production

rollout undo переключает Deployment на предыдущий ReplicaSet. Это работает, пока старая версия совместима с текущей схемой базы.

Схема совместимости:

Тип миграции Откат кода возможен
Добавление таблицы Да
Добавление допускающей NULL колонки Да
Переименование колонки Нет
Удаление колонки Нет
Изменение типа колонки Нет

Чтобы миграция не блокировала откат, применяют подход expand–contract (parallel change), описанный в ParallelChange:

  1. Expand. Добавить новую колонку рядом со старой, обе допускают NULL.
  2. Migrate. Новая версия пишет в обе, читает из новой; фоновая задача заполняет старые записи.
  3. Contract. После полного перехода удалить старую колонку отдельным релизом.

На каждом шаге и старая, и новая версия приложения работают с одной схемой, поэтому откат кода безопасен.

# Шаг expand: миграция обратима
def upgrade():
    op.add_column("orders", sa.Column("total_minor", sa.BigInteger(), nullable=True))

def downgrade():
    op.drop_column("orders", "total_minor")

Функция downgrade в Alembic обязательна: без неё откат схемы невозможен даже формально; о работе с миграциями — в статье про Alembic.

Практика: подготовка отката до релиза

1. Проверяйте откат до того, как он понадобится. Отрепетированный откат занимает минуты; первый выполненный в момент инцидента — часы.

2. Используйте feature flags для рискованной логики. Отключение флага быстрее и безопаснее, чем redeploy; о подходе — в статье про feature flags.

3. Разделяйте релиз схемы и релиз кода. Миграция применяется отдельным шагом до развёртывания кода. Это даёт окно, в котором можно откатить код, не трогая базу.

4. Не удаляйте данные в том же релизе, что и меняете код. Удаление колонки или таблицы выполняйте через несколько релизов после того, как новая версия стабилизировалась.

5. Учитывайте состояние вне базы. Сообщения в очередях, записи в кэше и объекты в хранилище, созданные новой версией, могут быть нечитаемы для старой. Формат сообщений обязан быть совместим с предыдущей версией.

6. Ограничивайте радиус поражения. Canary-развёртывание выявляет проблему на части трафика; о приёме — в статье про canary releases. Blue-green даёт мгновенный возврат переключением трафика; о схеме — в статье про blue-green.

7. Фиксируйте критерий отката заранее. «Откатываем, если доля ошибок выше одного процента в течение пяти минут» снимает обсуждение в момент аварии.

Рекомендации:

  1. Храните предыдущую версию готовой к запуску. Образ с предыдущим тегом должен быть доступен, а не пересобираться в момент инцидента.
  2. Не смешивайте в релизе функциональность и разрушающие изменения. Отдельный релиз на удаление упрощает откат основного.
  3. Проверяйте совместимость сообщений. Потребитель старой версии обязан прочитать сообщение, созданное новой; о версионировании — в статье про версии API.
  4. Ведите журнал релизов. В момент инцидента нужно знать, что именно изменилось, а не искать по истории коммитов; о версионировании — в статье про semver.
  5. Автоматизируйте откат при явном пороге. Автоматический возврат при превышении бюджета ошибок быстрее ручного решения; о бюджетах — в статье про SLO.

Практический сценарий: откат релиза не сработал из-за миграции

Контекст: релиз добавил новую колонку и переименовал существующую. Через сорок минут после выкатки выросла доля ошибок, и команда попыталась откатиться.

  1. Попытка отката. kubectl rollout undo вернул предыдущую версию пода, но она не запустилась: прежний код обращался к колонке со старым именем, которого в базе больше не было.
  2. Результат. Откат оказался невозможен без возврата схемы, а обратная миграция не была написана. Пришлось исправлять ошибку вперёд, под нагрузкой.
  3. Причина. Миграция была разрушающей, а критерий отката и совместимость со старой версией не проверялись.
  4. Действия. (а) Вернули прежнее имя колонки и добавили представление с новым, чтобы обе версии работали; (б) ввели правило: разрушающие изменения выполняются отдельным релизом после стабилизации; (в) перешли на expand–contract для переименований и изменений типов; (г) сделали функцию downgrade обязательной в ревью миграций; (д) добавили проверку в CI: предыдущая версия приложения обязана запускаться против текущей схемы; (е) зафиксировали критерий отката в описании релиза.
  5. Результат. Последующие релизы откатывались за одну-две минуты; разрушающие изменения вынесены в отдельные шаги, где риск не связан с функциональностью.

Вывод: откатываемость релиза определяется не кодом, а схемой данных. Разрушающая миграция в одном релизе с кодом лишает команду возможности вернуться назад.

FAQ

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

Нужно ли откатывать данные, созданные новой версией? Зависит от совместимости. Если старая версия читает новые записи, данные оставляют. Если нет, нужна процедура очистки или преобразования.

Что быстрее: откат или исправление вперёд? Откат, если он подготовлен. Исправление вперёд оправдано, когда откат невозможен из-за состояния данных или когда проблема локализована и отключаема флагом.

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

Источники: Kubernetes docs (Deployment), Martin Fowler (ParallelChange), Alembic docs — ссылки в шапке.