Боль
Если вы держите on‑prem кластер на десятки или сотни нод, дальше можно не объяснять. Обновили CNI, или ядро, или драйвер сетевой карты, или просто переехали частью нод в новый сегмент. Проходит какое‑то время, и начинают капать тикеты: «у нас иногда таймаутит между подами», «DNS иногда не резолвится», «health‑check то падает, то нет».
Ключевое слово тут «иногда». Проблема не глобальная, не про 100% трафика. Ломается конкретная пара нод или конкретный протокол, и ломается ровно так, чтобы спрятаться за агрегатом. 98% успешных проверок выглядят прекрасно. Ровно до той секунды, когда доходит: недостающие 2% это одна пара, один протокол, и так изо дня в день.
Привычный инструментарий в этот момент почти бесполезен:
kubectl execплюсpingилиcurlруками. Отлично, а какую пару проверять? Их сотни, и вы не знаете, какая именно сломалась.Node exporter и типовые дашборды покажут CPU, память и диск конкретной ноды. Про то, что нода A не достучалась до ноды B по UDP, там нет ни слова.
Логи CNI‑плагина. Если повезёт, найдётся что‑то релевантное. Постфактум и далеко не по каждой паре.
Хотелось получить инструмент, который постоянно и во всех направлениях гоняет между нодами живые пробы, а при сбое сразу говорит: вот пара, вот протокол, вот хоп.
Что уже есть: Goldpinger
Первое, что приходит в голову, это Goldpinger. Инструмент взрослый и обкатанный: DaemonSet, опрашивает своих собратьев по HTTP, отдаёт метрики и рисует граф связности в собственном web UI. Ставится за минуту и честно отвечает на вопрос, видит ли нода X ноду Y. Если у вас его нет, начните с него. Порог входа копеечный, польза сразу.
Но деградация бывает не бинарной, а частичной, да ещё и завязанной на конкретный протокол, и вот тут его модели перестаёт хватать. Основной меш между пирами у него HTTP, per‑peer ICMP и реактивного per‑hop трейсинга нет. А мне нужны были все пять протоколов отдельными проверками между каждой парой, плюс автоматический MTR прямо в момент сбоя.
Так появился kconmon‑ng, Kubernetes Node Connectivity Monitor, next generation (репозиторий, Apache 2.0).
В прошлый раз я писал в LinkedIn про измерительное ядро. С тех пор к нему приросла целая веб‑консоль, и большая часть статьи будет про неё.

Как оно устроено
Архитектурно это связка agent/controller. Но не классический full‑mesh HTTP‑опрос.
Controller (Deployment) держит реестр агентов с heartbeat‑eviction, следит за нодами через Kubernetes informer, отдаёт топологию по gRPC‑стриму и умеет leader election для HA (
controller.leaderElection: true).Agent (DaemonSet) получает от контроллера живой список пиров по gRPC. Никакого поллинга: контроллер сам пушит полный снапшот пиров при подключении и заново при каждом изменении топологии. Дальше агент гоняет между собой и всеми пирами пять типов проверок: TCP, UDP, ICMP, DNS, HTTP.
+-------------------------------------------+ | Controller (Deployment) | | agent registry (heartbeat eviction) | | node watcher (zone labels) | | topology API + gRPC event stream | | leader election (active/standby) | +---------------------+---------------------+ | gRPC stream: peer list, pushed on every change | +-------------------+ +-------------------+ +-------------------+ | Agent (node-1) | | Agent (node-2) | | Agent (node-3) | | TCP UDP ICMP | | TCP UDP ICMP | | TCP UDP ICMP | | DNS HTTP | | DNS HTTP | | DNS HTTP | | MTR on failure | | MTR on failure | | MTR on failure | | /metrics :8080 | | /metrics :8080 | | /metrics :8080 | +-------------------+ +-------------------+ +-------------------+ ^ ^ +------------------ probes between all pairs -----------------+
Каждый чекер отдаёт не бинарное жив/не жив, а свой набор метрик:
Чекер |
Что меряет |
|---|---|
TCP |
время коннекта и полный RTT по каждому пиру |
UDP |
средний RTT, джиттер и packet loss на burst из нескольких пакетов |
ICMP |
echo RTT и loss, IPv4 и IPv6 |
DNS |
время резолва по паре (hostname, resolver), системный или явные upstream |
HTTP |
пофазовый тайминг для заданных URL: DNS, connect, TLS, TTFB, total |
MTR |
реактивный трейс при сбое, RTT и loss по каждому хопу |
TCP, UDP, ICMP и DNS работают из коробки с интервалом 5 секунд. HTTP по умолчанию выключен, потому что URL знаете только вы.
Главное отличие в диагностике: когда TCP, UDP или ICMP проба даёт сбой, агент сам запускает MTR для этой пары. Есть cooldown на пару, иначе один сломанный линк зальёт кластер трейсами. Результат уезжает в метрику kconmon_ng_mtr_hop_rtt_seconds с лейблами hop_number и hop_ip. Вместо «между node-5 и node-17 что‑то не так» вы сразу видите, на каком хопе начинаются потери.
Ещё несколько решений. Инцидент они не ловят, зато сильно экономят нервы в проде:
Zone auto‑discovery. Контроллер сам резолвит зону из label ноды и раздаёт её агентам при регистрации, так что каждая метрика несёт
source_zoneиdestination_zoneбез ручной прописки. Смена лейбла разлетается пирам сразу через full sync.Сброс stale‑гейджей. При изменении топологии per‑pair гейджи сбрасываются, а не остаются висеть призраком от ноды, которой уже нет.
Дерегистрация при shutdown. Роллинг самого kconmon‑ng не пишет фейковые потери в собственные метрики.
Строгая валидация конфига. Незнакомые ключи отклоняются, опечатка роняет старт явно. При hot‑reload невалидный конфиг просто не применяется, остаётся старый.
Self‑monitoring. Контроллер знает, сколько нод должно иметь агента (
kconmon_ng_controller_expected_agents, из числа schedulable‑нод). Если зарегистрированных меньше, вы получите отдельный алерт, а не тишину.
Консоль
Вот этого в прошлый раз не было совсем. Опциональный веб‑интерфейс, по умолчанию выключен, деплоится тем же чартом. Читает он тот же Prometheus и тот же контроллер, которые у вас уже есть: второго пути данных нет, агенты не меняются, метрики нигде не дублируются.
Двенадцать страниц: Обзор, Онлайн, Расследование, Матрица, Топология, MTR, Диагностика, Цели и расписания, Метрики, Оповещения, Консоль с PromQL dev‑tools и Настройки. Read‑only страницы работают вообще без базы, хватит одного URL Prometheus. История, авторизация, инциденты и правила алертинга уже просят PostgreSQL.
Водить вас по всем двенадцати я не буду. Лучше покажу маршрут, которым реально хожу во время инцидента.
1. Матрица: какая пара
Матрица N×N, по ячейке на каждую упорядоченную пару, раскраска по loss и latency. Сломанная нода читается красным столбцом. Односторонняя проблема оставляет одну‑единственную ячейку, а поломка на границе зон рисуется целым блоком.

С controller.events.enabled матрица и топология пушатся по WebSocket. Без него они поллят, и страница прямо пишет, в каком из двух режимов она сейчас, а не делает вид, что живая.
2. Расследование: что было вокруг излома
Кнопка расследования прямо в ячейке ведёт на /investigate, скоуп и окно уже зашиты в URL. Страница сводит девять источников таймлайна вокруг этого скоупа: события топологии, события Kubernetes, записи аудита, изменения MTR‑путей, запуски диагностики, окна работ, аннотации, вычисленные пересечения порогов и горящие алерты.
Дальше она ранжирует кандидатов в причины. Арифметика простая: вес класса, умноженный на линейное затухание по 300 секундам до onset. Веса такие:
3. Под пробой поехала инфраструктура: сменился маршрут (path‑change) или кластер поменял ноду или под (k8s). Ближе к сетевому симптому не бывает.
2. Изменился флот или его конфигурация: событие топологии или запись в аудите консоли. Правдоподобно, но на шаг дальше.
1. Окно работ. Оно объясняет деградацию, а не обвиняет кого‑то, и стоит в рейтинге ниже настоящих подозреваемых, чтобы никогда их не перебить.
0. Аннотации, запуски диагностики, пересечения порогов и горящие алерты. Первые два это то, что человек сделал по поводу проблемы. Порог сам и есть симптом. А горящий алерт всего лишь пересказывает его, причём обычно по тому же ряду, из которого порог и вычислен. Дай ему вес выше нуля, и страница начнёт показывать «причина вашей аварии: сообщение о вашей аварии». Именно так эти панели обычно и начинают врать.
Никакого ML. Константы лежат в одном модуле, и документация их цитирует, а не пересказывает.

Расследование сохраняется как инцидент, а пермалинк поднимает из сохранённой строки точный скоуп и окно. Ссылка физически не может разъехаться с инцидентом, который она называет.
3. Машина времени: как это выглядело в 02:14
Утренний разбор ночного инцидента всегда упирается в один и тот же вопрос: а что там, собственно, было? В консоли на этот случай есть кнопка Now в правом верхнем углу. Она открывает пикер: пресеты от «15m ago» до «24h ago», календарь и точное время. Выбираете момент, и каждая страница, которая что‑то читает, начинает отвечать про него, а не про сейчас: топология складывается из сохранённых событий, PromQL считается в точке t, лента событий превращается в скроллбэк. Выбранный момент живёт прямо в URL как ?at= с меткой RFC 3339, поэтому ссылку можно кинуть коллеге в инцидентный чат, и у него откроется то же самое прошлое.
Пока Машина времени включена, все мутирующие контролы задизейблены, а баннер сверху называет момент и держит кнопку Return to Live. Поменять флот из прошлого нельзя.

4. Оповещения: превратить находку в правило
Когда понятно, как выглядит поломка, хочется, чтобы в следующий раз она разбудила вас сама. /alerting собирает правило из шести типовых шаблонов (loss на паре, latency между зонами, сбои DNS, HTTP TTFB, пропавший агент, лежащий внешний таргет) либо из сырого PromQL.
Две вещи, которые тут важны:
Валидация запускает выражение, а не парсит его. В сборке сознательно нет зависимости от парсера prometheus/prometheus. POST /api/v1/alert-rules/preview выполняет выражение как instant query против вашего живого Prometheus и говорит, сколько рядов оно поймало. Ошибка рендера и ошибка запроса разъезжаются: «правило не собирается» и «правило сейчас ничего не матчит» это разные новости.
Консоль управляет, Prometheus вычисляет. Все включённые правила реконсайлятся server‑side apply в один объект PrometheusRule. Цель применения одна: дрейф это одно сравнение, частичного применения не бывает. И ничто в консоли не решает, что алерт сработал.
Объекты PrometheusRule, которые консоль не писала, показываются read‑only. Усыновить такой объект можно только явной копией, оригинал не мутируется никогда. Значит обе копии будут вычисляться, пока вы одну не удалите, и отчёт импорта пишет об этом прямым текстом.

Дрейф сначала фиксируется, потом чинится, причём в одном проходе. Отсюда штука, которая на первый взгляд выглядит багом: правило может честно показывать drift и свежий lastSyncedAt одновременно. Расхождение увидели и тут же исправили. Ошибки не роняют цикл, они приземляются на конкретное правило с закрытым классом причины (crd-missing, forbidden, other).
Остальное коротко
MTR Explorer. Каждый пройденный путь хешируется по содержимому и дедуплицируется на входе, так что история пути это список изменений, а не стена одинаковых трейсов. Три панели, клиентский diff между любыми двумя снапшотами, опциональное обогащение хопов через rDNS и MaxMind (выключено по умолчанию и это единственная часть, которая ходит за пределы кластера).
Диагностика, внешние таргеты и расписания. Прогнать пару по требованию прямо посреди инцидента, с историей запусков и пермалинками. Внешние таргеты агенты чекают непрерывно, причём CIDR‑allowlist проверяет агент, а не консоль: агент без явного опт‑ина просто отказывается от внешней работы.
Лента Онлайн и командная палитра. Виртуализированный поток событий с фильтрами по типу, severity и скоупу, пауза с буфером и учёт пропущенных событий.
⌘Kпо навигации, действиям и Машине времени, написана руками, без зависимостей.Auth, RBAC, аудит, вебхуки.
anonymous | local | header | oidc, 25 permissions на четыре встроенные роли (viewer,operator,alert-editor,admin) плюс кастомные, журнал аудита, API‑токены и исходящие вебхуки на переходы инцидентов и алертов. Доставки подписаныX-Kconmon-Signature: sha256=<hmac>по сырому телу, секрет каждого эндпоинта write‑only через API и лежит зашифрованным AES-256-GCM.Настройки. Конфигурация выгружается версионированным бандлом и импортируется сначала в dry‑run. Вебхуки экспортируются с флагом
hasSecretи без самого секрета, поэтому импортом их создать нельзя: запечатанный секрет из API не выходит.
Установка
Что понадобится: Kubernetes 1.31+ (CI гоняется на 1.36), Helm 4 (чарт публикуется как OCI‑артефакт, Helm ≥3.14 тоже работает) и Prometheus Operator, если хочется ServiceMonitor и правила алертинга из коробки. Что немаловажно, дополнительных capability агенту не нужно вовсе: ICMP и MTR ходят через непривилегированный ICMP‑сокет, который открывает sysctl net.ipv4.ping_group_range. Этот sysctl у kubelet в списке безопасных, так что чарт сам его и выставляет, заодно с RBAC на node watch для контроллера.
helm upgrade --install kconmon-ng oci://ghcr.io/esdmitrii/charts/kconmon-ng \ --version 2.0.3 \ --set serviceMonitor.enabled=true \ --set prometheusRule.enabled=true
Проверяем, что всё поднялось. Должен быть один controller pod и по одному agent pod на каждую ноду:
kubectl get pods -l app.kubernetes.io/name=kconmon-ng -o wide
И что агенты реально отдают метрики:
AGENT=$(kubectl get pods -l app.kubernetes.io/component=agent -o jsonpath='{.items[0].metadata.name}') kubectl port-forward "$AGENT" 8080 & curl -s http://localhost:8080/metrics | grep '^kconmon_ng' | head
Консоль это флаг на том же релизе, остальное в чарте не меняется:
helm upgrade --install kconmon-ng oci://ghcr.io/esdmitrii/charts/kconmon-ng \ --version 2.0.3 \ --set console.enabled=true \ --set console.prometheus.url=http://prometheus-operated.monitoring:9090 kubectl port-forward svc/kconmon-ng-console 8081:8080
На http://localhost:8081 вы получите read‑only страницы под анонимным viewer. Дальше всё включается по одному флагу. Истории, авторизации, инцидентам и правилам алертинга нужен database.existingSecret с PostgreSQL DSN. Realtime‑пуш живёт на controller.events.enabled=true. Алертингу, кроме console.alerting.enabled=true, понадобится ещё CRD PrometheusRule от Prometheus Operator. Каждая ручка описана прямо в charts/kconmon-ng/values.yaml.
Что чарт 2.0.3 делает за вас
Двойка это в основном про шаги, которые раньше делались руками. Любой секрет по‑прежнему можно принести своим existingSecret, а можно попросить чарт отрендерить его блоком secret:. Значения полей при этом пишутся дословно, поэтому плейсхолдер для всякого рода операторов и инжекторов${vault:...} доезжает байт в байт и разрешается на admission.
Своей базы и своего кеша чарт не ставит. Направьте database.existingSecret на Secret с DSN вида postgres://, а redis.existingSecret на Secret с redis://, и подойдёт то, что у вас уже крутится: RDS, StatefulSet, свой кластер CloudNativePG. GeoLite2 больше не надо подкладывать: при geoip.mode=auto рядом с консолью крутится сайдкар из образа geoipupdate от самой MaxMind, а консоль перечитывает оба файла и переоткрывает тот, что изменился. Свежая база подхватывается без рестарта.
Дашборды раскладываются ConfigMap’ами с меткой grafana_dashboard для сайдкара, поды едут на дефолтах restricted‑PSS, агент в том числе. Он дропает ALL наравне со всеми: ICMP и MTR работают через непривилегированный ICMP‑сокет, который открывает sysctl net.ipv4.ping_group_range, а тот у kubelet в списке безопасных. Namespace на restricted принимает DaemonSet как есть.
Метрики и PromQL
Все метрики идут с настраиваемым префиксом (kconmon_ng по умолчанию) и общим набором лейблов для парных проверок: source_node, destination_node, source_zone, destination_zone. Набор не менялся между релизами. Дашборды и recording rules, написанные ещё под 1.5.0, ведут себя ровно так же.
Что я держу под рукой:
# Пять худших пар по UDP loss прямо сейчас topk(5, kconmon_ng_udp_packet_loss_ratio) # p95 времени TCP-коннекта в разрезе пар зон histogram_quantile(0.95, sum by (le, source_zone, destination_zone) ( rate(kconmon_ng_tcp_connect_duration_seconds_bucket[5m]) ) ) # Пары, где TCP именно падает, а не просто тормозит rate(kconmon_ng_tcp_results_total{result="fail"}[5m]) > 0 # p99 резолва DNS по каждому резолверу histogram_quantile(0.99, sum by (le, resolver) (rate(kconmon_ng_dns_duration_seconds_bucket[5m])) ) # Где именно умирают пакеты на конкретной паре kconmon_ng_mtr_hop_rtt_seconds{source_node="node-1", destination_node="node-2"}
Из коробки при prometheusRule.enabled: true едут семь правил:
- alert: UDPLossHigh expr: kconmon_ng_udp_packet_loss_ratio > 0.5 for: 5m - alert: TCPChecksFailing expr: >- sum by (source_node, destination_node, source_zone, destination_zone) (rate(kconmon_ng_tcp_results_total{result="fail"}[5m])) / sum by (source_node, destination_node, source_zone, destination_zone) (rate(kconmon_ng_tcp_results_total[5m])) > 0.05 for: 5m - alert: PairWentSilent expr: >- sum by (source_node, destination_node) (rate(kconmon_ng_tcp_results_total[1h] offset 5m)) > 0 unless sum by (source_node, destination_node) (rate(kconmon_ng_tcp_results_total[5m])) > 0 for: 10m - alert: DNSChecksFailing expr: >- sum by (source_node, source_zone, host, resolver) (rate(kconmon_ng_dns_results_total{result="fail"}[5m])) / sum by (source_node, source_zone, host, resolver) (rate(kconmon_ng_dns_results_total[5m])) > 0.05 for: 5m - alert: ExternalChecksFailing expr: >- sum by (source_node, source_zone, target, target_kind, check_type) (rate(kconmon_ng_external_results_total{result="fail"}[5m])) / sum by (source_node, source_zone, target, target_kind, check_type) (rate(kconmon_ng_external_results_total[5m])) > 0.1 for: 5m - alert: KconmonAgentsMissing expr: >- (kconmon_ng_controller_expected_agents - kconmon_ng_controller_registered_agents > 0) and (kconmon_ng_controller_leader == 1) for: 10m - alert: KconmonControllerDown expr: absent(kconmon_ng_controller_leader == 1) for: 5m
Три правила *ChecksFailing смотрят на долю неудач, а не на сырой rate: одна вспышка держала бы rate положительным все пять минут окна и будила бы вас на ровном месте, а доля так и остаётся в пределах нормы, пока линк реально не сломался. Пороги: 5% внутри кластера (TCP, DNS) и 10% для внешних целей, до которых сеть уже не ваша.
PairWentSilent тут стоит особняком: он ловит не плохие цифры, а тишину, когда пара ещё час назад слала результаты, а теперь молчит, и остальные правила по ней тоже замолкают, будто всё хорошо.
Последние два следят за самим kconmon‑ng. Мониторинг, который замолчал, разбудит вас, а не будет тихо выглядеть здоровым. Полный список метрик с типами и лейблами лежит в docs/metrics.md.
И три готовых дашборда для Grafana в dashboards/: Overview (Connectivity Matrix, success rate и латентность по каждому протоколу, статус контроллера, счётчик срабатываний MTR), Node Detail (то же самое в разбивке по destination‑ноде) и Zone Heatmap (тепловая карта латентности и потерь между зонами).
for f in dashboards/*.json; do curl -s -X POST "http://localhost:3000/api/dashboards/db" \ -H "Content-Type: application/json" -u admin:admin \ -d "{\"dashboard\": $(cat "$f"), \"overwrite\": true}" done



Из терминала
kubectl-kconmon ходит в HTTP API контроллера через port‑forward на client‑go, так что посмотреть топологию и прогнать разовую проверку можно без браузера. Проверка со сбоем возвращает код выхода 2, отдельно от 1 для ошибок CLI и API. В пайплайн это ложится нормально. -o json отдаёт сырой результат как есть.
$ kubectl kconmon topology NODE ZONE READY AGENT AGENT IP node-1 us-east-1a yes node-1-kconmon-ng-agent-aaaaa 10.0.0.1 node-2 us-east-1b yes node-2-kconmon-ng-agent-bbbbb 10.0.0.2 node-3 us-east-1c no - - $ kubectl kconmon check node-1 node-2 --type udp OK udp node-1 -> node-2 (us-east-1a -> us-east-1b) duration=1.1ms sent=5 recv=5 loss=0% rtt=1.1ms jitter=240µs
Ставится через krew, плагин уже в официальном индексе:
kubectl krew install kconmon
Ломаем связность руками
Проверить, что детект сбоев, MTR и алерты работают, проще всего сломав сеть самому. В hack/README.md есть NetworkPolicy, которая режет весь трафик между агентами:
kubectl apply -f - <<'EOF' apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: block-agent-traffic namespace: default spec: podSelector: matchLabels: app.kubernetes.io/name: kconmon-ng app.kubernetes.io/component: agent policyTypes: - Ingress ingress: - from: - podSelector: matchLabels: app.kubernetes.io/component: controller - namespaceSelector: matchLabels: kubernetes.io/metadata.name: monitoring EOF
Исключение для namespace monitoring тут несущее, и это тот случай, где я наступил на грабли лично. Метрики потерь экспортируют сами агенты. Если политика заодно отрежет скрейпы Prometheus, ряды агентов протухнут минут через пять, и поломка, которую вы только что устроили, исчезнет со всех дашбордов. Хаос ослепит собственного наблюдателя. Проверено живьём: без исключения две ноды, где Prometheus не живёт, ушли в down в списке таргетов за один интервал скрейпа.
Через 10 или 30 секунд в логах агентов:
kubectl logs -l app.kubernetes.io/component=agent --since=1m | grep -E "check failed|triggering MTR"
Ожидаемо: check failed с type: tcp/udp/icmp и i/o timeout либо 100% loss, следом triggering MTR trace и MTR trace completed.
В Grafana на Overview просядут success rate по TCP, UDP и ICMP, покраснеют loss ratio, счётчик MTR Triggers уйдёт в ненулевое значение. В консоли та же поломка видна на /matrix красными ячейками, а /investigate по одной из этих пар покажет таймлайн сбоя вместе с MTR‑трейсом, который агент снял в момент срыва.
Убрать политику:
kubectl delete networkpolicy block-agent-traffic
Через минуту‑две всё вернётся в зелёное. Полный локальный стенд на Minikube с kube‑prometheus‑stack, консолью, PostgreSQL и проверкой round‑trip алертинга поднимается одной командой ./hack/local-test.sh up, и там же лежит пошаговый вариант руками.
Более длинный сценарий есть в docs/demo/breaking-cni.md: заблэкхолить UDP между двумя нодами и смотреть, как краснеет ровно одна ячейка матрицы, пока TCP и ICMP на той же паре остаются зелёными. Потом сломать TCP, ICMP и HTTP ещё в трёх местах одновременно и увидеть каждый отказ отдельно, в своей паре и своём протоколе.
Что kconmon‑ng не делает
Он не перехватывает реальный прод‑трафик. Это пробы между нодами. Телеметрия сервис‑меша и eBPF‑инструменты отвечают на другой вопрос и отвечают на него лучше.
Консоль это потребитель Prometheus, а не замена ему. Снесите консоль завтра, и все метрики, дашборды и правила продолжат работать.
В статье нет ни одного бенчмарка. Просто потому, что я не гонял таких, которые не стыдно опубликовать.
Всё, что добавляет консоль, по умолчанию выключено либо read‑only‑additive. Апгрейд релиза без изменения values рендерит те же манифесты.
Про тесты, раз уж речь о доверии к цифрам: во фронтенде 3593 теста в 120 файлах, 29 Go‑пакетов несут тесты, и CI гоняет их с race‑детектором на каждый PR, вместе с линтом и helm‑lint по набору CI‑профилей values. Тег v* кросс‑компилирует бинарники, публикует образы и чарт в GHCR и запускает e2e.
Что дальше
v2.0.0 был последней запланированной вехой консоли, так что она доделана по задумке, а не брошена посередине. Чего мне теперь не хватает? Чужих кластеров: флотов побольше, CNI поэкзотичнее, инсталляций с упором на IPv6.
Issues и PR открыты. Если запустите и оно соврёт вам про вашу сеть, мне правда интересно про это услышать.
Репозиторий: https://github.com/EsDmitrii/kconmon‑ng
Чарт:
oci://ghcr.io/esdmitrii/charts/kconmon-ng, версия 2.0.3Лицензия: Apache 2.0
Комментарии (2)

esdmitrii Автор
20.08.2026 10:08Спасибо за комментарий. По пунктам.
Внешние агенты.
Тут приятная новость: в целом, архитектурно это ближе, чем кажется. Так как агент не ходит в API Kubernetes вообще, в коде нет ни одного импорта client-go и имя ноды он получает обычной переменной окружения через downward API, то есть для него это просто строка идентификатор, то зону можно задать явно в конфиге, и тогда контроллер не пытается резолвить её из объекта Node и регистрация проходит без всякой ноды. Фактически агент это самодостаточный бинарь, который знает адрес контроллера, своё имя и свою зону.
И исходя из этой мысли, то, чего не хватает по-настоящему двух вещей:
Первое: упаковка, сейчас единственный способ доставки это DaemonSet, нужен нормальный бинарь с systemd-юнитом.
Второе: gRPC между агентом и контроллером сейчас plaintext, потому что внутри кластера он закрыт NetworkPolicy и этого достаточно. Как только агент уезжает за пределы кластера, регистрация должна получить mTLS и авторизацию, иначе кто угодно, кто дотянулся до порта, объявляет себя нодой и попадает в меш.Полный меш или нет.
Постоянные проверки между агентами - полный меш, N×(N−1) направленных пар, без сэмплинга. Это сознательно: смысл инструмента ровно в парной асимметрии, когда одна пара нод деградировала, а остальные в порядке, и любое прореживание графа теряет именно её. Проверять несколько случайных пиров это уже другой инструмент, он отвечает на вопрос «сеть жива?», а не «между какими двумя нодами сломалось».
Оптимизация выбора источников есть, но в другом месте, в разовых и запланированных проверках из консоли:sourceSelection: all | per-zone | one-per-zone. Там источники можно ограничить одним агентом на зону, и тогда объём растёт по числу зон, а не нод.Про масштаб.
Реально гонял на кластерах до 50–100 нод, работает, ничего экзотического делать не пришлось. Дальше начинается место, где надо считать, и считать надо не зонды, а серии.
На направленную пару приходится около 70 активных временных рядов: четыре гистограммы (TCP connect, TCP total, UDP RTT, ICMP RTT) по 13 бакетов плюс+Inf,sum,count, три гейджа потерь и джиттера и три счётчика результатов.На сотне нод это 9 900 пар, то есть порядка 700 тысяч активных серий и около 6 тысяч зондов в секунду по кластеру, примерно 60 на агента.
Prometheus под это стоит посчитать заранее, а не ставить как «ну ещё один экспортер», но это обычная эксплуатация, а не героизм.
Тысяча и выше - честно: не тестировал и негде. Дальше только арифметика модели. Тысяча нод это уже 999 тысяч пар, порядка 70 миллионов серий и ~600 зондов в секунду с каждого агента; пять тысяч - 25 миллионов пар, тут обсуждать нечего.
Важно, что рост квадратичный и упирается всё в кардинальность TSDB, а не в CPU агентов или в сеть, они сдадутся заметно позже.Что тут напрашивается, если однажды понадобится: развести две плоскости наблюдения. Постоянно и для всех - агрегат зона <--> зона, где кардинальность растёт по числу зон, то есть остаётся маленькой. Плюс разреженный граф между нодами: каждый агент проверяет не всех, а фиксированное число пиров, выбранных детерминированно так, чтобы граф оставался связным и покрывал все межзонные направления. А полный per-pair меш, не как фон, а как режим расследования: включается точечно, на конкретное подмножество нод, когда агрегат уже показал, что где-то плохо. Ровно тот путь, которым сейчас идут MTR и разовые проверки.
Xelld
Выглядит интересно, спасибо.
Собирал аналогичный инструмент, только не для kubernetes, а вообще для мониторинга intra/inter DC трафика.
Как идея вам - добавьте возможность подключать внешние агенты (без kubernetes).
И вопросы напрашиваются:
Вы всегда полный mesh собираете, или все же как-то оптимизируете набор пиров для проверки?
На каком количестве нод это сейчас работает?
Как поведет себя на 100/1000/5000?