Современная инфраструктура — множество небольших приложений, запущенных в контексте одного менеджера приложений, который упра'вляет их количеством, обновлением и запросами на ресурсы. Она является таковой не потому, что админы решили, будто так удобно ею управлять. Такая инфраструктура отражает текущее мышление в области разработки программного обеспечения. Для того чтобы понять, почему мы сейчас говорим о микросервисной архитектуре как об идеологии, необходимо вернуться на 30 лет назад.

В конце 80-х/начале 90-х объектно-ориентированное программирование стало ответом на разрастающийся объем программного обеспечения — ведь именно тогда персональные компьютеры начали использовать повсеместно. Если раньше программное обеспечение представляло собой небольшие «утилиты», то где-то в это время разработка масштабного ПО превратилась в крупный бизнес. Команды разработки в тысячи человек, создающие огромную функциональность, перестали быть чем-то сверхъестественным. Бизнесу было необходимо понять, каким образом можно разделить работу команд программистов так, чтобы все не перемешалось — и объектно-ориентированное программирование стало ответом на этот вопрос. Цикл релизов приложений при этом по-прежнему оставался медленным.

Вы планировали релиз вашего продукта (пусть это будет Microsoft Office 95) за несколько лет. После того как разработка завершалась, вы детально его тестировали (ведь вы не можете легко исправить ошибки после того, как приложение будет поставлено у конечного пользователя). Затем вы отправляли «бинарники» на завод, где изготавливался тираж компакт-дисков (или дискет), все это упаковывалось в картонные коробки и доставлялось по всему миру в магазины, где пользователь их покупал и потом ставил приложение себе на компьютер… — в этом главная разница с тем, что происходит сейчас.

Начиная с 2010-го года, микросервисная архитектура стала ответом на потребность больших приложений и компаний в максимально быстром обновлении. Нам больше не нужно устанавливать приложение на компьютеры пользователей. Мы, фактически, «устанавливаем» приложение у себя в инфраструктуре и можем его быстро обновлять. Таким образом, мы можем (и бизнес хочет) проводить обновления максимально быстро с тем, чтобы ставить эксперименты и проверять гипотезы. Бизнесу нужно создавать новую функциональность, чтобы привлекать новых пользователей и удерживать текущих. Бизнесу нужно проводить эксперименты и смотреть, в результате чего пользователь может заплатить больше денег. Наконец, бизнесу важно не отставать от конкурентов. Бизнес хочет обновлять кодовые базы хоть десятки раз в день. В теории, это можно сделать и на одном большом приложении; но если разбить его на множество маленьких кусочков, управляемость обновлений увеличится. То есть переход к микросервисной архитектуре практически никогда не основывался на желании бизнеса увеличить стабильность приложения и инфраструктуры: микросервисы — это часть agile, и бизнес как раз руководствовался возможностью увеличить «гибкость» приложения.

Что означает гибкость? Это скорость, это легкость изменений, это возможность быстро передумать. Таким образом, во главу угла ставится не основательность решения, а максимальная скорость доставки этого решения, возможность быстро опробовать концепцию. Предполагается, однако же, что после того как решение будет опробовано, будут выделены ресурсы сделать его основательным — чего на практике (особенно в малых командах, в развивающихся бизнесах, где весь фокус направлен на развитие продукта) не происходит. Создается технический долг, опасность которого особенно увеличивается в условиях веры в то, что «кубернетис сам все поднимет».

Но это не так: недавно я наткнулся на отличную цитату, которая говорит одновременно и о плюсах, и об ужасах Kubernetes для эксплуатации.
«Kubernetes is so awesome that one of our JVM containers has been periodically running out of memory for more than a year, and we just recently realized about it».

Давайте вдумаемся в эту фразу. В течении года приложение время от времени падало по out of memory — и команда эксплуатации не обращала на это внимания. Означает ли это, что приложение большую часть времени продолжало стабильно работать и выполнять свою задачу? На первый взгляд, функция действительно хороша: вместо того, чтобы админ получил сообщение о падении сервиса и пошел его поднимать, Kubernetes сам обнаружил, что приложение упало, и сам его же перезапустил. В течении года это происходило регулярно, и сообщений службе администрирования не передавалось. Я видел проект, где, однако, подобная ситуация происходила строго в одном случае — при генерации ежемесячного отчета. Функциональность отчета была разработана и выкачена в продакшн в помощь бизнесу, но уже через короткое время пользователи стали получать HTTP 502 на свои запросы — приложение падало, запрос обрывался, а Kubernetes перезагружал приложение. И хотя само приложение работало при этом нормально, процесс формирования отчета не выполнялся никогда. Сотрудники компании, которым он был нужен, предпочли делать отчет по старинке и не репортили об ошибке (он же нужен всего раз в месяц — можно делать по-старому, зачем беспокоить людей), а служба эксплуатации не отдавала приоритет задаче, которая возникает от силы раз в месяц. Однако в итоге это приводит к тому, что все ресурсы, которые были затрачены для создания этой функциональности (бизнес-анализ, планирование, разработка), оказались затрачены зря. Об этом узнали также год спустя.

Мы в своей работе выбрали ряд практик, в которых стараемся максимально закрыть риски сопровождения микросервисных приложений на базе уже набитых шишек. В этой статье я попробую рассказать про 10 самых важных из них.

Ребуты сервисов не мониторятся, или им не придается должное значение


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

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

Ошибки сервиса (такие, как Fatal Error или Exceptions) не мониторятся


Пример:
Приложение может и не падать, но выдавать пользователю (или другому приложению через API) трейс эксепшена. Таким образом, даже если мы замониторили перезагрузки приложений — мы можем не обнаружить случаи, когда выполнение запроса завершилось некорректно.

Что делать?
Логи приложений следует агрегировать в какой-либо системе и анализировать. Ошибки нужно системно анализировать «глазами», а на критические ошибки — выставить алерты и эскалировать расследование немедленно.

Endpoint c health-check-ом сервиса отсутствует или не делает осмысленной работы


Пример:
Создание endpoint-ов, выдающих метрики сервиса (желательно в формате OpenMetrics) для того, чтобы их можно было читать (например, Prometheus-ом), стало, слава богу, практически стандартом. Однако в условиях, когда бизнес давит в сторону развития функциональности, тратить дополнительное время на продумывание метрик разработчикам зачастую не хочется. В результате, довольно часто встречаются health-check-и сервиса, вся суть которых сводится к выводу сообщения «оk». Если приложение способно вывести что-то на экран — считается, что это уже ок. Однако это не так. Такая проверка, если приложение не может достучаться до сервера БД, все равно сообщит «ок» и будет выдавать неверную информацию, затрудняя время на обнаружение проблемы.

Что делать:
Во-первых, сам факт endpoint-а с healthcheck-ом должен стать стандартом для любого вашего сервиса, если еще не стал. Во-вторых, такой healthcheck должен осмотреть здоровье/доступность критичных для функционирования сервиса систем (доступы до очередей, БД, доступность сервисов И так далее).

Время ответа API и взаимодействия с другими сервисами не мониторится


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

Что делать:
Используйте трейсинг. Jaeger стал практически стандартом. Есть офигенная рабочая группа, трудящаяся надо форматом OpenTracing по аналогии с OpenMetrics. Вот в этом докладе и вот здесь можно найти API для вашего языка программирования, которое сможет выдать метрики opentracing-а по времени приложений и взаимодействия с сервисами для того, чтобы вы выгружали это в Prometheus.

Сервис — это по-прежнему приложение, а приложение — это по-прежнему память и процессор (и иногда диск)


Пример и что делать: тут, я думаю все понятно. Довольно часто упускается мониторинг производительности отдельных сервисов: сколько они потребляют процессора, сколько оперативной памяти, если измеримо — как они потребляют «диск». В целом — все стандартные метрики для мониторинга «сервера». Часто мы видим, как мониторят всю ноду целиком, не мониторя отдельные сервисы — и напрасно.

Появление новых сервисов следует мониторить


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

Что делать:
Поставьте оповещение на появление новых сервисов в инфраструктуре.

Мониторинг времени доставки и других CI/CD метрик


Пример:
Еще одна проблема, которая появилась относительно недавно.
Производительность — это не только производительность приложения как таковая, но и скорость его выкладки. Сложные CI/CD процессы + усложнение процесса сборки приложения + сборка контейнера доставки = и вот простой процесс выкладки уже не такой уж простой ;-) Мы об этом написали статью.
В какой-то момент вы можете обнаружить, что выкладка сервиса стала занимать вместо одной минуты — двадцать…

Что делать:
Мониторьте время доставки приложения, от сборки до появления его в продакшне. Если время увеличилось — изучите, что произошло.

APM и профайлинг приложения


Пример и что делать:
В тот момент, когда вы узнали, что с сервисом случилась проблема — он начал долго отвечать или стал недоступен, или произошли ещё какие-либо проблемы, — вам меньше всего захочется копаться во внутренностях сервиса или пытаться его перезапустить, чтобы локализовать ошибку. В нашей практике очень часто получается отследить проблему, когда у вас есть детальный мониторинг на уровне Application Performance Monitoring: чаще всего проблема не возникает одномоментно, а «копится» во времени — и с данными APM-а вы сможете понять, когда же она произошла. Также научитесь пользоваться профайлерами, доступными на уровне системы. С развитием eBPF появилось огромное количество возможностей для этого.

Мониторинг событий WAF, мониторинг Shodan и мониторинг безопасности образов и пакетов


Пример:
Мониторинг сейчас касается не только вопросов производительности. Существует ряд простых действий, которые вы можете осуществить, чтобы улучшить безопасность вашего сервиса:

  • замониторьте результаты сборки вашего приложения при выполнении таких команд, как npm audit или аналогичных — вы сможете получите алерты, что в текущей версии библиотек, которые вы используете, и обновить их до защищенной версии.
  • подключите API сервиса Shodan, который занимается сканированием открытых баз и портов в интернете. Проверяйте свои IP через API, для того чтобы быть уверенным, что у вас нет открытых портов, а базы не утекли в открытый доступ.
  • если вы используете WAF — поставьте алерты на события WAF, чтобы понимать, когда к вам целенаправленно придет злоумышленник, и посмотреть на векторы атаки, которые он использует.

Бонус-трек: SRE, время ответа приложения — больше не время ответа сервера!


Мы привыкли к тому, что замеряем производительность системы по времени ответа сервера, однако современное веб-приложение на 80% своей логикой перенесено в фронтенд. Если вы еще не замеряете время ответа приложения как время выдачи страницы с фронтенд-метриками загрузки страницы — начните прямо сейчас. Пользователю уже совершенно неважно, за 200 или 400 миллисекунд отдал сервер ответ, если фронт на Angular-е или React-е дальше отрисовывает это в течении 10 секунд. В целом, я вообще верю, что оптимизация производительности во многом уйдет во фронт или появится как новая дисциплина.

А ещё я чуть более регулярно, чем блог здесь, веду свой телеграм-канал, подписывайтесь :-)