Форма развёртывания
Что это такое
Форма развёртывания отвечает на два вопроса: что поставляется вместе и что отказывает вместе. Монолит развёртывает каждую функцию как одну единицу, а набор сервисов — по отдельности. Выбор здесь операционный, а не чисто технический: от него зависит, сколько согласования требует релиз, насколько маленьким может быть изменение и как далеко распространяется один отказ.
Почему это важно
Форма развёртывания формирует команду не меньше, чем программное обеспечение. Если всё поставляется вместе, каждый релиз становится групповым занятием, а срочное исправление ждёт всей партии. Если сервис отказывает так, что забирает с собой вызывающих, независимость была только на бумаге. Ошибка здесь даёт худшее из обоих миров: операционные издержки множества развёртываемых единиц и связанность одной. Именно так и выглядят большинство случайных распределённых систем.
Ключевые идеи
У независимого развёртывания есть цена
Выпуск одной части без остальных требует, чтобы её интерфейс оставался совместимым между версиями, данные принадлежали ей, а вызывающие терпели её краткое отсутствие или медлительность. Это те же требования, что и у владения границей. Если они не выполнены, отдельное развёртывание всё равно придётся согласовывать: все издержки и ни одной выгоды.
совместимость между версиями: добавляйте поля, никогда не меняйте их назначение
терпите отсутствие: тайм-ауты, повторные попытки с задержкой, размыкание цепи
Монолит — защитимое значение по умолчанию
Одна развёртываемая единица с ясными внутренними границами проще для понимания, тестирования и отладки, чем тот же код, разделённый по процессам. И передумать потом легко: модули с чистыми границами можно извлекать по одному, когда появится настоящая операционная потребность. Начать распределённо, наоборот, — решение, которое очень трудно откатить. Выбирайте монолит, пока не можете назвать конкретное ограничение, которому он не удовлетворяет.
Отказы по умолчанию не уважают границы
Разделение системы на части не изолирует отказ; оно создаёт новые режимы отказа. Вызывающий, который вечно ждёт неотзывчивую зависимость, распространил отказ сам. Изоляцию нужно проектировать: явные тайм-ауты, ограниченные повторные попытки и определённое поведение в ухудшенном режиме для каждой зависимости. Поэтому «сервисы разделены» и «сервисы независимы» — разные утверждения.
зависимость недоступна, тайм-аута нет -> вызывающий исчерпывает свои потоки
зависимость недоступна, тайм-аут есть -> вызывающий ухудшается, остальные продолжают
Окружения и конфигурация — часть формы
То, чем отличаются разработка, стейджинг и продакшен, решает, насколько безопасно вы можете тестировать. Чем меньше различий, тем точнее результат стейджинга предсказывает продакшен. Конфигурация, живущая в окружении, а не в коде, и сборка, которую продвигают, а не пересобирают, удерживают протестированный артефакт идентичным тому, который вы запускаете.
Радиус поражения — это проектный выбор
Спросите о каждой части: если она откажет или ошибётся, что ещё остановится или испортится? Одна база данных на всё означает, что одна плохая миграция оборачивается сбоем у всех. Граница, проведённая вокруг самого разрушительного отказа — платежи, аутентификация, конвейер данных, — ограничивает, как далеко может распространиться ошибка. И это часто более веская причина для разделения, чем производительность или размер команды.
Практика
Назовите ограничение
Запишите, что нужно вашей системе и чего не может дать одна развёртываемая единица: команда, которой нужно поставлять, не ожидая остальных, компонент с другим профилем масштабирования или отказ, который нужно изолировать. Если ни одного назвать не удаётся, ответ о разделении у вас уже есть.
Готово, когда: список либо пуст, либо содержит хотя бы одно измеримое ограничение.
Изолируйте один отказ
Возьмите одну внешнюю зависимость, которую вызывает ваша система. Добавьте тайм-аут и определённый ухудшенный ответ, затем смоделируйте недоступность зависимости.
Готово, когда: остальная система по-прежнему обслуживает запросы, а ухудшенный путь наблюдаем в метрике или журнале.
Частые ошибки
- Распределение до появления операционной причины — сложность оплачивается вперёд за выгоду, которую так и не получают; исправить: начните с одной развёртываемой единицы и извлекайте по потребности.
- Разделение без владения данными — сервисы делят схему и не могут выпускаться независимо; исправить: считайте владение данными частью границы развёртывания.
- Вызов зависимости без тайм-аута — потоки вызывающего заполняются, и отказ распространяется; исправить: задайте явные тайм-ауты и ограничьте повторные попытки у каждого исходящего вызова.
- Расходящиеся окружения — результат стейджинга не предсказывает поведение продакшена; исправить: продвигайте одну сборку через окружения и держите различия в конфигурации.
- Игнорирование радиуса поражения общего ресурса — одна ошибка выводит из строя несвязанные функции; исправить: проводите границы вокруг отказа, который важнее всего сдержать.
Что читать дальше
- Владение данными и состоянием (01-cs-fundamentals/25-software-architecture/02-data-and-state-ownership) — владение и делает независимое развёртывание возможным, поэтому читайте первым.
- Документирование решений (01-cs-fundamentals/25-software-architecture/04-documenting-decisions) — форму развёртывания потом будут оспаривать, и рассуждения должны пережить это.
- Основы сетей (HTTP / DNS / TLS) (06-cloud-devops/10-networking-fundamentals-http-dns-tls) — поведение транспорта, с которым на самом деле работают ваши тайм-ауты и повторные попытки.