随着云原生时代的到来和Kubernetes(简称K8s)的日渐成熟,越来越多的互联网团队开始将Kubernetes作为新的重要基础设施,一些云计算厂商也将其视作云服务及应用交付的新底座。在大家的普遍认知里,Kubernetes是一个容器编排系统,擅长无状态的应用部署管理,在微服务领域起到了重要作用。由于容器对外部基础环境的不感知和状态易失的特性,与有状态应用的管理似乎有天然的矛盾。Operator就是“有状态应用容器化”的一个优雅的解决方案,本文将介绍网易数帆旗下的轻舟中间件基于Operator的Redis容器化实践。
Redis是基于Key-Value的缓存数据库,具有高性能、数据结构丰富、原生的高可用和分布式支持等特点,是目前业界使用最广泛的缓存中间件之一。从Redis 3.0版本开始,推出了Redis Cluster这一原生的、集群自治的分布式解决方案,支持在线水平扩缩容和故障自动转移,帮助我们突破了单机内存、并发、流量的瓶颈。
Kubernetes是Google开源的容器编排管理系统,是Google多年大规模容器管理技术Borg的开源版本,主要功能有:
下面简要介绍几种K8s中的几种常用的K8s核心组件和资源对象,便于下文的理解。
首先是几个本文中要多次提及的K8s基础组件:
下面介绍一些常用的资源对象:
整体K8s架构如下图所示:
在介绍Kubernetes Operator之前,我们先来分析一下传统的有状态应用部署方式所存在的问题。
以前当开发者想要在物理机或云主机上部署Redis、Kafka等有状态应用,并且对于这些应用的集群有一定程度的运维控制能力时,往往需要编写一套复杂的管理脚本、或者开发一个拥有诸多依赖的Web管控服务。站在普通使用者的角度,他们不得不为此学习与本身业务开发无关的运维知识。
如果是以脚本和运维文档的方式沉淀,缺乏标准化的管理会使得运维的学习成本和使用门槛随着使用过程中的修改而急速升高;而使用管控服务,则需要引入更多的底层依赖去满足管控服务与主机侧的交互的虚拟化和管理设施,云计算厂商许多都使用了OpenStack作为基础设施管理平台,严格来说,OpenStack和Kubernetes不属于同一层面的框架,前者更多是属于IaaS层,更多是面向基础设施做虚拟化,Kubernetes则更偏向上层的应用容器化编排管理。在实际使用中,我们认为自行组织OpenStack对于小规模用户私有云的使用需求,有些过于沉重了。
于是我们将目光投向对基础设施关注更少、自动化程度更高的Kubernetes。我们都知道Kubernetes在无状态应用部署管理,尤其是微服务领域,已经大放异彩。例如管理一个无状态的Web服务,我们可以使用K8s的Deployment部署多副本并且进行弹性伸缩和滚动升级,然后使用Sevice进行负载均衡,依靠K8s原生的资源对象基本上可以覆盖无状态服务的整个生命周期管理。
然而,对于Redis、Kafka这类“有状态”的应用,K8s似乎并没有准备好接纳它们。首先我们总结一下有状态应用的两个重要的特点:
这就给K8s带来了挑战,如果仍使用Deployment和ReplicaSet这些对象,Pod在故障时,对于Redis Cluster无法只是简单地重启Pod就能恢复到健康的集群状态,同理Kafka的Pod也会“忘记”自己所使用的存储卷。
K8s推出了StatefulSet这一资源对象旨在解决有状态应用的管理问题,允许在StatefulSet中配置持久卷(PersistentVolume,简称PV)、稳定的网络标识,对其内部的Pod进行编号排序等。但是对于Redis Cluster这种拥有自治能力的集群,StatefulSet也显得不够灵活而且会与其自治能力有冲突。
K8s的自动化哲学有两个核心概念:声明式API和控制器(controller)模式。比如我们声明一个三副本的Deployment提交给API Server,K8s中负责Deployment的controller就会监视(Watch)它的变化,发现该Deployment的Pod数为零,对其进行调谐(Reconcile),创建三个Pod,使它达到我们所声明的状态。
这引发了我们的思考:是否可以将Redis视作像Deployment一样的资源对象进行声明式的管理,同样拥有一个controller对它进行调谐呢?
这是一个合理且强烈的需求,即资源对象和controller都是由我们自定义,由我们自行编写资源的生命周期和拓扑关系的管理。于是Operator应运而生,可以将其简单的理解为:
Operator = CRD(Custom Resources Definition)+ Custom Controller
如今Operator在社区中已经非常火热,但我们在最初设计做调研时,发现社区的Redis Operator实现上虽简洁,但运维能力和我们作为云计算服务商所强调的风险掌控、兜底能力不足,于是我们NCR(Netease Cloud Redis)团队借鉴社区的经验开发了自己的Redis Operator,下面针对Redis Cluster模式的管理进行解读,总体架构如下图所示:
Redis Operator自身采用Deployment进行三副本部署,通过ETCD选主。Redis Cluster每个分片内的两个Pod上的Redis实例为一主一从,每个分片由一个StatefulSet管理;Pod的调度策略由K8s原生调度器和网易轻舟K8s提供的扩展调度器共同保障,对于StatefulSet、Pod等原生资源对象的管理仍使用原生API。
开始使用Redis Operator,首先我们需要提交一个属于Redis的资源定义(CRD),定义一个Redis集群所必要的规格描述(Specification),之后用户便可以提交CR(通常是以yaml文件的形式),并在CR的Spec中填写自己需要的规格信息后提交,剩下的工作,无论是创建、弹性扩缩容、故障处理,统统交给Redis Operator自动化调谐。下面是一个典型的CR实例:
apiVersion: ncr.netease.com/v1alpha1
kind: NcrCluster # 资源类型,在CRD中所定义
metadata:
name: cluster-redis # CR名称
namespace: ns # CR所在的namespace
spec:
availableZones: # 可用区列表,支持单、多AZ
- azName: az1
configFile: ncr-cluster-configmap # 默认的配置参数模板
master: 3 # 分片数
port: 6379 # 端口号
version: redis:4.0.14 # Redis引擎版本
resourceReqs: # K8s标准资源规格描述
requests:
cpu: 1
memory: 2Gi
limits:
cpu: 2
memory: 4Gi
下面简要分析一个Redis Cluster的CR提交后的Operator主干工作流程:
下图为Redis Operator的工作流程图,实际上Operator就是我们对Kubernetes API进行了扩展和自定义,整体的工作流程与原生内置的controller是一致的.
那么这种设计理念有什么好处呢?借用Operator Framework官网的一句话:
The goal of an Operator is to put operational knowledge into software.
即“Operator旨在将领域性的运维知识编写成代码融入其中”。这个理念会影响我们对Redis Operator的设计与开发。
列举一个场景说明,假如我们有在物理机上手工部署的Redis Cluster集群其中一个Master实例故障,并发生了Failover,此时该实例处于宕机状态,如果是人工恢复我们需要做的工作大致为:
上述每一步操作判断都涵盖了我们的Redis运维知识,根绝Operator的理念,我们应该将这些知识编写成代码。通过明确的指标去做判断,比如判断QPS低于5000、CPU使用低于60%,Master内存空闲大于2GB时允许自动重建修复,将故障自动恢复或调谐的能力交给Operator,极大地提高了自动化运维的程度,可以称Redis Operator为运行在Kubernetes上,拥有Redis运维知识和判断能力的“运维专家”。
最后总结一下Redis采用Operator方案做容器化的优势:
目前网易轻舟自研的Redis Operator提供的功能有:
时至今日,Redis Operator已经在网易云音乐线上环境自2019年底稳定运行至今,网易传媒、网易严选也在逐渐扩大线上的使用规模,得益于自动化运维的高效、资源成本的优化,相信Operator将成为成规模的有状态分布式应用容器化的标准。
作者:网易数帆-轻舟中间件团队