Hi!下午好!欢迎访问互联网
当前位置:主页 > 软件

听云APMCon采用容器部署应用的性能考

时间:2019-01-13 15:19:02| 来源:| 编辑:笔名| 点击:0次

听云APMCon:采用容器部署应用的性能考虑

中国应用性能管理行业盛宴2016中国应用性能管理大会(简称APMCon2016)于8月18日至19日在北京新云南皇冠假日酒店隆重召开。APMCon由听云、极客邦和InfoQ联合主办的作为国内APM领域最具影响力的技术大会,首次举办的APMCon以驱动应用架构优化与创新为主题,致力于推动APM在国内的成长与发展。

LinkedIn Staff Software Engineer 庄振运于基于云架构的性能优化专场发表了题为《容器里面乾坤大:采用容器部署应用的性能考虑》的演讲,现场解读了LinkedIn使用容器部署中的一些经验,重点介绍容器部署中几种影响性能的场景,比如内存压力场景,JVM在容器内运行的场景,不同容器互相干扰的场景等。

以下为演讲实录:

庄振运:大家好,我叫庄振运,今天很高兴能在这里跟大家分享关于容器部署性能方面的知识。今天我首先会介绍一下问题的背景,然后讲几个用容器部署时的性能陷阱,后面就是怎么解决这些陷阱,最后会有一点总结。

首先简单自我介绍下,我现在在美国的LinkedIn工作,如果大家关注就应该知道,应该说我是从Microsoft来的,因为Microsoft已经和LinkedIn合并了。我的专长是Performance engineering方面,十几年来一直都做这个方面。Performance工程师跟开发这个领域不太一样,基本上要求各方面的知识都要了解多一些,因为真正的性能出问题的时候,可能在任何一个层面,我这里列了几点,大家有兴趣可以多了解这些东西,可以帮助大家debug。括号里面是我自己亲自做过的一些东西,上面是应用层,往下如果用Java的话有Java的JVM,现在容器用的也很多,这是在OS之上的,OS本身还有很多机制,像内存管理、CPU调度等都是跟性能息息相关的,再下面就是络、硬件,像Storage等等。个人的专业之外还喜欢中国的文化,听起来很奇怪,在美国搞IT却喜欢中国的文化。

言归正传,今天要讲的问题背景就是Docker或者cgroups,大家知道Docker现在很流行,但是Docker的基础是什么?是cgroups。cgroups是用来分离资源使用的,比如如果有一个应用程序,我想限制CPU的使用,目的就是我可以部署好几个应用程序在一台机器上,它们不至于互相干扰。Docker是在cgroups之上加了package,就是怎么打包,但是它的性能方面基本上是由cgroups决定的。CoreOS也是同样的道理。

在APM的背景下有三方面的相关性,因为使用Docker和cgroups,APM也要相应的做一些调整。第一,因为程序是在cgroups里面运行的,所以有很多新的、跟容器有关的metrics。第二,如果出现性能问题,也需要考虑container这块,因为有的时候性能是由它造成的,不是由JVM,甚至不是由OS导致的,就是由cgroups本身造成的,这就是为什么我今天能有机会分享一下我们在这方面做过的东西。同样如果发现有些资源的使用超过量,APM要做一些alert。

今天我只讲cgroups不讲Docker,我不会告诉你怎么使用Docker。cgroups方面我只讲Performance,具体怎么设置cgroups大家回去可以自己看。cgroupsPerformance本身又是一个很大的内容,根据我们的经验它现在能分离出差不多十种资源,像CPU、内存等等,每一种都是有一些性能陷阱的。今天我只讲内存方面,这已经是非常大的题目了。关于这个题目我前几天发了一个LinkedIn的blog,大家可以看一下。

既然大家很少有人有cgroups的背景,我现在讲一下,cgroups已经是标准的linux内核机制了。大家百度或者Google一下怎么用,很容易设置的。这个一开始是Google开发的,后来整合到内核里面了。它有两个版本,V1和V2,现在大家用的都是V1,V2刚刚开始发布。一个Linux系统一开始就算你没有设置任何的东西,也有一个rootcgroup。rootcgroup就是你机器上运行的所有的进程,而且默认都在cgroup里面。但是你可以设置一个regularcgroup,这种普通的cgroup你可以告诉它说你最多只能用10G的内存,最多只能用2核的CPU等等,然后你把想要限制的进程或者应用程序放到这个cgroup里面,告诉它的进程ID就可以。这样以后所有在cgroup里面的应用程序,一起共享你给它定的限制,比如说10G,就是这个道理。你可以设置多个cgroup,每个cgroup可以有资源的限制。通过这样的限制,cgroup不至于互相干扰,不至于影响别人,你得到你要的份额,但是也不要能超过这个份额。

现在稍微讲一下cgroup的内存使用,如果说10G,实际上是分三块,一个是用户空间的内存使用,有的时候也叫匿名空间。另外一块是内核空间的page cache,如果你读一个文件或者写一个文件,数据都跑到page cache里面,这两块加上一块没有申请的备份,三块加在一起才是你的limit,一共是10G,这是基本的原理。Linux系统都有一个swappiness,swappiness所有的cgroup一起来共享它,你可以配置swappiness的值,这个值大家可能不太熟悉,操作系统回收内存的时候基本有两个途径,第一个就是丢page cache,第二个就是swap。怎么控制用哪块回收呢?就是用swappiness参数设置,0到100之间,100就是告诉操作系统去swap不要丢page cache,0正好是相反的,这些都是基础的知识。

下面讲一下cgroups使用时遇到的性能相关的陷阱。在讲之前我会提供一些跟每个陷阱相关的数据。我的setup是这样的,硬件是简单的Intel Xeon E,12个物理核,64GB内存,OS是RHEL7,总共有16GB的swap,swappiness值为1,为什么这么设置我后面会讲。然后workload我是用Java application,还有其他一些cgroup的统计,比如swap,就是cgroup本身有多少内存是被swap出去的,还有rss,就是这个cgroup到底占用多少的物理内存。free是操作系统提供的一个工具,就是看还有多少的空闲内存。

首先我们来看第一个陷阱,使用cgroup的时候你给它一个内存限制,比如说10G,这个10G不是说马上就给你的,它跟虚拟机很不一样,虚拟机是10G马上就给你,别人绝对用不到的。而这个cgroup,大家熟悉一个词叫且行且珍惜,这个就是且用且给你,你用1G就给你1G,用2G就给你2G。但是有一个问题,如果一个cgroup,有10G的limit,现在用了2G,想要再申请1G,操作系统能不能给呢?操作系统如果有空闲内存当然会给,但如果没有就只能释放其他的内存满足cgroup的请求。释放内存的时候又分几种情况,一种是丢page cache,如果page cache是clean的,就是说不需要写到硬盘上,那很简单直接丢掉就可以,这个是很快的,不会对申请内存的应用程序造成任何的影响。但是如果page cache不是clean的,是dirty的,这时候需要先写在硬盘上,就是比较困难的事情了。因为硬盘写的时候很慢,应用程序需要等待,只有等待之后拿到内存才可以继续往下。进行swap操作也是一样的,如果操作系统没有空闲内存,那申请内存的cgroup就会造成性能影响。比如我们简单做一个实验,随便一个场景申请16GB的内存需要20秒,非常慢,这时候基本就停滞了,但具体是几秒取决于很多因素,比如硬盘IO怎么样等等。

接下来我们看第二个陷阱,我讲的内存的限制为10G,其实包括两部分,一个是匿名内存,一个是page cache,你每次读一部分文件或者写一部分文件都会用到一定的page cache。如果你的应用程序在机器上,你要move到cgroup里面,你首先要决定到底给它多少内存合适。这个估量有多少内存呢?其实不是那么简单的事情,匿名内存很容易估算,但是page cache是很难确定的。要点就是如果你没有给cgroup足够的page cache,会造成什么结果呢?第一,page cache就会不够,不光是你给它的时候不够,还有就是cgroup申请匿名内存的时候,因为有优先级会把原来有些page cache强制改出去,最终的结果是一样的,你的应用程序page cache不够。如果你的应用程序有很多文件操作,那么性能肯定会受影响。

这是个实验的例子,我画了两个图,第一幅图就是讲占用了多少物理内存了。我方便大家看,分三步讲解,第一步就是setup是什么样?比如有一个regularcgroup20GB的limit,已经用了13GB的page cache,我做了什么事呢?我的cgroup再申请16GB的匿名内存,然后发生什么事情了?结果有两幅图,第一幅图是我发现cgroup的rss物理内存增加了16GB,为什么?因为我申请了16GB匿名内存,这个很正常。第二幅图就是第二个结果,page cache下降了8GB,这个差是因为有其他地方的page cache也下降了,所以才能弥补这16GB。

第三个性能陷阱,操作系统可以回收所有cgroup的page cache。我刚才讲的page cache属于每个cgroup的内存限制里面,举个例子来讲。假设你是cgroup

听云APMCon采用容器部署应用的性能考

,你有10G的limit,你现在只用了2G的匿名内存,2G的page cache,你还有6G的份额,你会觉得我才用了一点,不应该把我的page cache丢掉,但实际不是这样的。操作系统维护所有的page cache,如果别人需要新的page cache,操作系统决定丢page cache的时候会任选一个,根本不考虑你到底用了多少,也不考虑这个page cache属于谁,完全是盲目的。也就是说你作为一个cgroup,你才用了4G,你还有6G的空间,那很不幸你的2G的page cache也可能被干掉,这就会造成性能的影响,你的性能是不能预测的,因为别人会把你的东西偷走。

我做了一个实验,我有一个cgroup20GB limit,用了差不多4.8GB的page cache,另外的cgroup也开使用page cache,向操作系统申请。操作系统就决定把前一个cgroup的page cache丢掉2.6GB,同时rootcgroup也降低了5.5GB。这就告诉我们操作系统其实内部只有一个,大家如果对container稍微熟悉一点的话,page Cache有一个list,丢的时候是从list里丢最老的,这个最老的当然属于任何一个cgroup,它看到之后就丢掉,不管你是属于谁的。

第四个陷阱有点类似,我刚才讲过了page cache,另外一块就是匿名内存。道理也差不多,它不会被丢掉,但是会被swap出去到硬盘上。这个swap过程也是由操作系统完全维护的,它想swap哪个匿名内存,也是不考虑它的所有者,不考虑它属于哪个cgroup,完全是根据内部的算法。

大家看这个实验稍微复杂一点的,有两个regularcgroup都用了匿名内存。其中一个regularcgroup开始用新的内存,操作系统没有新的内存了,怎么办?它会swap掉一些匿名内存,这个swap分三块,一块是rootcgroup,另外两块是regularcgroup,都会被swap出去。我们可以看到,第一个cgroup的swap size在不断的增加,因为它被放到硬盘上去了。对于第一个cgroup的rss,因为它的内存被swap出去了,有一块被放到硬盘去了。第二个cgroup和第一块很像,也是swap越来越多,占用物理内存,它在申请匿名内存,在增加。第三个是rootcgroup的swap size也在增加。我再重复一下,重要的事情多重复几遍。如果操作系统决定swap匿名内存,它不会顾及你到底是哪个cgroup,它想swap就swap,没有人限制它。

第五个陷阱稍微有点不太一样,是关于虚拟内存地址空间的,大家可能对这个不是特别熟。这里有两个概念是有区别的,一个是Resident set size,就是它真正作用了多少物理内存,第二个是Virtual Memory,占用多少虚拟地址空间,这个肯定比物理上稍微大点。top我相信大家都用过,非常普通的命令。下面的数据就是整个系统的统计数据,重要的我用红圈画了。现在几个进程,比如说第一个进程是Java用了12G的物理内存,差不多25G的虚拟内存。这个大家可以看到,它远远超过了12G的虚拟地址空间,这个大家怎么想?可能觉得虚拟地址无所谓,其实这个会导致你的应用程序启动不起来,甚至突然死掉,为什么?因为系统里面有很多的设置,比如 overcommit,如果没有打开,整个系统的虚拟内存地址空间是有限的,有一个办法取决于你的swap size,还有你的物理内存size。这个配置有64GB的虚拟地址空间,这64BG不是一个进程的,也不是一个cgroup的,是整个系统的。有一个问题,作为一个应用程序它用多了,用了63G,那别人只有1G,那别人如果想再启动一个Java,就可能启动不起来,或者突然想多要一点内存也会死掉,因为没有了。

大家说为什么拿Java说事?因为Java在这方面是比较讨厌的。首先我还是解释一下这几个概念,第一个是Java的JVM heap,有一个小的值,也有一个大的值。小的值就是初始值,大的值就是最多不会超过的值,像这个就是5G。第二个就是RSS,RSS就是占用多少物理内存,包括堆和堆外的一些东西,像perm、meta、direct都属于堆外的内存。第三个就是虚拟内存,我有点简化了,基本上你的RSS加上glibcmemory pool。

这个表格看起来挺复杂的,我简单讲几列,我的setup的heap是5G,12核的一个机器,glibc大家都知道,如果12核乘8就是100个thread,一个thread是64M,总共就是6个G的虚拟空间,这是最大值。像这个表左边第一个列,就是应用有几个thread,我发现这个JVM有24个thread,有23个是JVM本身的thread。看最右边的一列,总共用了8.3G的虚拟地址空间,这是真正的值,其他是根据简单的方程算出来的。大家看最后两列很像,我们的理解基本是对的。要点就是大家看最后这两行值,对于50个thread或者100 个thread,基本上用了差不多12G、13G的虚拟地址空间。为什么这个值有用?还是回到整个系统的限制,整个系统的虚拟地址空间有限,假设你是 64GB的总的地址空间,如果一个就需要12G,那么顶多就是5个cgroup,而且还没考虑别人,比如操作系统。也就是说你这个服务器不管多强大,只能5个cgroup,超过5个就不行。

我们刚才讲了五个性能的陷阱,那怎么解决呢?

1、第一个方案,把应用程序搬到cgroup里面的时候,要特别小心我们需要给多大的内存限制,这里面仔细想想还挺复杂的。一个是匿名内存,一个是page cache。匿名内存就比较简单,第一个它很准确,你用多少就是多少,一般来说会不断增加,这个最大值就是它需要的。第二个就是Linux有很多tool可以帮你看,比如top;但是page cache比较麻烦。第一你没有tool可以帮你看。第二它不是很准确的,里面又有很多问题,我举个例子,比如log,你的应用程序其实不需要这么多的 page cache,因为log写完了没人读它,丢掉就完了。同样大家应用程序启动的时候可能一开始读很多的文件,这个时候也会占page cache,同样的道理,程序运行之中其实不需要它们的。

我们提供的方法就是先把它放到cgroup里面,先放在一个测试环境里面,cgroup本身提供了很好的机制,帮你确定多少匿名内存,多少page cache。匿名内存有一个rss的统计,page cache我们是用active_?le进行统计的。

2、第二个方案就是我们叫per-touchingcgroupsmemory,既然cgroup申请内存的时候并没有拿到它的limit,那我就先拿到再说,比如说Java有一个AlwaysPreTouch,你用10G就直接10G装到内存里面,不会是且用且给你的那种状态了。再有就是把cgroupswappiness调成0,就会保护你这个cgroup的匿名内存不会被swap出去。

3、这是最重要的一点就是对rootcgroup进行严格的控制,讲到内存的问题归根结底就是整个系统需要更多的内存,为什么需要更多的内存?大家可能会想到我计划好一点,就两个Java,一个10GB,整个系统64GB肯定没问题,其实不是这样的,你的系统不光有你的应用程序,还有其他的东西,比如SSD,用户远程登录之后SSD来处理请求。比如你读一个很大的文件,你的SSD就会占用很多的page cache,比如你干别的事,就会占用匿名内存,这些都会放到rootcgroup里面去,所以这个完全难以控制的。要点是你对root里面的cgroup进程最好要控制一下,方法就是你把它放到单独的cgroup里面。但是仔细想想还有些小问题,你想创建一个大的cgroup,把所有的Performance放进去,还是每一个Performance放一个小的cgroup,各有利弊。

4、我们讲了很多虚拟内存的事情。虚拟内存在现在的cgroup中完全没有任何的限制,我刚才讲限制了十种资源,CPU、络、IO等,可是没有虚拟内存的限制,我估计以后会加上。要防止每个cgroup用太多的虚拟地址,因为会影响别人,整个系统完全就不能预测了。

大家可能会觉得奇怪,cgroups为什么这么弱,它一点都不弱,这就是它的设计,设计就是这样的。因为我们认为虚拟机器是比较慢的,启动比较慢,可能几十秒启动。但是cgroups的设置理念非常快,1秒就启动了,内存不给你也正常,给的话肯定就达不到1秒了。你既然不用,那就给操作系统用,大家知道操作系统是贪得无厌的越多越好,你用的时候我再给你,这是他的设计理念。

第二个是对system processes如何进行限制?我刚才讲的一点,你把它分成小的、大的cgroup或者几个中等的分别放到容器里,根据你们系统自己的使用情况,比如说我很少用SSHD,SSHD可能就不用管它了。

第三个,大家可能会觉得你今天讲的这些情况都非常的极端,我们根本用不着,其实也不是这样的。有一个原因,一个是极端情况真的会发生,你想想现在一个大公司会有很多的机器,就算千分之一的故障率一天也是一台机器。第二就是大家商业的SLA,一般定义的时候也是99.99%,就算有一次出问题也会影响你的商业的SLA。还有就是从APM的角度来讲,既然cgroups有这么多的特征,我建议加上这个虚拟内存使用。还有就是alert,如果有cgroup影响到别人,你要实时发alert。你要再做得好一点的,你对cgroup资源的使用,包括虚拟内存的使用也进行限制就好了。最后如果发现系统问题了,你脑子里要知道有可能是由这方面导致的。

简单的回顾一下,我今天讲的cgroupsPerformance完全是内存相关的,我认为cgroups有很多的性能陷阱,有些情况下会导致性能的问题,分几种情况,我们需要针对每种情况采取相应的措施。谢谢大家!