架构团队:戈震、周培、李亮
当下,为了降本增效,各大厂都在通过 PaaS 平台,管理各类中间件服务。一种常规的实现思路是通过实现 PaaS 对虚拟机、物理机的直接操作,实现中间件生命周期的管理,这也是新东方技术团队之前的方案。但要做到完善的资源管理、可用性保证、可观测性、资源利用率最大化等方面的能力,需要大量的开发成本。因此我们决定引入 Kubernetes 来解决这个问题。现在,新东方集团架构团队提供的 Redis、Kafka、RocketMQ 等 PaaS 产品已经实现了 100% 的中间件服务运行在 Kubernetes 环境上。实现了集群秒级交付,监控告警运维一体化。在云化的过程中,既有收益也有挑战,本文将分为三大部分展开,分别是:
针对目前业务场景,架构团队自研并落地了 Capo 项目:基于 Calico 的“IP 预留延迟释放”方案,以解决网络治理遇到的挑战,保证大批量 Pod 重建时,集群和业务的稳定运行。目前该项目已经开源:
项目地址:https://github.com/xdfdotcn/capo
让我们来看一下其中的细节。
资源管理会一直伴随中间件的整个生命周期,在不同阶段对管理的需求也各不相同,概览如下:
本阶段的主要需求是资源分配。首先我们需要根据各类中间件的拓扑特点以及资源需求进行主机分配,然后在主机上进行实际的内存、CPU、磁盘规划分配,以及端口资源分配。
例如某个 3 主 3 从的 redis 集群,我们需要:
这里面实际是执行了工作负载调度和端口分配两个操作。
调度的依据是拓扑反亲和性和主机空闲资源情况,这时需要考虑的情况有很多,例如:
端口的分配需要考虑到端口资源空闲的情况,以及将来集群被释放后动态规划、调整。
若要在传统的 PaaS 平台中实现上述需求,则至少需要开发以下几个能力:
因此在云化的环境下,实现上述需求只需要开发以下能力:通过 Kubernetes API 构建工作负载资源。
动态需求主要包含以下几点:
若要在传统的 PaaS 平台中实现上述需求,则至少需要开发以下几个能力:
因此,在云化的环境中,实现上述需求只需要开发以下能力:通过 Kubernetes API 指定和修改工作负载资源阈值。
虽然大部分中间件天然就具备高可用的能力,可以允许同一个集群中的小部分节点不可用。但当故障发生时,虽然集群工作状态正常,但仍需要尽快将故障节点恢复,使集群恢复至高可用状态。比如在一个 3 主 3 从的 redis 集群中,某个 master 所在的主机发生了宕机。这时通过集群模式的自动故障恢复可使集群在 10 秒左右恢复正常的服务状态,但考虑到这时集群已不是高可用状态,运维人员仍需要尽快介入,恢复故障节点,使集群恢复 3 主 3 从的高可用拓扑。若宕机的主机不能立即恢复,我们还需要在另一台主机上恢复该实例。那么持久化数据如何迁移到新主机上。
持久化数据一般分为两种:元数据及应用数据。元数据是指集群运行过程中产生的必要元数据,例如 redis 集群模式下的 node.conf 配置文件。这部分数据只能通过备份到远程存储系统解决迁移问题。应用数据可以通过中间件本身的恢复机制,但会对集群中的其他实例造成一定性能压力,数据量较大时甚至会影响客户端应用。
因此,大概率要通过引入一套分布式存储服务来解决数据迁移问题。这时需要 PaaS 中实现远程卷的控制。若要做到很高的鲁棒性,会大幅提升整体的实现复杂度。
若要在传统的 PaaS 平台中实现上述需求,则至少需要开发以下几个能力:
通过 Kubelet、CSI、Kube-controller-manager 我们可以 0 成本实现上述能力中的 1、2、3、4、5、7所以云化需要的实现成本是:分布式存储系统的构建与运维。
作为 DevOps 系统,具备可观测性已经是必备能力,基本都是基于 Promethes + Grafana + Alertmanager 生态实现。指标的暴露和抓取需要根据 PaaS 平台的实例状态动态调整。
若要在传统的 PaaS 平台中实现上述需求,则至少需要开发以下几个能力:
因此,云化实现成本为 0。
通过对比两种方式实现 PaaS 中的部分功能:资源管理、可用性、可观测性,我们可以非常明显的体会到 Kubernetes 带给我们收益:
下图是架构团队基于 Kubernetes 的中间件 PaaS 产品生态
底层通过自研 Operator 和 基于开源 Operator 做二次开发,实现了中间件集群的自动化运维能力。集成了 Prometheus + Grafana + AlertManager + Xcloud-Alert 实现了监控告警自动化。底层存储使用了架构团队自研的 XLSS 存储系统,利用 DRBD 物理实时备份到多个主机的机制,保证主副本数据严格一致。有状态工作负载直接操作本地存储资源,避免网络传输延时,具有极致的 IO 性能。
Kafka PaaS 架构图如下:
Kakfa PaaS 产品界面如下:
RocketMQ PaaS 架构图如下:
RocketMQ PaaS 产品界面如下:
Redis PaaS 架构图如下:
Redis PaaS 产品界面如下:
在 Kubernetes 中,管理和配置容器网络是非常重要的,Calico 作为 Kubernetes 的第三方容器网络插件,为 Kubernetes 集群提供容器网络解决方案,使得集群中的容器能够相互通信,并且与外部网络进行交互。Pod IP 地址是由 Calico 自动动态分配的,可能会随着 Pod 的重建而变化。容器化的中间件集群,Pod 重建也是不可避免的。如何保证重建后集群拓扑不会混乱,客户端能重连成功,存在很大的挑战。为此架构团队落地了 Capo 项目,其中采用了“IP 预留延迟释放”的方案。当 Pod 重建时,会将 Pod 的 IP 地址保留一段时间,保证 IP 在短时间内不会被重复使用。总的来说,采用了一些措施来保证容器化中间件集群在 Pod 重建后不会出现拓扑混乱和客户端重连失败的问题。
新东方部分业务使用 ElasticJob 做分布式任务调度,使用了架构团队容器化的 Zookeeper。ElasticJob 调度框架使用 Curator 客户端连接 ZK 集群。当 ZK 集群所有 PodIP 变化之后,Curator 客户端连接集群会失败,导致业务 Job 运行异常,只能重启业务端服务才能恢复。
经过阅读 ElasticJob 和 Curator 源码之后发现,Curator 提供了一个 ensembleTracker 参数,默认为 true。会动态跟踪集群实例变化。1、我们给业务方提供的是 Service 域名(不会变化)连接 ZK 集群。2、连接成功后,会读取 /zookeeper/config 配置,获取集群各个实例的域名,然后解析为每个 ZK 实例的 PodIP(会变化),之后发生重连时会连接 PodIP。3、当集群内 PodIP 全部变化之后,无法获取到正确的 IP ,此时不会尝试通过种子节点 ClusterIP 连接集群,导致重试一直失败。
虽然 Curator 提供了 ensembleTracker 参数,但 ElasticJob 框架没有将此参数暴露出来供开发者修改。我们通过修改 ElasticJob 源码,暴露 EnsembleTracker 参数可配置,发布新东方内部版本,提供给业务做升级。将 ensembleTracker 设置为 false,这样 Curator 客户端总是和 Service ClusterIP 建立连接。
在基于 Kubernetes 的云原生环境下,多个 Redis 集群实例会分布到一个或多个主机上。
例如下面各 6 个实例的 A、B 两个集群:
A、B 集群的 6 个 Pod 实例都均匀分布到 node01~node06 主机上。当 node01 主机故障后,A1 和 B1 Pod 实例会一直处于 Terminating 状态直到 kubelet、docker 恢复正常才可以完成 Pod 删除。
此时 node01 主机恢复正常,A1、B1 还会调度回 node01,会发生 IP 交换的问题,即:
对于 Redis 集群特点,客户端必须使用 IP 连接集群,当多个 Redis 集群 Pod 同时重建之后,如果 IP 发生交换,会带来两个问题:1、Redis 集群中实例通过 Gossip 协议通信时不会校验密码, 导致多个 Redis 集群组成一个“大集群”,拓扑变的不符合预期;2、客户端仍然会连接旧的 IP,但此时这个 IP 被另一个集群占用,即:连接到错误的集群。如果密码错误,会一直重试,这会对客户端系统造成中断。
Calico 内置了 IPReservations 资源对象,可以从 IP 池中预留 IP 地址、CIDR,保证 IPAM 在分配 IP 时不会将预留的 IP 分配给 Pod。所以需要有一个组件实现这些功能:
本方案将上述功能点自研了名为 Capo 的项目,架构图如下:
IP 预留流程说明:在删除 Pod (Delete)和主机维护驱逐 Pod(Drain)时,会发起 Delete Pod 请求:
此时,Kube-APIServer 发送删除请求,触发 Kubelet 开始释放 IP 资源,调用 Calico CNI 执行 cmdDel 逻辑,将 Pod IP 释放掉。
重新分配 IP 说明:当 Pod 重建分配 IP 时,Calico IPAM 不会自动将 IPReservations 资源对象中的 IP 分配给容器,而是从其余的 IP 中进行分配,所以 IP 不会发生交换。
IP 释放机制:IP 预留数量不宜过多,否则会造成 IP 资源浪费,所以需要有种机制,在合适的时机将其释放,以便被重复利用。
Capo 程序每 5 秒检测一次是否需要释放,目前有两种释放机制:
IP 释放流程说明:满足释放条件的 IP,进行释放流程:
释放后,Calico IPAM 在分配 IP 时,则会将释放的 IP 分配给 Pod。
经过架构团队 2 年时间的中间件容器化经验沉淀,目前各类中间件接入集群数量共:140+,并总结出以下收益:
在下一篇文章中,将对架构团队 Capo 的原理及开源之路展开详细讲解。
项目地址:https://github.com/xdfdotcn/capo