针对无状态服务,业界已拥有成熟解决方案,但对于有状态服务(如数据库、Redis)是否适合容器化与 K8s 托管,仍存在争议。本文将基于快手在 Redis 云原生化实践中的经验,探讨有关有状态服务的云原生化思考及应对方案。
一、背景
随着行业技术的不断演进,快手的基础设施顺应技术潮流逐步迈向云原生化。在各业务团队的支持下,容器云成为服务与基础设施的新界面,目前在快手无状态服务已基本全面实现 Kubernetes (K8s) 的云原生化。然而,有状态服务的云原生化之路却仍然充满了挑战:
以 Redis 为例,它是快手广泛使用的有状态服务之一, 超大规模 是其显著特征。 即使微小的优化在如此庞大的规模下也能为企业带来巨大的收益 。在面向未来的长期规划中,快手高度认可 Redis 云原生化带来的潜在价值,尤其是资源利用率提升带来的成本优化。本文基于快手 Redis 云原生化实践中的经验,深入剖析有状态服务的云原生化思考及应对策略。
二、有状态服务是否适合运行在 K8S 上?
有状态服务运行在 K8S 上的风险与收益
将有状态服务运行在 Kubernetes 上的收益显而易见:
尽管有状态服务运行在 Kubernetes 上的收益显著,但潜在风险也需认真评估,尤其是对数据库等有状态服务,其重要性和稳定性要求极高。这里的主要风险包括:
针对上述风险,下文将展开探讨。
有状态服务运行在 K8S 上的可能性
在探讨有状态服务时,我们首先需要明确“状态”的具体含义:
数据状态 :单个实例保存的、区分其他实例的数据。每个实例承担不同角色,存储独特的数据状态。因此,任何实例不可随意丢弃;同时,在实例生命周期管理过程中,也需额外对数据进行处理(如备份、恢复、数据重平衡等)。
拓扑状态 :不同角色实例之间的关系状态信息;且这种关系在实例运行过程中通常是动态变化的。
综上,有状态服务的云原生化相比无状态服务面临新的挑战:如何保证数据的可用性、管理数据生命周期,以及建立和维护拓扑关系并实现基于拓扑结构的服务发现和灰度发布等能力。K8S 社区目前的应对措施包括:
方案一:StatefulSet Workload:
方案二:自定义 Workload + Operator:
综上所述,有状态服务部署在 Kubernetes 上相比无状态业务来说需要更多风险考量,且实现的成本和难度相对较高。然而,这种投入往往是一次性的,而云原生化带来的收益则是持续性的,可以为企业未来发展提供坚实基础。因此,企业在决策时应充分权衡这些因素,以做出更具战略性的选择。
三、快手如何将 Redis 运行在 K8S 上?
快手的 Redis 采用的是经典的主从架构,包含三个组件(Server、Sentinel 和 Proxy)。
迁移单个 Redis 集群上 K8S
快手 Redis 云原生化思路
基于以上分析,Redis 的云原生化实现无法基于 Kubernetes 社区现有 Workload 实现,具体而言:
针对上述挑战,我们提出了如下解决思路:
KubeBlocks 解决方案
经过调研,KubeBlocks 项目成为我们重点关注的解决方案之一,其贴合我们的思路与需求。作为一个开源 K8S Operator,KubeBlocks 抽象了管理各种数据库的 API,支持在 Kubernetes 上运行和管理多种类型的数据库。其愿景是“在 Kubernetes 上运行任何数据库”。
经过与 KubeBlocks 社区深度合作,我们通过如下方式实现一个 Redis 集群的编排:
1. InstanceSet Workload
2. Component、Shard 、Cluster Workload:
迁移大规模 Redis 集群上 K8S
前面提到,超大规模是快手 Redis 的显著特征,其实例规模远超单个 K8S 集群的容量。因此,我们不得不基于多个 Kubernetes 集群来支持业务。与传统模式下所有主机平铺的方式不同,因为 K8S 单个集群的容量限制,我们必须将主机资源池切分到多个 K8S 集群中。如果将多个 K8S 集群的复杂度直接暴露给 Redis 业务方,上云成本势必会被大幅增加。
联邦集群架构
在快手,我们通过基于联邦集群能力提供相应的统一调度和统一视图能力,降低业务方的复杂度,使其更专注于核心业务。
而如何将上述基于 KubeBlocks 的方案落地到联邦集群架构呢?以下是整体架构:
Fed-InstanceSet 控制器
针对 Fed-InstanceSet 控制器,有两个关键问题需要解决:
针对第一个问题,我们与社区合作,设计了 Ordinals 字段,允许指定编号的索引值。在多集群下发场景下,Fed-InstanceSet 控制器可以为每个子集群的 InstanceSet 设置不同的索引值,从而 保证实例在多 K8S 集群中的全局唯一性和有序性 。
针对第二个问题,我们需要结合全局顺序、角色关系、灰度变更策略、并发度管控和角色变更策略等因素,构建全局变更的有向无环图(DAG)。该图结构将用于保证 多 K8S 集群范围内的全局变更管控 。
应对 Redis 上 K8S 的风险
性能
Redis 基于云原生架构部署模式下,相较于传统主机部署增加了一层容器抽象。但根据业界分享和快手内部测试结果,这种架构带来的性能差距通常在 10% 以内,很多情况下基本持平。这种性能变化往往在可接受范围内。企业也可根据自身实际情况进行性能测试与评估,以确保满足业务需求。
稳定性
迁移有状态服务上 K8S 后,尽管 K8S 带来的自动化能力大幅提升了运维效率,但这也导致执行流程变得黑盒化,且微小的配置变动会影响大范围的业务实例。因此,为了有效应对非预期内的运维操作(如 K8S 发生实例驱逐、集群运维人员误操作、Operator 逻辑异常等场景)给业务带来的稳定性风险,我们需要在如下工作上做出努力:
针对问题一,答案显而易见: 是否由运维人员主动发起 可作为唯一的判断标准。基于此,我们可以为业务团队生成指定的 ServiceAccount 证书,并通过请求中的用户信息来区分变更发起来源;
针对问题二,沿着问题一的思路,我们可以利用 K8S APIServer 的 Admission Webhook 机制,对所有变更请求进行拦截和校验,从而直接拒绝非预期的变更操作;基于快手多 K8S 集群的场景,我们需要实现跨集群和多可用区(AZ)的变更管控能力。为此,快手内部研发了一套风险变更阻断系统: kube-shield ,关于该系统的更多细节,本文将暂时不做深入探讨,计划未来会单独撰写一篇文章进行详细介绍
另外,值得一提的是,在快手内部,通过增强对细粒度打散调度能力的支持,以及基于资源利用率的负载均衡调度等功能,进一步提升了业务的高可用性与稳定性。
运维复杂度
将基于主机的运维体系迁移至基于 K8S 的运维体系,并支持后续的运维工作,这需要对 Redis 和容器云两个领域具备深入的理解,若仅依靠 Redis 团队或容器云团队独立支持都将非常困难。而合理的分工,不仅可以提高生产效率,也能充分发挥各团队在各自领域的专业性。
以快手的 Redis 云原生方案为例:
四、总结
“有状态服务云原生化”是一个需要慎重考虑利弊且充满挑战的过程,但对于快手来说,其价值显而易见。我们以 Redis 为起点,与 KubeBlocks 社区深度合作,低成本完成 Redis 的云原生化方案落地。未来,快手将基于以上经验,继续推动更多有状态服务,如数据库和中间件的云原生化,从而获得技术和成本的双重收益。