美团容器平台架构及容器技术实践

本文根据美团基础架构部/容器研发中心技术总监欧阳坚在2018 QCon(全球软件开发大会)上的演讲内容整理而成。git

背景

美团的容器集群管理平台叫作HULK。漫威动画里的HULK在发怒时会变成“绿巨人”,它的这个特性和容器的“弹性伸缩”很像,因此咱们给这个平台起名为HULK。貌似有一些公司的容器平台也叫这个名字,纯属巧合。github

2016年,美团开始使用容器,当时美团已经具有必定的规模,在使用容器以前就已经存在的各类系统,包括CMDB、服务治理、监控告警、发布平台等等。咱们在探索容器技术时,很难放弃原有的资产。因此容器化的第一步,就是打通容器的生命周期和这些平台的交互,例如容器的申请/建立、删除/释放、发布、迁移等等。而后咱们又验证了容器的可行性,证明容器能够做为线上核心业务的运行环境。算法

2018年,通过两年的运营和实践探索,咱们对容器平台进行了一次升级,这就是容器集群管理平台HULK 2.0。docker

  • 把基于OpenStack的调度系统升级成容器编排领域的事实标准Kubernetes(之后简称K8s)。
  • 提供了更丰富可靠的容器弹性策略。
  • 针对以前在基础系统上碰到的一些问题,进行了优化和打磨。

美团的容器使用情况是:目前线上业务已经超过3000个服务,容器实例数超过30000个,不少大并发、低延时要求的核心链路服务,已经稳定地运行在HULK之上。本文主要介绍咱们在容器技术上的一些实践,属于基础系统优化和打磨。编程

美团容器平台的基本架构

首先介绍一下美团容器平台的基础架构,相信各家的容器平台架构大致都差很少。缓存

首先,容器平台对外对接服务治理、发布平台、CMDB、监控告警等等系统。经过和这些系统打通,容器实现了和虚拟机基本一致的使用体验。研发人员在使用容器时,能够和使用VM同样,不须要改变原来的使用习惯。安全

此外,容器提供弹性扩容能力,能根据必定的弹性策略动态增长和减小服务的容器节点数,从而动态地调整服务处理能力。这里还有个特殊的模块——“服务画像”,它的主要功能是经过对服务容器实例运行指标的搜集和统计,更好的完成调度容器、优化资源分配。好比能够根据某服务的容器实例的CPU、内存、IO等使用状况,来分辨这个服务属于计算密集型仍是IO密集型服务,在调度时尽可能把互补的容器放在一块儿。再好比,咱们能够知道某个服务的每一个容器实例在运行时会有大概500个进程,咱们就会在建立容器时,给该容器加上一个合理的进程数限制(好比最大1000个进程),从而避免容器在出现问题时,占用过多的系统资源。若是这个服务的容器在运行时,忽然申请建立20000个进程,咱们有理由相信是业务容器遇到了Bug,经过以前的资源约束对容器进行限制,并发出告警,通知业务及时进行处理。性能优化

往下一层是“容器编排”和“镜像管理”。容器编排解决容器动态实例的问题,包括容器什么时候被建立、建立到哪一个位置、什么时候被删除等等。镜像管理解决容器静态实例的问题,包括容器镜像应该如何构建、如何分发、分发的位置等等。服务器

最下层是咱们的容器运行时,美团使用主流的Linux+Docker容器方案,HULK Agent是咱们在服务器上的管理代理程序。网络

把前面的“容器运行时”具体展开,能够看到这张架构图,按照从下到上的顺序介绍:

  • 最下层是CPU、内存、磁盘、网络这些基础物理资源。
  • 往上一层,咱们使用的是CentOS7做为宿主机操做系统,Linux内核的版本是3.10。咱们在CentOS发行版默认内核的基础上,加入一些美团为容器场景研发的新特性,同时为高并发、低延时的服务型业务作了一些内核参数的优化。
  • 再往上一层,咱们使用的是CentOS发行版里自带的Docker,当前的版本是1.13,一样,加入了一些咱们本身的特性和加强。HULK Agent是咱们本身开发的主机管理Agent,在宿主机上管理Agent。Falcon Agent同时存在于宿主机和容器内部,它的做用是收集宿主机和容器的各类基础监控指标,上报给后台和监控平台。
  • 最上一层是容器自己。咱们如今主要支持CentOS 6和CentOS 7两种容器。在CentOS 6中有一个container init进程,它是咱们开发容器内部的1号进程,做用是初始化容器和拉起业务进程。在CentOS 7中,咱们使用了系统自带的systemd做为容器中的1号进程。咱们的容器支持各类主流编程语言,包括Java、Python、Node.js、C/C++等等。在语言层之上是各类代理服务,包括服务治理的Agent、日志Agent、加密Agent等等。同时,咱们的容器也支持美团内部的一些业务环境,例如set信息、泳道信息等,配合服务治理体系,能够实现服务调用的智能路由。

美团主要使用了CentOS系列的开源组件,由于咱们认为Red Hat有很强的开源技术实力,比起直接使用开源社区的版本,咱们但愿Red Hat的开源版本可以帮助解决大部分的系统问题。咱们也发现,即便部署了CentOS的开源组件,仍然有可能会碰到社区和Red Hat没有解决的问题。从某种程度上也说明,国内大型互联公司在技术应用的场景、规模、复杂度层面已经达到了世界领先的水平,因此才会先于社区、先于Red Hat的客户遇到这些问题。

容器遇到的一些问题

在容器技术自己,咱们主要遇到了4个问题:隔离、稳定性、性能和推广。

  • 隔离包含两个层面:第一个问题是,容器能不能正确认识自身资源配置;第二个问题是,运行在同一台服务器上的容器会不会互相影响。好比某一台容器的IO很高,就会致使同主机上的其余容器服务延时增长。
  • 稳定性:这是指在高压力、大规模、长时间运行之后,系统功能可能会出现不稳定的问题,好比容器没法建立、删除,由于软件问题发生卡死、宕机等问题。
  • 性能:在虚拟化技术和容器技术比较时,你们广泛都认为容器的执行效率会更高,可是在实践中,咱们遇到了一些特例:一样的代码在一样配置的容器上,服务的吞吐量、响应时延反而不如虚拟机。
  • 推广:当咱们把前面几个问题基本上都解决之后,仍然可能会碰到业务不肯意使用容器的状况,其中缘由一部分是技术因素,例如容器接入难易程度、周边工具、生态等都会影响使用容器的成本。推广也不是一个纯技术问题,跟公司内部的业务发展阶段、技术文化、组织设置和KPI等因素都密切相关。

容器的实现

容器本质上是把系统中为同一个业务目标服务的相关进程合成一组,放在一个叫作namespace的空间中,同一个namespace中的进程可以互相通讯,但看不见其余namespace中的进程。每一个namespace能够拥有本身独立的主机名、进程ID系统、IPC、网络、文件系统、用户等等资源。在某种程度上,实现了一个简单的虚拟:让一个主机上能够同时运行多个互不感知的系统。

此外,为了限制namespace对物理资源的使用,对进程能使用的CPU、内存等资源须要作必定的限制。这就是Cgroup技术,Cgroup是Control group的意思。好比咱们常说的4c4g的容器,其实是限制这个容器namespace中所用的进程,最多可以使用4核的计算资源和4GB的内存。

简而言之,Linux内核提供namespace完成隔离,Cgroup完成资源限制。namespace+Cgroup构成了容器的底层技术(rootfs是容器文件系统层技术)。

美团的解法、改进和优化

隔离

以前一直和虚拟机打交道,但直到用上容器,才发如今容器里面看到的CPU、Memory的信息都是服务器主机的信息,而不是容器自身的配置信息。直到如今,社区版的容器仍是这样,好比一个4c4g的容器,在容器内部能够看到有40颗CPU、196GB内存的资源,这些资源实际上是容器所在宿主机的信息。这给人的感受,就像是容器的“自我膨胀”,以为本身能力很强,但实际上并无,还会带来不少问题。

上图是一个内存信息隔离的例子。获取系统内存信息时,社区Linux不管在主机上仍是在容器中,内核都是统一返回主机的内存信息,若是容器内的应用,按照它发现的宿主机内存来进行配置的话,实际资源是远远不够的,致使的结果就是:系统很快会发生OOM异常。

咱们作的隔离工做,是在容器中获取内存信息时,内核根据容器的Cgroup信息,返回容器的内存信息(相似LXCFS的工做)。

CPU信息隔离的实现和内存的相似,再也不赘述,这里举一个CPU数目影响应用性能例子。

你们都知道,JVM GC(垃圾对象回收)对Java程序执行性能有必定的影响。默认的JVM使用公式“ParallelGCThreads = (ncpus <= 8) ? ncpus : 3 + ((ncpus * 5) / 8)” 来计算作并行GC的线程数,其中ncpus是JVM发现的系统CPU个数。一旦容器中JVM发现了宿主机的CPU个数(一般比容器实际CPU限制多不少),这就会致使JVM启动过多的GC线程,直接的结果就致使GC性能降低。Java服务的感觉就是延时增长,TP监控曲线突刺增长,吞吐量降低。针对这个问题有各类解法:

  • 显式的传递JVM启动参数“-XX:ParallelGCThreads”告诉JVM应该启动几个并行GC线程。它的缺点是须要业务感知,为不一样配置的容器传不一样的JVM参数。
  • 在容器内使用Hack过的glibc,使JVM(经过sysconf系统调用)能正确获取容器的CPU资源数。咱们在一段时间内使用的就是这种方法。其优势是业务不须要感知,而且能自动适配不一样配置的容器。缺点是必须使用改过的glibc,有必定的升级维护成本,若是使用的镜像是原生的glibc,问题也仍然存在。
  • 咱们在新平台上经过对内核的改进,实现了容器中能获取正确CPU资源数,作到了对业务、镜像和编程语言都透明(相似问题也可能影响OpenMP、Node.js等应用的性能)。

有一段时间,咱们的容器是使用root权限进行运行,实现的方法是在docker run的时候加入‘privileged=true’参数。这种粗放的使用方式,使容器可以看到所在服务器上全部容器的磁盘,致使了安全问题和性能问题。安全问题很好理解,为何会致使性能问题呢?能够试想一下,每一个容器都作一次磁盘状态扫描的场景。固然,权限过大的问题还体如今能够随意进行mount操做,能够随意的修改NTP时间等等。

在新版本中,咱们去掉了容器的root权限,发现有一些反作用,好比致使一些系统调用失败。咱们默认给容器额外增长了sys_ptrace和sys_admin两个权限,让容器能够运行GDB和更改主机名。若是有特例容器须要更多的权限,能够在咱们的平台上按服务粒度进行配置。

Linux有两种IO:Direct IO和Buffered IO。Direct IO直接写磁盘,Buffered IO会先写到缓存再写磁盘,大部分场景下都是Buffered IO。

咱们使用的Linux内核3.X,社区版本中全部容器Buffer IO共享一个内核缓存,而且缓存不隔离,没有速率限制,致使高IO容器很容易影响同主机上的其余容器。Buffer IO缓存隔离和限速在Linux 4.X里经过Cgroup V2实现,有了明显的改进,咱们还借鉴了Cgroup V2的思想,在咱们的Linux 3.10内核实现了相同的功能:每一个容器根据本身的内存配置有对应比例的IO Cache,Cache的数据写到磁盘的速率受容器Cgroup IO配置的限制。

Docker自己支持较多对容器的Cgroup资源限制,可是K8s调用Docker时能够传递的参数较少,为了下降容器间的互相影响,咱们基于服务画像的资源分配,对不一样服务的容器设定不一样的资源限制,除了常见的CPU、内存外,还有IO的限制、ulimit限制、PID限制等等。因此咱们扩展了K8s来完成这些工做。

业务在使用容器的过程当中产生core dump文件是常见的事,好比C/C++程序内存访问越界,或者系统OOM的时候,系统选择占用内存多的进程杀死,默认都会生成一个core dump文件。

社区容器系统默认的core dump文件会生成在宿主机上,因为一些core dump文件比较大,好比JVM的core dump一般是几个GB,或者有些存在Bug的程序,其频发的core dump很容易快速写满宿主机的存储,而且会致使高磁盘IO,也会影响到其余容器。还有一个问题是:业务容器的使用者没有权限访问宿主机,从而拿不到dump文件进行下一步的分析。

为此,咱们对core dump的流程进行了修改,让dump文件写到容器自身的文件系统中,而且使用容器本身的Cgroup IO吞吐限制。

稳定性

咱们在实践中发现,影响系统稳定性的主要是Linux Kernel和Docker。虽然它们自己是很可靠的系统软件,可是在大规模、高强度的场景中,仍是会存在一些Bug。这也从侧面说明,咱们国内互联网公司在应用规模和应用复杂度层面也属于全球领先。

在内核方面,美团发现了Kernel 4.x Buffer IO限制的实现问题,获得了社区的确认和修复。咱们还跟进了一系列CentOS的Ext4补丁,解决了一段时间内进程频繁卡死的问题。

咱们碰到了两个比较关键的Red Hat版Docker稳定性问题:

  • 在Docker服务重启之后,Docker exec没法进入容器,这个问题比较复杂。在解决以前咱们用nsenter来代替Docker exec并积极反馈给RedHat。后来Red Hat在今年初的一个更新解决了这个问题。access.redhat.com/errata/RHBA…

  • 是在特定条件下Docker Daemon会Panic,致使容器没法删除。通过咱们本身Debug,并对比最新的代码,发现问题已经在Docker upstream中获得解决,反馈给Red Hat也很快获得了解决。github.com/projectatom…

面对系统内核、Docker、K8s这些开源社区的系统软件,存在一种观点是:咱们不须要本身分析问题,只须要拿社区的最新更新就好了。可是咱们并不认同,咱们认为技术团队自身的能力很重要,主要是以下缘由:

  • 美团的应用规模大、场景复杂,不少问题也许不少企业都没有遇到过,不能被动的等别人来解答。
  • 对于一些实际的业务问题或者需求(例如容器内正确返回CPU数目),社区也许以为不重要,或者不是正确的理念,可能就不会解决。
  • 社区不少时候只在Upstream解决问题,而Upstream一般不稳定,即便有Backport到咱们正在使用的版本,排期也很难进行保障。
  • 社区会发布不少补丁,一般描述都比较晦涩难懂。若是没有对问题的深入理解,很难把遇到的实际问题和一系列补丁联系起来。
  • 对于一些复杂问题,社区的解决方案不必定适用于咱们自身的实际场景,咱们须要自身有能力进行判断和取舍。

美团在解决开源系统问题时,通常会经历五个阶段:本身深挖、研发解决、关注社区、和社区交互,最后贡献给社区。

性能

容器平台性能,主要包括两个方面性能:

  • 业务服务运行在容器上的性能。
  • 容器操做(建立、删除等等)的性能。

上图是咱们CPU分配的一个例子,咱们采用的主流服务器是两路24核服务器,包含两个Node,每一个12核,算上超线程共48颗逻辑CPU。属于典型的NUMA(非一致访存)架构:系统中每一个Node有本身的内存,Node内的CPU访问本身的内存的速度,比访问另外一个Node内存的速度快不少(差一倍左右)。

过去咱们曾经遇到过网络中断集中到CPU0上的问题,在大流量下可能致使网络延时增长甚至丢包。为了保证网络处理能力,咱们从Node0上划出了8颗逻辑CPU用来专门处理网络中断和宿主机系统上的任务,例如镜像解压这类高CPU的工做,这8颗逻辑CPU不运行任何容器的Workload。

在容器调度方面,咱们的容器CPU分配尽可能不跨Node,实践证实跨Node访问内存对应用性能的影响比较大。在一些计算密集型的场景下,容器分配在Node内部会提高30%以上的吞吐量。按Node的分配方案也存在必定的弊端:会致使CPU的碎片增长,为了更高效地利用CPU资源。在实际系统中,咱们会根据服务画像的信息,分配一些对CPU不敏感的服务容器跨Node使用CPU资源。

上图是一个真实的服务在CPU分配优化先后,响应延时的TP指标线对比。能够看到TP999线降低了一个数量级,全部的指标都更加平稳。

性能优化:文件系统

针对文件系统的性能优化,第一步是选型,根据统计到的应用读写特征,咱们选择了Ext4文件系统(超过85%的文件读写是对小于1M文件的操做)。

Ext4文件系统有三种日志模式:

  • Journal:写数据前等待Metadata和数据的日志落盘。
  • Ordered:只记录Metadata的日志,写Metadata日志前确保数据已经落盘。
  • Writeback:仅记录Metadata日志,不保证数据比Metadata先落盘。

咱们选择了Writeback模式(默认是oderded),它在几种挂载模式中速度最快,缺点是:发生故障时数据很差恢复。咱们大部分容器处于无状态,故障时在别的机器上再拉起一台便可。所以咱们在性能和稳定性中,选择了性能。容器内部给应用提供可选的基于内存的文件系统tmpfs,能够提高有大量临时文件读写的服务性能。

如上图所示,在美团内部建立一个虚拟机至少经历三步,平均时间超过300秒。使用镜像建立容器平均时间23秒。容器的灵活、快速获得了显著的体现。

容器扩容23秒的平均时间包含了各个部分的优化,如扩容链路优化、镜像分发优化、初始化和业务拉起优化等等。接下来,本文主要介绍一下咱们作的镜像分发和解压相关的优化。

上图是美团容器镜像管理的整体架构,其特色以下:

  • 存在多个Site。
  • 支持跨Site的镜像同步,根据镜像的标签肯定是否须要跨Site同步。
  • 每一个Site有镜像备份。
  • 每一个Site内部有实现镜像分发的P2P网络。

镜像分发是影响容器扩容时长的一个重要环节。

  • 跨Site同步:保证服务器总能从就近的镜像仓库拉取到扩容用的镜像,减小拉取时间,下降跨Site带宽消耗。
  • 基础镜像预分发:美团的基础镜像是构建业务镜像的公共镜像,一般有几百兆的大小。业务镜像层是业务的应用代码,一般比基础镜像小不少。在容器扩容的时候若是基础镜像已经在本地,就只须要拉取业务镜像的部分,能够明显的加快扩容速度。为达到这样的效果,咱们会把基础镜像事先分发到全部的服务器上。
  • P2P镜像分发:基础镜像预分发在有些场景会致使上千个服务器同时从镜像仓库拉取镜像,对镜像仓库服务和带宽带来很大的压力。所以咱们开发了镜像P2P分发的功能,服务器不只能从镜像仓库中拉取镜像,还能从其余服务器上获取镜像的分片。

从上图能够看出,随着分发服务器数目的增长,原有分发时间也快速增长,而P2P镜像分发时间基本上保持稳定。

Docker的镜像拉取是一个并行下载,串行解压的过程,为了提高解压的速度,咱们美团也作了一些优化工做。

对于单个层的解压,咱们使用并行解压算法替换Docker默认的串行解压算法,实现上是使用pgzip替换gzip。

Docker的镜像具备分层结构,对镜像层的合并是一个“解压一层合并一层,再解压一层,再合并一层”的串行操做。实际上只有合并是须要串行的,解压能够并行起来。咱们把多层的解压改为并行,解压出的数据先放在临时存储空间,最后根据层之间的依赖进行串行合并。前面的改动(并行解压全部的层到临时空间)致使磁盘IO的次数增长了近一倍,也会致使解压过程不够快。因而,咱们使用基于内存的Ramdisk来存储解压出来的临时文件,减轻了额外文件写带来的开销。作了上面这些工做之后,咱们又发现,容器的分层也会影响下载加解压的时间。上图是咱们简单测试的结果:不管对于怎么分层的镜像并行解压,都能大幅提高解压时间,对于层数多的镜像提高更加明显。

推广

推广容器的第一步是能说出容器的优点,咱们认为容器有以下优点:

  • 轻量级:容器小、快,可以实现秒级启动。
  • 应用分发:容器使用镜像分发,开发测试容器和部署容器配置彻底一致。
  • 弹性:能够根据CPU、内存等资源使用或者QPS、延时等业务指标快速扩容容器,提高服务能力。

这三个特性的组合,能够给业务带来更大的灵活度和更低的计算成本。

由于容器平台自己是一个技术产品,它的客户是各个业务的RD团队,所以咱们须要考虑下面一些因素:

  • 产品优点:推广容器平台从某种程度上讲,自身是一个ToB的业务,首先要有好的产品,它相对于之前的解决方案(虚拟机)存在不少优点。
  • 和已有系统打通:这个产品要能和客户现有的系统很好的进行集成,而不是让客户推翻全部的系统从新再来。
  • 原生应用的开发平台、工具:这个产品要易于使用,要有配合工做的工具链。
  • 虚拟机到容器的平滑迁移:最好能提供从原有方案到新产品的迁移方案,而且容易实施。
  • 与应用RD紧密配合:要提供良好的客户支持,(即便有些问题不是这个产品致使的也要积极帮忙解决)。
  • 资源倾斜:从战略层面支持颠覆性新技术:资源上向容器平台倾斜,没有足够的理由,尽可能不给配置虚拟机资源。

总结

Docker容器加Kubernetes编排是当前容器云的主流实践之一,美团容器集群管理平台HULK也采用了这样的方案。本文主要分享了美团在容器技术上作的一些探索和实践。内容主要涵盖美团容器云在Linux Kernel、Docker和Kubernetes层面作的一些优化工做,以及美团内部推进容器化进程的一些思考,欢迎你们跟咱们交流、探讨。

做者简介

欧阳坚,2006年毕业于清华大学计算机系,拥有12年数据中心开发管理经验。曾任VMware中国Staff Engineer,无双科技CTO,中科睿光首席架构师。现任美团基础架构部/容器研发中心技术总监,负责美团容器化的相关工做。

招聘信息

美团点评基础架构团队诚招Java高级、资深技术专家,Base北京、上海。咱们是集团致力于研发公司级、业界领先基础架构组件的核心团队,涵盖分布式监控、服务治理、高性能通讯、消息中间件、基础存储、容器化、集群调度等技术领域。欢迎有兴趣的同窗投送简历到 liuxing14@meituan.com

相关文章
相关标签/搜索