Операционная стоимость
Что это такое
Работа, которая приходит вместе с распределённой системой: независимое развёртывание сервисов, версионирование интерфейсов между ними, наблюдение за запросом, пересекающим несколько из них, и обработка отказов, которых у одного процесса никогда не было. Ничего из этого не является необязательным, и всё это платится непрерывно, а не один раз.
Почему это важно
Проектные решения сервисной архитектуры увлекательны, а определяет её успех операционная сторона, поэтому команды, которые разделили систему и не вложились в автоматизацию развёртывания, трассировку и обработку отказов, получают больше работы и меньше надёжности, чем было в начале: архитектуру винят за то, что на самом деле является незаконченной платформой. Знание цены заранее и позволяет заложить её в бюджет, а не обнаружить постфактум.
Ключевые идеи
Независимое развёртывание требует конвейера на каждый сервис
Если развернуть один сервис означает пересобрать всё, независимость остаётся теоретической. Каждому сервису нужен свой путь сборки, тестирования и выпуска, воспроизводимый по тому же стандарту, и это один из самых ясных способов увидеть, настоящая ли это граница: если команда не может развернуть один сервис в одиночку за час, он не развёртывается независимо.
репозиторий сервиса -> сборка -> тесты -> образ -> релиз (повторяется для каждого сервиса)
Разброс версий — постоянное состояние, а не переходный этап
В любой момент работают несколько версий нескольких сервисов одновременно: постепенное развёртывание, канареечный выпуск, сервис, который ещё не обновили, поэтому каждое изменение интерфейса должно какое-то время выдерживать предыдущую версию, отчего аддитивное изменение становится нормой, а ломающее — запланированной миграцией. Проектировать это с самого начала намного дешевле, чем приделывать после первого инцидента.
Наблюдаемость становится требованием, а не приятным дополнением
Один запрос теперь проходит через несколько процессов и, возможно, через очередь, и логи ни одного отдельного сервиса не содержат всей истории. Ответить, куда ушло время, позволяет практически только идентификатор корреляции, передаваемый через каждый вызов, и трассировки, соединяющие спаны, а инструментировать систему до первого инцидента намного легче, чем восстанавливать путь во время него.
Изоляция отказов должна быть осознанной
Смысл разделения сервисов в том, что один может отказать, не утянув остальные, однако это верно лишь при условии, что у каждого вызова есть тайм-аут, ограниченное число повторных попыток и определённое поведение при недоступности зависимости. Без этого медленный сервис передаёт свою медлительность каждому вызывающему, и система отказывает целиком; разделение даёт возможность изоляции, а не саму изоляцию.
Конфигурация и секреты размножаются
Больше сервисов — больше конфигурации, больше учётных данных и больше мест, где они могут отличаться: управляемые централизованно, через идентичность, а не через хранимые секреты, они остаются обозримыми, а скопированные вручную в каждый сервис становятся самым частым источником ошибок, зависящих от окружения. Подход через идентичность ещё и делает журнал аудита осмысленным.
Стоимость растёт с числом сервисов, а не с их размером
Каждый дополнительный сервис добавляет цель развёртывания, цель мониторинга, зависимость, которую нужно обновлять, и интерфейс, который нужно версионировать, а добавляет ли он ещё и независимую скорость целой команды — тот вопрос, который решает, стоит ли ему существовать. Сервис, достаточно маленький, чтобы им никто не владел, становится обузой.
Обнаружение сервисов, балансировка нагрузки, доставка сообщений, трассировка и секреты входят в инфраструктуру, которую архитектура подразумевает, и там, где такой платформы нет, команда её строит, — а эта стройка и есть настоящая цена решения, приходящая раньше любой продуктовой выгоды. Выбрать архитектуру и выбрать построение платформы — одно и то же решение, и именно притворство, будто это не так, и заставляет такие проекты буксовать.
Практика
Замерьте развёртывание одного сервиса
Возьмите один сервис в системе из нескольких и измерьте, сколько времени проходит от влитого изменения до его работы в продакшене.
Готово, когда: вы можете назвать это время и сказать, нужно ли было выпускать что-то ещё, чтобы он вышел.
Перечислите отказ одной зависимости
Возьмите сервис, который вызывает другой, и опишите, что с ним происходит, когда вызываемый отвечает медленно, а не падает.
Готово, когда: вы можете назвать тайм-аут, поведение повторных попыток и то, что вызывающий возвращает своим клиентам тем временем.
Частые ошибки
- Совместный выпуск сервисов «только один раз» — связанность возвращается, а независимость так и не была настоящей; исправить: доведите до конца конвейер на каждый сервис.
- Отношение к разбросу версий как к временному — несколько версий всегда работают одновременно; исправить: делайте каждое изменение интерфейса аддитивным и выдерживайте предыдущую версию.
- Предположение, что существующих логов достаточно — запрос проходит через процессы, и его не проследить; исправить: передавайте идентификатор корреляции и трассируйте между сервисами.
- Тайм-ауты без определённого запасного поведения — вызывающий отказывает, и его собственные клиенты отказывают следом; исправить: решите, что возвращается, когда зависимость недоступна.
- Недооценка работы над платформой — цена приходит раньше выгоды; исправить: закладывайте в решение обнаружение сервисов, обмен сообщениями, трассировку и секреты.
Что читать дальше
- Границы и владение (05-backend-data/12-microservices/01-boundaries-and-ownership) — проектные решения, стоимость которых описывает этот дочерний урок.
- Микросервисы (05-backend-data/12-microservices) — архитектура, к которой относится эта стоимость.
- CI/CD (06-cloud-devops/08-ci-cd) — конвейер на каждый сервис, который делает независимое развёртывание настоящим.