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


Я делаю 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:
Подключиться к кластеру с нужной аутентификацией быстрее, чем за минуту, включая импорт сертификатов.
Найти сообщение в топике по подстроке в ключе, значении или заголовке, ограничив поиск временем или оффсетом.
Отправить сообщение с ключом и заголовками, в том числе пачку сгенерированных.
Прочитать топик как участник consumer group и закоммитить оффсеты вручную.
Создать топик, поменять конфиги, добавить партиции, очистить или удалить.
Посмотреть 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. Паранойя в наше время — не болезнь, а полезный скилл.


Под капотом
Стек: 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 ничего сделать не может.

Как это устроено
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, что применилось на самом деле. Отклонить можно с причиной, которую модель тоже увидит.

Получается такой цикл: модель диагностирует, объясняет и предлагает; человек смотрит 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.

Обратите внимание на три детали. Модель сама сказала, что в кластере ничего не меняла, и объяснила, почему предлагает явное значение, а не сброс к умолчанию: она не знает 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=compactReplication 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 не связан.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.