Я делаю 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 не связан.
Автор: VVN50


