数人云“容器助力产品迭代力MAX”沙龙干货分享实录来啦,今天是来自数人云工程师金烨的分享。谁说运维只是体力活?从Java开发到Mesos、Marathon,还有各种组件的Docker化以及运维的自动化,金烨与我们分享了在持续集成道路上的实践经验。
金烨
数人云运维工程师
持续集成的价值
首先讲一下持续集成的优势。过去公司做测试可能需要十几、二十几个组件,集成一次往往要一两个小时,费力费时,而且复杂容易出错,而一旦配置出错的话耗时会更久。因此,一次集成测试一周才会做一次,测出Bug要到下一周才能更新,再做测试,这个周期会非常漫长。而持续集成的意义就在于减少风险,和重复的过程,最终提高工作效率。
Docker
Docker是现在非常火的一门技术,使用Docker首先解决的是环境的问题。因为开发环境和运维的部署环境千奇百怪,依赖的环境和包各不一样。其次,Docker是可以实现更快速地交付和部署,只要写一个Dockerfile,把服务打成一个镜像,然后再迁移到各种服务器上就行了,部署的过程也非常方便,服务器只要有Docker的环境就可以运行。因为是内核级虚拟化解决方案,Docker利用的额外资源很少,同时,它的更新管理也非常容易,只需要把Dockerfile修改一两行,其他的服务器则不需要做改动,也不需要下载其它的安装依赖包,打好Docker镜像直接就可以部署镜像、直接运行。这些都是它的优势。
Mesos
Apache Mesos是一款开源集群管理软件。Mesos经过了Facebook,Twitter这些大型公司的万台主机验证,在国内,爱奇艺、去哪网,小米网等公司也拥有大规模的Mesos集群应用。Mesos实现了两级调度架构,可以管理多种类型的应用程序。第一级调度是Master的守护进程,管理Mesos集群中所有节点上运行的Slave守护进程。集群由物理服务器或虚拟服务器组成,用于运行应用程序的任务,比如Hadoop和MPI作业。第二级调度由被称作Framework的“组件”组成。Framework包括调度器(Scheduler)和执行器(Executor)进程,其中每个节点上都会运行执行器。Mesos能和不同类型的Framework通信,每种Framework由相应的应用集群管理。图中只展示了Hadoop和MPI两种类型,其它类型的应用程序也有相应的Framework。Mesos支持多种Framework,比如说Hadoop,Spark,Storm等等,各类型的Framework在它上面都可以运行。
Marathon
Marathon是Mesosphere为Mesos生态圈打造的一个轻量级管理服务APP框架,它可以用RESTful API方便地进行操作。
Marathon 协调应用和Frameworks
下图是在Marathon上发布各种的服务,它们都可以通过Marathon发到Mesos的集群资源中。
扩展和故障恢复
例如我们发了三个服务,分别是有一个节点的,三个节点的和五个节点的。
我们想拓展这些服务的话,可以调用Marathon的API进行扩展,秒级扩展到下图。
如果有一台主机宕掉了, Marathon会均匀地把服务迁移到其他的机器上,选择资源有空余的机器进行迁移。这样能就保证服务是动态的调度,保证服务的高可用,并且这些都是它内部自行处理的,不需要手动干预。
Jenkins
介绍
Jenkins是Java开发的一种持续集成的工具,我们在它的基础上做了一些重复的工作,比如版本发布、测试代码,以及调用外部接口。Jenkins支持很多插件,可以很方便地选择使用适合自己团队的插件工具。
问题
Jenkins为我们提供了便利,当然Jenkins本身也有它自己的问题,比如传统的Jenkins是单点的,任务大多是需要排队的,如果每个任务都自己建一套Jenkins的话,资源会非常浪费。为了解决这些问题,我们引入了Jenkins On Mesos。
Jenkins On Mesos
Jenkins 分为Master 节点和Slave 节点,Master 进行调度,Slave节点负责负责执行Job任务。
Jenkins master
首先我们把Jenkins的Master通过Marathon发布,Marathon 去调用 Mesos Master,之后Mesos Master再去Slave节点起Jenkins的Master。这个机制保证了Jenkins Master的高可用,Marathon会监控Jenkins Master的健康状态,当Jenkins Master出现崩溃挂掉,Marathon会自动再启动一个Jenkins Master的任务。Jenkins Master使用Mesos整个大的资源池,Mesos的资源池是一个资源共享的状态,资源利用率也会更高一些。
Jenkins Slave
Jenkins Master做的是调度,Jenkins Slave则是真正执行构建任务的地方。Jenkins Master会在Mesos Master上注册一个它自己的Framework,如果有任务需要构建的话,Jenkins Master 会通知 Jenkins Framework 调用 Mesos Master构建一个任务。之后Mesos Master 再调用 Mesos Slave去起一个Jenkins Slave的节点去构建任务,它是实时的,用户资源可能被分配到各个机器上,在不同的机器上并行存在。此外,Jenkins还具备动态调度功能,也就是说,当任务运行完后一定时间,资源会再返还给Mesos,实现合理地利用整个集群的资源,提高集群的资源利用率。
Mesos 整体流程
这张图是Mesos整体的调度流程。第一步,它的集群有三个Mesos Slave节点资源,资源上报到Mesos Master,Mesos Master收集到这些资源之后把这些资源再提供给Marathon,Marathon如果要发任务,它确认一个Offer1,Offer1足够任务来运行,就拒绝其他的Offer,并把这个任务发送给Mesos Master,之后Mesos Master去找Slave1起Marathon的任务。这是Marathon的任务启动过程。Mesos本身可以和多框架进行通信,Jenkins Master要跑一个任务,Mesos Master同样提供资源给Jenkins,提供的资源包括了Marathon 任务使用剩下的资源,比如Task1 确认的 Offer1没有用完和没有使用的资源,Mesos也会它提供给Jenkins。而Jenkins也会选择,比如Jenkins选择了Offer3,拒绝了其他的Offer,把Jenksin的任务再通过Mesos Master去Mesos Slave中起起来。
Jenkins On Mesos Docker化
Jenkins部署起来非常麻烦,需要安装各种依赖,如果代码是从Git上下载的,那么还需要安装一些Git包。而动态调度需要每台机器上都装这些依赖,为了解决这种问题,我们把服务全部进行了Docker化,主机上只需要有Docker环境就可以运行我们的服务。
遇到的坑
进行Docker化之后就会面临Docker化的问题,因为Docker和 Mesos都是比较新的技术,我们遇到了很多坑,也需要去解决。
我们遇到的第一个问题是Jenkins on Mesos需要调度Mesos的Lib库,如果Docker化之后是隔离的,就调不到Mesos Lib。
第二个问题是Docker化的数据,因为Jenkins是没有数据库的,数据都是存在本地的JENKINS_HOME目录中,如果Jenkins Master挂掉之后,相应的数据就没有了。
第三个问题是Jenkins需要升级,插件也需要升级,而镜像本身没办法自动升级。
解决
为了解决这些问题,首先我们将Mesos打成一个基础镜像,在这个基础镜像上安装Jenkins的服务制作成一个Jenkins的镜像,这样就可以调用Mesos的Lib库文件了。
第二是数据备份,我们在Jenkins上安装了一个插件,就是SCM Sync Configuration Plugin 这个插件,它会同步数据到Github。如果这个Jenkins Master挂掉了,迁移到另外一台主机上,它会从Github上面把数据克隆下来,数据是不会丢失的。此外,如果插件出了故障,或者因为网络问题导致Github访问不了,我们把JENKINS HOME目录挂在主机上进行备份,进行恢复的时候也会很方便。用以上这两点来保证数据的完整性。
第三对于Jenkins升级或其插件的升级,我们现在的做法是把它重新打一个镜像,重新在Mararhton发布Jenkins Master。
Jenkins Slave On Docker 工作流程
首先Jenkins Master如果要构建一个Job,让Mesos Slave起一个Jenkins Slave这样的容器,容器里面是没有Docker环境的,因为容器里面再装一个Docker环境就太重了。我们现在的做法是把Jenkins Slave用到的命令挂载,其中包括 /usr/bin/docker /var/lib/docker /sys/fs/cgroup /var/run/docker.sock ,这样在容器内部就可以操作主机上的Docker环境,Jenkins Slave 可以在容器内部进行一些Docker Build,Docker Run,Docker Push等工作。
Jenkins Job List
这是我们运维平台一些Job List,左下角是Mesos各个Slave的节点,每一个节点上都有任务,这些任务都是并行的。因为我们的服务模块比较多,可能一个模块一天提交十几次,这么多的模块在一天提交几十次或者上百次的情况也出现过。手动更新、构建的话是非常难做到的。现在,做成Jenkins 分布式执行Job的模式,一天构建几百上千次也没有问题,它会把构建的任务均匀的分布在主机资源里。
数人云运维平台持续集成实践
这是数人云运维平台的持续集成实践。首先讲几个组件,比如Github,它是存储代码的,第二个组件是我们自己开发了的一个Configserver的API,第三个组件是Jenkins和Marathon。第四个组件是我们的私有Registry,是Docker的一个存储镜像的镜像仓库。最右下角是CDN,安装包传送到达的地方。
首先Jenkins触发任务,Jenkins调用Configserver提供的API,这个API就去Github上获取最新代码的Tag,并对比现有的已经更新过的Tag。如果不需要更新,这个任务就完成了,结束了。如果需要更新的话,就进行第三步,把从Github拉的代码放到Configserver上,Jenkins Slave的节点会从Configserver去拉取代码到Slave的容器之中。把代码下载之后再去Pull一个我们编译环境的镜像,然后再用这个镜像去编译我们的代码。比如编译出来一个二进制代码,我们把这个二进制重新打一个运行时的镜像。这个Runtime镜像打好之后我们再使用Docker Push把它推到私有的Registry,镜像Push到Registry后,就可以发布到各种的环境,比如说Dev Demo生产环境,调用Marathon API直接发布就可以了。同样在Jenkins Slave,推完镜像之后就可以去调用。这样一个整体构建镜像再部署的任务就构建完成了。
如果有一些安装包,比如我们的Agent包,它不需要发布Marathon,需要上传到CDN,也是在Jenkins Slave 中执行的。
配置管理
问题
因为我们引入了Docker,而且是分布式动态调度的,传统的配置工具如Ansible、Puppet 、SaltStack都已经不适用了,没办法去管理Docker内部的东西。这些配置文件修改起来非常麻烦。
配置中心一期
讲一下如何进行配置的更新。首先我们做了一个配置中心的一期,把镜像嵌入自己的脚本,这个脚本实现的功能就是 根据传入的ENV CONFIG_SERVER和 SERVICE ,访问 Configserver的API,API返回的数据就是这个服务需要下载的配置文件的列表,以及它需要下载到哪个目录底下。Configserver也非常简单,起一个Nginx,去Vim手动修改。容器一重启,就会自动拉这些配置文件,把配置进行更新。
一期的问题
一期完成之后,因为是多种环境,如Dev Demo环境、预生产环境、生产环境等等,虽然把这些配置都在Configserver上集中配置,但还需要手动修改这些配置。手动修改容易出错,例如Dev更新了一个服务,可能过了一周才会更新到生产环境。那个时候再去修改生产就很容易出错,导致无法运行。此外,如果配置发生了Bug需要回滚,手动修改也是不合适的。
配置中心二期
为了解决这些问题,我们做了ConfigCenter的二期。其中有我们自己开发的一个运维平台,以及Github和Gitlab。Github是个存储项目的代码,Gitlab是用了存储配置模板和最终配置文件的。我们用Jenkins把它们整体的串起来,我们的第一个工作是把所有的配置文件抽象化,各种环境的文件抽象出来一个模板,放在Gitlab上。它的数据是放在数据库里面,这样组合起来是一个完整的配置文件。各个环境的值是不一样的。 我们的运维平台触发Jenkins,Jenkins去调度我们的ConfigCenter API,它传入两个参数,一个是需要更新的环境,第二个是更新哪个服务。之后API去做对比,从数据库去读现有的配置文件的模板Tag,再去读新模板的Tag进行对比。如果这个文件需要更新,把它从数据库拉过来,数据做匹配,渲染成我们最终的配置文件,再传到Gitlab上。剩下的通过Jenkins 触发 Configserver去调Gitlab下载最新的配置文件到Configserver服务器,Jenkins再去调用Marathon去重启服务,服务就会成功更新配置文件。
这里有两个需要注意的点,一方面模板需要更新,另一方面值要更新,这是两种模式。模板更新是所有的流程都要走一遍,如果模板没有更新,只是值更新的话,我们只需要在运维平台上做修改。修改的同时它会做一个标记,说明这个服务配置文件是需要更新的,之后就会生成一个最新的配置继续下面的操作。如果这两个都不需要更新的话就返回,不再操作。
二期的问题
二期解决了从dev更新到demo、生产,模板同步的问题,但一个服务开发交付给运维配置时还是需要关注多个配置文件,各个服务的配置文件又都不尽相同,所有的配置文件加起来还是有些偏多,添加修改容易出错。
服务ENV化
为了解决配置多问题,我们把所有的服务都进行ENV化。
规约产品代码和目录文件
- 在服务服务代码添加添加 代码编译环境的dockerfile、运行时的dockerfile
- 产品统一配置从环境变量读取,在代码目录中添加环境变量ENV文件
- 如果代码不支持从环境变量读取,编写启动脚本,在容器启动前读取ENV 使用sed替换相关文件
发布
- 开必根据编译需要的dockerfile打出编译镜像,推送到registry
- jenkins根据编译镜像 和 运行时dockerfile打出运行时镜像,推送到registry
- 发布服务的脚本,根据service的发布脚本模板和ENV列表生成 发布脚本
服务ENV化配置实例,如图中Environment列表:
配置中心三期
通过上面这些机制,配置中心三期调整的主要内容为三期兼容二期的所有配置文件,在这个基础之上,把ENV列表当一个配置文件来管理,ENV的更新流程大部分跟二期的其它配置文件一样。不同的是ENV模板是根据代码中的ENV列表自动生成并推送到gitlab中,发布服务的脚本是根据service的发布模板和ENV列表生成最终发布脚本。 这样所有的配置都集中在ENV列表中,如果需要修改配置,运维只需要关注ENV,大大的减少查找配置时间和修改配置出错的机率。
这就是我分享给大家我们的容器公司产品迭代实践,大家有问题可以交流一下,谢谢。