万级 Redis 集群的 K8s 驯化记:快手云原生 Operator 架构揭秘
作为头部短视频平台,快手高度依赖 Redis 为用户提供低延迟响应。在私有云基础设施上运维时,如何以最小人工干预实现大规模 Redis 集群的自动化管理面临重大挑战。一个极具前景的解决方案是利用 Operator 在 Kubernetes 上运行 Redis。
尽管容器化无状态服务(如应用和 Nginx)已成为行业标准,但在 Kubernetes 上运行数据库、Redis 等有状态服务仍存争议。基于快手将 Redis 从物理机迁移至云原生解决方案的实践经验,本文将探讨通过 KubeBlocks Operator 在 Kubernetes 上管理有状态服务的解决方案与核心考量。
背景
随着技术演进,快手基础设施正全面转向云原生技术栈。基础设施团队为应用和 PaaS 系统提供容器与 Kubernetes 支持。尽管无状态服务已基本完成 Kubernetes 迁移,但云原生有状态服务的转型之路仍面临多重挑战。
以 Redis 为例,作为快手应用最广泛的有状态服务之一,其显著特征在于规模庞大。在此量级下,即使微小的成本优化也能为公司带来可观收益。在长期规划中,快手认识到在 Kubernetes 上运行 Redis 的巨大潜力,特别是在通过提升资源利用率实现成本优化方面。本文将分享快手迁移 Redis 至 Kubernetes 的经验,涵盖解决方案、挑战应对及相应策略。
快手如何在 Kubernetes 上运行 Redis?
Redis 部署架构
为满足灵活分片管理及支持热点迁移与隔离的需求,快手采用水平分片的主从高可用 Redis 架构,由 Server、Sentinel 和 Proxy 三大组件构成。
分析:快手对 Redis Operator 的核心诉求
第一层:Redis Pod 管理需分层实施
Redis Pod 管理需分两个层级:首层管理多个分片,次层管理单个分片内的多个副本。系统必须支持分片数量与单分片副本数的动态扩缩,以适应不同工作负载和使用场景。
这意味着在 Operator 的实现中,需使用工作负载(如 StatefulSet)管理每个分片内的多个副本,并在此基础上构建额外层级(某些 CRD 对象)以实现对整个 Redis 集群多分片的管理。
第二层:确保故障处理与日常运维中的数据一致性
在分片或副本的生命周期变更过程中(如分片扩缩容需数据重平衡,副本扩缩容需数据备份恢复),必须保障数据一致性与可靠性。
因此,Operator 需支持分片层和副本层的生命周期钩子,使自定义数据管理操作能在不同生命周期阶段执行。
第三层:支持拓扑感知的服务发现与金丝雀发布
分片内多个 Redis Pod 的拓扑关系可能因高可用故障转移、升级或扩缩容等事件动态变化。服务发现与金丝雀发布等特性都依赖实时拓扑信息。
为此,Operator 需通过引入角色检测与角色标注能力实现动态拓扑感知,从而支持基于实时拓扑的服务发现与金丝雀发布。
这些需求已超越现有开源 Redis Operator 的能力范畴,通常需要开发高度复杂的 Kubernetes Operator 来实现。然而,从零开始构建具备完善 API 设计的稳定 Operator,对大多数平台团队而言极具挑战,因为这需要兼具 Kubernetes 与数据库领域专业知识,以及大量实际场景验证。
KubeBlocks 解决方案进入视野
经过多方案评估,开源 Kubernetes 数据库 Operator 项目 KubeBlocks 引起了我们的关注。其独特之处在于强大的可扩展性 —— 通过 Addon 机制,允许用户使用其 API 描述数据库的 Day-1 初始化与 Day-2 运维特征及行为,从而实现 Kubernetes 上的全生命周期管理。如官网所述,KubeBlocks 的愿景是在 Kubernetes 上运行任意数据库。这种灵活性使我们能够定制 KubeBlocks Redis Addon,以适配内部 Redis 集群部署架构。
KubeBlocks 的 API 设计也高度契合我们对 Redis 集群管理的需求:
1. InstanceSet:比 StatefulSet 更强大的工作负载
InstanceSet 是 KubeBlocks 内部用于替代 StatefulSet 的工作负载,专为管理数据库 Pod 设计。与 StatefulSet 类似,InstanceSet 支持管理多个 Pod(称为 Instance),其核心区别在于能追踪每个数据库 Pod 的角色(如主节点、从节点)。对于不同数据库(KubeBlocks 支持多类型),可自定义 Pod 角色定义、角色检测方式,以及金丝雀升级时基于角色的升级顺序。InstanceSet 控制器在运行时动态检测角色变化,并将角色信息以标签形式更新至 Pod 元数据,实现基于角色的 Service 选择器。
StatefulSet 为每个实例分配全局有序递增标识符,该机制提供稳定的网络与存储身份,集群内拓扑关系依赖这些标识符。然而,当运行时拓扑动态变化时,StatefulSet 提供的固定标识符可能无法满足需求。例如,StatefulSet 标识符不允许存在空缺,也不允许删除中间标识符。
快手平台团队向 KubeBlocks 社区贡献了多个 PR,包括支持同一 InstanceSet 内 Pod 差异化配置、允许下线指定序号的 Pod(无需先下线更高序号的 Pod)、控制升级并发度等增强功能。这些改进使 InstanceSet 更适应生产环境中大规模 Redis 集群的管理需求。
2. 分层式 CRD 与控制器设计:Component 与 Cluster 对象
KubeBlocks 采用 Component、Cluster 等多层 CRD 结构管理数据库集群的复杂拓扑,这与快手 Redis 集群部署架构完美契合:
Component:代表 Redis 集群中的一组 Pod。例如 Proxy Pod 构成一个 Component,Sentinel Pod 构成另一个,Redis-Server Pod 则按分片组织为一个或多个 Component,每个对应一个 Shard。Component 数量随分片数动态变化。
⛱️ Shard:特殊类型的 Component,定义水平扩展数据库的分片行为。每个 Shard 共享相同配置。例如在快手 Redis 集群中,每个 Shard(Component)包含一个主 Pod 和一个副本 Pod。扩容时新增 Shard(Component),缩容时移除,实现分片级扩缩容与生命周期管理。
Cluster:代表整个 Redis 集群,整合 Proxy、Server、Sentinel 等 Component,同时管理它们的启动拓扑与关联关系。
这种层次化设计简化了扩缩容操作,强化了生命周期管理,为生产环境的复杂 Redis 部署架构提供必要灵活性。
通过与 KubeBlocks 社区的密切协作,我们以下列方式实现了 Redis 集群编排:
Redis 集群包含三个 Component:redis-server、redis-sentinel 和 redis-proxy。各 Component 内部使用 InstanceSet(而非 StatefulSet)管理 Pod。
通过 Kubernetes 联邦管理超大规模 Redis 集群
在快手,多个应用以多租户形式运行于单个超大规模 Redis 集群中。例如单个集群可能包含超 10,000 个 Pod,远超单个 Kubernetes 集群容量。为此,我们需将 Redis 集群部署至多个 Kubernetes 集群,同时需对 Redis 应用用户屏蔽多集群管理复杂性。
联邦 K8s 集群架构
幸运的是,快手 Kubernetes 基础设施团队提供了成熟的联邦服务,具备统一调度与统一视图能力:
统一调度:联邦作为中心化资源调度入口,支持跨成员集群资源调度 统一视图:联邦作为统一资源访问点,可无缝获取联邦与成员集群资源
问题转化为:如何将基于 KubeBlocks 的 Redis 集群管理方案融入快手内部联邦集群架构?以下是整体架构:
联邦 Kubernetes 集群作为管理多个成员集群的中心控制平面,负责 Redis 集群的跨集群编排、资源分发与生命周期管理,其职责包括:
跨集群实例分发与管理:根据资源需求将 Redis 组件(Proxy、Sentinel、Server)分发至成员集群 并发控制:协调跨集群操作,确保一致性并避免冲突 状态聚合:收集并聚合各成员集群的组件状态,提供统一视图
成员 K8s 集群是实际部署和管理 Redis Pod(实例)的独立 Kubernetes 集群,每个负责运行整体 Redis 集群的子集,其职责包括:
实例管理:通过 InstanceSet 对 Redis Pod(Proxy、Sentinel、Server)进行本地化管理
为此,我们将 KubeBlocks Operator 拆分为两部分并部署于不同 Kubernetes 集群:
InstanceSet Controller部署于成员集群,负责本地 Pod 管理 Cluster Controller 与 Component Controller部署于联邦集群,负责全局资源编排与协调
KubeBlocks 的分层 CRD 与控制器设计再次成为实现此部署的关键。若采用单体式 CRD 与控制器设计,将无法在联邦与成员集群间拆分部署。
Fed-InstanceSet Controller
由于可能存在多个成员集群,需将联邦集群的 InstanceSet 分割为多个 InstanceSet 并分配至各成员集群。同时,原 InstanceSet 管理的 Instances(Pod)需分发至成员集群的新 InstanceSet。
为此,快手开发了 Fed-InstanceSet Controller 管理联邦集群与成员集群的交互,其核心职责包括:
调度决策:根据预设策略确定各成员集群应部署的实例数量 InstanceSet 分割与分发:将联邦集群的 InstanceSet 拆分并分发至目标成员集群
为管理实例分割并确保成员集群中 Redis 实例的全局唯一性与顺序正确性,快手向 KubeBlocks 社区提交 PR,为 InstanceSet 添加 Ordinals 字段,实现精确的索引分配。Fed-InstanceSet Controller 利用该字段为各成员集群分配唯一索引范围,确保跨集群实例唯一性与顺序正确性。
探讨:有状态服务是否适合 Kubernetes?
在 Kubernetes 上运行有状态服务的收益与风险
我们认为在 Kubernetes 上运行有状态服务可带来显著收益:
资源利用率提升:通过合并多个小型资源池进行统一调度,实现应用与 Redis 或 Redis 与其他有状态服务的混合部署,优化资源使用,显著降低成本 运维效率提升:借助 Kubernetes 声明式 API 与 Operator 模式,以基础设施即代码(IaC)方式管理 Redis 服务,减少人工干预 维护成本降低:此前 Redis 运行于物理机,需专人管理硬件基础设施。通过统一至容器与 Kubernetes 平台,降低基础设施维护成本,提升整体管理效率
尽管收益显著,但需审慎评估潜在风险——尤其是数据库、Redis 等高重要性、高稳定性的有状态服务。挑战包括:
性能劣化风险:容器化进程相比物理机直跑引入额外抽象层(特别是 overlay 网络延迟),引发服务性能下降担忧 稳定性疑虑:在 Kubernetes 基础设施上构建数据库平台(DBaaS)可能影响数据库/Redis 的稳定性(可用性与可靠性) 运维复杂度增加:出现问题时,是否需要兼具数据库与 K8s 技术专长的专家才能有效排查?
降低 Kubernetes 运行 Redis 的风险
性能
相比传统主机部署,云原生架构中的容器化 Redis 引入额外抽象层。但行业基准测试与快手内部测试表明,性能差异普遍控制在 10% 以内,大多数场景可忽略不计。尽管该差异通常可接受,仍建议企业根据自身业务负载进行专项性能测试。
稳定性
将有状态服务迁移至 Kubernetes 虽通过自动化大幅提升运维效率,但也使执行过程更不透明 — 微小配置变更可能影响大量实例。为规避 Pod 驱逐、人为误操作或 Operator 缺陷等意外场景引发的稳定性风险,快手利用 Kubernetes API Server 的 Admission Webhook 机制拦截变更请求进行校验,可直接拒绝未授权操作。鉴于多可用区(AZ)的多集群部署,需确保跨集群变更控制。为此,快手开发了内部风险控制系统 kube-shield。
值得一提的是,快手还通过增强细粒度调度分布支持、引入基于资源利用率的负载均衡特性,进一步提升可用性与稳定性。
运维复杂度
从主机系统迁移至 Kubernetes 环境并确保持续维护,需要兼具 Redis 与 K8s 技术专长的深度知识。若仅依赖 Redis 团队或 K8s 团队单方面支持将极具挑战。合理的职责划分不仅能提升生产力,更能使各团队充分发挥领域专长。
例如在快手云原生 Redis 解决方案中:
Redis 团队:聚焦定义 Redis 集群对象,将运维经验封装为声明式配置 容器云团队:负责 Kubernetes 侧工作,包括 Operator 开发维护、调度管理与集群生命周期保障
总结
有状态服务的云原生转型是充满挑战的复杂旅程,需审慎权衡利弊。但对快手而言,其价值不言而喻。从 Redis 出发,快手与 KubeBlocks 社区深度协作,实现了高性价比的云原生解决方案。
未来快手将以此经验为基础,推动更多有状态服务(如数据库、中间件)的云原生转型,收获技术与成本的双重红利。
作者简介:刘雨兴,快手资深软件工程师。曾任职阿里云与快手云原生团队,专注云原生领域,具备云原生技术开源、商业化与规模化经验。CNCF/Dragonfly 项目与 CNCF/Sealer 项目维护者,现致力于推动快手有状态业务云原生转型。
原文链接:https://kubeblocks.io/blog/manage-large-scale-redis-on-k8s-with-kubeblocks