Proxy Admin 管理接口

英文版见 docs/proxy-admin.md。

1. 动机

RocketMQ 5.0 把客户端访问放到了无状态的 Proxy 后面,但运维侧仍然通过 broker 侧的结构(broker 上的 ConsumerManager、Remoting 时代的 admin 命令)来观测客户端。连到 Proxy 的 gRPC 客户端对这些工具不可见:控制面无法回答“哪些 SDK 客户端在线、它们订阅了什么、是否健康”,只能依赖间接的指标推断。

控制面 dashboard 需要一个标准的服务端接口来读取完整的 gRPC 客户端数据。本次改动在 Proxy 上实现这个接口。

2. 契约选择:上游 Admin service

gRPC 契约是上游 Admin service,定义在 apache/rocketmq-apis 仓库(main 分支,作为 git submodule 引入,见 §6)的 apache/rocketmq/v2/admin.proto 中。本实现刻意不在协议层引入自定义的 ProxyAdminService:控制面 RPC 接口位于 dashboard 和多语言 SDK 已经在消费的、经过社区评审的版本化 proto 中,并与上游演进保持增量兼容。

proxy 实现 AdminGrpc.AdminImplBase(全部 16 个 RPC),并把它绑定在一个独立的 admin gRPC server 上。

3. 目标

  1. 在 Proxy 上提供一个独立、专用的 gRPC Admin server,与数据面 MessagingService 隔离。
  2. 来自 rocketmq-apis main 的稳定、向后兼容契约(Admin service,apache/rocketmq/v2/admin.proto)。
  3. 通过专用的 proxy.admin.* ACL 2.0 资源实现一等公民的鉴权,区分只读与高权限动作。
  4. 集群级语义但不把拓扑泄漏进协议:每个 proxy 都暴露连接/订阅数据的集群可见视图,并把客户端定向 RPC 转发到持有该客户端的 proxy。

4. 设计决策

D1 — 服务放置

上游 Admin service 绑定在自己的 gRPC server/端口(grpcAdminServerPort,默认 8088)上,有自己的拦截器链,与数据面(8081)分离。整个接口由全局 kill switch grpcAdminServerEnable 门控,默认为 false:admin server 是 opt-in 的,除非显式启用否则不会启动。

理由:控制面流量绝不能与数据面竞争,且 admin 端口可以只对运维网络开放防火墙。admin server 刻意不暴露 channelz 或 proto reflection。它复用数据面的 GrpcChannelManager / GrpcClientSettingsManager,使在线客户端对 admin 查询可见。本特性引入的运行时线程池均归实例所有并接入 Proxy 生命周期:admin timeout scheduler 随 ProxyAdminGrpcService 关闭,blocking gateway executor 随 AdminService 关闭。

D2 — 鉴权:专用 proxy.admin.* 资源

凭据通过标准 gRPC Authorization metadata 传入(与数据面相同的方案,ACL 2.0 签名)。认证以拦截器方式执行,顺序排在标准链路的 HeaderInterceptor 之后(因 gRPC 按注册的逆序调用拦截器,故注册在其之前)。这个顺序是必需的:HeaderInterceptor 会把入站的 x-mq-channel-id 替换为从传输层派生的 channel id,而认证结果按 channel id 缓存,若在客户端自带的值上做认证,一条连接的请求就能复用另一条连接的缓存结果、跳过签名校验。

每个 RPC 映射到一个资源 + 一个动作:

资源RPC动作
proxy.admin.clientListSubscription, ListConsumerConnection (List); DescribeSubscription, DescribeGroupAccumulation, GetConsumerRunningInfo, QueryTimeSpan (Get)List / Get
proxy.admin.configChangeLogLevelUpdate
proxy.admin.connectionPrintThreadStackTrace, VerifyMessageUpdate(高权限)
proxy.admin.routeGetTopicRouteGet
proxy.admin.opsGetProxyRuntimeStats, DescribeTopicStatus, QueryMessage (Get); ResetGroupOffset (Update); DeleteSubscription (Delete); AdminSendMessage (Pub)Get / Update / Delete / Pub

建模说明:ACL 2.0 的资源类型是 cluster/namespace/topic/group。admin 资源建模为 CLUSTER 类型的字面量,使用保留名称(资源 key cluster:proxy.admin.<module>),从而在不与真实集群名冲突、不改动鉴权核心的前提下实现精确的最小权限匹配。

模式:

  • 集群鉴权关闭、grpcAdminServerAuthEnable=false → 开放接口(与数据面语义相同);
  • 集群鉴权开启 → 针对每方法 proxy.admin.* 资源的标准 authenticate + authorize 流程;
  • grpcAdminServerAuthEnable=true → fail-closed。admin 接口必须既能认证调用方又能强制每方法 ACL,因此该模式要求集群级的认证和授权开关都开启。任一关闭时所有请求都被拒绝(认证关 → UNAUTHENTICATED; 授权关 → FAILED_PRECONDITION),而不是在没有真正权限校验的情况下放行。这是刻意为之:ACL 2.0 评估器本身受集群授权开关门控,若在授权关闭时服务请求,会静默放行每个调用,包括破坏性的那些。

审计:每个被服务的 RPC 都会向鉴权审计 logger 写入 [PROXY-ADMIN-AUDIT] subject/method/resource/action/sourceIp(与 ACL 2.0 引擎自身的审计日志一起,满足 Console 用户 + AK + 资源 + 操作的审计四元组要求)。

D3 — 多 proxy 语义:集群可见,协议不含拓扑

一个 gRPC 客户端同一时刻只连到一个 proxy,但 dashboard 仍需从任意单个 admin 端点看到整个集群。两个机制在不给协议加 scope/peering 字段的前提下做到这点:

  • 连接 / 订阅数据是集群可见的。 列表类 RPC(ListConsumerConnection、ListSubscription、DescribeSubscription)把 broker 侧的 ConsumerConnection 视图(覆盖 remoting 客户端,以及被 HeartbeatSyncer 在 proxy 间同步的 gRPC 客户端)与本 proxy 自己的 ConsumerManager(覆盖本地接入的 gRPC v2 客户端)合并。结果按 group + topic 去重,因此应答不随在线消费者数量膨胀,也不取决于查询的是哪个 proxy。
  • 客户端定向 RPC 转发到持有该客户端的 proxy。 PrintThreadStackTrace、VerifyMessage、GetConsumerRunningInfo 需要只有持有方 proxy 才有的实时 telemetry 流。ProxyAdminForwarder 识别出属于 peer 的客户端(其 ConsumerManager 条目是 RemoteChannel,getRemoteProxyIp() 为 peer 的 localServeAddr),把整个 RPC——连同原始 metadata——转发到 peer 的 admin 端口,并用 x-mq-admin-forwarded 头防止转发成环。peer 的 admin 端口按集群统一假设(心跳同步载荷不携带它); 异构 admin 端口的集群需要先把它加入心跳记录。

GetProxyRuntimeStats 刻意报告本进程的状态(自身的连接/生产者/消费者计数),因此想要按节点统计的 dashboard 直接查询每个 proxy。

D4 — 每个 RPC 的数据源

数据源RPC机制
集群可见连接ListConsumerConnection, ListSubscription, DescribeSubscriptionbroker 侧 ConsumerConnection(remoting + proxy 间同步的客户端)与本 proxy 的 ConsumerManager(本地 gRPC v2 客户端)合并
客户端定向 telemetry relayPrintThreadStackTrace, VerifyMessage, GetConsumerRunningInforelay 到持有方 GrpcClientChannel 的 telemetry command; 客户端在 peer 上时转发到 peer proxy(D3)
Broker 网关(异步、多 broker fan-out)DescribeTopicStatus, DescribeGroupAccumulation, ResetGroupOffset, QueryMessage, QueryTimeSpan, GetTopicRoute, DeleteSubscriptionproxy 自己的异步 AdminService remoting 网关; 每个 broker hop 有独立 deadline,慢/无响应的 broker 不会挂起整个 RPC
Proxy producer 路径AdminSendMessageMessagingProcessor.sendMessage(定时 / FIFO 消息通过 system properties 生效)
Proxy 运行时ChangeLogLevelroot logger 上重定位的 logback API

所有 broker 侧调用都走异步 AdminService 网关并并发扇出到所有相关 broker,因此 gRPC 执行线程不会被阻塞。

D5 — 协议覆盖

连接/订阅列表是集群可见的,同时包含 remoting 客户端(通过 broker 侧 ConsumerConnection)和 gRPC v2 客户端(通过 proxy 的 ConsumerManager)。客户端定向 telemetry RPC(PrintThreadStackTrace、VerifyMessage、GetConsumerRunningInfo)作用于持有 proxy telemetry 流的 gRPC v2 客户端,并支持跨 proxy 转发; 它们的完整 running-info 载荷受 telemetry 协议能承载的内容限制(见 §8 诚实边界)。

5. 能力映射

需求交付为说明
ListClients(按 group/topic/clientId 前缀过滤,分页)ListConsumerConnection(group[, topic])clientId 前缀过滤和分页在上游契约里无法表达; 这是本次部分交付的已记录边界
DescribeClient(SDK 版本、订阅、心跳、鉴权、Pop 进度)DescribeSubscription(每客户端订阅)+ GetConsumerRunningInfo(订阅 + 运行信息)心跳/鉴权状态/Pop 进度字段在上游契约里不存在
ListClientsByGroup / ListClientsByTopicListConsumerConnection(group[, topic])完全覆盖
多 proxy 集群聚合集群可见连接数据 + 客户端定向 RPC 转发见 D3

M2+ 项(配置热更新、配额、连接控制、Pop/batch 诊断、路由观测流)不属于上游 Admin 契约,不在本次交付范围内。

6. 构建 proto 源码

apache/rocketmq-apis 仓库(main 分支,包含 apache/rocketmq/v2/admin.proto)作为 git submodule 引入(rocketmq-apis/,见 .gitmodules); CI workflow 用 submodules: true 检出。

  • Maven:rocketmq-proto 模块在构建时通过 protobuf-maven-plugin 为 apache/rocketmq/v2/{definition,service,admin}.proto 生成 Java + gRPC stub; 它的版本跟随 reactor(${revision}),因此构建本仓库不需要已发布的 proto 制品。
  • Bazel://rocketmq-proto:rocketmq-proto 通过对 submodule 的 genrule(作为 @rocketmq_apis 外部仓库呈现)构建相同的类,使用 pin 的 protoc / protoc-gen-grpc-java 二进制和 well-known-type proto。
  • 其他消费者(dashboard、SDK)继续使用 rocketmq-apis 发布的制品; 只有本仓库从源码构建 proto。

7. 配置参考

Key默认值含义
grpcAdminServerEnablefalsekill switch; admin 接口是 opt-in 的。设为 true 启动 admin gRPC server; false(默认)= 不启动
grpcAdminServerPort8088专用 admin gRPC 端口(<=0 禁用)
grpcAdminServerAuthEnablefalsefail-closed 模式; 要求集群认证和授权都已开启(见 D2)
grpcAdminServerRequestTimeoutMillis3000admin RPC 扇出的每个 broker hop 的 deadline

8. 诚实边界

admin 接口绝不填写自己无法如实提供的字段; 下列情况以明确 status 回应或留空,而不是伪造:

  • DeleteSubscription 在开源里事实上是 no-op。 开源代码没有任何地方写入 SubscriptionGroupConfig.subscriptionDataSet(broker 只读它),因此没有持久化的 per-topic 订阅可删。该 RPC 返回 NOT_FOUND 并说明原因,消费组及其位点保持不动。需要该 RPC 的发行版用外部订阅存储填充同一字段。
  • QueryMessage 支持按 message_id(unique-key 索引)和 message_key 查询。subscription / lite_topic / 纯时间范围扫描三种变体需要开源 broker 不提供的分页游标,返回 BAD_REQUEST。
  • GetProxyRuntimeStats 的 in_tps / out_tps 留空:开源 proxy 没有进程级吞吐计数器,填 0 会与空闲 proxy 无法区分。
  • GetConsumerRunningInfo 对 gRPC v2 客户端只返回 subscriptions:v2 telemetry 协议没有承载 properties、message_queue_table、consume_status_table 的回包。该 RPC 返回 OK 并附说明性 status message,而不是空壳。
  • DescribeTopicStatus 的 create_timestamp / tags 留空,因为 broker 不记录 topic 创建时间。

9. 里程碑

  • M1(本次交付):基于上游 Admin service 的在线客户端查询 — ListConsumerConnection / DescribeSubscription / GetConsumerRunningInfo / DescribeGroupAccumulation,以集群可见方式并支持跨 proxy 转发提供,外加完整的 16-RPC Admin 接口用于 dashboard 集成。
  • 未来(需要上游 proto 演进):客户端列表的 clientId 前缀过滤 / 分页、心跳和鉴权状态字段,以及 M2+ 接口(配置/配额/连接/路由观测)。

10. 验收标准映射

标准状态
设计文档 + 稳定向后兼容的 proto 契约本文档 + 上游 admin.proto(rocketmq-apis main)
客户端查询 RPC 合入服务端仓库ProxyAdminGrpcService 在 proxy 上实现全部 16 个 Admin RPC
独立 ACL 控制、只读/高危分离、最小权限文档D2 资源/动作 + 下方 §11(最小权限配置指南)
与 dashboard 的 E2E契约 = 上游 Admin service,为 dashboard 集成冻结(跨仓库)

11. 最小权限配置指南

admin 接口对每个 RPC 按专用的 proxy.admin.* ACL 2.0 资源鉴权(见上方 §4 决策 D2)。本节给出每个运维角色的最小权限策略。

11.1 资源与动作模型

资源(ACL 2.0 key; 建模为带保留名称的 cluster 类型字面量):

资源 key保护
cluster:proxy.admin.client在线客户端查询与客户端诊断
cluster:proxy.admin.configproxy 运行时日志级别变更
cluster:proxy.admin.connection客户端定向 telemetry 命令:线程栈、verify message(高危)
cluster:proxy.admin.routetopic 路由视图
cluster:proxy.admin.opsbroker 侧运维:stats/topic status/message query(读)和 reset offset / delete subscription / admin send(高危)

动作分类:

  • 只读:Get, List
  • 高权限(变更 / 破坏性):Update, Delete, Pub

服务端把每个 RPC 映射到恰好一个(资源, 动作)对; 授予只读动作绝不会授权高权限 RPC。

11.2 角色模板

所有命令针对集群的任意 broker/namesrv 运行(ACL 2.0 存储)。先创建用户:

sh mqadmin createUser -n <namesrv-addr> -u <username> -p <password>

角色 A — 只读观察者(dashboard 服务账号)

在线客户端、订阅、堆积、诊断、配置/路由视图。

sh mqadmin updateAcl -n <namesrv-addr> \
  -s user:rip2-ro \
  -r cluster:proxy.admin.client,cluster:proxy.admin.config,cluster:proxy.admin.route,cluster:proxy.admin.ops \
  -a Get,List \
  -d Allow

说明:proxy.admin.ops 上的 Get,List 覆盖只读的 broker 侧 RPC; 变更类 ops RPC 需要 Update/Delete/Pub,仍被拒绝。

角色 B — on-call 运维(观察者 + 客户端诊断命令)

角色 A 加上向已连接客户端下发 telemetry 命令(线程栈 / verify message)的能力。

sh mqadmin updateAcl -n <namesrv-addr> \
  -s user:rip2-oncall \
  -r cluster:proxy.admin.connection \
  -a Update \
  -d Allow
# 外加上面的角色 A 授权

角色 C — Admin(完全控制,break-glass)

offset 重置、订阅删除、admin send、客户端诊断命令。

sh mqadmin updateAcl -n <namesrv-addr> \
  -s user:rip2-admin \
  -r cluster:proxy.admin.client,cluster:proxy.admin.config,cluster:proxy.admin.connection,cluster:proxy.admin.route,cluster:proxy.admin.ops \
  -a Get,List,Update,Delete,Pub \
  -d Allow

(把角色 C 账号控制在最小范围; 每次使用都会以 [PROXY-ADMIN-AUDIT] 前缀记录在鉴权审计日志里。)

环境限制(推荐)

通过 -i sourceIp 选项把 admin 访问限制到运维网络:

sh mqadmin updateAcl -n <namesrv-addr> \
  -s user:rip2-ro \
  -r cluster:proxy.admin.client \
  -a Get,List \
  -d Allow \
  -i 10.0.0.0/8

11.3 Fail-Closed 模式

默认情况下 admin server 遵循集群级认证/授权开关(与数据面行为相同)。fail-closed 模式让 admin 接口拒绝服务任何它无法完全授权的请求:

# proxy.json / -D grpcAdminServerAuthEnable=true
grpcAdminServerAuthEnable: true

grpcAdminServerAuthEnable=true 时,admin 接口要求集群认证和授权开关都开启。认证关闭时请求以 UNAUTHENTICATED 拒绝; 授权关闭时以 FAILED_PRECONDITION 拒绝。这保证 proxy.admin.* ACL 被真正强制执行——当 admin 端口无法做网络隔离时使用。

11.4 启用 / 禁用接口

接口默认关闭(grpcAdminServerEnable=false)。要启用:

grpcAdminServerEnable: true    # 在 grpcAdminServerPort 上启动 admin gRPC server

要保持禁用(默认),保留 grpcAdminServerEnable: false,或把 grpcAdminServerPort 设为 0 / 负数,此时 admin gRPC server 完全不启动。

11.5 审计

每个被服务的 admin RPC 都会把 subject(Console 登录用户 / AK)、method、resource、action 和 source IP 记录到鉴权审计 logger; 被拒绝的请求由 ACL 2.0 引擎自身记录。这满足审计四元组要求(Console 用户 + AK + 资源 + 操作)。