53

干货 | 携程容器云优化实践

 4 years ago
source link: https://mp.weixin.qq.com/s?__biz=MjM5MDI3MjA5MQ%3D%3D&mid=2697266625&idx=1&sn=8ecfac33e2f3f88176034b7cba560f53&chksm=8376faf5b40173e3f729d8569dfc030fbbc512a67f63b40bfbdd07651b1d08687aa647c15f07&mpshare=1
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

干货 | 携程容器云优化实践

王潇俊 携程技术 2017-11-10 10:00 Posted on



作者简介 

王潇俊,多年来致力于云平台及持续交付的实践。2015年加入携程,参与携程部署架构的全面改造,主导设计和打造新一代的适用于微服务的发布系统。同时负责基于携程私有云的兼容虚机与容器的持续交付平台。ROR狂热粉丝,敏捷文化的忠实拥趸。 

随着微服务架构的流行,把容器技术推到了一个至高点上;而随着Docker,K8S等容器技术的日趋成熟,DevOps的概念也再次热度上升;面对容器化的大潮趋势,各家公司都在积极地响应和实践,携程也在这方面做了不少工作,形成了自己的容器云平台。

从容器云的打造思路上,携程将其划分成了水上、水下两大部分:

  • 水下部分是指容器云服务的基础架构

  • 水上部分是指面向容器而产生的一系列工程实践配套

水下部分对Dev来说相对透明,而水上部分则会对Dev工作有直接影响,也就是DevOps概念里所提到“混乱之墙”所在的地方。所以只要水上、水下同时做好了,容器云才能真正落地,并符合DevOps理念的设想。

一、基础架构

容器云在携程主要经历了以下3个阶段:

Image

第一阶段,模拟虚机,通过OpenStack进行管理

在这个阶段,主要的目的是验证携程已有的应用是否能够在Docker容器下正常运行,并提升系统与容器的兼容性。这个过程中系统的主要架构还是使用OpenStack的nova模块,把Docker模拟成虚机的形式进行管理,除了应用实际运行的环境产生了变化,其他任何流程,工具都不变,从而使影响范围控制在最小。

第二阶段,实现镜像发布,使用Chronos运行Job应用

在这个阶段,主要的目的是通过镜像的方式实现应用的发布和变更。真正实现 Immutable Delivery,即一旦部署后,不再对容器进行变化。并且在这个过程中,架构从比较繁重的OpenStack体系中解脱出来。使用轻量级的Mesos+Chronos来调度Job应用,在这个过程中我们同时去掉了对Long Running的Service类型应用的支持,以方便测试在极端情况下调度的消耗,和整个系统的稳定性。

此时,整个容器云的架构如下图,

Image

实践证明这个架构在应对大量并发job的调度时,Mesos自身调度消耗过大,因为每启动一次job都需要拉起一个docker实例,开销客观。同时也证明在携程这样的应用体量下,直接使用开源Framework是无法满足我们的需求的,这也促使我们开始走向自研Framework的方向。

第三阶段,自研Framework

在这个阶段,我们主要要解决的问题有:

  • 同时支持Job与Service两种类型的应用

  • 为每个docker实例分配独立的IP

  • 支持stateful的应用

  • 完善容器的监控体系

此时的总体架构如下图:

Image

与第二阶段的架构不同之处:

  • 首先,重新封装了Mesos的Rest API层,使得对外提供的API更丰富(可以与其他已有系统结合,提供更多的功能),同时基于一些规范统一的考虑,收拢了一些个性化参数的使用。除此之外,独立抽象API层也是为了将来能够快速适配其他架构体系,如K8S时,可以做到对上应用透明。

  • 其次,对Mesos做了集群化分布,从而提高Mesos本身的可用性。

  • 最后,为了应对大量Job类应用的调度,采用了与long running一样的方式,将executor放置于容器内部。做到Job调度时,不重新启动容器,而是在容器内部调度一个进程。

说完了系统架构后,还有2个比较重要的问题:

网络

携程对容器实例的要求是,单容器单IP,且可路由,所以网络选项上采用的仍旧是Neutron+OVS+VLan这个模式,这个模式比较稳定,网络管理也比较透明。在实际对每个容器配置网络的过程中,携程自研了一套初始化hook机制,以通过该机制在容器启动后从外部获取对应的网络信息,如网段,或者Neutronport等,在配置到容器内,这样就完成了网络配置的持久化。大致的机制如下图所示:

Image

当然利用这个hook机制还能处理其他一些特殊的case,之后也会有提到。

监控

Image

监控分为2个部分,一块是对Mesos集群的监控。携程用了很多开源技术,如:Telegraf、influxdb、Grafana等,并做了一些扩展来实现mesos集群的监控,采集mesos-master状态、task执行数量、executor状态等等,以便当mesos集群出现问题时能第一时间知道整个集群状态,进而进行修复。

另一块是对容器实例的监控,携程监控团队开发了一套监控系统hickwall, 实现了对容器的监控支持。 hickwall agent部署在容器物理机上,通过Docker client 、cgroup等采集容器的运行情况,包括 CPU 、Memory、Disk IO等常规监控项;由于容器镜像发布会非常频繁的创建、删除容器,因此我们把容器的监控也做成自动发现,由hickwall agent发现到新的容器,分析容器的label信息(比如: appid、版本等)来实现自动注册监控;在单个容器监控的基础上,还可以按照应用集群来聚合显示整个集群的监控信息;

Image

自研Framework的动机

  • 轻量化,专注需求

开源Framework为了普适性,和扩展性考虑,相对都比较重,而携程实际的使用场景,并不是特别复杂,只需要做好最基础的调度即可。因此自研的话更可以专注业务本身的需求,也可以更轻量化。

  • 兼容性,适配原有中间件

由于携程已经形成了比较完整的应用架构体系,以及经过多年打造已经成熟的中间件系列。所以自研Framework可以很好地去适配原有的这些资源,使用开源项目反而适配改造的成本会比较大,比如路由系统,监控系统,服务治理系统等等。

  • 程序员的天性,改不如重写

最后一点就比较实在了,开源项目使用的语言,框架比较分散,长远来说维护成本比较大

自研Framework的甜头

正如前面所说,自研Framework能够很方便地解决一些实际问题,下面就举一个我们碰到的实际例子。

我们知道mesos本身调度资源的方式是以offer的模式来处理的,简单来说就是mesos将剩余资源的总和以offer的形式发送出来,如果有需求则占用,没有需求则回收,待下次发送offer。但是如果碰到下图这样的情况,即mesos一直给出2核的资源,并且每次都被占用,那一个需要4核的实例什么时候能拿到资源呢?

Image

我们把这种情况叫做offer碎片,也就是一个先到的大资源申请,可能一直无法得到合适的offer的情况。

解决这个问题的办法其实很简单,无非2种:

1、将短时间内的offer进行合并,再看资源申请的情况

Image

2、缩短mesosoffer的timeout时间,使其强制回收合并资源,再次offer

Image

携程目前采用的方案2,实现非常简单。

以上大致介绍了一下携程容器云的水下部分,即基础架构的情况,以及自研Framework带来的一些好处。关于k8s,由于我们封装了容器云对外的API层,所以其实对于底层架构到底用什么,已经可以很好的掌控,我们也在逐步尝试将一些stateful的应用跑在k8s上,做到2套架构的并存,充分发挥各自的优势。

二、工程实践

容器化的过程除了架构体系的升级,对原先的工程实践会带来比较大的冲击。也会遇到许多理念与现实相冲突的地方,下面分别介绍携程遇到的一些实际问题和解决思路。

代码包到镜像,交付流程如何适配,如何迁移过渡?

DevOps理念提倡“谁开发,谁运行”,借助docker正好很方便的落地了这个概念。携程的CI/CD系统同时支持了基于镜像与代码包的发布。这样做的好处是能够在容器化迁移的过程中做到无缝和灰度。

Image

能像虚机一样登陆机器吗?SSH?

docker本身提倡单容器单进程,所以是否需要sshd是个很尴尬的问题。但是对于docker实例的控制,以及执行一些必须的命令还是很有必要的,至少对于ops而言是一种非常有效的排障手段。所以,携程采用的方式是,通过web console与宿主机建立连接,然后通过exec的方式进入容器。

Image

Tomcat能否作为容器的主进程?

我们知道主进程挂掉,则容器实例也会被销毁。而Java开发都知道,tomcat启动失败是很正常的case。由此就产生了一个矛盾,tomcat启动失败,并不等同于容器实例启动失败,我们需要去追查tomcat启动失败的原因。由此可见,tomcat不能作为容器的主进程。因此,携程仍旧使用Supervisord来维护tomcat进程。同时在启动时会注册一些自定义hook,以应对一些特殊的应用场景。比如:某些应用需要在tomcat成功启动,或成功停止后进行一些额外的操作,等等。

Image

JVM配置是谁的锅?

容器上线后一段时间,团队一直被一个JVM OOM的问题所困扰,原来在虚机跑的好好的应用,为什么到容器就OOM了呢?最后定位到问题的原因是,容器采用了cpu quota的模式,但JVM无法准确的获取到cpu的数量,只能获取到宿主机cpu的数量;同时由于一些java组件会根据cpu的数量来开启thread数量,这样就造成了堆外内存殆尽,最终造成OOM。

虽然,找到了OOM的原因,但是对于容器云来说,却面临了一个棘手的问题。容器实例不像虚机,在虚机上,用户可以按需定义JVM配置,然后再将代码进行发布。在容器云上,发布的是镜像,JVM的配置则变成了镜像的包含物,无法在runtime时进行灵活修改。

而且,容器本身并不考虑研发流程上的一些问题。比如,我们有不同的测试环境,不同的测试环境可能有不同的JVM配置,这显然与docker设想的,一个镜像走天下的想法矛盾了。

最后,对于终端用户而言,在选择容器时,往往挑选的是flavor,因此我们需要对应不同的flavor定义一套标准的JVM配置,利用之前提到的容器启动时的hook机制,从外部获取该容器匹配的标准JVM配置。

我们也总结了一些对于对外内存的最佳实践,如下:

•     Xmx = Xms = Flavor * 80%

•     Xss = 256K

•     堆外最小800,最大2G,符合这个规则之内,以20%计

问题又来了,用户需要自定义JVM?

最终,我们将JVM配置划分成了3个部分:

1、系统默认推荐部分

2、用户自定义override部分

3、系统强制覆盖部分

允许用户通过代码或外部配置系统,对应用的JVM参数进行配置,这些配置会覆盖掉系统默认推荐的配置,但是有一些配置是公司标准,不允许覆盖的,比如统一的jmx服务地址等,这些内容则会在最终被按标准替换成公司统一的值。

Image

Dockerfile的原罪

Dockerfile有很多好处,但同时也存在很多坏处:

  • 无法执行条件运算

  • 不支持继承

  • 维护难度大

  • 可能成为一个后面,破坏环境标准

因此,如果允许PD对每个应用都自定义dockerfile的话,很有可能破坏已有的很多标准,产生各种各样的个性化行为,使得统一运维变成不可能,这种情况在携程这样的运维体谅下,是无法接受的。

打造“plugin”服务平台

所以,携程决定通过 “plugin”服务的方式,把dockerfile的使用管控起来,将一些常规的通过dockerfile实现的功能形成为“plugin”,在Image build的过程中进行执行。这样做的好处是,所提供的服务可标准化,并且可复用,还可以任意组装。比如:我们分别提供“安装FTP”,“安装Jacoco”等插件服务。用户在完成自己的代码后,进行image build时就可以单选或多选这些服务,那最后形成的image中就会附带这些插件。并且针对不同的测试环境可以选择不同的插件,形成不同的镜像。

Image

对于一个“plugin”而言,甚至可以定义一些hook(注册supervisord hook),以及一些可exec执行的脚本,从而进一步扩展了“plugin”的能力。比如可以插入一个tomcat的启停脚本,从而获取从外部控制容器内tomcat的能力。

Image

公司内的每个PD都可以申请注册“plugin”,审核通过后,就可以在平台上被其他应用所使用。注册步骤:

1、为服务定义名称和说明

2、选择服务可支持的环境(如:测试,生产)

3、上传自定义的dockerfile

4、上传自定义的可运行脚本

Image
Image

“Jacoco Plugin”的实例

Jacoco是一个在服务端收集代码覆盖率的工具,以帮助测试人员确认测试覆盖率。这个工具的使用有以下几个需求: 

1、需要在代码允许环境中安装Jacoco agent

2、只需要在特定的测试环境进行安装,生产环境不能安装

3、被测应用启动后,需要往Jacoco后端服务进行注册

4、测试过程中可以方便控制Jacoco的启停(通过tomcat启动参数控制)

针对以上的需求,定制一个“JacocoPlugin”的工作,如下图:

Image

1、通过dockerfile安装 jacoco agent

2、注册一个supervisord hook,在tomcat启动成功后向Jacoco service进行注册

3、利用一个自定义tomcat重启脚本,并在平台的web server上暴露api来控制jacoco的启停

这样,所有容器云上的应用在image build时就都可以按需选择是否需要开通 jacoco 服务了。

Image

利用这样的平台机制,还提供了一系列其他类型的“plugin”服务,以解决环境个性化配置的问题。

三、总结

1、devops或者容器化是理念的变化,更需要接地气的实施方案

2、基础架构,工程实践和配套服务,需要并进,才能落地

3、适合自己的方案才是最好的方案

携程的容器云进程还在不断的进化之中,很多新鲜的事务和问题等待着我们去发现和探索。

推荐阅读:

Image

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK