Когда продакшн падает: почему вам нужна трассируемость образов и откат

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

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

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

Трассируемость начинается на этапе сборки

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

Теги вроде v1.2.3 или production удобны для людей. Они помогают быстро распознать версии. Но теги ненадежны для трассируемости. Тег — это просто метка, указывающая на образ, и эта метка может измениться. Образ myapp:production сегодня может указывать на версию 1.2.3, а завтра — на версию 1.3.0. Если вы отслеживаете только теги, вы никогда не будете точно знать, какая версия на самом деле запущена.

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

Записывайте дайджест, а не только тег

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

Где хранить эту информацию? Самое практичное место — ваш манифест деплоя. Манифест деплоя — это файл, который сообщает вашей системе, как запускать контейнер. В Kubernetes это YAML-файл. В Docker Compose — это compose-файл. Каждый раз при деплое манифест должен ссылаться на точный дайджест, а не просто на тег.

Вот как это выглядит в деплое Kubernetes:

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

# Сборка и отправка образа
docker build -t myregistry.com/myapp:latest .
docker push myregistry.com/myapp:latest

# Захват дайджеста из реестра
export DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' myregistry.com/myapp:latest)
echo "Deploying image: $DIGEST"

# Использование дайджеста в манифесте деплоя
sed "s|image: myregistry.com/myapp:latest|image: $DIGEST|" deployment.yaml > deployment-digest.yaml
kubectl apply -f deployment-digest.yaml

Это гарантирует, что ваш деплой всегда ссылается на точное содержимое образа, а не на изменяемый тег.

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

sequenceDiagram participant Dev as Разработчик participant CI as CI-пайплайн participant Reg as Реестр participant K8s as Kubernetes participant User as Пользователи Dev->>CI: Отправка кода CI->>Reg: Сборка и отправка образа<br/>(запись дайджеста) CI->>K8s: Деплой с дайджестом<br/>@sha256:... K8s->>User: Обслуживание новой версии User->>K8s: Сообщение об ошибках K8s->>CI: Оповещение: продакшн сломан CI->>K8s: Откат: kubectl rollout undo<br/>(использует предыдущий дайджест) K8s->>User: Обслуживание предыдущей версии
spec:
  template:
    spec:
      containers:
      - name: myapp
        image: myregistry.com/myapp@sha256:a1b2c3d4e5f6...

Обратите внимание на часть @sha256:.... Это и есть дайджест. Когда вы используете этот формат, вы говорите Kubernetes запустить именно этот образ, а не то, на что указывает latest.

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

Без этой записи вы гадаете. А гадание во время инцидента обходится дорого.

Откат: страховочная сеть, которую строят до того, как она понадобится

Трассируемость дает ответ на вопрос «что запущено?». Откат дает ответ на вопрос «как вернуться к тому, что работало?».

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

Хорошая стратегия отката начинается с трех вопросов:

  1. Доступен ли предыдущий образ в реестре?
  2. Можно ли использовать предыдущий манифест деплоя?
  3. Совместим ли предыдущий образ с текущей конфигурацией?

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

В Kubernetes вы можете использовать kubectl rollout undo для возврата к предыдущей ревизии. Эта команда работает, потому что Kubernetes хранит историю ревизий деплоя. Но вам нужно настроить, сколько ревизий хранить. Слишком мало — и вы потеряете возможность откатиться достаточно далеко. Слишком много — и вы будете потреблять память кластера на историю, которая может никогда не понадобиться.

Когда откат работает, а когда нет

Откат быстр и эффективен для проблем на уровне приложения. Если новая версия внесла баг в бизнес-логику или обновление библиотеки что-то сломало, возврат к предыдущему образу быстро восстанавливает сервис.

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

Знайте границы вашего механизма отката. Тестируйте его регулярно. Убедитесь, что ваша команда знает, когда его использовать, а когда искать другое решение.

После отката устраните первопричину

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

Образ, вызвавший проблему, нужно исправить. Пайплайн должен продолжать работать с исправленной версией. Этот новый образ проходит тот же процесс: сборка, сканирование, продвижение, деплой. Откат был страховочной сетью, а не концом пути.

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

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

Перед следующим деплоем в продакшн пройдитесь по этому чек-листу:

  • Каждый образ в пайплайне ссылается на дайджест, а не только на тег
  • Манифесты деплоя хранятся в системе контроля версий с точным дайджестом
  • Предыдущие образы сохраняются в реестре как минимум для последних N версий
  • Процедура отката документирована и протестирована в непродуктивной среде
  • Команда знает разницу между проблемами, которые можно исправить откатом, и проблемами, требующими другого подхода

Что это значит для вашей команды

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

В следующий раз, когда продакшн сломается, первый вопрос все еще будет: «Какая версия запущена?» С трассируемостью образов у вас будет ответ через секунды. А с протестированным механизмом отката вы сможете восстановить сервис за минуты, а не часы.

Постройте страховочную сеть до того, как она понадобится. Ваше будущее «я», отлаживающее код в 2 часа ночи, скажет вам спасибо.