PPTV聚力传媒的Docker与DevOps

大家好,今天主要和大家分享PPTV聚力传媒在容器化过程中,如何通过Docker优化DevOps流程,提高项目交付的效率和可靠性。包括:

1.DevOps简介
2.Docker在PPTV的应用
3.DevOps与Docker的结合
4.实现方案

1.DevOps简介

首先简单介绍一下DevOps 。DevOps通常指的是2009年前后兴起的一个概念,提倡开发和IT运维之间的高度协同,主要目的在于提高用户和业务需求提高产品的交付能力与效率。
相信对很多朋友来说DevOps已经不是陌生的词,到今天为止,可能已经有很多公司在尝试通过Docker实现DevOps。可至今也并没有一个完善的DevOps实现方案和标准。

2.Docker在PPTV的应用

PPTV基于Docker打造的DCOS平台。底层基于MESOS + Marathon 为核心,结合Docker和Nginx,在此基础上开发了DCOS管理控制台,权限管理模块,统一日志管理模块,IP池管理模块,存储管理模块,服务发现模块,并与持续集成平台jenkins集成,实现应用容器的创建、运行。

MESOS,Marathon,Docker,Jenkins大家应该都有了解,这里就不多说了,下边主要介绍PPTV的Docker使用情况。
目前为止PPTV的测试环境基本上都已经迁移到了Docker上,生产环境的容器化还在进行中。所以后边的内容主要是与测试环境的架构相关。

功能框架

tu1

当前架构和发布流程

tu2

容器网络

我们采用了网络桥接的方式,基于docker的bridge模式,将默认的docker bridge网桥替换为linux bridge,把linux bridge网段的ip加入到容器里,实现容器与传统环境应用的互通。

创建一个使用linux bridge的docker network :  
 docker network create --gateway 10.199.45.200 --subnet 10.199.45.0/24 -o com.docker.network.bridge.name=br-oak --aux-address "DefaultGatewayIPv4=10.199.45.1"  oak-net

该命令中创建了一个docker bridge网络,并与docker所在主机的br-oak网桥做桥接,该网络的使用了10.199.45.0/24这个子网,同时通过  –aux-address “DefaultGatewayIPv4=10.199.45.1” 这个参数将容器启动时的网关指向到10.199.45.1
关键参数:  –aux-address “DefaultGatewayIPv4=10.199.45.1”

使用桥接的方式,需要考虑IPAM,我们在DCOS管理平台中加入了IP池管理模块,实现全局的IP管控,并提供请求申请IP的接口。

思路是以marathon上的app信息作为数据源,定期去调用marathon的API更新IP列表。当我们要在marathon上创建一个使用固定IP的容器时,首先会请求IP池管理模块的IP分配接口,请求的时候把app id发给分配接口,管理平台根据app id 判断这个应用是否是新应用,如果是新应用则从IP池中返回一个未使用的IP,并将此IP与应用关联。

tu3

我们目前是在jenkins上构建项目的时候调用marathon的api创建容器,在jenkins的job中调用ip分配的接口获取ip。容器run起来的时候添加 — net 和– ip 参数指定容器ip

tu4

容器存储

容器存储驱动
我们的OS用的是centos。所以在过去的一段时间,使用Loop模式的DeviceMapper作为存储驱动,是Docker默认的,在使用过程中遇到一些问题。

如上面所示问题,不只一次出现,目前都没有完全解决,最终只能用重启Docker的方法,如果在生产环境中遇到这样的问题,应该是大家不希望看到的。

tu5

经过对比各个存储驱动,对比详细链接如下 http://dockone.io/article/1688,这里不详细说明,我们决定在Btrfs和DeviceMapper的Direct LVM中选定一个存储驱动。对于我们目前打算用Docker上线的服务,主要关注从几百KB到几MB大小的文件,只需考虑读,写的操作是采用挂载volume的方式,不会直接写在容器里。在读的方面,DeviceMapper比Btrfs性能略好。在稳定性方面的比较,由于线下的试验并不能完全模仿线上的场景,初步打算上线时一部分容器运行在DeviceMapper存储驱动的环境下,一部分容器运行在Btrfs存储驱动的环境下,进行观察、比较。

容器外部卷使用glusterfs,以分布式存储支持容器持久化数据。比如应用日志和应用静态文件。选glusterfs的原因,因为glusterfs此前公司内部一直在使用,足以满足测试环境的存储需求。

容器日志管理

我们将应用日志统一收集到glusterfs上:
在gluster上建了一个log volume ,将此volume挂载到每一台mesos slave主机上。比如 /home/logs
容器启动的时候加上volume参数,比如容器APP1启动的时候添加  –v /home/logs/app1:/home/logs
这种做法可以在测试环境中解决容器数据持久化的问题,但是在线上环境中不建议这样做。

线上环境我们目前考虑的做法是,先首先将容器日志目录挂载到宿主机上,再通过logstash或其他方式,统一收集。

服务发现

服务发现模块订阅了marathon的消息,我们对marathon app的label属性做了约定,属性里包含了这个app的ip、服务端口、服务域名等信息。每当marathon上有app创建、停止、删除。服务模块都能接收到相应的消息,并将获取到这些信息后处理后更新到nginx和dns中。

Label属性:

tu6

tu7

服务发现app信息

tu8

3.DevOps与Docker的结合

Docker的口号是Build, Ship, and Run Any App, Anywhere。所以使用docker的理想状态是这样的 :

tu9

而通过Docker实现DevOps的理想流程应该是这样的:

tu10

但是实际的情况通常是

tu11

因为在测试环境里测试通过的镜像,很有可能是不能直接拿到线上RUN起来的。原因是测试环境和线上环境的架构不一致,或者是配置不一致。

比如上边图里的app1 -> redis1  ,就有可能存在两个问题
1.线上环境的配置文件里redis1的链接地址可能是线上环境redis IP。而线下的配置是线下的redis IP。
2.线上的redis有可能是redis集群,比如端口是19000.而线下redis可能是单节点,服务端口是6379。
除此之外可能还有其他的配置参数不一致的问题。

解决上边两个问题的一个思路是保证线下线上配置一致。
在过去的解决办法中
问题1,通常各项目各组件之间调用的时候选择通过域名去调用,线上环境通过dns解析域名,线下环境一般会绑host。
问题2,需要在项目初期就规划好,线下和线上是否使用相同的端口。如果后期想要统一配置,则修改服务端口,有可能会导致很多未知的问题产生,代价太大。

线上线下配置不一致,相信是在不少公司都存在的情况,而且这样的情况很有可能会造成线上发布的问题。以我们自己的实际情况为例。回头看看上边提到我们当前的发布流程:

我们通过jenkins编译好了测试环境的应用包,在编译的过程中将应用包里的配置文件替换成线下的配置,然后通过挂载的方式,把应用包放到容器中运行。当测试通过之后,再通过jenkins编译线上环境的应用包,这次编译的过程中会将配置文件替换成线上的配置。最后将应用包提交给线上运维发布。

tu12

也就是说我们最终发布到线上的应用包,其实是没有经过测试的。也因此出过生产事故,幸运的是有灰度发布,同时及时回退,降低了影响范围。

为了更好解决类似的问题,尽量接近上边的Docker实现DevOps的理想流程,我们希望能通过docker实现的目标:
1.在jenkins构建项目的产出物为包含了应用包(代码+线上配置)和环境(例如tomcat)的docker 大镜像
2.在测试环境中模拟线上架构,保证环境尽量一致
3.测试环境下测试通过的大镜像可以直接在线上发布。在测试环境部署大镜像,执行测试任务,测试通过后,运维将大镜像发布到线上环境。

而要达到这个目标,我们需要解决的问题:
1.在docker集群中运行db、zookeeper、mq等需要持久化数据的组件
2.保证线上线下配置一致,包括访问链接和服务端口统一
3.测试环境可能同时存在多套相同应用,需要实现环境隔离,保证每套环境能使用相同的配置,并行进行不同测试

4.实现方案

问题1
在没有docker之前,在测试环境中模拟线上环境的架构,是很不现实的,需要大量的人力和物力。而借助docker,我们可以轻松的创建很多套环境出来,只要有相应的镜像。而对于需要持久化数据的文件,因为在测试环境中访问压力小,我们采用glusterfs存储,同时对CPU,内存等计算资源超配,最大限度的压榨服务器资源。

对于上边提到的3个问题

问题2、3。我们希望实现的方案尽量的简单,影响尽量小。所以结合服务发现模块,我们对app的label重新做了约定,将测试环境隔离为一套集成环境、多套功能环境。同时每套环境使用各自的一套dns。尽可能的在测试环境中模拟线上的架构。

tu13

如上图,最终达到的效果:
一套完整的集成测试环境,包括主服务、redis、mq、db、zookeepoer等,供其他功能环境测试调用。环境内部各项目各组件调用,都使用与线上一致的地址。比如域名,dns负责解析测试环境内部域名。

多套功能测试环境(上图只画了一套),包含需要进行功能测试的项目,各项目在功能环境中有各自的redis、mq、db等服务,配置与集成环境相同,跨项目调用的时候,默认调用集成环境。通过dns解析区分集成环境和测试环境。

完成到这一步,我们基本上能够保证大部分项目的配置、环境一致了。同时因为在jenkins构建的产出物就是docker镜像,在环境一致的前提下,通过测试的镜像是可以直接在线上环境中正常运行的。

当然也不能排除,有一些配置和项目,确实是无法做到完全一致,那就只能尽量减小差异化。特殊项目通过其他特殊途径处理。

简单小结一下,我们将Docker与DevOps结合的主要思路:在尽可能的减小测试环境与线上环境的差异的前提下,在测试环境同时部署多套运行环境进行测试,同时借助Docker的Build, Ship, and Run特性,缩短项目上线周期,提高项目的交付能力与效率。

本人此次分享就到这里,谢谢大家。

分享到:更多 ()