查看原文
其他

我的容器集群优化之路

2018-03-01 钟成 K8sMeetup

 | 为  |  容  |  器  |  技  |  术  |  而  |  生  |


钟成 / 华为 CloudBU 资深工程师


讲师介绍:

复旦大学物理系毕业,zlog 日志函数库作者,曾工作于网易 PaaS,从 2014 年开始设计研发容器集群和产品,是云计算和 PaaS 领域专家。


讲师:钟成

校对:夏天


想看本次在线课堂回放,点击文末“阅读原文”。对话框回复 “2” 获取本文 PPT(PDF 版)。



我今天演讲的这个主题是“容器集群优化之路” ,这个主题也是我在华为 2016 年到 2017 年这段时间的一些工作。我在中间积累了一些经验,也有一些教训。希望与大家做一个分享。这个分享主要是关于社区及来源技术相关的部分。今天我,大概会讲三个部分,一个是容器集群的基本架构,第二个是我们做的一些性能优化的实践,第三个是经验总结


容器集群的基本架构


容器集群系统架构图解

在谈容器集群优化之前,我先简单介绍一下我们做的这个容器集群是怎么样一个技术栈,或者是怎么样一个架构。


大家可以看一下这幅容器集群系统构架图,它的底层是一个 IaaS,可以是 OpenStack,可以是 VMware,也可以是 FusionStage。再上面一层是它的一些点,就是这些 IaaS 创造出了很多节点,然后在节点左边是一些平台的管理。就是说我去管理这套系统,那么中间这块就是我们说的 PaaS 这一层,就是资源管理跟调度这一层。它就是由这么几个部分组成,一个是 ETCD,然后是 Kubernetes 这部分,还有容器网络和镜像仓库等等。


然后中间可以看到它下面那一层,就中间那一层是 Kubernetes 这些,再往上面还得有一些微服务的框架,中间栈,然后还有这些编排的工作。你对外可能有开发流水线,API 开发网关,那这个就是一个典型的 IaaS——PaaS—— SaaS 这样一个典型的三层架构。然后社区所覆盖的范畴,其实只有中间的一部分,就是左边我用红框画出来的这部分。这部分是社区做的那些事儿,我的工作其实也主要用在这部分上面。


常见问题

从整个系统的角度去讲,我怎么去看待这个系统的性能瓶颈,或者说整体系统的性能问题?因为这是一个非常复杂的系统,我就以这下图几点说明吧:



性能优化实践


我们应该怎样对这样的系统进行优化呢?我们都知道,要想解决一个复杂的问题,最基本的思路就是把这个问题进行拆分。对于这个问题,接下来我会通过数据流在系统当中的流动方式来对它做一个拆分。


拆分三个子系统

我把整个复杂的系统分成三个部分:

  • 控制子系统负责管理控制指令的下发和执行,包括 K8S 和创建容器。

  • 数据子系统是 PaaS 上面所运行的这些应用程序对外提供的网络负载。这个网络负载比较典型的例子就是:一个终端用户发一个请求进来,这个请求通过负载均衡,经过容器网络,到最后的 Pod 上面去,这就是一个数据子系统。

  • 监控子系统,负责告警数据的采集,是对这个系统的可靠性跟采集维度的衡量。


如果用人体的器官来比喻的话,可以说控制子系统就是人的运动神经,数据子系统是人的肌肉和血液,监控子系统则是人的感觉神经,负责整体的感受跟反馈。


我的目标就是在这三个子系统中对性能做新的优化。这个拆法的好处在于,这三个系统互相干扰较小。控制子系统不会影响到数据子系统。数据子系统也不会影响到监控子系统。


你可以把这三个子系统看作处于三个不同的维度上,他们相互独立、互不干扰,非常适合各个击破。当然这个拆法可能不是最优的。但我想把这个拆分思路引申得更通用一些,假定说你要优化一个更加复杂的软硬件系统,你也可以试着去对它进行拆分,拆分的原则是:这些拆分部分所处维度越独立,互相干扰越少越好。


业务场景构建


拆分完了之后,不能分完就结束了,还得合。那合的过程是啥呢?我需要有一个真实的业务场景。对于这套系统我构建了这么一个场景,这个场景就是在这个系统当中,我可能需要部署多少大的软件包,然后这个软件包会有一些项目模板之类的东西,每个模板会有多少大。然后这个系统当中,大概会有多少个节点。可能是 1000 个节点,那一个 Pod 一个实例,这个其实可以大致地规划一下。包括有怎么样的软件包,然后有多少个 VM,这些相当于是我对整体工作负载的预估吧。


这个预估完了之后,我就需要把这个预估假想的场景放到这个系统上去跑。如果说有条件你最好真实地去布。拆分到这几个子系统里面去,你就可以看出来部署当中,整个流程是怎么样的。启动之后,这些 Pod 对外提供服务了,它又是怎么样一个过程。这些关键路径你可以大致地把它画出来。


画出来之后可以去看,在这三个子系统上面,一些关键指标大概是多少。根据这个画的图纸,大致上就可以估测出中间这个核心的调度组件 K8S,它的调度速度、吞吐,要大于 50 Pod 每秒,然后仓库要支持 300 个并发下载。数据面、容器网络的 TCP 性能损耗要小于 5%,那监控面和告警处理能力就必须超过 100 条每秒。通过对一个场景和面的分拆,最后把指标落到了每一个组件上面。


子系统指标

为了落到组件上面,我还要说说这个指标的选取。要想选取几个比较好的指标,有时候要靠你自己对系统的认知。你对这个系统的知识认知越多,你就越能知道用哪几个指标能比较好地反映这个系统的状态。


比如说我选错了一个指标,我不选 Kubernetes 调度速度超过 50 Pod 每秒,而在选它的调度时延时,选了每个 Pod 调度时延在 5 秒以内,那这个指标就不能完全真实地反映这个系统的情况。


假定说每个 Pod 调度时延的确是在 5 秒以内,但是这个时候有 100 个用户,可能会有 100 个用户并发地进行部署。这种情况之下,虽说每个 Pod 的时延都是小于 5 秒,但是,由于调度器是串行的,这就会导致最后一个用户的调度时间是 5 秒乘以 100,就是 500 秒。也就是说最后那个用户他可能要等将近 10 分钟,他的 Pod 才能被调度。这就造成系统给最后这个用户的体验不够好。因此指标的选择一定要有代表性,还得尽量少。


指标越少你后面去做实测,进行反馈等等,这样的成本就会低。如果你针对某些比较少的指标去做测量,去做优化,再去回归,这样你的成本会比较低一些。如果说你定了100 个指标,这个系统每个指标都需要人看,但是首先人可能没有这么多的时间跟精力去处理这么多指标。第二个如果这 100 多个指标要去抓的话,就不能抓住这个事情的主要矛盾了。对整个系统进行完优化分析后,就可以认为,基本上已经把这个系统的一些关键的性能分解到各个组件上面去了,然后就可以开始针对这些单组件进行优化。


优化测试 & 工具

这里其实还有一个简单的验证跟测试的过程,这里可能就先不讲。假定说我要优化一个组件,那我要怎么操作呢?我们去优化一个组件通常会用到一些工具。假如说你已经有个指标了,那我具体应该怎么去进行优化?


优化的第一步,要先进行测量。我这边可以先给大家介绍一些业内常用的工具。譬如说普罗米修斯(Prometheus),它就是一个容器化部署的监控兼性能测量工具。它做的事儿是啥呢?就是在上图左边这些组件里面,我都埋了普罗米修斯的 SDK,这些 SDK 它最终会把所有指标通过一个 http 的 restfulAPI 暴露出来。暴露出来之后,普罗米修斯的 Server 会去作为一个 http 的 Client 去抓取,抓取这些组件里面的这些指标,然后在这边进行统计跟汇总。


关于抓取,在这个系统当中有个比较关键的要点,就是说它为什么是右边的监控工具去抓取这些组件的这些关键指标,而不是反过来,左边有了指标推过去,左边是 Client,右边是 Server,去做请求呢?这里有个比较关键的原因,就是说对于一个组件来说,作为一个 Client 频繁地对外发起请求的话,这个发请求的过程本身也是需要耗费非常多的 CPU 资源、网络资源,还有 IO 资源。这样会为了测量性能,而引入了一个性能瓶颈。


为了把这个过程问题减到最小,普罗米修斯的设计把所有数据都放在这些组件的内存里面,然后 Server 抓取这个指标。就是被测组件对外提供的这个 API 里面,只是一个最简单的 http 的页面,这个页面它放的只是一些统计的数值。


也就是说这个数据在左边的 SDK 里面已经经过了一些处理,处理完后,右边只要把最小的这个数据量抓过来就可以了。这种测试方式的好处是,可以自定义一些指标。程序员如果猜测这东西可能成为瓶颈,就可以把它塞到普罗米修斯的 SDK 里面去。然后右边先把它抓出来,继续进行测量跟分析。对!这就是自定义的好处。


指标的编码实现

讲到自定义的好处,并不是对前文“指标要少”讲法的推翻。如果说我定了整个系统大的指标之后,在里面我还可以根据组件代码的分段,把它拆分成几个小的指标。这个拆分是每个组件里面自己做的一件事情了。指标并没有增加,而这个过程还是对“拆——合” 的一个重复。



OK,那么简单介绍一下这些指标吧。这里我演示一段代码,这段代码就是普罗米修斯提供的指标类型。它包括四种:一种是计数,第二种是测量,第三种是直方图测量,第四种是概要测量。关于他们的区别建议大家去看官方文档。



直方图测量其实是比较有意思的,通常计算一个请求的时延有两种方法:


  • 一种做法就是通过指数的分区。就指数的这个区间,把这些请求都落在这几个区间内,然后统计,在这些区间内大概是多少。

  • 另一种方法是算它的平均请求时延。最简单的就是如果有 100 个请求,去算它的平均请求时延。但是平均值有时候并不准。这就需要把这 100 个请求从最慢到最快进行排序,然后看第 90 个请求的时间是多少,第 100 个请求的时间是多少。这里有很多种统计学方法,但这个方法是我个人比较喜欢的。


这里我就是用普罗米修斯对 K8S 这个系统进行一些测量,这个测量就包括了 CPU 的消耗,然后我可以按照组件维度、按照机器维度去找平行点,然后我还可以一边做场景测试去显示这个指标。还可以对这个请求进行分类,就是 Verb 进行分类。这里就是几个案例(上图右侧)。我就是按照应用跟进程来计算内存的可用量,对每个实例按照角色来显示 CPU 的波动幅度。


数据挖掘,设计表达式获取因果关系

以上这都是一些我的挖掘跟设计的过程。这过程需要你自己去猜测,如果你认为这段代码引起了系统的一个瓶颈或者说问题,那你就可以想,是否可以去通过某个统计数值把这个因果关系给挖掘出来呢?


这其实有点像一个大数据,或者说数据分析的过程吧,这个过程其实很有意思。当系统有多个维度需要去测量指标,因为你最终要去做优化,所以你必须自己去发现究竟是哪一段代码引起了整个系统的瓶颈。


这个对应关系其实是一个非常多维的一个关系,这有点像警察破案抓小偷一样,你要在几千万行代码里面,去找到底是哪段代码引起了这个系统的瓶颈。在这过程中你要去设计这些表达式去获取这个因果关系。


光是靠自己看代码,去推测究竟哪段代码引起了系统问题,是不切实际的。任何做过系统优化的同学可能都知道,你自己看到的代码,你以为它是性能瓶颈,但很有可能它并不是,你必须设计一个实验去证明,它的确引起了系统的问题,就是说你得把这个因果关系,通过这些工具把它建立起来。


吞吐和 CPU

OK,我这边还有其它的一些方法,大家也可以看一下。比如说我以 K8S 为例,我创建一个 Pod 主要经历过四个阶段,这四个阶段是 Create、Pending,然后是 Waiting、Running,这四个阶段这里是创建了 10 万个 Pod,如果说这 10 万个 Pod,并发地压下去,压下去之后,可以看它在哪个阶段是最慢的,这样就可以看出哪个代码行有问题。这是因为这些阶段是针对开发系统当中不同组件所做的工作。

测试 A

假如我一开始去往 apiserver 里面写数据,那它是 Create 的,就是这个红线,它的上升速度其实取决于 apiserver 和 ETCD 的性能。然后接下来是 Pending,Pending 有两种可能,一种可能是我这个 Pod 还没有被调度,就是 Pending 包括 Pod 被调度,第二个是 Pod 还没有被从远端的容器拉取过来。


为了排除镜像拉取的干扰,在测试中,可以把所有的镜像在每台机器上我先预下载,或者说我用一个非常小的镜像去做这个测试,在做这个测试过程当中,我就看这条偏绿的线,它可以表现 Pending 的程度,如果说调度器速度足够快的话,我们可以期望这个 Pending 的 Pod 数量是非常小的,就基本上为零,那这里它还没有贴到零的这条横线上面,还没有贴到横的这条线上面。那么 Waiting 可能是这个 Pod 正在启动当中,从 Waiting 这个指标,我可以看出,在 K8S 里面这套系统里面的响应速度。


那么最后再到这个 Running,Running 就是一个结果,从上图里面可以看到。如果说这个系统真的是非常理想的话,调度器的速度是非常快的,也就是说绿线基本上贴到底,然后下面这条 Waiting 的这条线它基本上也得贴到底。那我希望我这个系统创建,它真的把 Pod 跑起来的速度,跟我往这个系 apiserver 发请求的速度是一样,也就是说最后 Running 那条线最好是能够跟 Create 的这条线基本上是重合的。如果说你在这个图上面能够看到这样一个曲线特征的话,就可以认为这个系统已经达到了一个最好的优化状态。


以上就是你应该如何解读这个图,然后根据这个图,看是哪个组件是瓶颈,然后再去根据组件再去做优化的全过程


测试 B


上图是一个 Go 语言的 pporf 的图,它是用来统计 apiserver 的 CPU 消耗的。这个图,也是 Go 语言的一个通用工具。测试 A 和 B 使用的,其实都是通用工具,社区里面都有。


测试 A 那个是社区里面的 density 的测试,测试 B 则是 Go 语言官方的一个功能,它可以测量每个组件。测试 A 时我分析到组件,而测试 B 中我拿这个组件去看它 CPU 耗在什么地方。测试 B 不是一个真正能够反映系统瓶颈的一个图,它只能告诉你,在这个系统当中哪段代码耗的 CPU 时间最长,这个 CPU 时间长跟我这个请求在这里是否产生了阻碍,其实并没有绝对的因果关系,但它可以帮助你去排除一些问题。


其实社区基本上已经把测试 B 这幅图里面大部分需要优化的东西都优化掉了,包括那个就它们会发现拆包跟解包,你会发现 json 的一个序列化和反序列化可能耗的 CPU 时间最长。这个可能在一开始看 K8S 的代码你是不可能发现的嘛,其实这些优化点都是通过挖掘这些指标,然后从这些图里面把它找出来的。


如何搭建大规模集群环境

如果要进行大规模的优化,你最好能够去搭建一个大规模的集群环境,但是现实世界,你要搭建一个大规模的集群环境是非常困难的,你不一定有那么多资源。为了解决这个问题,可以通过软件来解决,怎么解决?就是写一个模拟器。


这个项目就是社区里面的 Kubermark 项目,它使用一个容器去模拟单节点上面的 Kubernetes 和 Kube-proxy 的软件,它可以达到 1 比 20 的模拟比。就是我用一台虚拟机,可以模拟 20 台虚拟机对 apiserver 端产生的压力。在测试当中,我们可以用 500 台虚机去模拟一万台节点产生 apiserver 的压力。


模拟器是一个比较好的一个方式,它的优点在于它可以替代系统当中一部分的组件。如果我认为这个系统当中的瓶颈并不在于 Kubernetes,也不在 node 节点上面,而是在 apiserver 的级别。我就可以使用容器进行模拟,对 apiserver 产生压力。我可以随意替换系统当中的任何一部分,但是这个替换,因为我去写模拟器要耗费一定的时间跟精力,所以我基本上会有针对性地去针对系统当中的瓶颈组件,把它作为我的目标组件,那周边的组件,我可以去写模拟。


软件业之外的工业实践

实际上我再多说一句,模拟器不仅仅是在计算机领域,在任何一个工业系统当中也有广泛应用。譬如说,大家知道都 SpaceX 的技术,有些人对它进行分析就通过写模拟器软件,它达到了可以跨 20 个数量级的模拟,这个是个什么概念?


就是说我可以从一毫米,我记不起来是一毫米还是一微米了,然后乘以这个 10 到 20 次方就是几千微米,就在这个量级上面,去模拟整个系统的状态。这也是为什么 SpaceX 它们能够造出能造出可回收利用的火箭,而 NASA 造不出来,因为它可以用软件去解决问题,而不需要直接去测试物理世界当中的实体。而如何去写这个软件,非常考察抽象能力,怎么把这个系统当中最关键的那些指标抓住,就是把这个能力抓住,OK,这就是一个例子。这边有一个 B 站的视频推荐给大家。

(https://www.bilibili.com/video/av3747884/)


分布式优化流程改进

那么回过头来讲,我们做了一些优化之后,不仅仅是针对这个组件,也是针对我们自己的一个优化的流程,我可以把我自己的工作也进行优化。传统的优化方法可能是代码日志打点,然后下载日志,然后分析日志数据,然后猜测瓶颈点,然后再重复,那这个整一个循环是非常非常慢的,可能要一两天才行。



那如果说变成右边这个循环,就是先把数据发布在这个端口上,然后去容器化地启动,然后同步抓数据,然后长期保存,然后去找瓶颈点,这样一个反馈循环就非常快,可能在一两个小时之内,我就可以把系统的场景跟过程就经历过一遍了。


其实在优化过程当中,这种例子很多。之后来反思优化流程,本身可不可以再优化?我把这个流程称之为“优化的平方”,它的目标就是降低我们迭代的时间成本。因为在现在这个时代,程序员的时间比机器的时间更加值钱。就是说在以前大型机的时代,大家都要抢着用计算机,去抢计算机的使用权。但是在我们现在这个时代,即使是一只手机它的计算能力也非常强,所以说要尽可能地把工作自动化,来节省人的时间。


优化成果

那这个是我们后面的一个优化结果,这是个阶段性成果,分支数据是 10000 个节点(下图 1000 是笔误),然后的部署吞吐可以超过 100 Pod 每秒,然后端到端时间可以小于 2 秒,这些都是一些指标,就说最后做完这些步骤之后,就可以看出来我这个指标的确比社区版的要强。这里还有一个测试环境的问题,就是我当时做的时候,社区还没有做超过 5000 节点测试嘛,现在社区已经赶上来了,所以说这也是一个很动态的过程。所以说性能在一步一步地持续优化。


经验总结


最后我说几句总结,这些总结其实前面大致上也都讲了,怎么去分析、测试、开发、工具链,然后再去优化自身,把自身的时间成本降下来。我把这个过程称之为另外一种意义上的 DevOps,就是 Develop Optimization,即“开发者的优化”。


这个优化并不依赖于测试人员,也不依赖于运维人员,当然开发人员需要跟他们合作,从场景当中去把这个数据拿出来,然后再去做一个完整的流程。关键生产力还是在开发者上面,开发者必须一个人完成整个过程,虽说这是一个很大的系统,但是作为开发者,应该完全有能力去分析这个系统,然后去一步步地去把这个系统做一个优化,不假外物。



本文整理自 2 月 1 日,K8sMeetup 线上课堂《容器优化之路》钟成老师讲课语音。


推荐阅读:

编排的艺术| K8S 中的容器编排和应用编排

Kubernetes 1.9 |可扩展准入机制进入 Beta 阶段

如何用 Kubernetes 管理超过 2500 个节点的集群

容器化 RDS|计算存储分离架构下的 IO 优化

江湖路远,Kubernetes 版图扩张全记录

干货| 详解 Kubernetes 的稳定性和可用性

K8S 实用|  献给初学者的 Spinnaker 指南



END


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存