Почему стратегия развертывания зависит от типа приложения
Когда вы только начинаете писать софт, все приложения кажутся одинаковыми. Вы пишете код, запускаете его, кто-то им пользуется. Но как только нужно доставить это приложение туда, где на него полагаются реальные пользователи, вы быстро обнаруживаете, что у приложений очень разные характеры. И эти различия определяют всё — от того, как вы будете их развертывать.
Stateless-приложение: простое и заменяемое
Представьте API погоды. Оно принимает название города, возвращает температуру и влажность. Каждый запрос независим. Приложение каждый раз получает свежие данные, ничего не хранит локально и не помнит, кто что спрашивал.
Такое приложение называется stateless (без состояния). Ему нет дела до предыдущих запросов. Каждый вызов — это чистый лист.
Stateless-приложения развертывать проще всего. Если новая версия сломалась, вы просто подменяете её старой. Никаких данных, о которых нужно беспокоиться, никаких миграций, которые нужно откатывать, никакой головной боли с совместимостью. Откат занимает секунды. Вы можете горизонтально масштабировать их, запуская больше копий. Вы можете убивать инстансы без лишних церемоний.
Риск локализован. Если что-то пошло не так, ущерб ограничивается запросами, которые были в обработке. Никто не теряет данные. Ничья сессия не повреждается.
Stateful-приложение: осторожное и сложное
Теперь представьте систему бронирования билетов в кино. Это приложение хранит, какие места заняты, кто их купил и как оплатил. Каждая транзакция меняет состояние системы. База данных хранит истину, и код приложения должен оставаться с этой истиной синхронизированным.
Это stateful-приложение (с состоянием). Оно зависит от сохраненных данных. И это меняет всё в развертывании.
Вы не можете просто подменить версии. Если новая версия меняет способ чтения или записи данных, сначала可能需要 мигрировать схему базы данных. Если миграция выполнилась, а в новом коде оказалась ошибка, откат становится нетривиальным. Старый код может не понять новую схему. Данные могли уже измениться. Возможно, придется восстанавливаться из бекапа, а это занимает время и рискует потерять недавние транзакции.
Stateful-приложения требуют тщательной последовательности: мигрировать базу данных, развернуть новый код, проверить, что всё работает, и иметь план на случай, если что-то пошло не так. Откат — это редко одна команда. Чаще это многошаговая процедура, требующая координации.
Спектр влияния: внутренний инструмент vs публичный сервис
Приложения также различаются по тому, какой ущерб они могут нанести при сбое.
Возьмем внутреннюю админ-панель, которой пользуются пять человек в вашей компании. Если она упадет на десять минут, кто-то, возможно, расстроится, но никто не потеряет деньги. Развертывание может быть простым. Вы можете позволить себе некоторый даунтайм. Можно запустить изменения и исправить проблемы позже.
Теперь представьте платежный шлюз или страницу оформления заказа в интернет-магазине. Тысячи пользователей обращаются к ней каждую секунду. Если она сломается, транзакции не пройдут, пользователи жалуются, падает выручка. Влияние немедленное и измеримое.
Эта разница во влиянии определяет, насколько осторожным нужно быть при развертывании. Приложения с низким влиянием можно деплоить напрямую с минимальными формальностями. Приложения с высоким влиянием требуют поэтапных развертываний, мониторинга, feature flags и четкого плана отката. Вы можете сначала развернуть на одном сервере, следить за ошибками, затем постепенно увеличивать трафик. Вы можете запустить новую версию рядом со старой на какое-то время. Вы можете использовать canary-развертывания или blue-green стратегии.
Следующая блок-схема поможет выбрать стратегию развертывания в зависимости от природы приложения и уровня риска:
Уровень процесса должен соответствовать уровню риска. Не каждому приложению нужен многочасовой пайплайн развертывания с утверждениями и runbook'ами. Но те, которые могут нанести реальный ущерб, заслуживают такого внимания.
Зависимости: скрытая связанность
Некоторые приложения самодостаточны. Они работают без необходимости в чем-то еще. Вы развернули их — они работают.
Большинство приложений не такие. Они зависят от баз данных, внешних API, очередей сообщений или других внутренних сервисов. Новая версия вашего приложения может полагаться на новый эндпоинт API, который еще не обновлен. Она может ожидать определенный формат ответа, который старое API не предоставляет. Ей может потребоваться миграция базы данных, которая еще не выполнена.
Зависимости создают связанность. Когда вы развертываете новую версию, вы должны убедиться, что всё, от чего она зависит, доступно и совместимо. Это не просто о доступности. Это о совместимости контрактов. Ваш новый код может отлично работать изолированно, но упасть в продакшене, потому что изменилась схема БД, ответ API или формат сообщения.
Вот почему порядок развертывания имеет значение. Если ваше приложение зависит от базы данных, миграция БД обычно идет первой. Если оно зависит от другого сервиса, этот сервис должен быть развернут первым или, по крайней мере, быть обратно совместимым. Если вы не можете гарантировать совместимость, нужно спроектировать развертывание так, чтобы одновременно работали и старая, и новая версии.
Что это значит для вашего CI/CD пайплайна
Все эти различия — stateless против stateful, низкое влияние против высокого, независимость против зависимостей — формируют то, как вы строите свой CI/CD пайплайн.
Не существует единой стратегии развертывания, которая подходит для всех приложений. Пайплайн, спроектированный для stateless микросервиса, провалится для stateful монолита. Процесс развертывания, созданный для внутреннего инструмента, будет слишком рискованным для платежной системы, ориентированной на клиентов. План отката, который работает для самодостаточного приложения, сломается для того, что зависит от мигрированной базы данных.
Ваш пайплайн должен отражать природу приложения, которое он обслуживает. Это означает:
- Stateless-приложения могут использовать простые пайплайны с быстрым откатом.
- Stateful-приложения требуют тщательных шагов миграции и протестированных процедур отката.
- Приложения с высоким влиянием требуют поэтапных развертываний и шлюзов мониторинга.
- Приложения с зависимостями нуждаются в проверках совместимости и упорядоченных развертываниях.
Практический чеклист для оценки потребностей приложения в развертывании
Прежде чем проектировать или настраивать пайплайн развертывания, пройдитесь по этому чеклисту:
- Хранит ли приложение данные, которые должны пережить развертывания? Если да, планируйте миграции БД и обратимые изменения схемы.
- Сколько пользователей пострадает, если приложение упадет? Если число велико или финансовое влияние высоко, добавьте поэтапные развертывания и мониторинг.
- Зависит ли приложение от других сервисов или баз данных? Если да, проверьте совместимость и спланируйте порядок развертывания.
- Можете ли вы откатиться, просто подменив старую версию, или нужно откатывать изменения данных? Если последнее, протестируйте процедуру отката до того, как она понадобится.
- Может ли приложение работать с двумя версиями одновременно? Если нет, возможно, потребуется даунтайм или blue-green развертывание.
Конкретный вывод
То, как вы развертываете приложение, определяется не языком программирования, фреймворком или инструментом. Это определяется природой приложения: хранит ли оно состояние, насколько велик ущерб от сбоя и от чего оно зависит. Сначала поймите эти три вещи. Затем проектируйте пайплайн. Всё остальное — детали реализации.