导读
针对无状态服务,业界已拥有成熟解决方案,但对于有状态服务(如数据库、Redis)是否适合容器化与K8s托管,仍存在争议。本文将基于快手在 Redis 云原生化实践中的经验,探讨有关有状态服务的云原生化思考及应对方案。
随着行业技术的不断演进,快手的基础设施顺应技术潮流逐步迈向云原生化。在各业务团队的支持下,容器云成为服务与基础设施的新界面,目前在快手无状态服务已基本全面实现 Kubernetes (K8s) 的云原生化。然而,有状态服务的云原生化之路却仍然充满了挑战:
有状态服务究竟是否适合在 K8S上运行?
有状态服务的云原生化如何实现与落地?
以 Redis 为例,它是快手广泛使用的有状态服务之一,超大规模是其显著特征。即使微小的优化在如此庞大的规模下也能为企业带来巨大的收益。在面向未来的长期规划中,快手高度认可 Redis 云原生化带来的潜在价值,尤其是资源利用率提升带来的成本优化。本文基于快手 Redis 云原生化实践中的经验,深入剖析有状态服务的云原生化思考及应对策略。
有状态服务运行在K8S上的风险与收益
将有状态服务运行在 Kubernetes 上的收益显而易见:
提升资源利用率 :通过“合池”、“统一调度”和“混部”,优化资源使用,显著降低成本。提高运维效率:利用 Kubernetes 的声明式 API 和控制器模型,提升业务运维效率,确保技术先进性。
降低运维成本:统一基础设施减少运维维护成本,提升整体管理效率。
尽管有状态服务运行在 Kubernetes 上的收益显著,但潜在风险也需认真评估,尤其是对数据库等有状态服务,其重要性和稳定性要求极高。这里的主要风险包括:
1. 性能下降风险:容器化部署增加了一层抽象,是否会导致服务性能下降?
针对上述风险,下文将展开探讨。
有状态服务运行在K8S上的可能性
在探讨有状态服务时,我们首先需要明确“状态”的具体含义:
综上,有状态服务的云原生化相比无状态服务面临新的挑战:如何保证数据的可用性、管理数据生命周期,以及建立和维护拓扑关系并实现基于拓扑结构的服务发现和灰度发布等能力。K8S 社区目前的应对措施包括:
方案一:StatefulSet Workload:
K8S 社区推出了 StatefulSet Workload,为每个实例分配一个全局有序递增的编号。这一机制为实例提供了稳定的网络标识和存储标识,从而维护了拓扑状态和数据状态。
然而,拓扑状态在运行过程动态变化,仅靠 StatefulSet 提供的编号难以满足需求。
方式二:自定义Workload + Operator:
针对已有 Workload 无法应对动态拓扑关系等复杂场景的问题,自定义 Workload 和 Operator 提供了扩展能力,这也促进了 Redis Operator、MySQL Operator 等多种有状态 Operator 的出现。
然而,从零开始开发一套 Operator 的成本往往使许多数据库团队却步。即使复用现有的 Operator,企业内自定义运维逻辑与现有逻辑的融合依然复杂且具挑战性。
快手的 Redis 采用的是经典的主从架构,包含三个组件(Server、Sentinel 和 Proxy)。
迁移单个Redis集群上K8S
快手 Redis 云原生化思路
基于以上分析,Redis 的云原生化实现无法基于 Kubernetes 社区现有 Workload 实现,具体而言:
多分片与多实例关系表达:需清晰定义多分片架构及单分片下的多实例层次关系。同时,需要支持分片数量和单分片下多实例数量的动态变化,以应对不同负载和使用场景的需求。
生命周期变化过程中数据管理:在分片或实例生命周期变化时,如何有效管理数据是一个重要挑战。需确保数据的一致性和可靠性,包括分片数量变化时的数据重平衡,以及分片内实例数量变化时的数据备份与恢复策略等。
单分片内多实例动态拓扑关系的表达:在一个分片下,如何实时表达多个实例之间的动态拓扑关系变化,并基于实时拓扑结构实现服务发现和灰度发布等能力。
针对上述挑战,我们提出了如下解决思路:
分层架构设计:基于不同 Workload 分别管理分片及其下的实例;具体而言,使用一个 StatefulSet 管理每个分片下的多个实例,并在此基础上构建一层新的工作负载,以实现对整个 Redis Server 集群中多个分片的统一管理;
生命周期钩子支持:支持分片和实例维度的生命周期 Hook 能力,允许在不同生命周期阶段执行自定义的数据管理操作;
动态拓扑感知与服务发现:引入角色探测和角色标记能力,进一步实现系统基于动态拓扑关系的服务发现和灰度发布能力。
KubeBlocks 解决方案
经过调研,KubeBlocks 项目成为我们重点关注的解决方案之一,其贴合我们的思路与需求。作为一个开源 K8S Operator,KubeBlocks 抽象了管理各种数据库的 API,支持在 Kubernetes 上运行和管理多种类型的数据库。其愿景是“在 Kubernetes 上运行任何数据库”。
经过与 KubeBlocks 社区深度合作,我们通过如下方式实现一个 Redis 集群的编排:
1. InstanceSet Workload
与 StatefulSet 相比,其增加支持了角色定义、角色探测方式以及角色更新策略的定义抽象;同时,InstanceSet 控制器在实例运行过程中会动态探测角色变化,并将角色信息以标签(label)的方式更新到实例的元数据中,从而支持基于角色的服务发现等能力。
除此之外,快手和 KubeBlocks 社区一起共建了 InstanceSet 直管 Pod 和 PVC、InstanceSet 实例异构配置、指定实例缩容、并发控制、多种更新策略等增强能力,使其能够灵活应对复杂的业务场景。
Component:将组件的定义与组件实例解耦。通过引用组件定义,可以生成对应的 InstanceSet,使得组件管理更加灵活;并支持实例维度的生命周期管理。
Shard:用于生成一组相同的 Component 实例,主要适用于类似 Redis Server 的分片场景,便于管理和扩展;并支持分片维度的生命周期管理。
Cluster:用于定义整个有状态服务集群,在 Redis 场景下,可以统一表达 Proxy、Server 和 Sentinel ,并设置它们的启动拓扑关系。
迁移大规模Redis集群上K8S
前面提到,超大规模是快手 Redis 的显著特征,其实例规模远超单个 K8S 集群的容量。因此,我们不得不基于多个 Kubernetes 集群来支持业务。与传统模式下所有主机平铺的方式不同,因为 K8S 单个集群的容量限制,我们必须将主机资源池切分到多个 K8S 集群中。如果将多个 K8S 集群的复杂度直接暴露给 Redis 业务方,上云成本势必会被大幅增加。
联邦集群架构
在快手,我们通过基于联邦集群能力提供相应的统一调度和统一视图能力,降低业务方的复杂度,使其更专注于核心业务。
统一调度能力:通过联邦作为统一资源下发入口,实现实例在多个成员集群之间的调度。
统一视图能力:联邦作为统一资源获取入口,统一获取联邦和成员集群的相关资源。
而如何将上述基于 KubeBlocks 的方案落地到联邦集群架构呢?以下是整体架构:
InstanceSet Controller 放置在成员集群中
Cluster Operator 和 Component Operator 放置在联邦集群中
调度决策:根据调度建议,决策每个集群应该部署多少个实例;
InstanceSet 拆分与分发:负责拆分 InstanceSet,并将其分发到多个成员集群。
Fed-InstanceSet 控制器
针对 Fed-InstanceSet 控制器,有两个关键问题需要解决:
实例拆分管理:确保在联邦部署模式下,Redis 集群的实例列表全局有序唯一;
管控规则拆分:保证在联邦部署模式下,InstanceSet 的管控规则全局符合预期。这包括管理灰度变更的顺序和并发度控制等。
针对第一个问题,我们与社区合作,设计了 Ordinals 字段,允许指定编号的索引值。在多集群下发场景下,Fed-InstanceSet 控制器可以为每个子集群的 InstanceSet 设置不同的索引值,从而保证实例在多 K8S 集群中的全局唯一性和有序性。
针对第二个问题,我们需要结合全局顺序、角色关系、灰度变更策略、并发度管控和角色变更策略等因素,构建全局变更的有向无环图(DAG)。该图结构将用于保证多 K8S 集群范围内的全局变更管控。
应对Redis上K8S的风险
稳定性
迁移有状态服务上 K8S 后,尽管 K8S 带来的自动化能力大幅提升了运维效率,但这也导致执行流程变得黑盒化,且微小的配置变动会影响大范围的业务实例。因此,为了有效应对非预期内的运维操作(如K8S 发生实例驱逐、集群运维人员误操作、Operator 逻辑异常等场景)给业务带来的稳定性风险,我们需要在如下工作上做出努力:
如何区分预期与非预期的运维操作?
如何系统性避免非预期的运维操作?
针对问题一,答案显而易见:是否由运维人员主动发起可作为唯一的判断标准。基于此,我们可以为业务团队生成指定的 ServiceAccount 证书,并通过请求中的用户信息来区分变更发起来源;
运维复杂度
将基于主机的运维体系迁移至基于 K8S 的运维体系,并支持后续的运维工作,这需要对 Redis 和容器云两个领域具备深入的理解,若仅依靠 Redis 团队或容器云团队独立支持都将非常困难。而合理的分工,不仅可以提高生产效率,也能充分发挥各团队在各自领域的专业性。
以快手的 Redis 云原生方案为例:
Redis 团队重点关注如何定义 Redis Cluster 对象,并将已有的运维经验通过定义的方式进行转化;
而将 Redis Cluster 对象提交给 K8S 后的工作,则由容器云团队进行保障,包括 Operator 的研发与维护、调度处理等。
“有状态服务云原生化”是一个需要慎重考虑利弊且充满挑战的过程,但对于快手来说,其价值显而易见。我们以 Redis 为起点,与 KubeBlocks 社区深度合作,低成本完成 Redis 的云原生化方案落地。未来,快手将基于以上经验,继续推动更多有状态服务,如数据库和中间件的云原生化,从而获得技术和成本的双重收益。
附注:在今年 8 月份的香港 KubeCon 上,快手与 KubeBlocks 团队进行了联合演讲,如果您对此感兴趣,可以查看演讲的回顾内容。
本文作者:刘裕惺
END
点个「在看」 你最好看
有奖互动:是否应该在 Kubernetes 上运行有状态服务?你所在公司是怎么做的?目前取得那些成果或经验?欢迎在快手技术原文评论留言。我们将选取1条优质评论,送出快手小六公仔1个(下图随机一款)。评论截止11月6日中午12点。
如果您在阅读这篇文章后深感其价值,恳请您慷慨点赞。您的每一次认可和鼓励,都将成为我们不断前行的动力!
文章转载自快手技术。点击这里阅读原文了解更多。
CNCF概况(幻灯片)
扫描二维码联系我们!
CNCF (Cloud Native Computing Foundation)成立于2015年12月,隶属于Linux Foundation,是非营利性组织。
CNCF(云原生计算基金会)致力于培育和维护一个厂商中立的开源生态系统,来推广云原生技术。我们通过将最前沿的模式民主化,让这些创新为大众所用。请关注CNCF微信公众号。