Kafbat UI(Kafka UI):从只读观察到受控集群入口
页面能打开,只说明 HTTP 进程活着
Kafbat UI 是目前持续维护的 Kafka UI 发行线。浏览器连接它的 HTTP 端口,应用再以 Kafka 客户端身份连接 broker、Schema Registry、Kafka Connect 或其他扩展服务。这里有两套身份:操作者登录 UI 的身份决定能看到哪些页面和按钮;应用持有的 Kafka 服务身份决定后台请求最终能做什么。
这两层经常被误当成一层。给页面接上 OIDC,却让应用继续使用集群管理员账号,泄漏的服务凭据仍能绕过 UI 写入 Kafka。反过来,Kafka 服务账号只有只读 ACL,但 UI 没有认证,任何能访问 HTTP 端口的人都能查看获准范围内的元数据和消息。共享入口必须同时收紧浏览器身份、UI RBAC、Kafka ACL、网络入口和消息正文。
官方 Getting Started 提供使用动态配置和 latest 镜像的演示命令。它适合快速看界面,不适合直接成为团队基线。长期实例应锁定发行标签或镜像 digest,使用可评审的静态配置,并把动态配置是否开启当作明确的架构选择。
先用静态配置跑通一个本地集群
准备 compose.yaml 和 config.yml。下面的镜像值由部署环境提供,避免把漂移的 latest 写进仓库:
先完成 Kafka 开发 Compose。在其 Compose 目录用 Linux shell 取得运行中的容器和网络,再创建独立的 UI 目录;不能留在 Kafka 目录执行后面的 UI Compose 命令:
KAFKA_CONTAINER=$(docker compose ps -q kafka)
test -n "$KAFKA_CONTAINER" || { echo 'Kafka 未启动' >&2; exit 1; }
KAFKA_NETWORK=$(docker inspect -f '{{range $name, $config := .NetworkSettings.Networks}}{{$name}}{{end}}' "$KAFKA_CONTAINER") || exit 1
test -n "$KAFKA_NETWORK" || exit 1
docker network inspect "$KAFKA_NETWORK" >/dev/null || exit 1
export KAFKA_NETWORK
export KAFBAT_UI_IMAGE=ghcr.io/kafbat/kafka-ui:v1.5.0
KAFBAT_LAB_DIR=$(mktemp -d "$HOME/kafbat-lab-XXXXXXXX") || exit 1
export KAFBAT_LAB_DIR
export KAFBAT_PROJECT="kafbat-lab-$(date +%s)-$$"
printf '配置目录:%s\nCompose 项目:%s\n' "$KAFBAT_LAB_DIR" "$KAFBAT_PROJECT"
cd "$KAFBAT_LAB_DIR" || exit 1Kafka 起步配置只有一个默认网络,服务名 kafka 在该网络可解析;自定义多网络部署应明确选择双方共享的一张网络。网络检查失败时先停下,不要用空网络变量继续启动。保存打印出的目录和项目名,后文状态、日志与退出都要使用同一组值。外部网络由 Kafka Compose 持有,UI 不负责创建或删除。容器客户端使用 INTERNAL 29092,宿主应用才使用 HOST 19092,否则 broker 元数据会把 UI 引向它自己容器内的 localhost。
把下面第一段保存为 $KAFBAT_LAB_DIR/compose.yaml,第二段保存为同目录的 config.yml。
services:
kafka-ui:
image: ${KAFBAT_UI_IMAGE:?set approved Kafbat UI tag or digest}
ports:
- "127.0.0.1:18080:8080"
environment:
SPRING_CONFIG_ADDITIONAL_LOCATION: /etc/kafkaui/config.yml
DYNAMIC_CONFIG_ENABLED: "false"
SWAGGER_UI_ENABLED: "false"
volumes:
- ./config.yml:/etc/kafkaui/config.yml:ro
networks:
- dev-messaging
healthcheck:
test: ["CMD", "wget", "-q", "-O", "-", "http://127.0.0.1:8080/actuator/health"]
interval: 10s
timeout: 3s
retries: 12
networks:
dev-messaging:
external: true
name: ${KAFKA_NETWORK:?set Kafka Compose network}config.yml 只接入一个开发集群:
kafka:
clusters:
- name: dev-local
bootstrap-servers: kafka:29092
read-only: true
polling-throttle-rate: 2
properties:
security.protocol: PLAINTEXTread-only 会阻止 UI 发起写操作,但不会修改 Kafka ACL。polling-throttle-rate 给页面轮询留出间隔,避免多人打开控制台后持续枚举集群。完整字段以目标发行线的配置文件说明为准,尤其要复核 Kafka client properties、消息分页与 AdminClient timeout。
两个文件保存后先验证 Compose;如果变量缺失、配置错误或 18080 已有监听者,先处理,不继续启动。启动后分别看进程健康与 Kafka 集群状态:
docker compose -f "$KAFBAT_LAB_DIR/compose.yaml" -p "$KAFBAT_PROJECT" config -q
ss -lntp | grep ':18080' || true
docker compose -f "$KAFBAT_LAB_DIR/compose.yaml" -p "$KAFBAT_PROJECT" up -d --wait
docker compose -f "$KAFBAT_LAB_DIR/compose.yaml" -p "$KAFBAT_PROJECT" ps
docker compose -f "$KAFBAT_LAB_DIR/compose.yaml" -p "$KAFBAT_PROJECT" logs --tail=120 kafka-ui
curl -q --noproxy '*' --fail-with-body -sS http://127.0.0.1:18080/actuator/health
curl -q --noproxy '*' --fail-with-body -sS http://127.0.0.1:18080/api/clusters
curl -q --noproxy '*' --fail-with-body -sS http://127.0.0.1:18080/api/clusters/dev-local/brokers健康返回 UP 仍未证明 Kafka 可用:即使 broker 不可达,UI 仍可能返回 UP,而集群停在 INITIALIZING。接通前面的 Kafka 后,/api/clusters 中 dev-local 应为 status: ONLINE、readOnly: true,broker 列表应给出 kafka:29092;否则先查日志、外部网络和 advertised listener,不把 UP 当成接入完成。再打开页面查看 broker、Topic 和 consumer group,并用 Kafka CLI 对照同一集群元数据。容器端口绑定回环地址,是为了防止这个无 UI 认证的本地实例被局域网直接访问。
bootstrap 成功后仍报错,通常要查 advertised listener
Kafbat UI 先连接 bootstrap-servers,拿到集群元数据后再访问元数据中的 broker 地址。容器里的 127.0.0.1 指向控制台自己,不是宿主机 Kafka;bootstrap 可达而 broker 列表持续超时,常见原因是 Kafka 向容器客户端公布了宿主机视角的 listener。
先从 Kafbat UI 所在网络验证 DNS 与端口:
docker run --rm --network "$KAFKA_NETWORK" busybox:1.37 \
sh -c "nslookup kafka && nc -vz kafka 29092"DNS 和 TCP 成功,只能证明入口可达。若日志随后尝试 localhost:19092 或另一个不可解析地址,应修正 Kafka 的 advertised.listeners,让容器客户端获得 kafka:29092 这样的内部地址。宿主机 CLI 可以继续走单独的外部 listener。更换 UI 镜像、提高连接超时都不会修复错误元数据。
故障可以按第一条异常分层。UnknownHostException 和 timeout 先查 DNS、路由、listener 与防火墙;SSLHandshakeException 查信任链、主机名与协议;SaslAuthenticationException 查机制、JAAS、用户名和密码。页面显示空集群时先读应用日志,不要直接把服务账号换成管理员。
SASL/SSL 与 Registry 是两条独立连接
共享 Kafka 通常需要 SASL/SSL。Kafka 客户端属性和证书路径进入集群配置,真实密码由运行环境注入:
kafka:
clusters:
- name: shared-dev
bootstrap-servers: broker-a.dev.example:9093,broker-b.dev.example:9093
read-only: true
ssl:
truststore-location: /run/kafka-secrets/client.truststore.jks
truststore-password: ${KAFKA_TRUSTSTORE_PASSWORD}
verify-ssl: true
properties:
security.protocol: SASL_SSL
sasl.mechanism: SCRAM-SHA-512
sasl.jaas.config: >-
org.apache.kafka.common.security.scram.ScramLoginModule required
username="${KAFKA_UI_USERNAME}"
password="${KAFKA_UI_PASSWORD}";
schemaRegistry: https://schema.dev.example
schema-registry-auth:
username: ${SCHEMA_REGISTRY_USERNAME}
password: ${SCHEMA_REGISTRY_PASSWORD}信任库用只读 Secret 挂载,密码不提交。Kafka 认证成功并不表示 Schema Registry 可用:Registry 是另一条 HTTP、TLS 和身份链。Topic 可见而 Avro、JSON Schema 或 Protobuf 无法解码时,检查 subject 命名策略、Registry 读取权限、证书和消息格式;不要为了显示正文关闭 TLS 校验。
消息探针只使用虚构数据。选择一个专用 Topic,写入 key 为 ui-probe-001 的小消息,再按分区、offset 或 key 查找。这样能区分“控制台读不到消息”和“业务数据恰好不在当前窗口”,也不会拿真实客户数据做调试样本。
UI 认证、RBAC 与 Kafka ACL 要分别验收
Kafbat UI 支持 Basic、OAuth2、LDAP/AD 等页面认证。共享实例更适合使用 OAuth2/OIDC,把身份提供方中的 group 或 role 映射到应用 RBAC。回调地址、issuer 与反向代理外部地址必须一致,否则常见现象是登录成功后循环跳转或回到错误协议。
下面的角色只允许查看特定项目 Topic 和消费组:
rbac:
roles:
- name: project-a-observers
clusters: ["shared-dev"]
subjects:
- provider: oauth_team_oidc
type: group
value: project-a-observers
permissions:
- resource: clusterconfig
actions: ["view"]
- resource: topic
value: "project-a\\..*"
actions: ["view", "messages_read"]
- resource: consumer
value: "project-a\\..*"
actions: ["view"]资源名和 action 必须按目标版本的 RBAC 文档核对。不要给只读角色使用 all,也不要把 Topic 的 view 与 messages_read 当成同一权限:查看元数据不必天然获得消息正文。
验收先用只读用户查看目标 Topic 配置、分区、副本和 group lag。然后尝试创建 permission_probe_should_fail、修改 retention 或重置测试 group offset。UI 应隐藏或拒绝,Kafka 服务身份本身也应收到 TopicAuthorizationException、GroupAuthorizationException 或等价拒绝。只有把服务凭据拿到 CLI 后仍不能写,才能证明安全不依赖按钮。
消息遮罩比“只读”更接近真实风险
只读用户仍可能看到手机号、地址、token 或业务密钥。Kafbat UI 的消息遮罩支持 REMOVE、REPLACE 和 MASK,可按 Topic key/value 模式与字段配置。敏感 Topic 可以先整段遮罩,再为确有需要的结构化字段设计更细规则:
kafka:
clusters:
- name: shared-dev
masking:
- type: MASK
topic-values-pattern: "project-a\\.private-.*"
- type: REMOVE
fields: ["accessToken", "phone"]
topic-values-pattern: "project-a\\.events"遮罩规则要用虚构消息做正反测试:敏感字段必须不可恢复,正常诊断字段仍可见,非 JSON 内容不能意外绕过规则。截图、浏览器缓存、访问日志和工单附件也属于消息正文的复制面。控制台不应默认承担生产消息浏览;能从 headers、时间、partition、offset 和 trace 标识定位问题时,就不开放 value。
用 lag 和 offset 缩小问题,不用控制台猜业务语义
消费延迟上升时,先看每个分区的 current offset、log end offset 和 lag。单个分区持续上升通常指向热点 key、分区分配或单条慢消息;所有分区一起上升,再查消费者实例、下游依赖和生产速率。页面值要与 kafka-consumer-groups.sh --describe 交叉验证,先排除连错集群、缓存刷新和同名 group。
offset reset 是写操作。向前重置会重放,向后跳转会跳过未处理消息;业务幂等也不能自动证明这次操作安全。日常观察角色不应拥有 reset_offsets。临时提权前保存原 offset、目标 offset、分区、业务暂停状态和回退命令,执行后再用 CLI 核对。Kafbat UI 只是入口,不知道一次重放会不会重复扣款或覆盖下游状态。
大集群要限制页面抓取。Topic、partition、group、并发用户、消息大小、刷新周期和副本数共同决定控制台对 broker 与自身堆内存的压力。出现 UI 堆增长或 Kafka 管理请求突增时,先降低分页、消息条数和轮询频率,收窄接入集群,再决定是否扩容。两个副本会重复轮询,不会天然把读取成本减半。
动态配置决定谁是事实源
官方的配置向导说明,动态配置默认关闭;开启后,运行时修改会写入容器文件系统中的 dynamic_config.yaml。这对个人演示很方便,对共享实例则会产生第二个配置事实源:Git 里的静态 YAML 与页面保存的动态配置可能不同。
团队必须二选一。静态模式由 Git、Secret 系统和部署流水线负责变更;动态模式则要持久化文件、限制编辑权限、备份并纳入审计。无状态多副本若各自保存动态文件,会得到不同集群清单。升级前不仅要保存镜像 digest,还要保存静态配置、动态配置和 Kafka ACL 基线。
退出时先关闭 HTTP 入口。本机实验应在保留启动变量的同一 shell 中核对项目并停止 UI,不会停止 Kafka Compose 或删除它拥有的外部网络:
docker compose -f "$KAFBAT_LAB_DIR/compose.yaml" -p "$KAFBAT_PROJECT" ps
docker compose -f "$KAFBAT_LAB_DIR/compose.yaml" -p "$KAFBAT_PROJECT" down若已换 shell,先从启动记录恢复 KAFBAT_LAB_DIR、KAFBAT_PROJECT 和 Compose 所需环境变量,不能猜项目名清理。共享环境还需撤销 OIDC client secret、Kafka/Registry 服务身份和临时 ACL,清理动态文件、信任库副本、日志与测试消息。docker compose down --remove-orphans 不会替你撤销集群权限。新电脑仅凭批准镜像、静态契约和访问申请入口能够重建只读实例,才算这套工具真正可迁移。
权威资料与规范地址
安装、运行与版本
- Getting Started:https://ui.docs.kafbat.io/overview/getting-started
配置与使用
- 配置文件说明:https://ui.docs.kafbat.io/configuration/configuration-file
- 配置向导:https://ui.docs.kafbat.io/configuration/configuration-wizard
