Распределенные приложения: типичные проблемы и решения
Небольшая заметка о построении распределённых приложений, как сохранять эффективность маленькой команды несмотря на рост сложности на проекте.
Прежде всего хочется отметить, что перед стартом проекта важно обозначить архитектурные ограничения и обосновать их до начала этапа кодирования, что облегчит жизнь в будущем, так как архитектура проекта — это прежде всего ограничения, а не возможности, которые не дают приложению стать чрезмерно сложным и неконтролируемым. Таким образом, определяя основной набор ограничений, мы определяем варианты имплементации, делая код относительно шаблонным и предсказуемым, что важно для цены поддержки в будущем.
Мотивация создания распределённого приложения:
- модульность, некоторые части должны быть относительно автономны;
- безопасность, приложение должно взаимодействовать как с сервисами из Internet так и с внутренними корпоративными, в связи с чем некоторые его части могут находиться в различных сегментах сети, например в DMZ;
- максимально эффективная утилизация ресурсов оборудования из-за серьезных ограничений на цену транзакции, в противном случае создание и использование приложения может терять экономический смысл.
- требования определённого уровня отказоустойчивости.
Теперь можно перейти к списку проблем, которые я постараюсь описать в порядке их возникновения и о том, как их можно преодолеть.
Управление конфигурационными параметрами П: Каждый сервис обычно имеет некоторый набор конфигурационных параметров, настраивать каждый по отдельности нудно и требует много времени.
Р: Использовать REST-сервис, который раздаёт конфигурационные параметры другим сервисам и оповещает об их изменении через Message Broker. Сервис-клиент при старте имеет лишь адрес сервиса конфигурации и bearer-токен, с помощью которого идентифицируется клиент и отдаются его параметры. В случае необходимости сервис подписывается на обновление конфигурационных параметров через MB. Для облегчения тестирования и возможности использовать каждый сервис в автономном режиме имеет смысл использовать под одной абстракцией два типа клиента: для использования локальных конфигурационных параметров и для получения конфигурационных параметров с удалённого сервиса.
Логирование
П: Много сервисов — много логов и на разных серверах, также следует помнить, что интенсивное логирование может создать конкуренцию за ресурсы сервера.
Р: Для логов следует организовывать два таргета: централизованный и локальный. В первом случае логи собираются и используются когда инфраструктура функционирует в штатном режиме, во втором - когда проблемы с сетью.
В качестве примера предлагаю рассмотреть следующий вариант из собственной практики: Так как сервисы уже использовали RabbitMQ, для получения нотификаций от других сервисов, было решено использовать его, дописав для него таргет для NLog. Таким образом, мы получаем логи в JSON-формате и отправляем через RabbitMQ. Основным потребителем логов является Logstash c плагином к RabbitMQ, который их индексирует и отправляет в Elasticsearch. Графически схему логирования можно представить следующим образом:
Конечно можно было бы обойтись и без RabbitMQ, отправляя логи напрямую в Logstash, но это бы создало две следующие проблемы:
- Конкуренция за ресурсы между сервисом и Logstash, а он очень прожорлив;
- Каждый environment приходилось настраивать с учётом необходимости дополнительно разворачивать Logstash и организовывать доступ к Elasticsearch
Тестирование
П: При работе со сложной предметной областью, большим количестве кейсов, когда спецификации для форматов и протоколов могут меняться независимыми от нас факторами, появляется очень болезненная проблема тестирования: удовлетворительное регрессионное тестирование превращается в нереальное требование, а юнит-тесты становятся очень дорогими.
Р: В ситуациях когда невозможно зафиксировать требования и спецификации, юнит-тесты становятся очень хрупкими, а как следствие очень дорогими для бюджета, но при этом не решают проблему комплексно. В таких случаях стоит сфокусироваться на интеграционных тестах, когда можно тестом покрыть use case или вообще бизнес-процесс с конкретным business value, при этом не вдаваясь в детали реализации, (продаём не код, а решение). Решая эту проблему мы отчасти создаём себе две следующие.
Сборка и развёртывание
П: Увеличение количества собираемых приложений, ресурсов, инсталляторов неминуемо влечёт увеличение времени на компиляцию, сборку, развёртывание и тестирование. Особенно если основная статья расходов — фонд заработной платы.
Р: Билды не должны строиться долго, чтобы у инженера не было повода выходить из рабочего контекста. Если проекты становятся слишком большие, имеет смысл их дробить. Чем быстрее будет собран проект - тем быстрее его можно запустить и протестировать, тем больше эффективность инженеров и тестеров. Разработчик должен иметь возможность запустить юнит и интеграционные тесты одной - двумя командами. Continuos Integration и Continuous Delivery решения отличные помощники, но для автоматизации прежде всего стоит думать с позиции расходов времени персоналом и без создания отдельных environment для билд-сервера. Грубо говоря, приложение должно собираться тем же набором инструментов и на компьютере разработчика, и на билд-сервере.
Избегайте мыслей: “Наймём DevOps инженера и сразу заживём хорошо!” Хорошо не заживёте, каждый инженер и тестер должен обладать знанием, что и как собирается, где взять различные версии билдов, где билд может быть развёрнут. Большим плюсом будет возможность разворачивать систему для Nighltly-билда автоматически, либо по потребности, чтобы для всех заинтересованных лиц на проекте была возможность видеть приложение в действии. Для того, чтобы не путаться в билдах имеет смысл использовать semantic versioning.
Программно-аппаратное обеспечение
П: Большие затраты времени на сборку, запуск, развёртывание, невозможно запустить все компоненты приложения, падение эффективности персонала. Р: Не стоит недооценивать значимость программно-аппаратного обеспечения для людей вовлеченных в разработку распределённых приложений. Прежде всего стоит разобраться с тем, какие есть текущие показатели (цифры), что даст апгрейд или дополнительное ПО и как это повлияет на экономические показатели проекта и удовлетворённость персонала. Когда час работы инженера стоит $10–20, а 500GB SSD и 32GB RAM стоят ~$450, которые могут увеличить его производительность хотя бы на 1/4, то подобное вложение может окупить себя уже через месяц - полтора, а потом увеличивать доход из-за сэкономленных человеко-часов. Хорошее вложение в 25% годовых, не правда ли?
Мониторинг
П: Отсутствие информации о состоянии приложения ведёт к недоступности информации для принятия обоснованных решений и фактической потерей контроля над ним.
Р: Мониторинг состояния приложения является отличным элементом оперативного контроля и управления. Позволяет уменьшить время на выявление внештатных ситуаций, уменьшить затраты времени на их устранение, а также даёт возможность анализировать поведение программных агентов и пользователей. В качестве примера можно упомянуть Open Monitoring Distribution (OMD) со следующим набором инструментов: Icinga2, Thruk, Grafana, InfluxDB.