KRaft 运维从静态到动态 Controller
一、KRaft 基础概念与角色配置
1)进程角色(process.roles
)
在 KRaft 模式下,每台 Kafka 服务器可配置为:
process.roles=broker
:仅作为 brokerprocess.roles=controller
:仅作为 controllerprocess.roles=broker,controller
:combined(同时充当 broker + controller)
生产建议:
combined 模式仅适合开发/小型场景;关键生产环境不建议使用,因为无法分别滚动/扩缩容 controller 与 broker,隔离性差、变更风险高。
2)Controller 集群规模与多数派
-
常见规模:3 或 5 台 controller
-
容错规则:需 多数派 存活
- 3 台可容忍 1 台故障
- 5 台可容忍 2 台故障
3)集群发现(controller.quorum.bootstrap.servers
)
所有 broker 与 controller 都必须配置,用于发现活动 controller,枚举尽可能多的 controller 节点,类似于客户端的 bootstrap.servers
:
controller.quorum.bootstrap.servers=host1:9093,host2:9093,host3:9093
controller.listener.names=CONTROLLER
示例(纯 controller 进程):
process.roles=controller
node.id=1
listeners=CONTROLLER://controller1.example.com:9093
controller.quorum.bootstrap.servers=controller1.example.com:9093,controller2.example.com:9093,controller3.example.com:9093
controller.listener.names=CONTROLLER
二、从静态到动态:升级你的 KRaft 集群
1)检查 KRaft 特性版本
动态 controller 集群在 **kraft.version=1
(4.1 起)**引入:
bin/kafka-features.sh --bootstrap-controller localhost:9093 describe
# 输出中若看到:
# Feature: kraft.version ... FinalizedVersionLevel: 0 -> 需升级到至少 1
2)升级方式(选择其一)
- 升级所有特性到指定发行版(如 4.1)
bin/kafka-features.sh --bootstrap-server localhost:9092 upgrade --release-version 4.1
- 仅升级 KRaft 特性版本
bin/kafka-features.sh --bootstrap-server localhost:9092 upgrade --feature kraft.version=1
3)配置切换:弃用 controller.quorum.voters
KRaft 版本升级到 1 后,移除 controller.quorum.voters
,改用 controller.quorum.bootstrap.servers
,并在所有节点(controllers 与 brokers)添加:
process.roles=...
node.id=...
controller.quorum.bootstrap.servers=controller1.example.com:9093,controller2.example.com:9093,controller3.example.com:9093
controller.listener.names=CONTROLLER
小贴士:如果你不确定当前是静态还是动态仲裁,再次运行
kafka-features.sh ... describe
:
kraft.version=0/缺失
→ 静态kraft.version>=1
→ 动态
三、节点预配置与格式化:让集群“身份确定”
1)统一的 Cluster ID
为新集群生成 cluster ID,并在所有待加入该集群的服务器上格式化时使用同一 ID:
bin/kafka-storage.sh random-uuid
bin/kafka-storage.sh format --cluster-id <CLUSTER_ID> --config config/xxx.properties ...
重要变化:KRaft 不再“遇空目录自动格式化”。这是为了避免“误清空/误覆盖元数据目录”引起的错误 leader 选举与已提交数据丢失。
2)引导方式一:先单投票人,再动态扩容
推荐路径:先以 1 个投票人(one voter)引导,随后动态添加其余 controllers:
bin/kafka-storage.sh format \--cluster-id <CLUSTER_ID> \--standalone \--config config/controller.properties
该命令会在元数据目录创建初始快照(含 KRaftVersionRecord
/ VotersRecord
),让该节点成为唯一投票者,便于快速起步。
3)引导方式二:一次性多个投票人
也可一次性为多个 controllers 生成格式化快照(确保 所有节点使用相同 <CLUSTER_ID>
,且 --initial-controllers
参数在所有 controller 上一致):
CLUSTER_ID="$(bin/kafka-storage.sh random-uuid)"
C0="$(bin/kafka-storage.sh random-uuid)"
C1="$(bin/kafka-storage.sh random-uuid)"
C2="$(bin/kafka-storage.sh random-uuid)"bin/kafka-storage.sh format --cluster-id ${CLUSTER_ID} \--initial-controllers "0@controller-0:1234:${C0},1@controller-1:1234:${C1},2@controller-2:1234:${C2}" \--config config/controller.properties
说明:
0@controller-0:1234:3Db5QL...
中
0
为副本 ID,controller-0
为主机名,1234
为端口,最后的字符串为副本目录 ID。
4)格式化新增 broker / controller
向已存在的集群添加新节点时,避免重建投票者集合,使用:
bin/kafka-storage.sh format \--cluster-id <CLUSTER_ID> \--config config/server.properties \--no-initial-controllers
四、Controller 成员变更:动态增删的正确姿势
1)静态 vs 动态仲裁
- 静态仲裁(旧):
controller.quorum.voters
显式列出所有 controller 的 ID/主机/端口 - 动态仲裁(新,KIP-853):
controller.quorum.bootstrap.servers
作为发现入口,可不包含所有节点,但建议尽可能多地列出
新建/重建集群时,若在 Kafka 3.9+ 且未设置
controller.quorum.voters
,格式化即为动态仲裁。
若你希望在无法动态仲裁时格式化失败(以避免误建静态),可在格式化 controllers 时加:
bin/kafka-storage.sh format -t <CLUSTER_ID> --feature kraft.version=1 -c controller.properties
# 注意:格式化 broker 时不要带该参数
2)添加新 Controller(动态仲裁)
步骤建议:
-
预置并启动新 controller(先格式化、再启动)
-
观察其与 leader 的追赶进度:
bin/kafka-metadata-quorum.sh describe --replication
-
当追平(与活动 controller 同步)后,执行 加入集群 命令(任选其一):
-
通过 broker 端口:
bin/kafka-metadata-quorum.sh \--command-config config/controller.properties \--bootstrap-server localhost:9092 \add-controller
-
通过 controller 端口:
bin/kafka-metadata-quorum.sh \--command-config config/controller.properties \--bootstrap-controller localhost:9093 \add-controller
-
3)移除 Controller(动态仲裁)
在 KIP-996(Pre-vote) 发布前,建议先停机待移除的 controller,再执行移除命令。
-
通过 broker 端口:
bin/kafka-metadata-quorum.sh \--bootstrap-server localhost:9092 \remove-controller \--controller-id <id> \--controller-directory-id <directory-id>
-
通过 controller 端口:
bin/kafka-metadata-quorum.sh \--bootstrap-controller localhost:9092 \remove-controller \--controller-id <id> \--controller-directory-id <directory-id>
五、现场排障三板斧
1)元数据仲裁状态
bin/kafka-metadata-quorum.sh --bootstrap-server localhost:9092 describe --status
# 关注:LeaderId、LeaderEpoch、HighWatermark、CurrentVoters、MaxFollowerLag/TimeMs 等
2)日志/快照解码(定位奇怪的元数据记录)
-
解码第一个日志段:
bin/kafka-dump-log.sh \--cluster-metadata-decoder \--files metadata_log_dir/__cluster_metadata-0/00000000000000000000.log
-
解码指定快照:
bin/kafka-dump-log.sh \--cluster-metadata-decoder \--files metadata_log_dir/__cluster_metadata-0/00000000000000000100-0000000001.checkpoint
3)交互式查看元数据(Metadata Shell)
bin/kafka-metadata-shell.sh \--snapshot metadata_log_dir/__cluster_metadata-0/00000000000000007228-0000000001.checkpoint>> ls /topics
>> cat /topics/foo/0/data
注意:起始快照
00000000000000000000-0000000000.checkpoint
不包含业务元数据,排查请使用有效快照文件。
六、部署与容量规划建议
- 角色隔离:生产建议将
process.roles
设置为 broker 或 controller 之一,避免 combined。 - 奇数台 controller:至少 3 台,容错遵循 2N+1 原则(容忍 N 台故障)。
- 元数据开销预算:controller 需要常驻内存并持久化全部元数据。一般给 metadata 日志目录预留 ≈5GB 内存与 ≈5GB 磁盘即可支撑典型规模(视主题/分区规模增长适度上调)。
- 引导首选 one-voter:先单投票人起步,平稳后再动态扩容 controllers,降低最初一致性复杂度。
- 发现配置尽量“富”:
controller.quorum.bootstrap.servers
建议列出多数 controller,提升恢复/变更时的可达性。
七、从 ZooKeeper 迁移到 KRaft(路线提示)
迁移需要经过桥接版本(bridge release);最新桥接版本是 Kafka 3.9。请参考 Kafka 3.9 文档中的“ZooKeeper → KRaft 迁移步骤”完成平滑切换,再按本文方法升级至 kraft.version=1 的动态 controller 集群。
八、实践清单(Checklist)
建新集群
bin/kafka-storage.sh random-uuid
生成 CLUSTER_ID- controller-0:
--standalone
先引导 one-voter - 其他 controllers:
--no-initial-controllers
格式化并启动 kafka-metadata-quorum.sh describe --replication
观察追平add-controller
逐台加入- 所有节点统一使用
controller.quorum.bootstrap.servers
;不再使用controller.quorum.voters
给老集群“动”起来
kafka-features.sh ... describe
确认kraft.version
- 若
FinalizedVersionLevel=0
→ 升级到1
- 配置切换为
controller.quorum.bootstrap.servers
- 用
add-controller
/remove-controller
做在线变更
排障
kafka-metadata-quorum.sh --status
看领导与落后kafka-dump-log.sh
解码日志/快照kafka-metadata-shell.sh
直接读出元数据结构
九、示例配置片段速查
controller.properties(纯 controller)
process.roles=controller
node.id=3001
listeners=CONTROLLER://controller1.example.com:9093
controller.listener.names=CONTROLLER
controller.quorum.bootstrap.servers=controller1.example.com:9093,controller2.example.com:9093,controller3.example.com:9093
metadata.log.dir=/data/kafka/meta
server.properties(纯 broker)
process.roles=broker
node.id=1
listeners=PLAINTEXT://0.0.0.0:9092
controller.quorum.bootstrap.servers=controller1.example.com:9093,controller2.example.com:9093,controller3.example.com:9093
log.dirs=/data/kafka/logs
十、常见坑与规避
- 误用 combined:生产环境请避免,变更与事故处置都会更难。
- 忘记统一 Cluster ID:不同 ID 会导致“各自为政”,无法组成同一集群。
- 配置残留
controller.quorum.voters
:升级到kraft.version=1
后应移除,统一用controller.quorum.bootstrap.servers
。 - 用起始快照排错:
000...000.checkpoint
不含业务元数据,定位不到主题/分区信息。 - 未等“追平”就加入投票:先用
--replication
看清追赶状态,避免把落后副本拉入投票集引发选举抖动。
结语
将 KRaft 集群升级到 动态 controller 后,运维团队可以像管理普通 Kafka 客户端那样,通过 bootstrap 风格的发现 与 在线成员变更 管理控制面,高风险操作(如扩缩容、迁移)都会变得更平滑。
按照本文的“升级 → 引导 → 变更 → 排障 → 部署”路径执行,你可以在保证一致性与可用性的前提下,稳妥完成生产演进。祝你零宕机、零惊吓。