Операционная стоимость

Что это такое

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

Почему это важно

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

Ключевые идеи

Независимое развёртывание требует конвейера на каждый сервис

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

репозиторий сервиса -> сборка -> тесты -> образ -> релиз   (повторяется для каждого сервиса)

Разброс версий — постоянное состояние, а не переходный этап

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

Наблюдаемость становится требованием, а не приятным дополнением

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

Изоляция отказов должна быть осознанной

Смысл разделения сервисов в том, что один может отказать, не утянув остальные, однако это верно лишь при условии, что у каждого вызова есть тайм-аут, ограниченное число повторных попыток и определённое поведение при недоступности зависимости. Без этого медленный сервис передаёт свою медлительность каждому вызывающему, и система отказывает целиком; разделение даёт возможность изоляции, а не саму изоляцию.

Конфигурация и секреты размножаются

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

Стоимость растёт с числом сервисов, а не с их размером

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

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

Практика

Замерьте развёртывание одного сервиса

Возьмите один сервис в системе из нескольких и измерьте, сколько времени проходит от влитого изменения до его работы в продакшене.

Готово, когда: вы можете назвать это время и сказать, нужно ли было выпускать что-то ещё, чтобы он вышел.

Перечислите отказ одной зависимости

Возьмите сервис, который вызывает другой, и опишите, что с ним происходит, когда вызываемый отвечает медленно, а не падает.

Готово, когда: вы можете назвать тайм-аут, поведение повторных попыток и то, что вызывающий возвращает своим клиентам тем временем.

Частые ошибки

  • Совместный выпуск сервисов «только один раз» — связанность возвращается, а независимость так и не была настоящей; исправить: доведите до конца конвейер на каждый сервис.
  • Отношение к разбросу версий как к временному — несколько версий всегда работают одновременно; исправить: делайте каждое изменение интерфейса аддитивным и выдерживайте предыдущую версию.
  • Предположение, что существующих логов достаточно — запрос проходит через процессы, и его не проследить; исправить: передавайте идентификатор корреляции и трассируйте между сервисами.
  • Тайм-ауты без определённого запасного поведения — вызывающий отказывает, и его собственные клиенты отказывают следом; исправить: решите, что возвращается, когда зависимость недоступна.
  • Недооценка работы над платформой — цена приходит раньше выгоды; исправить: закладывайте в решение обнаружение сервисов, обмен сообщениями, трассировку и секреты.

Что читать дальше