Часто возникает ситуация, когда в кластере работает много взаимодействующих между собой сервисов, но из-за спонтанности разработки эти взаимодействия могут быть нигде не документированы. То есть ни команды разработки, ни команды эксплуатации доподлинно не знают, какие приложения куда обращаются, как часто, и какую нагрузку создают эти обращения. И когда возникает проблема с производительностью какого-то сервиса, не совсем понятно, на что нужно обратить внимание. В идеале хотелось бы иметь какую-то карту взаимодействия сервисов в Kubernetes, которая сама автоматически обновляется. Такую карту можно построить с помощью инструментов типа Istio и Cilium. Но иногда можно обойтись и более простыми решениями — например, NetFlow. Вообще Istio — это огромный комбайн, который решает множество задач: авторизация сервисов и шифрование трафика между ними, маршрутизация запросов, управление балансировкой запросов, трассировка и т. д. Но минус Istio в том, что он внедряет свои sidecar-контейнеры с envoy между приложениями, то есть по сути вмешивается в трафик. В результате этого могут возникать проблемы. Они, безусловно, имеют свои решения, но использовать Istio в режиме «включил и ничего не сломалось» практически никогда не получается. Что-нибудь обязательно начинает работать не так, как работало ранее. Это не говоря уже о повышенной нагрузке — каждый sidecar потребляет ресурсы CPU и памяти, а также привносит дополнительные, пусть и небольшие, задержки. Нам хотелось получить какую-то систему мониторинга межсервисного взаимодействия, которая стоит «сбоку» от приложений. Чтобы в случае, если эта система не работает или не успевает обрабатывать трафик и так далее, это никак не влияло на трафик между приложениями, и они продолжили работать. Что-то подобное на просторах Open Source найти не удалось. Некоторые CNI частично предоставляют такую функциональность, но не в полном объеме. К примеру, у Cilium есть Hubble, показывающий взаимодействие контейнеров. Но, во-первых, он потребляет просто огромное количество ресурсов (об этом мы упоминали в нашей статье), во-вторых, не хранит историю — посмотреть взаимодействие можно только «здесь и сейчас», без ретроспективы.  В Calico есть подобная функция, но в enterprise-версии.  В любом случае не хотелось бы привязываться к CNI. Хотелось бы чего-то более универсального. В голове вертелась мысль про NetFlow и модуль ядра ipt-netflow, который обрабатывает соединения и отправляет статистику по ним по протоколу NetFlow в коллектор.  NetFlow — сетевой протокол, предназначенный для учёта сетевого трафика, разработанный компанией Cisco Systems. Является фактическим промышленным стандартом и поддерживается не только оборудованием Cisco, но и многими другими устройствами, включая свободные реализации для UNIX-подобных систем. Так как модуль работает на уровне ядра, то может обрабатывать огромный поток трафика, как PPS так и BPS, что практически незаметно на фоне остальной нагрузки на узле. Этот инструмент выглядел как отличное решение по снятию статистики трафика с узлов. Но ipt-netflow содержит только информацию про IP-адреса (и порты) источника и назначения, но ничего не знает про сервисы, Pod'ы, пространства имён и остальные сущности Kubernetes. Да и что делать с огромным числом данных о соединениях, которые будут отправлять сенсоры ipt-netflow? Мысль не давала покоя, и в итоге было решено «скрестить ужа с ежом». С помощью библиотеки goflow2 был написан собственный коллектор для NetFlow, который экспортировал информацию о межсервисном взаимодействии в формате Prometheus. Во-первых, при запуске коллектор подписывается через API Kubernetes на информацию от всех Pod'ов: запущенных, новых и удаляемых. Это позволяет знать, какой IP-адрес соответствует какому Pod'у или приложению (по лейблам), а также в каком пространстве имён запущено это приложение. Во-вторых, получая пакеты NetFlow с сенсора ipt-netflow с каждого узла, коллектор разбирает эти пакеты по IP-адресу источника и назначения, производит маппинг адресов в имен сервисов, а затем обновляет счетчики Prometheus с соответствующими лейблами. В NetFlow для каждого соединения нас интересовало количество байт в соединении, а также общее количество соединений, которые приближенно можно считать количеством обращений одного сервиса к другому. То есть мы собираем 2 метрики: netflow_connection_count и netflow_connection_bytes. Можно экспортировать еще netflow_connection_packets, но полезного применения этой метрике не нашлось. Получилось что-то подобное: Тут надо понимать, что по данным из NetFlow непонятно, какое приложение инициировало подключение. То есть на каждое соединение у вас будет 2 записи в NetFlow — трафик из приложения А в приложение В и трафик из приложения В в приложение А. Так как NetFlow фиксирует вообще весь трафик, проходящий через узел, там присутствует информация о соединениях, ушедших из кластера наружу, и пришедших снаружи. Все соединения с неизвестных IP-адресов (тех, которые не принадлежат ни одному Pod'у в API Kubernetes) маркируются меткой unknown*.  В перспективе можно сделать какой-то дополнительный маппинг для таких внешних адресов. Например, подставлять AS (Autonomous System) или AS Name — тогда вместо unknown в лейблах будут записи вроде AS13238 или YANDEX LLC. Для доставки модуля ipt-netflow на все узлы кластера мы использовали custom resource NodeGroupConfiguration в Deckhouse, под управлением которого работает наш кластер: Из описания CR видно, что на узле выполняются: установка пакета iptables-netflow-dkms (вариант для Ubuntu); указание целевого адреса доставки NetFlow;  выбор необходимой 9-й версии NetFlow; отправка при помощи iptables всех пакетов с интерфейса cni0 в цепочку NETFLOW, где их обработает модуль. Интерфейс cni0 может отличаться в зависимости от используемой версии CNI.  Важно отметить, что перехватывается только входящий в интерфейс трафик. Так как взаимодействующие приложения находятся на разных узлах, при учете всего трафика он будет удваиваться из-за одновременного учета на обоих узлах: на каждом из узлов сенсор будет отправлять его в коллектор. Далее мы запустили наше приложение и сделали для него сервис ClusterIP с адресом 10.222.2.222, который указан в манифесте выше:  Сервис принимает UDP-поток с NetFlow на порту 2055, обрабатывает его, формирует метрики и лейблы, которые получает для Pod'ов из API Kubernetes, и затем экспортирует метрики на порту 8080. Также нам понадобился ServiceAccount с правами get watch list на все Pod'ы в кластере. Сначала была идея выкатить коллектор на каждый узел кластера с помощью DaemonSet’а, чтобы каждый узел отправлял NetFlow в свой коллектор. Но оказалось, что в кластере из 60 узлов и 4000+ Pod'ов «NetFlow-коллектор+prometheu-exporter» потребляет менее 1 CPU. Пришлось даже сгенерировать повышенный flow-rate, чтобы убедиться, что сервис не будет упираться в одно ядро при обработке трафика. DaemonSet или Deployment в нескольких репликах запустить тоже возможно, если кластер очень большой, и один Pod не сможет обработать весь поток данных. Но здесь нужно понимать, что при этом кратно увеличится количество собираемых метрик (а их и так будет довольно много), которые нужно потом агрегировать. Исходные коды приложения-коллектора на Go и сервиса-конвертера на Python, а также все необходимое для развертывания доступно в репозитории. Для построения более надежной системы можно запустить несколько коллекторов на разных адресах. Модуль ipt-netflow может дублировать данные для нескольких получателей: для этого нужно указать несколько destination'ов. Подробнее об этом и о многих других возможностях модуля можно прочитать в официальной документации. Для визуализации сделали дашборд в Grafana и карту взаимодействия сервисов с помощью плагина hamedkarbasi93-nodegraphapi-datasource. Мы написали небольшое приложение на Python, которое, используя ServiceAccount, получает данные из Prometheus и отдает их в формате, требуемом для Nodegraph: В результате у нас получился очень легковесный инструмент, с помощью которого можно автоматически построить карту взаимодействия между сервисами в Kubernetes. Причем его работа абсолютно никак не влияет на работоспособность самих сервисов и никак не вмешивается в трафик. Также он позволяет мониторить объем передаваемых между сервисами данных и количество открываемых соединений, что условно можно рассматривать как количество запросов к сервису.  Читайте также в нашем блоге: «Что ждать от внедрения Istio? (обзор и видео доклада)» «Service Mesh: что нужно знать каждому Software Engineer о самой хайповой технологии» «Мониторинг и Kubernetes (обзор и видео доклада)»