背景介绍以及总体规划
首先我先介绍一下魅族大数据上云的背景,即我们为什么要上云?
在开始之前我们默认今天参与直播的各位同学对Hadoop相关技术和docker都有一定的了解,另外以下提到Hadoop是泛指目前魅族大数据使用的Hadoop生态圈技术,资源除特别说明则泛指存储资源、计算资源和网络资源的总和。
我们先来看一下魅族大数据在没有上云的时候所遇到的主要问题有以下几个方面:
1.资源隔离不彻底
- 由于一些历史问题,我们跑在Hadoop上的任务脚本质量参差不齐,导致经常有一些异常任务会短时间吃掉所有的机器资源,导致整台机器down掉。
2.资源利用效率低
- 目前我们的业务增速很快,每个Q都需要一定数量的机器扩容,但是业务的增速往往不是线性的,为某些关键时间点的峰值需求而准备的机器常常在峰值过去之后存在大量的资源闲置。
3.集群运维成本高
- 由于一些存储、网络方面的物理故障以及异常任务导致故障恢复都需要运维同学人工介入。
- 常见的高可用解决方案都需要侵入到Hadoop技术体系内部,有一定技术门槛。公共运维部门的同学无法很好的支持大数据团队服务器运维。
- 集群部署模型复杂,过程繁琐
基于以上这些存在的问题,我们经过一番技术预研发现,上云之后可以很好的解决我们的问题。
在讨论上云的总体规划之前,我觉得有必要先把几个非常重要但是却容易混淆的概念先做一下简单解释,这里只是点到为止,希望这对大家理解后面的内容会很有帮助
Docker≠容器技术
Linux很早就推出了内核虚拟化技术LXC,Docker是有Docker公司研发的,它把LXC做了进一步的封装(现在已经替换成了它自己实现的libcontainer,加上镜像管理等一系列功能,变成了一套完整、易用的容器引擎。2015年的dockerCon大会上,docker和CoreOS共同推出了Open Container Project 来统一容器的定义和实现标准)。
这里提一些圈儿内的轶事给大家提供点儿谈资:
刚才提到的OCP(Open Container Project)的建立,google才是背后的真正推手,因为当年Docker的快速发展,打败了google曾经的开源容器技术lmctfy,Docker公司和CoreOS原本和睦,共同发展Docker技术,后来由于意见上的分歧,两家都想做容器技术的标准制定者,google暗中支持CoreOS,后来CoreOS随即自立门户,发布了自己的容器技术Rocket,并且和google的kubernetes合作发布了容器服务平台Tectonic,Docker公司和CoreOS由此彻底决裂。后来Linux基金会出面调和,google也从中协调,双方都退让了一步,才最终和解推出了OCP,但是有心人可以看一下OCP项目的成员名单就知道,Dcoker公司在这中间只占很小的一部分,google在其中扮演了重要角色。此外Docker公司也放弃了自己对Docker技术的独家控制权,做为回报Docker的容器实现被确定为OCP的新标准,但源代码都必须提交给OCP委员会。不难看出google实际上是为了报当年lmctfy的一箭之仇,借CoreOS之手狠踩了Docker公司一脚,自己也成为了容器技术领域的实际控制者
总结下来Docker只是众多容器技术中的一种,只是由于它最近的火爆而几就成了容器技术的代名词。
容器技术≠虚拟化技术
容器技术只是实现虚拟化的一种轻量级的解决方案,常见的虚拟化方案还包括
KVM、Xen和vmware的解决方案等。
虚拟化≠等于云
虚拟化技术只是一个完整云服务体系中包含的一个技术手段,但不是全部,甚至不是必须的。完全使用物理机也可以搭建一个云。
常见的云服务体系中的核心服务还包括:
- 认证服务
- 镜像服务
- 网络服务(例如:SDN(软件定义网络)和物理网络)
- 计算服务(管理、分配、调度计算任务)
- 对象存储服务(直接为应用提供数据读写服务)
- 块存储服务(为主机提供类似裸物理磁盘的存储服务)
(以上的分类方式参考了亚马逊AWS的云服务体系)
魅族大数据上云的一个整体规划,基本上分为3个大的阶段:
PART I — Hadoop on Docker
PART II — Container Cluster Management System
PART III — Hadoop on Cloud Operating System
(这次分享针对PART I的内容)
魅族大数据目前已经来到第三个阶段,前两个阶段也是我们曾经走过的,由于今天篇幅的限制重点介绍第一部分,hadoop on docker的内容。
Hadoop on Docker方案演进
- 首先来说明一下我们为什么要上docker,通过在Docker 上部署Hadoop,我们可以实现:有效的提升集群部署效率,原本需要运维手工部署的过程,现在可以通过一个命令直接从Docker Registry中pull对应的镜像,一键启动。
- 安全性:在Docker中运行的应用,在没有主动配置的情况下,基本无法访问宿主机的文件系统,这样可以很好的保护宿主机的文件系统和其他设备。
- 资源调控/隔离:Docker可以对应用所需要的资源,如CPU计算资源、内存资源、存储资源、网络带宽等进行调控(资源调控目前只支持容器启动前的一次性配置),不会因为单一任务的一场导致整个hadoop集群挂起。
- 一致性:只要源自同一个镜像,hadoop集群中的任何节点都可以保持统一的软件环境,不会由于管理员的人为失误导致集群环境不一致。
- 可用性:由于容器节点的轻量级特征,我们可以非常方便的部署一些针对namenode 的HA方案。另外即使遇到极端情况集群整个挂掉,我们也可以快速的重启整个hadoop集群。
开发者只需要关心代码本身,代码编写完成后提交到GIT,之后的构建,测试、build docker 镜像,提交镜像到docker registry都可以由jenkins自动化完成,运维只需要负责在合适的机器上启动对应镜像的服务即可。
目前魅族大数据已经经历过docker方案的预研和实施,并且在线下测试和开发环境中已经开始使用docker驱动DevOps提高开发->测试的效率,目前在测试环境中docker宿主机集群规模50+,容器数1000+。
在这个过程中,我们也经历了从单纯的docker,到一个完整的容器云环境的演变,docker的build ship run理念看起来很美,但是实际使用起来如果只有docker还是会面临很多的问题和挑战。
具体的原因我分了以下几个方面来介绍,这些也是我们曾经踩过的坑,是构建一个完善的云计算环境来服务大数据技术不可避免的几个核心点。
资源隔离/动态分配
资源的隔离其实使我们在使用docker之前面临的一个大问题,经常会由于ETL同学不是特别注意写了一些有问题的任务脚本,上线后导致整个hadoop集群资源耗尽,关键任务出现延迟甚至失败,运维的同学每天被这种问题折腾的死去活来。
而采用了docker之后,Docker通过libcontainer调用linux内核的相关技术(现在windows和其他操作系统也开始native支持容器技术了,具体进展大家可以关注Open ContainerProject和docker的官网)实现了容器间的资源隔离,资源的分配可以在docker容器启动时通过启动参数来实现,这里举几个简单的例子:
CPU配额控制:docker run-it --rm stress --cpu 4
--cpu代表指定CPU的配额参数,4代表配额的值,这里的配额可以简单理解为CPU的占用率,比如一个宿主机中有两个容器在运行时都指定了—cpu 4,则相当于运行时这两个容器各站CPU配额的50%,注意这里的配额并不是一个严格的限制,在刚才的例子中,如果容器1完全空闲,则容器2是可以完全占用100%的CPU的,当容器1开始使用CPU时,则容器2的CPU配额才会逐步下降,直到两个容器都各站50%为止。
内存控制:docker run-it --rm -m 128m
-m参数指定了内存限制的大小,注意这里的值指的只是物理内存空间的限制,docker在运行容器的时候会分配一个和指定物理内存空间一样大的swap空间,也就是说,应用在运行期占用内存达到了-m参数指定值的两倍时,才会出现内存不足的情况。
Docker的资源限制目前必须在容器启动时手工完成,但是在大规模集群的环境下,手工逐一根据物理机的资源情况进行配额的指定显然是不现实的。而且docker并不支持存储空间的配额限制,只能限制读写的速率。
Docker现阶段还不能满足需求,我们需要一个可以根据集群的实际运行情况进行计算资源、存储资源、网络资源的合理分配的解决方案。
我们经过实践的检验,发现Docker的资源隔离相比于传统虚拟机技术还是有很多做的不彻底的地方,除了上面所属的之外,在安全上也一直存在一些比较大的隐患,比如docker一直以来都不支持user namespace。
所以如果容器内的应用使用root账号启动,而启动时又带上—priviledge参数的话,会导致容器内的root和宿主机的root权限一直,带来非常大的安全隐患。在这里稍微补充一点,在最新的1.10版本的docker中已经支持了user namespace(通过--userns-remap参数)。
但是魅族大数据并没有升级到这一版,这也是由于我们的服务编排和容器集群管理使用的是google开源的kubernetes,目前最新版的kubernetes1.2官方声明最新支持的docker版本只到1.9,有关kubernetes的内容有机会会在后续的分享中跟大家交流。
定义镜像/存储结构
定义存储结构的目的是为了实现部署环境的标准化,之前我们也尝试过直接提供一个带SSH服务的容器节点,这样感觉用起来跟虚拟机是没什么差别,但是这样一来之前虚拟机运维时碰到的各种环境不一致的问题还是会出现,docker的多层镜像结构的优势就完全没有发挥出来。
通过一个base镜像定义好一套标准,这就像maven的parent pom一样(这个比喻有些不够恰当,maven更多的是进行包依赖的管理,而docker的多层镜像存储结构可以定义一个完整的基础运行环境,并且这个环境是可以继承的),可以给大家提供一个标准的环境基础设置,另外上层应用非常方便的可以收集容器中的数据,使用容器的服务。
首先我们按照部署模型划分镜像结构,对每一个Hadoop生态中的服务都抽象出一个对应的镜像,这个服务镜像包含了主从架构的两套配置,使用环境变量来区分该服务镜像到底是作为master还是slave部署。
这样做的好处是可以降低镜像的数量,方便做配置和服务版本的管理和升级,另外需要注意的是mz agent,这是我们自定义的一个镜像,用来进行临时文件的清理、日志数据收集,以及对接第三方的监控服务实现,向我们的监控平台发送自定义的一些集群健康度指标数据。
此外它还负责接收客户端的管理指令请求,可以让集群管理员像操作一台服务器一样的对整个集群或集群中的特定主机进行操作。
为了方便做统一的监控和存储的管理,我们在镜像内定义了一些基本的目录结构
- bin目录用来存放镜像服务的binary包以及shell脚本
- tmp目录用来存放临时文件,mz agent会定时进行文件的清理
- data存放一些需要持久化的数据文件,通过volume挂载到宿主机的目录中,包括一些需要进行分析的应用日志,应用的数据文件等。
- conf保存服务相关配置信息
镜像管理使用git+docker registry,git中保存镜像的构建过程,Dockerfile,docker registry保存实际镜像。
这样定义镜像结构可以帮助我们通过统一的标准更好的进行监控、报警、发布管理等相应特性的实现。上层的平台应用只需要将一些必要的实现诸如到base镜像中,就可以实现统一的运行环境升级。
这里会存在一个问题,docker容器在运行时产生的数据是保存在宿主机的特定目录的,而且这些数据的生命周期跟容器的生命周期一样,一但容器销毁,则这些数据也会一并删除,如果将HDFS部署在容器中,这也是不能容忍的。
我们如果通过volume的方式将数据挂载到宿主机则会导致宿主机中存在大量跟容器的ID相关的存储目录,而且一旦节点挂掉想要在其他节点上启动相同的服务还需要进行数据的迁移。大规模集群的环境下在每个节点上管理这些目录会带来额外的运维成本。
为了解决这一问题,我们需要一个存储管理的服务来托管容器运行时生成的数据,使得Hadoop集群能够随时创建,随时销毁。集群内的应用服务不需要关心运行时生成的数据存放在哪里,如何管理等等。
在分布式环境下运行容器
由于hadoop生态圈技术的特点,docker必须部署在分布式的环境中,而这会带来很多新的挑战。如果有熟悉docker的同学就会发现一个问题,HDFS默认会将数据做3个副本。
但docker可以在同一个宿主机中启动多个容器,如果这三个副本都落在了同一个宿主机上,那么一但宿主机down掉,三个副本就一起失效,HDFS的这种利用多副本来提高数据可用性的策略就无效了。
不仅是HDFS,其他需要通过类似双master或多副本的结构来做HA的方案都会遇到同样的问题。
在只使用docker的情况下,只能通过人工的方式,将namenode和datanode分开在不同的宿主机上启动。
这样一来,一但某个容器内的服务挂掉,为了保持有足够数量的服务中容器,则需要运维的同学人工的通过监控平台去检查集群目前各宿主机的负载和健康度情况,同时结合不同容器服务的部署架构要求选择合适的宿主机启动容器。
虽然最后部署的动作已经相对自动化,但是选择部署服务器的过程依然是痛苦的。
再补充一点,如果希望升级Hadoop生态中的某个服务的版本,一般都是先在测试环境进行部署验证,ok之后才会将镜像发送到线上的Registry,这就涉及到了如何隔离测试和线上生产环境的问题,不同环境的存储、计算、网络资源必须相对隔离。
如果是在运行中的集群中更新服务的版本则还会涉及到灰度发布的问题。如果线上集群已经存在了多个版本的服务,则还要考虑个版本升级过程是否有差异,是否需要进行滚动发布等,这些docker暂时都不能帮我们解决,仅仅依靠人工或者自己搭建平台来完成依然需要很大的工作量。
这也就引出了Docker仅仅作为一个容器引擎的不足,如果需要大规模的部署,我们需要一个容器调度管理服务,来按照我们指定的策略分配容器运行的实际宿主机。同时能够根据预定义的发布更新策略,对运行中的容器服务进行动态的滚动更新(rolling update)
多宿主机网络解决方案
另外我们遇到的一个无法忽略的就是多宿主机环境下的网络问题,既然要在集群环境下运行,那么如何在多宿主机的环境下让各台宿主机上运行的容器都能分配到一个指定网段内的IP,并且彼此之间可以进行正常网络通信。
由于大数据相关的应用和服务往往需要进行大量的网络吞吐,这一方案在性能上也不能有很大的损失。
Docker自带的网络模块如上图所示,通过一个虚拟化的docker0网桥,把主机上的eth0端口和docker engine内部的container通过veth pair连接到一起,容器间的网络通讯都是通过docker0网桥来转发,容器带外网的请求是通过docker0网桥来进行NAT转换后发送到外网,这样的网络方案能够在单一宿主机上解决网络通信的问题。
但一旦进入多宿主机的集群环境,各个的宿主机上运行的容器就无法得知彼此的IP网段和路由信息。
在Docker的技术体系中,解决这一问题目前主流的方案有以下几种:
- Dockernetworking(from 1.9)
- Flannel
- OpenvSwitch
- Weave
- calico
这几种解决方案的优缺点和适用场景涉及内容很多,有兴趣的同学可以参考一下这篇文章。
http://www.chunqi.li/2015/11/15/Battlefield-Calico-Flannel-Weave-and-Docker-Overlay-Network/
我们采用了上述的最后一种calico,主要是出于性能上的考虑,calico是一个纯layer 3 的解决方案,使用了一个etcd作为外部配置信息存储,通过BGP广播路由表配置,直接用主机的静态路由表定义路由,整个过程没有NAT,没有Tunnel封包拆包过程。
使得数据包的体积减小,降低了网络开销,所以只要理论上layer3可达的网络环境,同时对协议支持的要求不是太苛刻(由于是layer3 的解决方案,所以只支持TCP、UDP、ICMP协议)同时对性能、吞吐量、网络延迟有要求的场景都是适合的。当然其他的解决方案也有他们的适用场景,具体大家可以参考上面的那篇文章。
小结
今天由于时间关系没办法把Hadoop on Docker方案的方方面面都做详细的介绍,因此主要选了一些重点的问题和挑战来跟大家分享,主要的内容就到这里了,但是还有一些上面提到的遗留问题没有深入下去:
- 如何搭建一个存储管理的服务来托管容器运行时生成的数据,使得Hadoop集群能够随时创建,随时销毁。集群内的应用服务不需要关心运行时生成的数据存放在哪里,如何管理等等。
- 如何搭建一个容器调度管理服务,来按照我们指定的策略分配容器运行的实际宿主机。同时能够根据预定义的发布更新策略,对运行中的容器服务进行动态的滚动更新(rolling update)。
- 网络方面,如何在一个多集群、多租户的环境下有效的管理和分配网络资源,让各集群之间相对隔离,又有统一的网关可以彼此访问。
- 如何对一个复杂的微服务环境进行编排和管理。
这些都是构建一个完善云服务所不能避免的问题,docker自身也深知这些问题,如果细心关注docker的官方网站就会看到,他们也在通过docker-machine、docker-compose、docker-swarm等意图为docker打造一个完整的解决方案,使其拜托最初人们认为docker只是一个容器引擎这样的印象。
魅族大数据为了能够在生产环境中搭建一套完整的容器云服务环境,针对这些问题实践出了一套解决方案,基本架构组成是
- Docker 容器引擎
- Registry负责镜像管理
- Kubernetes容器编排,集群资源管理监控
- Swift 存储服务
目前已经有30%的任务量运行在这套容器云平台下,线下、测试、正式三套环境通过registry配合kubennetes的跨namespace调度实现任务的快速发布和更新。
除了hadoop集群之外,我们自己的平台应用也正在逐步的迁移到这套环境当中。但为了保证线上任务的稳定性,hadoop集群和平台应用集群是namespace和宿主机双隔离的。
今天的内容都是以单纯的docker为主,其他几块的内容以后可以在Hadoop on Container Cluster Management System和Hadoop on Cloud Operating System当中再跟大家逐一介绍。