AKHQ:配置驱动的 Kafka 观察、权限与消息安全
AKHQ 默认状态不适合直接放进共享网络
AKHQ 把 Kafka、Schema Registry、Kafka Connect 和 ksqlDB 组织在 akhq.connections 下,页面很快就能看到 Topic、消费组和消息。便利背后有一个必须先处理的默认值:官方认证说明指出,安全与角色默认关闭,匿名用户拥有完整访问。把容器端口暴露到团队网络,再补一个反向代理登录页,并没有自动收紧 AKHQ API。
另一个容易漏掉的条件是 JWT 签名密钥。AKHQ 使用签名 JWT 承载认证状态;配置 group 却没有设置签名密钥时,官方 Groups 文档明确警告 API 不执行组角色,限制只停在 UI。批次验收因此不能只看按钮是否消失,必须直接验证后台写接口和 Kafka ACL 都拒绝。
AKHQ 适合作为配置驱动的 Kafka 观察入口。它不是 Kafka 权限系统,也不替业务判断 offset 重置、消息重放和 Topic 变更是否安全。连接、页面认证、AKHQ role、Kafka ACL 与数据遮罩是五个不同控制点。
用一份 application.yml 跑起本地实例
官方 Installation 提供 Docker、独立 JAR 和 Helm 入口。运行时与安装要求会随发行线变化,团队应按目标 release 固定 Java、镜像或 chart 版本。下面用批准的镜像标签或 digest 启动,不在长期配置中写 latest:
先完成 Kafka 的开发 Compose,并保留它运行。Linux 宿主先在 Kafka 的 Compose 目录取得 broker 所在网络,再创建独立的 AKHQ 实验目录。这里复用 Kafka 已有的默认网络,不另外创建一个没有 broker 的空网络:
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 AKHQ_IMAGE=tchiotludo/akhq:0.27.1
export AKHQ_JWT_SECRET=$(openssl rand -hex 32)
read -rsp '本机 reader 密码: ' AKHQ_READER_PASSWORD; printf '\n'
export AKHQ_READER_SHA256=$(printf '%s' "$AKHQ_READER_PASSWORD" | sha256sum | cut -d' ' -f1)
unset AKHQ_READER_PASSWORD
AKHQ_LAB_DIR=$(mktemp -d "$HOME/akhq-lab-XXXXXXXX") || exit 1
export AKHQ_LAB_DIR
export AKHQ_PROJECT="akhq-lab-$(date +%s)-$$"
printf '配置目录:%s\nCompose 项目:%s\n' "$AKHQ_LAB_DIR" "$AKHQ_PROJECT"
cd "$AKHQ_LAB_DIR" || exit 1上述网络提取针对 Kafka 起步配置的单网络容器;自定义多网络时,先用 docker inspect 查看名称,再手工设置共享网络。docker network inspect 若失败,就不要继续创建 AKHQ。保存打印出的目录和项目名,后面的状态、日志和退出命令都使用它们;这两个值不是 Kafka Compose 的目录或项目名。密码不回显、不写入 shell 历史;本机演示使用官方支持的 SHA-256 格式,共享入口优先采用 BCrypt 或企业身份源。Basic Auth定义了散列算法和用户组字段。
把下面第一段保存为 $AKHQ_LAB_DIR/compose.yaml,第二段保存为同目录的 application.yml。不要把两段配置写进 Kafka 的 compose.yaml。
services:
akhq:
image: ${AKHQ_IMAGE:?set approved AKHQ tag or digest}
ports:
- "127.0.0.1:18081:8080"
environment:
MICRONAUT_CONFIG_FILES: /app/application.yml
MICRONAUT_SECURITY_TOKEN_JWT_SIGNATURES_SECRET_GENERATOR_SECRET: ${AKHQ_JWT_SECRET:?set JWT secret}
AKHQ_READER_SHA256: ${AKHQ_READER_SHA256:?set reader password hash}
volumes:
- ./application.yml:/app/application.yml:ro
networks:
- dev-messaging
networks:
dev-messaging:
external: true
name: ${KAFKA_NETWORK:?set Kafka Compose network}必须挂载具体文件,不能把宿主机目录覆盖到 /app。官方 Docker 文档说明,覆盖整个目录会遮住镜像内的 AKHQ 文件,容器随后报 exec: ./akhq: not found。较长配置使用单一文件更容易评审;AKHQ_CONFIGURATION 或 MICRONAUT_APPLICATION_JSON 适合部署系统生成,不适合把整份含密配置塞进普通环境清单。
最小 application.yml 只接入本地 Kafka,并显式启用安全:
micronaut:
security:
enabled: true
akhq:
connections:
dev-local:
properties:
bootstrap.servers: "kafka:29092"
security.protocol: PLAINTEXT
security:
default-group: no-roles
basic-auth:
- username: reader
password: "${AKHQ_READER_SHA256}"
passwordHash: SHA256
groups: [reader]
pagination:
page-size: 25
topic-data:
size: 20
poll-timeout: 1000
kafka-max-message-length: 1048576default-group: no-roles 让未映射用户无法操作;本机临时只读可以使用 reader,但不能把匿名全局 reader 当成团队身份体系。page-size、topic-data.size、poll-timeout 和最大消息长度共同限制页面查询与内存放大。演示值不是生产标准,生产值要根据 Topic 数、消息体、并发用户和堆基线测量。
两个文件保存后,从同一 shell 验证配置、检查本机端口并启动。docker compose config -q 报缺少镜像、JWT、散列或外部网络时,先修正相应变量和文件,不继续启动;18081 若已有监听者,先确认归属,也不要启动第二个实例:
docker compose -f "$AKHQ_LAB_DIR/compose.yaml" -p "$AKHQ_PROJECT" config -q
ss -lntp | grep ':18081' || true
docker compose -f "$AKHQ_LAB_DIR/compose.yaml" -p "$AKHQ_PROJECT" up -d
docker compose -f "$AKHQ_LAB_DIR/compose.yaml" -p "$AKHQ_PROJECT" ps
docker compose -f "$AKHQ_LAB_DIR/compose.yaml" -p "$AKHQ_PROJECT" logs --tail=120 akhq
curl -q --noproxy '*' -sS -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1:18081/HTTP 状态只证明 Micronaut 应用在响应;刚启动时若连接尚未建立,先等日志出现 Startup completed 再重试,不把一次启动竞态当成 Kafka 故障。未登录时出现登录页或重定向也不代表 Topic 可读。页面必须能列出目标 broker、Topic 与 consumer group,Kafka CLI 也应在同一网络看到相同集群标识。若容器能连 bootstrap、随后 broker 超时,仍要回到 Kafka 的 advertised listener,而不是在 AKHQ 中继续加地址。
打开 http://127.0.0.1:18081,用 reader 和刚才输入的密码登录,选择 dev-local 后查看 broker 与 orders.events.v1。未登录时不应有集群访问权限;reader 登录后只能读取,不能创建 Topic 或重置 offset。默认组说明中的 no-roles 负责拒绝匿名,basic-auth 才建立可登录身份。若页面反复返回登录页,先查密码散列、环境变量和 JWT 密钥是否加载,不把默认组改成 admin。
页面检查后,用同一个本机实验实例核对后台 API。重新输入 reader 密码,经 /login 取得短期会话;会话 cookie 写在本次目录中的临时文件,不打印到终端。下面的空 JSON 是故意无效的创建请求,只用于确认角色在进入写入逻辑前被拒绝,不对共享 Kafka 运行:
read -rsp '再次输入 reader 密码: ' AKHQ_LOGIN_PASSWORD; printf '\n'
AKHQ_COOKIE_JAR=$(mktemp "$AKHQ_LAB_DIR/session.XXXXXXXX") || exit 1
chmod 600 "$AKHQ_COOKIE_JAR"
LOGIN_STATUS=$(printf '%s' "$AKHQ_LOGIN_PASSWORD" | \
curl -q --noproxy '*' -sS -c "$AKHQ_COOKIE_JAR" \
-o /dev/null -w '%{http_code}' \
--data-urlencode 'username=reader' --data-urlencode 'password@-' \
http://127.0.0.1:18081/login)
unset AKHQ_LOGIN_PASSWORD
printf 'login HTTP %s\n' "$LOGIN_STATUS"
curl -q --noproxy '*' -sS -o /dev/null -w 'anonymous read HTTP %{http_code}\n' \
http://127.0.0.1:18081/api/dev-local/topic
curl -q --noproxy '*' -sS -b "$AKHQ_COOKIE_JAR" \
-o /dev/null -w 'reader read HTTP %{http_code}\n' \
http://127.0.0.1:18081/api/dev-local/topic
curl -q --noproxy '*' -sS -b "$AKHQ_COOKIE_JAR" \
-H 'Content-Type: application/json' --data '{}' \
-o /dev/null -w 'reader create HTTP %{http_code}\n' \
http://127.0.0.1:18081/api/dev-local/topic
rm -- "$AKHQ_COOKIE_JAR"固定 0.27.1 的预期依次是登录 303、匿名读取 401、reader 读取 200、reader 创建 403。单独一个 303 不是登录成功证据:密码错误时也会重定向到 /ui/login/failed;必须继续取得 reader 的 200。若 reader 返回 401,先核对输入密码、SHA-256 完整长度和 JWT 配置;若返回 500,再查 Kafka 连接及 advertised listener,不能把后端不可达判成权限拒绝。创建不是 403 时停止本机测试并检查 group/JWT 配置。AKHQ 官方将这组 API标为实验性接口,升级时应按目标版本重新核对路径和状态码。curl 手册说明 password@- 从标准输入取值,以及 -c/-b 的 cookie 写入与读取;不要把密码或会话 cookie 放进命令行参数、日志或工单。
connections 把多个后端放进同一个权限半径
AKHQ 的集群配置以 URL 友好的 connection key 标识集群,标准 Kafka consumer properties 放在 properties 下。Schema Registry、Connect 和 ksqlDB 是该 connection 的可选子连接,它们各自拥有 URL、认证和 TLS 状态。
SCRAM over TLS 可以这样组织:
akhq:
connections:
shared-dev:
properties:
bootstrap.servers: "broker-a.dev.example:9093,broker-b.dev.example:9093"
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}";
ssl.truststore.location: /run/kafka-secrets/client.truststore.jks
ssl.truststore.password: ${KAFKA_TRUSTSTORE_PASSWORD}
schema-registry:
url: "https://schema.dev.example"
type: confluent
basic-auth-username: ${SCHEMA_REGISTRY_USERNAME}
basic-auth-password: ${SCHEMA_REGISTRY_PASSWORD}
properties:
schema.registry.ssl.truststore.location: /run/kafka-secrets/client.truststore.jks
schema.registry.ssl.truststore.password: ${KAFKA_TRUSTSTORE_PASSWORD}connection key 不允许点号,避免用域名直接当 key。Kafka 读取成功、Registry 失败时,Topic 和 group 仍可能正常,结构化消息却无法反序列化。Connect 或 ksqlDB 接入又会增加独立管理能力;观察角色若不需要它们,就不要配置。一个 connection 下能显示更多系统,不代表一个服务身份应横跨所有系统。
凭据和 truststore 由 Secret 系统只读挂载。若测试证书与 FQDN 不匹配,不能把关闭 endpoint identification 留在生产配置中。网络错误、TLS 错误、SASL 错误和 Registry 解码错误必须分别保留第一条异常,避免用管理员凭据一次性掩盖全部问题。
JWT、group 与 role 决定 API 是否真的拒绝
JWT secret 至少应满足官方 JWT 配置要求,并通过 Secret 注入。启用 Basic、LDAP 或 OIDC 后,把外部用户映射到 group;group 再引用 role,并用 patterns 和 clusters 收窄资源。
akhq:
security:
roles:
project-observer:
- resources: ["TOPIC", "TOPIC_DATA", "CONSUMER_GROUP"]
actions: ["READ"]
- resources: ["TOPIC"]
actions: ["READ_CONFIG"]
groups:
project-a-observers:
- role: project-observer
patterns: ["project-a\\..*"]
clusters: ["shared-dev"]AKHQ 的 Groups 文档列出了资源与 action 组合。TOPIC_DATA READ 与 Topic 元数据读取分开,UPDATE_OFFSET、DELETE_OFFSET、ALTER_CONFIG 和 DELETE 不应进入日常观察角色。旧配置跨越 group 权限模型的破坏性变更时,YAML 能解析不代表权限语义相同,升级必须重跑 API 反例。
先用只读用户查看 project-a.* Topic 和目标消费组,再访问其他项目 Topic,预期被拒绝。随后尝试创建测试 Topic、改配置、删除 group 或更新 offset。按钮应不可用,直接请求 AKHQ API 也要返回拒绝,后台 Kafka 身份还应被 ACL 拦住。若 API 成功而页面隐藏,先检查 JWT secret 是否真实注入、Micronaut security 是否启用,以及是否有另一份配置覆盖当前文件。
这组实验比截图有用。截图只能证明某次渲染没有按钮;API 与 Kafka 的双重拒绝才能证明操作者无法绕过前端。
默认遮罩让 Registry 故障不泄露原文
消息读取是 AKHQ 最敏感的能力。官方数据遮罩支持正则和 JSON 两类策略。json_mask_by_default 默认遮住所有字段,只按 Topic 规则放行诊断字段;Schema Registry 不可用或消息无法结构化时,AKHQ 不显示不可过滤的原文,这个失败方式更适合生产。
akhq:
security:
data-masking:
mode: json_mask_by_default
jsonMaskReplacement: "xxxx"
json-filters:
- description: expose safe diagnostics only
topic: "project-a.events"
keys:
- eventType
- traceId
- status用虚构消息验证允许字段可见,姓名、地址、token 和自由文本仍显示为替换值。再让 Registry 暂时不可用,预期 AKHQ 不回退展示原始二进制或未过滤字符串。若业务消息是多 record type、无 schema 或字段结构不稳定,遮罩策略需要专门验证;不要因为页面“不方便”就切成 none。
消息浏览会创建读取请求,从选定 offset 或时间位置拉取记录。看不到探针消息时,检查分区、起始位置、retention/compact、Topic READ、反序列化器和 kafka-max-message-length。探针 key 使用 akhq-probe-001,value 只含虚构字段。真实用户数据不应成为权限测试材料。
消费延迟与 offset 操作必须分开
AKHQ 可以查看 group 的 current offset、log end offset 和 lag。先按分区观察:单分区增长通常与热点、分配或慢消息有关;全分区同步增长再看消费者实例、处理耗时和下游故障。用 Kafka CLI 交叉验证同一 cluster、Topic 和 group,页面刷新差异不能直接解释成消息丢失。
更新 offset 是另一种权限。向早期位置移动会重放,向后移动可能跳过未处理记录。观察 group 的人不应自然获得 UPDATE_OFFSET 或 DELETE_OFFSET。临时操作先记录每个分区原位置、目标位置、消费者是否暂停、业务幂等条件和回退值,执行后再核对 lag 与业务副作用。
AKHQ 的角色只能决定谁能请求操作,不能判断重放订单事件是否重复扣款。高风险动作应进入独立临时 group 和短期 Kafka ACL,事件结束立即撤销。把 offset 按钮藏起来但让后台身份长期可写,仍然把事故半径留在控制台容器中。
JVM、分页和外部连接决定共享实例的上限
AKHQ 是 JVM 应用。大量 Topic、partition、group、并发消息浏览和反序列化会同时增加管理请求、堆对象和浏览器响应体。容量基线至少记录连接数、Topic/partition 数、group 数、最大消息体、页面并发、抓取条数、poll timeout、堆使用和 GC 暂停。出现堆持续增长时先收窄连接、分页与消息长度,再检查是否有人浏览大消息或外部 Registry/Connect 持续超时。
共享实例通常不需要和 Kafka 同等级高可用。控制台不可用不应影响生产消费。多副本会重复拉取元数据,还要验证 JWT secret、配置文件和外部会话是否一致;仅为“高可用”翻倍副本,可能先把 broker 管理请求翻倍。
升级时固定镜像或 chart、application.yml、JWT 配置、group/role、服务身份 ACL 和遮罩样例。新版本先在测试集群验证登录、API 拒绝、Registry 解码、group lag、消息遮罩和 offset 提权。失败时恢复旧镜像与旧配置,检查临时放宽的 Kafka ACL 是否已经撤销。
退出顺序从权限开始。对本机实验,先确认打印过的 AKHQ_PROJECT 指向本次独立项目,再在保留启动变量的同一 shell 中停止控制台:
docker compose -f "$AKHQ_LAB_DIR/compose.yaml" -p "$AKHQ_PROJECT" ps
docker compose -f "$AKHQ_LAB_DIR/compose.yaml" -p "$AKHQ_PROJECT" down
unset AKHQ_JWT_SECRET AKHQ_READER_SHA256down 不会停止 Kafka 的 Compose 项目,也不会删除它的外部网络。若已换 shell,先从启动记录恢复目录、项目名以及 Compose 所需变量,核对 ps 只列出本次 AKHQ 容器后再停服务;不要猜一个项目名执行清理。共享环境还需撤销 UI 身份集成、JWT secret、Kafka/Registry/Connect 凭据与临时 ACL,再删除 truststore 副本、日志和测试消息。卸载 AKHQ 不会让服务账号自动失效。新电脑只凭批准镜像、审查过的 application.yml 模板和访问申请入口能够重建同样的只读行为,才算迁移完成。
权威资料与规范地址
身份、权限与安全
- 认证说明:https://akhq.io/docs/configuration/authentifications/
- Basic Auth:https://akhq.io/docs/configuration/authentifications/basic-auth.html
- 默认组说明:https://akhq.io/docs/configuration/authentifications/groups.html
- JWT 配置:https://akhq.io/docs/configuration/authentifications/jwt.html
- 数据遮罩:https://akhq.io/docs/configuration/akhq.html#data-masking
安装、运行与版本
- Installation:https://akhq.io/docs/installation.html
- Docker 文档:https://akhq.io/docs/configuration/docker.html
- curl 表单与 cookie:https://curl.se/docs/manpage.html
