Reddit 如何将 PB 级 Kafka 集群从 EC2 迁移到 Kubernetes
Reddit 工程团队完成了一项极具挑战性的基础设施迁移:将整个 Apache Kafka 集群从 Amazon EC2 虚拟机迁移到 Kubernetes 之上。该集群拥有超过 500 个 Broker,承载着超过 1 PB 的实时数据。整个迁移过程实现零停机,且无需任何客户端应用修改其连接方式。
Kafka 在 Reddit 的角色
Apache Kafka 是一款开源消息流平台。被称为 Producer 的应用程序将消息写入 Kafka 分区,另一批被称为 Consumer 的应用程序则从中读取消息。Kafka 位于两者之间,无论生产者和消费者是否 24/7 不间断运行,都能可靠地存储消息。单个 Kafka 服务器称为 Broker,多个协同工作的 Broker 则组成集群。
在 Reddit,Apache Kafka 并非边缘工具。它支撑着数百个关键业务服务的运行,每秒处理数千万条消息。一旦 Kafka 故障,Reddit 的大部分功能都将瘫痪。

为什么 Reddit 选择离开 EC2
迁移前,Reddit 通过 Terraform、Puppet 和自定义脚本在 Amazon EC2 实例上管理 Kafka Broker。运维人员直接通过笔记本电脑运行命令来完成升级、配置变更和机器替换。这套方式在一定规模内运行良好,但随着集群规模增长,操作越来越慢、容易出错,成本也水涨船高。Reddit 需要一种更可扩展、更可靠的方式来运营 Kafka。
Kubernetes 配合名为 Strimzi 的工具提供了这条路径。Kubernetes 是一个用于运行和管理容器化应用的开源平台,开发者只需描述"应该运行什么",平台便会自动处理部署、扩缩容和故障恢复。Strimzi 则是云原生计算基金会(CNCF)旗下的项目,专门用于在 Kubernetes 上运行 Kafka——它提供了一种声明式管理 Kafka 集群的方式,开发者只需在配置文件中描述需求,Strimzi 便会自动处理部署、升级和维护工作,从而减少人工干预,提升运维可预测性。
四大约束如何塑造迁移方案
Reddit 并未贸然开始迁移 Broker。在编写一行迁移代码之前,团队首先明确了四项硬性约束,这些约束直接排除了一整类方案:

- Kafka 必须保持在线。不存在可接受的服务维护窗口,停机、数据丢失或强制客户端修改配置均不可行。这排除了定时切换、双写策略和基于回放的迁移方案。
- Kafka 元数据无法从头重建。Apache Kafka 维护着名为 metadata 的详细内部状态,包含:哪些 Broker 存在、各 Broker 持有哪些数据、副本存储位置等。ZooKeeper 负责管理这些元数据。在系统可用状态下,没有官方方式能够在新集群上重建这些元数据。因此新 Broker 必须以加入现有集群的方式运行,而非替换。
- 客户端连接与特定 Broker 强耦合。Reddit 内部各类应用长期配置为直接连接特定 Broker 主机名(通常是集群中的前几个 Broker),而非使用单一的负载均衡端点。关闭这些 Broker 会立即破坏数百个服务,而 Reddit 无法控制客户端发现和连接 Kafka 所经过的那一层。
- 每一步操作都必须可逆。迁移过程中任何单一操作都不能使系统进入无法恢复的状态。这意味着 Reddit 必须接受 EC2 Broker 与 Kubernetes Broker 长期并行运行的过渡期,风险较高的变更必须等到其他一切稳定后才能进行。
第一阶段:掌控命名层
迁移的第一阶段完全不触及 Kafka 本身。
Reddit 引入了一层 DNS Facade(DNS 外观),即一套 DNS 记录,作为客户端应用与实际 Kafka Broker 之间的中间层。DNS 是将人类可读名称转换为服务器地址的系统。通过创建新的、由基础设施控制的 DNS 名称(最初仍指向同一批 EC2 Broker),Reddit 在客户端看来一切如常,未做任何改变。
随后,Reddit 通过自动化工具(生成批量 Pull Request 来更新配置文件)在超过 250 个服务中推广这些新连接字符串。一旦所有客户端都通过这层 DNS 通信,Reddit 就可以随意更改这些名称指向的位置——从 EC2 切换到 Kubernetes——而无需修改任何客户端代码。

第二阶段:为新 Broker 腾出空间
每个 Kafka Broker 都有一个唯一的数字 ID。Strimzi 默认从 0 开始分配 Broker ID,但 Reddit 现有的 EC2 Broker 已经占用了这些低编号。
混合集群:两个环境并存
这是整个迁移过程中技术复杂度最高的阶段。

Reddit 需要让运行在 Kubernetes 上的 Strimzi Broker 与现有 EC2 Broker 加入同一集群,并直接互通。Strimzi 原生不支持这种跨环境集群模式,因此 Reddit 对 Strimzi Operator 做了定向 Fork,只改动了一小部分代码:
- 监听器配置:将 Broker 间监听器设为明文模式,EC2 和 Kubernetes 两端均可访问,确保不同环境下的 Broker 能正常通信。
- ZooKeeper 连接:指向 Reddit 现有的 EC2 托管 ZooKeeper,使新旧 Broker 共享同一元数据存储,属于同一逻辑集群。
- Cruise Control Topic:在两套 Broker 集合间保持一致。Cruise Control 是 Kafka 的数据再平衡工具,能以可控、可量化方式自动化完成数据迁移,是实际迁移过程中的核心工具。
- 风险控制:在生产环境运行 Forked Operator 存在风险,因此 Reddit 刻意缩小改动范围,并计划在迁移完成后立即切换回标准 Strimzi Operator。
分阶段迁移数据和流量
两套 Broker 在同一集群内运行后,Reddit 通过 Cruise Control 将分区 Leadership 和副本数据从 EC2 Broker 逐步迁移至 Kubernetes Broker。
分区 Leadership 决定哪台 Broker 负责处理某段数据的读写请求。Kafka 为每个分区在多台 Broker 上存储副本以保证冗余,这称为副本复制因子(Replication Factor)。数据迁移意味着将 Leadership 和副本逐一分配到新 Broker 上,每次仅移动一个分区。

整个迁移持续约一周。Reddit 全程监控各项指标:EC2 上的 Leadership 逐步下降,Strimzi 上的 Leadership 同步上升,网络流量也跟随这一趋势变化。整个过程可随时暂停或回滚,一旦出现异常可立即干预。
控制平面迁移:ZooKeeper 到 KRaft
在数据平面完全稳定在 Kubernetes 之前,Reddit 有意暂不改动控制平面——ZooKeeper 一直管理着 Kafka 元数据,直到数据迁移全部完成。这种关注点分离策略有效降低了连锁故障风险。
所有 EC2 Broker 下线、数据和流量全部运行在 Kubernetes 上之后,Reddit 才执行从 ZooKeeper 到 KRaft 的迁移。KRaft 是 Kafka 内置的元数据管理系统,可彻底替代 ZooKeeper。由于 Strimzi 和 Kafka 官方都提供了完整的迁移文档,且此时系统其余部分已稳定运行,这一阶段相对顺利。

收尾:回归标准 Strimzi
数据平面和控制平面全部在 Kubernetes 上稳定运行后,Reddit 移除了 Forked Strimzi Operator 引入的所有配置覆盖项,将集群控制权交接给标准版 Strimzi Operator,随后下线了相关 EC2 基础设施。
核心经验总结
Reddit 的这次迁移证明,大规模基础设施变更不必是高风险事件。将工作拆解为小步、可逆、可控的阶段,并尊重系统本身的约束,就能在不停机的状态下完成 PB 级别的平台迁移。
几个关键教训值得借鉴:
- 在客户端与基础设施之间引入可控抽象层(DNS、代理或 API 网关),是迁移过程中杠杆效应最高的改动之一。它能解耦两端,让基础设施变更无需推动每个团队同步更新代码。
- 元数据和逻辑状态往往比承载它们的物理机存活得更久。规划大规模迁移时,应将逻辑状态视为保护对象,基础设施只是围绕它进行替换。
- 每一步都设计成可撤销的,不只是安全措施。它改变了你能以多快的速度和信心推进工作——因为你知道出了错随时可以退回来。
- 过程看起来不够整洁、但生产始终不宕机的迁移,远好过设计看似完美、但没有回退路径、一旦出错就无法挽回的方案。


评论