在近日的 ArchSummit 全球架构师峰会 2021 上海站上,继网易副总裁、杭研院执行院长、互联网技术委员会主席、网易数帆总经理汪源发表主题演讲《打造开放的云原生操做系统和系统软件架构》以后,网易技术委员会委员、网易数帆基础架构总监张晓龙向与会者进一步讲述了网易数帆在云原生中间件上的思考、实现与经验。本文为演讲内容实录。前端
今天给你们分享咱们面向生产环境的中间件容器化实践,主要包括四个部分的内容:数据库
第一部分从基础中间件面临的运维挑战出发,介绍网易解决这些挑战的技术演进路径,以及为何要去作中间件容器化。编程
第二部分介绍中间件容器化的需求以及网易数帆总体平台架构。后端
第三部分针对中间件容器化过程当中的一些共性问题,给出咱们的思考,以及最佳实践。缓存
最后是中间件容器化工做的总结和将来的计划。性能优化
基础中间件的挑战
在容器技术出来以前,基础中间件技术如 MySQL、Redis、Kafka 等早已开源,并成为服务端架构设计的标准组件,一个典型的互联网应用,数据库、缓存、消息队列三大中间件是必不可少的。网络
架构师应用这些中间件去架构一个个应用平台很是简单,但运维人员遇到了较大的问题,包括以下 5 个方面:架构
- 中间件自己是比较复杂的分布式系统,运维须要理解这些分布式系统的工做原理,编写出适合它们的运维脚本,复杂性很是高;
- 运维效率比较低下,50 个如下 MySQL 实例用手工运维可能没有问题,但 500、1000 个数据库实例,或者如网易云音乐的数千个 Redis 实例,若是还用手工脚原本运维,效率必然很低;
- 稳定性不足,这是因为运维人员老是用手工脚原本运维,在线上抄命令,不当心抄错命令可能中间件就宕了;
- 传统的中间件是部署在物理机上面的,而物理机制没办法提供很强的资源弹性;
- 全部比较资深的中间件运维都基本上在互联网上大厂,由于这些运维很是复杂,通常企业很难招到一个很是专业的运维,咱们认为解决这个挑战的最佳实践,是将中间件运维能力云服务化。
将这些中间件作成云服务有几个优点。第一是运维简单易上手,第二可以高效地实现大批量实例的自动化运维,第三有很强的 SLA 保障,由于不须要敲太多手工的一个命令。第四是能借助 IaaS 弹性资源能力快速扩容。最后由于整个运维变得简单,再也不须要大量的专业人员就能够帮业务运维好中间件。框架
其实公有云厂商也看到了这个趋势,国内三大主流公有云都把开源的基础中间件作成了云服务。我想这主要有两个缘由:首先,IaaS 资源层面竞争趋于同质化,把 PaaS 中间件作成云服务能够消耗更多的资源,把用户绑定得更深;其次,中间件做为云上的增值服务,毛利率远高于云主机、云硬盘,因此不少公有云用户不喜欢 RDS,本身买云主机搭 MySQL。运维
为了解决中间件运维复杂性的挑战,网易在六七年前就研发了一个云基础中间件平台。这个平台有一些技术特色,首先是基于 IaaS 提供资源弹性,也就是说中间件运行的计算资源是云主机,存储资源是云盘,网络资源可能就是在租户的 VPC 里面。
第二它采用了 IaaS 的租户隔离策略,若是一个租户想要中间件实例,平台就用他的云主机、云硬盘自动化地帮他搭起来,能够作到不一样租户之间很好的隔离。
咱们当时研发了 6 款基础中间件云服务,业务团队研发产品须要中间件,它只须要接入这些云服务就能够了,不须要从新作一遍。咱们主要作的是左边的控制管理部分,好比实例高可用、部署安装、实例管理等。当时咱们也取得了一些成效,大大提高了运维团队对中间件的运维能力。
随着时间的推移,第一代基础中间件暴露出了三大缺陷,难以解决。第一大缺陷是极限性能不足。由于它使用 KVM 虚拟机做为计算资源,比在物理上运行有很是大的性能折损,没办法知足业务高负载/高压力下对中间件性能和稳定性的苛刻要求。
第二是实现资源成本过高,由于它是基于 OpenStack 来提供资源编排能力,另外 KVM 虚拟化技术强隔离的特性使得内存资源没办法在多个中间件实例之间共享,这两个因素使得跑在虚拟机上的中间件实例部署密度很是低,哪怕有租户的中间件负载不高,他也不可能把内存释放,由于 KVM 是强隔离的。
第三点它的交付很是不灵活,它就跟网易的 IaaS 绑定,没办法支持咱们将来把它商业化,输出到网易之外的企业,这个企业的基础设施多是在公有云上,也多是在本身的 IDC 机房。
中间件容器化的思考
近几年,Docker、Kubernetes 等容器技术诞生并飞快发展,无状态应用的容器化已经成熟,咱们认为容器做为一个新的已经普遍落地的基础设施的技术,完美地对应了第一代基础中间件的缺陷能力—弱隔离有有助于资源共享;轻量化的虚拟化可以消除性能损耗,知足业务在高负载场景;基于镜像进行标准化的封装,有利于高效交付;还有强大灵活的调度能力;最关键的一点,它是整个云原生技术栈的一个基石。
Kubernetes 编排技术,最关键的是它跟基础设施是松耦合的,使得咱们可以将应用搬到任何一个地方,由于它就是面向混合云设计的。另外它是面向大规模生产环境的设计,继承了 Google 的大规模生产环境的经验,因此用容器技术解决中间件服务化的问题是有但愿的。
网易内部基于 Kubernetes 构建了一套云原生操做系统,它向下可以适配各种的基础设施资源,向上可以做为各类应用负载的统一提供商--这也是 Kubernetes 的目标之一。中间件正是整个云原生操做系统所要支撑的一类业务。从这个角度来看,中间件容器化也是瓜熟蒂落的。
中间件容器化要解决它的运维问题,尤为下面几个需求必需要考虑的。
第一,生命周期的管理,咱们须要容器化中间件平台可以帮助运维完成对于中间件实例级别的各类运维操做,网易数帆会基于 Kubernetes Operator 这一套框架来实现。
第二点是高可用的部署,中间件,特别是在追求更高的可用性的状况下,每每要作多机房的部署,一个中间件集群里面的全部实例,要按照什么样的比例分布在不一样的机房,标准的 Kubernetes 调度器没办法作到,咱们须要扩展 Kubernetes 的调度器来实现这样的编排。
同时,还要完善监控告警的指标,这个指标就对应云原生的 Prometheus 的可观测性体系。
性能是第一代中间件的一个痛点,咱们要确保容器化中间件基本达到物理机部署的性能才能支撑核心应用,这须要有针对性地优化各种中间件实例的性能。
还有一点是产品化,由于咱们但愿中间件容器化不只可以在网易使用,还可以商业化输出,因此咱们参考公有云上 RDS、Redis 的产品形态,须要有同等的产品能力,可以在任意的基础设施上低成本、灵活交付,咱们必须采用松耦合和高复用的架构设计。
网易数帆选择了 Kubernetes Operator 的机制。从深层次理解,Kubernetes 构建了一个分布式系统部署运维所需的“原语”,它内置的对象如 Pod、Node、Deployment、StatefulSet 等,都是为了实现一个典型的无状态分布式系统提出来的。这些内置的对象相互配合,使得无状态应用的部署和运维很是高效。
可是 Kubernetes 内置的这些对象没办法直接解决中间部署运营的问题。第一点,中间件是有状态的,它的状态是存储,可能网络 IP。第二,中间件实例与无状态应用的实例不一样,后者的副本相互之间没有关系,而中间件实例和实例之间、副本和副本之间是有关系的,是要相互访问的,中间件之间造成一个复杂的拓扑关系,好比在作故障恢复时,Redis 两个副本之间是有主从关系的。
社区在两年多以前也开始实现中间件或者说有状态的应用,提出了一套 Operator 开发框架。若是咱们把 Kubernetes 理解成为一个操做系统,那么 Operator 就是在这个操做系统上开发原生应用的一套开发框架,支持更高效、更自动化、更可扩展的开发方式。
Operator 有 4 个特色,第一它是须要开发出来,是遵循的声明式的编程理念,有对象的定义,还有控制器部署。Operator 实际上是一个控制器,遵循着观察、分析、行动的决策链闭环。若是用户定义了 4 个资源,Operator 就分析这 4 个资源当前的状态和目标状态有哪些不一致。
图中能够看到当前的状态有 1 个 Pod,他如今是 0.0.1 的版本,咱们定义的状态要求 0.02,还少了一个 Pod,若是发现了不一致,它会有一些 Action,再扩一个 Pod,把它升级到 0.0.2。咱们实现 Operator,其实就是去写这些 Action 应该怎么作。这其实是封装了特定领域的运维知识跟经验,可以被设计用来管理复杂的状态应用。
Operator 开发框架的主体包括三部分,第一部分 operator-sdk,研发的一个脚手架;第二部分是 operator-lifecycle-manager,一个生命周期管理的组件;第三部分是 operatorhub.io,既然任何人均可觉得开发一个应用,一个它能够部署安装运维的应用,他就应该能够把这个应用放到一个应用市场,operatorhub.io 就是这样的一个应用市场。
不一样的机构去开发 Operator,在运维看来是有必定的成熟级别的,应用部署都可以自动化运维,这是对应运维最但愿的一个级别。最基本的第一个级别就是基本安装 Operator,该怎么去作到把原来安装部署脚本,用 Operator 这种工程模式实现。
这是网易数帆实现的一个基于 Kubernetes Operator 的中间件平台架构,包括控制面和数据面。左边控制面面向运维管理的能力,包括一些跟中间件业务无关的可是你们都须要的通用组件,如审计、认证权限、控制台等。
中间就是中间件 Operator,在这里咱们用 Operator 的机制研发了 Redis、Kafka、MySQL 等中间件。
咱们实现了中间件的生命周期管理,这些 Operator 自己也是运行在 Kubernetes 的上面,并且它是一种无状态应用,以 Deployment 方式能够运行在上面,由于它的状态都是存在 etcd 里面的。
再下面是 Kubernetes 的管控面,Master 节点须要的一些组件。
最下面是日志、监控、报警的组件,咱们自研的一个日志管理平台实现从采集信息去动态更新它的配置,以及把日志收集上来。
右边是中间件的数据面,我画了三个 Node,咱们把一个中间件的集群用 StatefulSet 来实现,每个实例跑在一个 Pod 上,每一个 Pod 可能会声明它的对持久卷的用途,Pod 跟 Node 之间是有拓扑关系的,它须要相互进行数据和拓扑同步,用于状态变动以及故障恢复。每一个节点上都会运行 Kubernetes 的两个组件,Kublet,kube-proxy,还有一个采集器,用于日志监控。
咱们还实现了 Pod 的挂盘功能,无论是本地盘仍是远程盘,经过 StorageClass 的方式去实现,这也是 Kubernetes 的标准。
中间件容器化的共性问题与解决之道
接下来探讨中间件容器化过程当中的一些共性问题的解决办法。中间件最大的特色在于它是有状态的,Kubernetes 只负责计算的编排,中间件的状态存储有两种可能,一种是远程存储,一种是本地存储。
咱们认为远程存储是最佳实践。若是你在私有云环境上有一套相似于开源 Ceph 的远程分布式存储,应该绝不犹豫地使用它来存。若是说 Ceph 性能不足,你能够找其余更好的分布式存储来去直接用。若是你在公有云上,那你应该绝不犹豫地用云盘来做为中间件的存储。
不少状况下,本地存储是不得已而为之的一个选择,由于没有太靠谱的分布式存储,有可能这个分布式存储性能不行,和用本地盘跑起来相差很远,也有可能分布式系统后端可靠性不行,会丢数据。
为此,咱们实现了本地存储的接入。咱们作本地存储需求有两个,一是要求当 Pod 去申请 PVC 的时候作好动态管理配置,本地盘在建立、删除时,要去作对应的操做。同时在 Pod 调度时,要实现它与本地盘强绑定,既然 Pod 开始建立的时候,有本地盘在某一个 Node 上,你必须保证 Pod 通过故障恢复或者重调度以后仍是跑在那个 Node 上,以确保中间件数据不丢失。
在技术实现上,咱们对于节点上的本地磁盘引入了一个 LVM 去动态的管理,也采用了 Kubernetes Local PV,后者的不足在于须要运维提早在节点上建立 PV,这个是不可取的。因此咱们作了两件事,一是调度器扩展,实现本地存储的资源准备,在建立 Pod 时声明所需本地盘的大小,它就可以动态给建立挂载到这个 Pod 里面去,不须要运维提早手动准备。
如图中一个 Pod 的调度过程,用户建立了一个 Pod,它声明了一个 PVC,咱们加了一个本地存储调度器扩展,先作一个预调度,算一下每一个节点上的本地盘的存储容量够不够,若是够就把 Node 的信息也放到 PVC 里面,接下来通知这个 Node 上一个本地存储资源准备器,让资源准备器收到请求的时候去调用 LVM 把存储资源给建立出来,并把对应的 PV 建立出来。在资源准备器上把 PV 和 PVC 绑定,而后通知调度器能够把 Pod 调度到这个节点上,由于声明的本地存储已经准备好。接下来用 Kubernetes 把那个节点所在的本地盘挂载到 Pod 里面去,完成一个总体的调度。
关于中间件容器化的网络,有两个场景的实现。第一个场景,咱们设计的中间件运行在不一样的基础设施上,对应不一样的网络配置,若是是物理网络,能够用 Calico、Flannel 这样的网络方案,直接用它的 CNI;若是是公有云,就对接公有云上的 VPC 网络,好处是每一家公有云都为 Kubernetes 提供了一个标准 CNI,使得运行在云主机上的 Kubernetes 能够去接入他们的网络。
第二个场景,咱们须要优化网络性能。咱们引入了一个容器的 SR-IOV 方案,好处是可以作到优于物理机的低时延。它采用的是网卡直通技术实现,可以下降 50%的时延,能够知足一些对时延要求很高的超高性能任务需求,但 PPS 提高不了。直通少了网络传输的虚拟化开销,可是缺点也比较明显,这个方案只能用在物理网络,由于它彻底依赖于硬件网卡,没法用在公有云上实现网络加速。
在物理网络环境上要去处理网卡异构问题,包括说是咱们可能用英特尔网卡,可能有 Mellanox 的网卡,须要对 VF(SR-IOV 的一个概念)进行精细管理。咱们把 VF 当成一个扩展的调度资源,经过标准的 Kubernetes Device Plugin 来发现和注册节点的 VF 资源, 结合 label 和 taint 标记,原生的调度器就能够进行资源管理和分配。
轻舟中间件的集群是用 StatefulSet 抽象的,每一个实例都是 StatefulSet 的一个 Pod,StatefulSet 只能作到 Pod 的名字不变,它发生不一样更新的时候,或者挂了再恢复的时候,都保持 Pod 的名字不变,可是它没办法保持 Pod 的 IP 不变。然而,在传统的中间件运维眼里,基于物理机部署的 IP 是不变的,机器重启以后也仍是原来的 IP,因此他们的一些运维习惯,都是喜欢用 IP 而不是域名。
为了让容器化中间件可以更快地推广落地,以及兼顾已有的应用,咱们作了保持 StatefulSet 的 IP 不变的功能,经过引入一个全局的容器地址池组件接管对 Pod IP 的分配来实现。建立 StatefulSet 的时候,把分配给它的 IP 记录好,哪怕 Pod 更新的时候被删掉,IP 还给保持住不释放,等它从新建起来以后,若是名字跟原来那个是同样的,就把这个 IP 从新分配给他。
工程化,咱们研发容器化中间件,相对于第一代基于虚拟化的中间件,由于重用了 Kubernetes 内置的一些概念以及它在运维、控制上的一些机制,使得咱们去研发相同的基础中间件,研发代价可以大幅度减小,这个体如今代码比第一代基础中间件要减小不少,固然这个代码减小也是有代价的——开发人员必须很是了解 Kubernetes Operator 这套开发框架,必须得深入地理解 Kubernetes 声明式编程的概念,他才能写出来。
在质量保障方面,咱们作了两个事情,第一个就是混沌测试,就是故障测试,基于开源的 ChaosBlade 去模拟 Kubernetes 资源故障对中间件服务的影响,另外咱们也借助 Kubernetes e2e 测试框架来确保运维人员可以模拟各类中间件实例的生命周期操做是否正常。
还有一点,要作中间件实例生命周期管理,须要作监控、告警,不少状况下它的 UI 都是有共同之处,UI 的使用模式都是同样的,这是咱们设计的一个前端页面渲染,渲染引擎使得用动态表单机制可以很快地开发控制台,后端经过配置一下就能够实现控制台业务的开发能力,这样使得研发代价更小。
性能优化,咱们采起了一些策略,使得容器化中间件的性能基本接近于它运行在物理机上的水平。咱们在 CPU 开了性能模式,下降唤醒延迟。在内存方面,咱们关闭 SWAP 及透明大页,调优同步内存脏页回写阈值,这些都是参数级的调优。
I/O 方面使能内核 blk-mq,增大预读缓存。还有一个比较重要的就是网卡中断,咱们将物理方法中断跟容器的 veth 虚拟网卡中断处理跟 CPU 给隔离了,确保系统性能不发生抖动。
NUMA 也是咱们优化的一点,这在高负载上面体现得比较明显。咱们使得容器部署感知 NUMA 拓扑,将 Pod 尽可能的分配在本地的 NUMA,尽可能不要让一个 Pod 跨 NUMA,以避免带来比较大的 CPU 缓存的开销。
第一代中间件的一个缺陷是不可以去往外交付。去年咱们作了容器化中间件这个产品,名字叫轻舟中间件,具有基础中间件的标准能力。在接入层咱们也增长了一些能力,由于咱们基于 Kubernetes 来作的,运维人员甚至能够经过 Kubectl、YAML 文件就能够运维中间件。中间件服务层,咱们实现了 7 个基础中间件服务,这些中间件基本上具有了前面提到的核心运维能力。
总体上中间件基于 Operator,可以跑在任意 Kubernetes 集群之上,底层的资源无所谓,公有云的虚拟机能够做为 Kubernetes 的 Node,云盘能够做为 Kubernetes 的存储。另外,咱们也容许社区基于 Operator 开发的一些中间件在咱们的平台上跑。
将来展望
技术是为业务服务的,中间件最大的痛点是运维,要把它作到托管的云服务去解决,而容器技术的优点使得中间件容器化成为实现中间件云服务的最佳实践。在实现上须要 Operator,须要有更加云原生的模式来把容器化中间件给研发出来,固然对开发人员的要求也很高的。
将来的计划有两点,第一,咱们如今的容器化中间件平台能够跑在任意 Kubernetes 上面,可是咱们仍是要作到跑在 Kubernetes 发行版上,如 OpenShift、Rancher 等,但愿容器化中间件这些 Operator 也能跑在上面,可是须要作一些兼容。第二,咱们总体是想建设云原生操做系统,中间件是其中的一个负载,我为何不把中间件的负载和无状态应用负载实现混部?这样能够给公司带来更高的一个资源利用率,能够下降成本。
谢谢你们!