Справочник31 июля 2026 г.·обновлено 31 июля 2026 г.

Как устроен деплой: сборка, релиз, откат

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

Что это решает

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

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

Как устроено

Три стадии

Сборка. Исходники плюс зависимости превращаются в артефакт: docker-образ, каталог dist/, архив, пакет. Артефакт не зависит от окружения и не содержит ни адресов баз, ни ключей. У него есть идентификатор — обычно короткий хеш коммита.

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

Запуск. Релиз стартует: процессы приложения, воркеры очередей, задачи по расписанию.

Правило продвижения простое: артефакт собирается один раз и проходит стенд, препрод и прод в неизменном виде. Пересборка перед продом означает, что на прод едет то, чего никто не тестировал, — код тот же, а результат сборки может отличаться из-за подтянувшихся версий зависимостей.

Идентификация версии

Тег артефакта — короткий хеш коммита. Приложение отдаёт свою версию наружу:

GET /version
{ "sha": "9f3c1a2", "built_at": "2026-09-08T11:20:14Z", "env": "prod" }

Этот эндпоинт закрывает половину споров при разборе инцидента и позволяет автоматически сверять, что выкатилось именно то, что мержили.

Каталог релизов и атомарное переключение

Классическая схема для приложений, выкладываемых файлами:

/srv/app/
  releases/
    2026-09-08-1120-9f3c1a2/
    2026-09-07-1804-7ab19cd/
  shared/
    storage/
    .env
  current -> releases/2026-09-08-1120-9f3c1a2

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

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

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

  • Пересоздание. Остановить, запустить новое. Простой на время старта. Подходит внутренним сервисам и всему, где минута недоступности ничего не стоит.
  • Постепенная замена. Инстансы обновляются партиями. Простоя нет, но какое-то время работают две версии сразу — API и схема базы обязаны это выдерживать.
  • Сине-зелёный. Рядом поднимается полный второй комплект, трафик переключается разом, старый комплект остаётся горячим на случай возврата. Платится удвоением ресурсов.
  • Канареечный. Новая версия получает малую долю трафика, метрики сравниваются с основной, доля растёт при отсутствии отклонений. Требует и трафика, и настроенных метрик по версиям.

Миграции — то, что запирает откат

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

  1. Миграция только добавляет: новая колонка допускает NULL, новая таблица создаётся пустой. Выкатывается отдельно и раньше кода.
  2. Код учится читать оба варианта, а писать — в новый. Выкат.
  3. Данные переносятся фоновым скриптом порциями, с контролем нагрузки.
  4. Код перестаёт читать старое поле. Выкат.
  5. Отдельным релизом, спустя время, старая колонка удаляется.

Ни один шаг не ломает предыдущую версию приложения, поэтому откат возможен на каждом. Разрушающие операции (DROP, RENAME, добавление NOT NULL без значения по умолчанию) не едут в одном релизе с кодом, которому они нужны.

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

Конфигурация

Конфиг живёт вне артефакта. Секреты — в хранилище секретов или в переменных окружения на машине, не в репозитории и не в слоях образа. Любое изменение конфига проходит тем же путём, что и код: правка, проверка, релиз с номером. Значение, поправленное руками на сервере, при следующем выкате исчезнет, а причину будут искать долго.

Проверки после релиза

Смоук-набор — короткий скрипт, который дёргает несколько ключевых адресов и проверяет код ответа и наличие ожидаемой строки:

set -e
curl -fsS https://app.example.com/version | grep -q "$TAG"
curl -fsS https://app.example.com/ | grep -q "Вход"
curl -fsS -o /dev/null -w '%{http_code}' https://app.example.com/api/health | grep -q 200

Эндпоинт здоровья проверяет не «процесс жив», а зависимости: доступна ли база, отвечает ли кэш, разбирается ли очередь. Проверка, которая возвращает 200 всегда, бесполезна.

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

Кэш, клиенты и очереди

Ассеты именуются с хешем содержимого, тогда старый и новый файл сосуществуют и кэш не надо сбрасывать. HTML-точка входа кэшируется коротко. У CDN сброс делается явной командой в конце выката.

Открытые вкладки продолжают работать на старом JavaScript и стучаться в старые эндпоинты. API обязан держать совместимость хотя бы один релиз назад, иначе выкладка ломает тех, кто уже на сайте.

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

Откат

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

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

Полезные сценарии

  • Ночной выкат крупного изменения флагом: код на проде с вечера, включение утром при дежурном разработчике.
  • Разделение миграции и кода на два релиза перед сменой формата ключевого поля.
  • Сине-зелёный выкат перед сезонным пиком, когда простой стоит выручки.
  • Канареечный выкат правки, влияющей на конверсию: доля трафика и сравнение метрик вместо спора о вкусах.
  • Автоматическая сверка /version после деплоя, чтобы поймать выкат не в то окружение.
  • Хранение трёх-пяти прошлых артефактов и релизов на машине как страховка на случай недоступного реестра.

Ограничения

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

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

Сине-зелёная схема на общей базе не изолирует схему данных: обе версии работают с одной таблицей, и несовместимая миграция кладёт оба комплекта сразу.

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

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

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

Мелкому сайту полноценный конвейер не окупается. Выгрузка файлов с бэкапом предыдущей версии закрывает задачу, пока релизы редкие, а откат — распаковка архива.

Как проверить результат

Сверить версию: curl -s https://app.example.com/version должен вернуть хеш того коммита, который мержили. Расхождение означает, что выкат не доехал или уехал в другое окружение.

Прогнать откат на стенде и засечь время. Это делается до того, как понадобится в проде: команда, выполняемая впервые под давлением, выполняется неправильно.

Убедиться, что предыдущий артефакт на месте: docker image ls на хосте, список тегов в реестре, содержимое releases/. Реестр с политикой очистки может удалить именно тот тег, на который планировалось вернуться.

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

Прогнать миграцию на копии прод-базы и замерить время выполнения и длительность блокировок. Копия должна быть свежей по объёму, а не по структуре.

Проверить очереди: время старта процессов воркеров должно быть позже времени релиза. Живые процессы со старым кодом — частая причина ошибок, которые «появляются сами через полчаса».

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

Правило одной строкой: релиз считается завершённым только после проверки отката — предыдущий артефакт доступен, база совместима со старым кодом, а команда возврата уже выполнялась на стенде.

  • деплой
  • ci-cd
  • релизы
  • миграции