The Daily Newsstand · Free, Always
Saturday, October 24, 2026

Glafira for Apache Kafka: открытый desktop-клиент на Rust, который даёт LLM читать кластер, но не трогать его

Translate
Главное окно: дерево подключений слева, вкладка топика с таблицей сообщений и панелью деталей справа

Главное окно: дерево подключений слева, вкладка топика с таблицей сообщений и панелью деталей справа

Я делаю Glafira for Apache Kafka — бесплатный desktop-клиент для Kafka под macOS, Windows и Linux. Код открыт под Apache 2.0, все функции доступны без ключей и тарифов. В этой статье расскажу, зачем понадобился ещё один GUI для Kafka, как он устроен внутри (Tauri 2 + Rust + librdkafka) и про самую необычную его часть: встроенный MCP-шлюз, через который Claude, GigaCode, Cursor или любой другой клиент с поддержкой MCP может диагностировать кластер, оставлять замечания и предлагать изменения, но не может ничего в кластере поменять.

Самое вкусное — про шлюз для LLM — в самом конце, так что дочитайте )

Зачем ещё один GUI для Kafka

Если вы работаете с Kafka не как админ, а как разработчик или тестировщик, ваш типичный день выглядит так: kafka-console-consumer с пятью флагами, kcat с другими пятью флагами, самописный скрипт «найди сообщение по подстроке в value», и ещё один — «сбрось оффсеты группы перед прогоном автотестов». Каждый раз вспоминаешь синтаксис, каждый раз копируешь SASL-настройки в очередной конфиг.

GUI-инструменты есть, но у каждого свой компромисс: кто-то закрытый и платный, кто-то требует JVM, кто-то разворачивается как серверное приложение, и для «посмотреть топик на dev-стенде» это слишком. Мне хотелось простого: обычное приложение, которое ставится за минуту, хранит пароли в системном хранилище секретов и умеет те шесть вещей, которые нужны каждый день.

Мечтать не вредно, и я думал, что воплотить это в реальность мне не по силам. А потом познакомился с одним толковым товарищем по имени Клод, который и помог мне по-братски.

Вот эти шесть вещей, они же критерии, по которым я проверял MVP:

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

  2. Найти сообщение в топике по подстроке в ключе, значении или заголовке, ограничив поиск временем или оффсетом.

  3. Отправить сообщение с ключом и заголовками, в том числе пачку сгенерированных.

  4. Прочитать топик как участник consumer group и закоммитить оффсеты вручную.

  5. Создать топик, поменять конфиги, добавить партиции, очистить или удалить.

  6. Посмотреть consumer groups, их лаг по партициям и сбросить оффсеты.

Почему имя — Glafira? Просто понравилось имя ) Мне ещё нравится звать ласково — Глаша ) В планах создать целое семейство инструментов для своей работы, сейчас в работе Glafira for PostgreSQL — подобный инструмент для PostgreSQL и кластеров Patroni, так что Glafira for Kafka лишь первый из них.

Что умеет

Коротко, без маркетинга.

Подключения. PLAINTEXT, SSL, SASL PLAIN и SCRAM-SHA-256/512, mTLS с клиентскими сертификатами в PEM, PKCS#12 или JKS. Доверенный CA — тоже PEM, PKCS#12 или JKS, либо системные корневые сертификаты. Подключения раскладываются по вложенным папкам, экспортируются в файл без паролей и импортируются на другой машине с предпросмотром. Пароли и пароли ключей лежат в Keychain, Credential Manager или Secret Service, а не в JSON рядом.

Просмотр сообщений. Чтение без group.id через assign, так что кластер не замечает наблюдателя. Начальная позиция: последние N, с начала, с оффсета, с метки времени, «последние X минут». Фильтры применяются в Rust до отправки в интерфейс: подстрока или regex по ключу, значению и заголовкам, JSONPath-условие вроде $.order.status == "FAILED", диапазон времени. Live tail с кольцевым буфером, лимит до 100 000 сообщений. Форматы: auto, string, json, hex, base64.

Producer. Ключ, значение, заголовки, выбор партиции, acks, сжатие, идемпотентность. Шаблоны с плейсхолдерами {{uuid}}, {{now}}, {{seq}}, {{randomInt 1 100}}, {{env NAME}}. Пакетная отправка: N копий формы или файл JSON Lines / CSV. «Отправить повторно» из таблицы сообщений копирует ключ, значение и заголовки байт в байт, и шаблонизатор к чужому тексту не прикасается.

Consumer-сессии. Честное вступление в группу с ребалансом, назначения партиций, ручной коммит кнопками. Автокоммит по умолчанию выключен.

Топики и группы. Создание, редактор конфигов с diff «было → стало», добавление партиций, очистка через DeleteRecords, удаление. Для групп: лаг по партициям, участники с хостами и group.instance.id, сброс оффсетов с обязательным предпросмотром, только для группы в состоянии Empty.

Проверка кластера. Набор встроенных правил: нет контроллера, under-replicated и offline партиции, фактор репликации 1, min.insync.replicas больше RF, unclean leader election, автосоздание топиков, расхождение важных настроек между брокерами, пустые группы с лагом, вечный ребаланс, лаг, выросший с прошлой проверки. Строгость зависит от окружения подключения: RF=1 на prod критично, на stage предупреждение, на dev не показывается вовсе.

Мелочи, которые оказались важными. В быстрое время живут быстрые люди, которым некогда путешествовать по линкам и выискивать нужную информацию среди «многа букв» на не очень понятном языке. Поэтому интерфейс на 12 языках. Встроенное руководство с поиском по F1. Подсказки «?» у каждого поля и у каждого параметра конфигурации топика. Всё в одном месте — прямо перед глазами. Корпоративная политика через MDM, GPO или /etc: запретить автообновления, указать зеркало, зафиксировать язык, запретить MCP. Паранойя в наше время — не болезнь, а полезный скилл.

Просмотр сообщений с фильтром JSONPath: из 240 сообщений найдено 15 с кодом ошибки PAYMENT_DECLINED

Просмотр сообщений с фильтром JSONPath: из 240 сообщений найдено 15 с кодом ошибки PAYMENT_DECLINED

Вкладка «Замечания»: находки встроенной проверки, предложения и замечания LLM-ассистента

Вкладка «Замечания»: находки встроенной проверки, предложения и замечания LLM-ассистента

Под капотом

Стек: Tauri 2, Rust (edition 2024), React 19 + TypeScript, TanStack Query и Virtual, Zustand. Kafka-клиент — rust-rdkafka поверх статически собранной librdkafka с vendored OpenSSL и zstd. Сейчас в Rust около 36 тысяч строк вместе с тестами, во фронтенде около 10 тысяч без тестов.

Главный принцип: вся работа с Kafka в Rust. Фронтенд не знает о librdkafka. Он вызывает команды через IPC и получает потоки данных. Длительные операции (просмотр, consumer-сессии, пакетная отправка) — это фоновые tokio-задачи с SessionId; сообщения уходят в WebView через tauri::ipc::Channel батчами, отмена — через CancellationToken. На одно подключение лениво создаётся один AdminClient и один producer, consumer’ы — на каждую сессию отдельно.

Ошибки. librdkafka умеет сообщать об ошибках десятком способов, и половина из них не похожа на ошибку. Все они приводятся к одному сериализуемому AppError с кодом (AUTH_FAILED, TLS_ERROR, TIMEOUT, READ_ONLY, …), и фронтенд показывает их одинаково. Просмотр сообщений переживает перезапуск брокера: временные ошибки терпятся, и только если связь не восстановилась за 60 секунд, сессия завершается ошибкой.

Сертификаты. JKS-файлы разбираются парсером на Rust и конвертируются в PEM, без JVM. Файлы читаются при каждом подключении, поэтому после ротации сертификата достаточно переподключиться. Для кластера в Kubernetes с Istio или cert-manager достаточно вытащить tls.crt, tls.key и ca.crt из секрета.

Защита от «ой»

Приложение, которое умеет удалять топики на prod, обязано мешать это делать. Два механизма:

  • Только чтение — флаг профиля. Все изменяющие операции отклоняются в Rust с кодом READ_ONLY, кнопки в интерфейсе скрыты. Отклоняются именно в Rust: фронтенд может ошибиться, ядро — нет.

  • prod — окружение подключения. Каждая изменяющая операция требует ввести имя подключения; удаление и очистка — ещё и имя объекта. Подтверждения живут внутри страницы, не в системных диалогах.

Сброс оффсетов и удаление группы работают только для группы без участников, иначе ошибка с объяснением. Сброс всегда с dry-run: таблица «текущий → новый» по каждой партиции, и только потом кнопка «Применить».

LLM через MCP: читать можно, менять нельзя

Это часть, ради которой я и пишу статью.

Идея простая. У модели хорошо получается читать метаданные кластера и объяснять, что не так: почему группа вечно ребалансится, почему лаг растёт только на двух партициях, какие топики на prod живут с RF=1. У неё плохо получается нести ответственность. Поэтому Glafira работает как MCP-шлюз (Model Context Protocol): клиент вроде Claude Desktop, Claude Code, GigaCode или Cursor запускает glafira-kafka mcp, и дальше модель видит кластер глазами Glafira, но руками Glafira ничего сделать не может.

Раздел «LLM-ассистенты (MCP)» в настройках: фрагменты конфига для клиентов, подтверждённые клиенты и журнал вызовов

Раздел «LLM-ассистенты (MCP)» в настройках: фрагменты конфига для клиентов, подтверждённые клиенты и журнал вызовов

Как это устроено

Claude Desktop ──stdio──► glafira-kafka mcp (шим: перекачка байтов)
                              │ первая строка: токен из mcp.json
                              ▼
          Unix socket (macOS/Linux) или named pipe (Windows)
                              ▼
 Glafira: crates/glafira-mcp — rmcp-сервер, сессии, инструменты, лимиты, журнал
                              │ трейт Backend: только операции чтения
                              ▼
          src-tauri/src/mcp/ — реализация Backend, права, события в UI
                              ▼
          существующие функции чтения кластера

Сервер живёт внутри запущенного приложения. glafira-kafka mcp — это тот же бинарь в режиме без окна, который просто перекачивает байты между stdin/stdout клиента и локальным сокетом. Tauri, WebView и librdkafka в этом режиме не инициализируются. Если Glafira не запущена, шим запустит её и подождёт сокет до 30 секунд.

Сокет лежит в каталоге данных с правами 0600, токен генерируется заново при каждом запуске сервера и сверяется за постоянное время. Первая строка соединения — рукопожатие GLAFIRA-MCP/1 <token>, дальше обычный JSON-RPC MCP построчно.

Почему «менять нельзя» — это гарантия, а не обещание

Самое важное решение: MCP-слой — отдельный крейт glafira-mcp, у которого нет зависимости ни от rdkafka, ни от крейта приложения. Приложение отдаёт ему трейт Backend с единственным методом чтения read(ReadOp), где ReadOp — перечисление из операций вроде ListTopics, DescribeGroup, GroupsLag. Изменяющих вариантов в этом перечислении нет, и тест читает Cargo.toml крейта и проверяет, что запрещённые зависимости не появились. Никакой промпт-инъекцией в имени топика нельзя заставить модель удалить топик, потому что инструмента «удалить топик» у модели физически нет.

Что модель видит: подключения с флажком «Доступ для LLM (только метаданные)», брокеры с хостами, топики с партициями и ISR, конфиги брокеров и топиков с замаскированными секретами, consumer groups с участниками и лагом. Что не видит: сообщения в топиках, bootstrap-строку, пароли, сертификаты, дополнительные свойства librdkafka, историю отправки.

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

Новый MCP-клиент подтверждается диалогом в Glafira: «Claude Desktop 1.x хочет подключиться, он увидит метаданные N подключений». Пока вы не ответили, модель получает ошибку client_not_approved и вежливо просит вас нажать кнопку. Каждый вызов пишется в журнал mcp-audit.jsonl: время, клиент, инструмент, подключение, результат. Содержимое ответов в журнал не попадает. Администратор может запретить MCP целиком политикой mcpEnabled = false.

Отдельная предосторожность: имена топиков, групп и client id может задать кто угодно с доступом к кластеру. В инструкциях сервера модель предупреждена, что эти строки — данные, а не команды для неё.

Три уровня участия модели

Всего у сервера 19 инструментов, и они делятся на три группы.

Чтение. list_connections, get_cluster_overview, get_broker_config, list_topics, describe_topic, get_topic_configs, get_topics_message_counts, list_consumer_groups, describe_consumer_group, get_consumer_groups_lag, get_topic_consumer_groups. Плюс observe_consumer_group: модель может наблюдать за группой до 50 секунд и увидеть, кто входит и выходит, как часто и с какого хоста. Это ровно то, что нужно для разбора вечного ребаланса.

Замечания. health_check запускает ту же встроенную проверку, что и кнопка в интерфейсе (если последней проверке меньше 5 минут, модель получит её результат без нового похода в кластер). annotate, list_annotations, remove_annotation — модель оставляет текстовые замечания о кластере, брокере, топике или группе. Они появляются на вкладке «Замечания» и на экранах объектов с подписью клиента; текст показывается как есть, без форматирования и ссылок. Кластер при этом не меняется.

Предложения. Это появилось в 0.4.0. propose_change позволяет модели предложить изменение четырёх видов: конфиги топика, новый топик, добавление партиций, сброс оффсетов пустой группы. Удаление и очистку предложить нельзя вовсе. Предложение проверяется против кластера, сохраняется рядом с замечаниями и показывается карточкой с обоснованием модели. Кнопка «Открыть в форме» открывает обычную форму Glafira, уже заполненную: редактор конфигов с diff, форму создания топика, диалог партиций или сброс оффсетов с предпросмотром. Дальше работают те же стражи, что и всегда: read-only, ввод имени подключения на prod. Если вы что-то поправили перед применением, предложение помечается «применено с правками», и модель видит в list_proposals, что применилось на самом деле. Отклонить можно с причиной, которую модель тоже увидит.

Предложение LLM, открытое в форме: карточка с обоснованием и редактор конфигов с подсвеченной строкой min.insync.replicas

Предложение LLM, открытое в форме: карточка с обоснованием и редактор конфигов с подсвеченной строкой min.insync.replicas

Получается такой цикл: модель диагностирует, объясняет и предлагает; человек смотрит diff, правит и нажимает кнопку. Ответственность не делегируется.

Как это выглядит в жизни

Настоящий диалог с Claude Code, подключённым к локальному стенду. Запрос один, ответ приведён как есть, убран только идентификатор предложения:

Я: Проверь здоровье кластера dev и предложи со строгостью warning исправление для smoke-g2-isr

Claude: Кластер dev здоров, но топик smoke-g2-isr сейчас не принимает записи с acks=all. Предложение с исправлением (severity: warning) я оставил в Glafira, в кластере ничего не менял.

Состояние кластера (подключение «Local», один брокер localhost:9092):

  • 5 топиков, 57 партиций. Офлайн-партиций и under-replicated партиций нет.

  • health_check нашёл две проблемы:

    • warning, топик smoke-g2-isr: min.insync.replicas = 2, а replication factor = 1. Продюсеры с acks=all получают NotEnoughReplicas и не могут писать.

    • info (вы скрыли), группа smoke-g2-empty: в группе нет участников, 3 сообщения не прочитаны.

Предложение для smoke-g2-isr:

  • Что меняется: min.insync.replicas с 2 на 1. Оно привязано к находке topic.minIsrUnreachable.

  • Почему так: брокер один, поэтому поднять replication factor нельзя. Остаётся только снизить min.insync.replicas до RF.

  • Почему не сброс к значению по умолчанию: я не знаю значение брокера по умолчанию, и оно тоже может оказаться больше 1. Поэтому в предложении стоит явная 1.

Применить или отклонить его можно в Glafira рядом с топиком. Потом я могу проверить ваше решение через list_proposals.

Тот же диалог в терминале Claude Code

Тот же диалог в терминале Claude Code

Обратите внимание на три детали. Модель сама сказала, что в кластере ничего не меняла, и объяснила, почему предлагает явное значение, а не сброс к умолчанию: она не знает default брокера и честно об этом говорит. Скрытую находку она тоже видит, но с пометкой «вы скрыли».

Дальше я открыл предложение в Glafira, посмотрел diff и применил. Продолжение того же диалога:

Я: проверь, применил ли я предложение

Claude: Да, вы применили предложение без изменений, и предупреждение по smoke-g2-isr пропало.

  • Статус предложения: applied, без правок (appliedAsProposed: true).

  • Конфиг топика: у smoke-g2-isr теперь min.insync.replicas = 1. Это значение задано на уровне топика и равно replication factor, так что продюсеры с acks=all снова могут писать.

  • Новая проверка health_check: находки topic.minIsrUnreachable больше нет. Осталась одна, которую вы раньше скрыли: в группе smoke-g2-empty нет участников и 3 непрочитанных сообщения.

Я: оставь заметку к группе smoke-g2-empty

Claude: Оставил заметку к группе smoke-g2-empty (severity info). Она привязана к находке group.abandoned и видна в Glafira рядом с группой.

Что в заметке:

  • Группа читает только партицию 0 топика smoke-g2. Закоммичен offset 0, а в партиции 3 сообщения, то есть группа не прочитала ни одного. Скорее всего, консьюмер подключился, закоммитил начало и остановился.

  • Для smoke-группы в dev это не проблема.

  • Что можно сделать: если тест закончен, удалить группу вручную (через Glafira удаление предложить нельзя); если группа нужна, а эти 3 сообщения нет, сбросить offset на latest, пока группа пустая; если сообщения нужны, просто запустить консьюмер.

Другие заметки: открытая заметка к брокеру 1 советует просто удалить override min.insync.replicas у smoke-g2-isr. Вы уже применили явное значение 1, так что совет устарел, и эту заметку можно закрыть.

Я: Предложи создать топик smoke-g3-dlq на 2 партиции с cleanup.policy=compact, фактор репликации не указывай

Claude: Предложение создать топик готово, оно в Glafira (severity info). В кластере ничего не создано, пока вы его не примените.

  • Имя: smoke-g3-dlq

  • Партиции: 2

  • Конфиги: cleanup.policy=compact

  • Replication factor: не указан, поэтому возьмётся default.replication.factor брокера. Судя по более ранней заметке к брокеру, здесь это 1.

Продолжение диалога: проверка применения, заметка к группе, предложение создать топик

Продолжение диалога: проверка применения, заметка к группе, предложение создать топик

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

Чего пока нет

Честный список, чтобы не было сюрпризов.

  • Schema Registry, Avro, Protobuf. Нет. Сообщения показываются как string, json, hex или base64. Это самая частая просьба, и она в планах.

  • Kerberos (GSSAPI), OAUTHBEARER, AWS MSK IAM. Нет, только PLAIN и SCRAM.

  • ACL, Kafka Connect, ksqlDB, квоты, графики метрик во времени, реассайнмент партиций. Не планировалось в MVP.

  • Размер топика на диске. librdkafka не поддерживает DescribeLogDirs.

  • Подпись сборок для Windows. macOS-сборки подписаны и нотаризованы, Windows пока ругается SmartScreen.

  • MCP работает только через stdio-шим. HTTP-транспорта нет, и это сознательно: сервер не должен быть доступен снаружи машины.

В следующей версии планирую сосредоточиться на метриках во времени: сбор, визуализация и анализ через LLM. Это та пища для ума, которой не хватает для глубокого анализа.

Лицензия и деньги

До версии 0.3 у проекта были тарифы Free и Pro. В 0.3.0 я убрал лицензионные ключи целиком: код открыт под Apache 2.0, все функции бесплатны для всех, проект живёт на пожертвования. Подсистема ключей именно удалена, а не отключена: мёртвому коду в публичном репозитории не место. Имя Glafira и значок под лицензию не попадают: форк — пожалуйста, но под своим именем.

Попробовать

Установщики для macOS (.dmg, .pkg), Windows (.msi) и Linux (.deb, .rpm, .AppImage): https://github.com/vvnovg/glafira-kafka-releases/releases

Исходный код: https://github.com/vvnovg/kafka-explorer

Локальная Kafka для практики, одного брокера достаточно:

docker run -d --name kafka -p 9092:9092 apache/kafka:3.9.0

Подключение: localhost:9092, PLAINTEXT, окружение local.

Чтобы подключить Claude: «Настройки» → «LLM-ассистенты (MCP)» → включить → скопировать фрагмент конфига для своего клиента → отметить у подключения «Доступ для LLM (только метаданные)». Первое подключение клиента Glafira попросит подтвердить.

Буду рад вопросам, багам и спорам о том, можно ли вообще пускать модель к prod-кластеру, пусть и только на чтение.

Apache Kafka — товарный знак Apache Software Foundation; проект с ASF не связан.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.