云栖TechDay活动第十八期中,阿里云的高级研发工程师莫源带来了题为《像搭积木一样玩转Docker的持续交付》的分享,主要讲解了阿里云容器服务实现基于Docker的持续交付、容器持续交付的设计思路和未来发展反向。
幻灯片下载地址:https://yq.aliyun.com/attachment/download/?filename=dbf464e5883344e9dd010214576889bf.pdf
以下为现场分享观点整理。
最近Docker越来越火,持续交付的概念也随着被大家越炒越火。对于持续交付而言,1000个人有1000种理解,关于持续交付的演讲内容即打通小异又大相径庭。大同小异是指大家对持续交付的基本理解是相似的,但对持续交付的实现方式是大相径庭的,这是为什么呢?
这是因为持续交付其实是一个概念,它并不是一个具体实现的方式,而是说按照持续交付的指导思想和规范来实现自己的持续交付系统。
用Lego的思维看基于Docker的交付方式
交付方式一直在不断地演进、不断地变更,如上图所示,一个较大的公司内会有多个技术站、多个不同应用。不同应用有着各自不同的开发环境和运行环境,所对应的交付场景也更不相同。因此,当公司体量越来越大、业务越来越丰富时,你就会发现交付场景也会变得越来越丰富,带来的挑战也越来越大。
常见的交付流程主要包括三要素:开发、测试、运维。开发人员开发代码并在本地或测试环境测试,通过后将其提交给测试人员;测试人员在测试环境中利用手工测试或自动化测试,将整个流程验证开通,如果不存在问题,则应用即可上线;最后一个步骤是运维人员将相应的代码部署到机器上,然后对后期应用做运维,例如可能会初始化一些脚本、软件环境,做一些后期日志的收集、监控、告警以及系统的调优。
这几个步骤运行流畅实际上有几个基本前提:
第一个前提是要有一套基础的技术栈和环境自动化流程,才能实现应用的快递交付;
第二个前提是团队的技术框架尽可能保持稳定;
第三个前提是需要具有良好的文档和技术沉淀,文档中应该写清楚如何运维、部署、调试以及问题的解决方法,同时相同问题出现第二次时,应该可以凭借技术沉淀立即修复;
第四个前提是团队成员相对稳定,当团队人员经常发生变化时,尽管有之前的文档等等资料,但还需要一个熟悉过程,不免会导致效率的降低。
这些问题在公司都是非常常见的现象,寻根究底时,其实主要是由三个问题导致的:
第一,交付流程不能良好的自动化或自动化成本过高,如果部署应用可以变成一个按钮,屏蔽技术障碍,则可以轻松实现应用的交付。
第二,交付缺乏自描述的机制,严重依赖文档或口传心授。
第三,不同角色对于职责的交叉地带缺乏合作,例如开发、测试、运维同学之间,最重要的事是将软件交付出去,但是子啊职责的细分过程中,很多交叉领域是没有一个明确地划分的,导致有的地方是谁有责任心谁做,职不明,进而导致缺乏合作。
三个问题总结成三个词就是:自动集成难、持续交付难、合作协同难。
很多人都在安利Docker为交付而生,是一种轻量级的操作系统虚拟化方案。使用Docker时,虚拟机还可以使用之前的Hypervisor,依旧可以使用VM的方式进行部署;上方应用可以利用Docker技术部署,实现弹性应用架构,还可以完美地实现隔离。
这些看上去都很美好,但到底如何解释它和交付之间的关系呢?
在乐高积木中,有件非常伟大的事情,就是标准化模组,各个小模块之间有相互的接口,最终可以完成不同形状的拼接。Docker同样做了一件伟大的事情:标准化交付,也就是所谓的软件定义交付。将复杂的PHP环境、JAVA环境转变为标准的Dockerfile,不同的环境可以同样的方式打包,标准haunted了软件环境。
在Docker中,Docker的命名不用关心容器内是PHP应用还是JAVA应用,也就是说不同种类的应用可以用相同的命令或API管理,规避了不同软件之间的差别,标准化了软件形态。
不同应用之间是具有相互调用关系的,之前,是在配置文件内加以说明;现在,可以将这件事情一到Docker场景之下,利用编排模板解决该问题。在编排模板中,可以申明不同应用之间的相互调用关系、部署形态以及节点的数目和分布策略,也就是不同应用拓扑关系变成了遵循特定语法的编排模板,编排模板可自描述,编排模板确定时,应用形态也随之确定,标准化了交付流程。
持续交付+Docker=ContainerOps
虽然这里写到持续交付+Docker=containerOps,其实持续交付更像是一个哲学问题,不同的人会有不同的答案,因为持续交付是和场景密切相关的。我们所要做的是:不要将持续交付当成一个固定的流程,真正的持续交付是和业务密不可分的,符合你自己的业务场景的持续交付才是最好的持续交付。
在持续交付中,真正严肃的问题只有三个,分别是:如何重建系统?如何安全地部署系统?部署后的问题监控与解决?逐个问题进行翻译,重建系统的意思是部署一个软件之后,如何初始化环境,如何将对应的技术站做好;如何部署系统是说,软件代码已经准备好了,环境也已经搭建好了,如何进行发布;第三个问题是只软件发布部署后,在运行时所遇到的各类问题,如何去做警告如何去监控、回滚等。将这个三个问题解决好,属于你自己的支付系统就搭建完毕了。
首先来看一下如何重建系统,即如何初始化环境。正如上文提到,最终软件环境都会变成Dockerfile,Dockerfile中具备了全部的软件基础环境,如乌班图;同时也具备软件运行环境,如Java和PHP;此外,还包括软件运行的代码和软件运行的命令,最终所有的软件环境都会变成Dockerfile定义的镜像,该镜像可以运行应用、重建系统,解决初始化环境的问题。
软件发布时,不同的软件之间是有相互依赖的,相互关联的软件可以通过编排模板标准化部署,本地可以运行远程便可以交付。
对于运维时的监控、告警、回滚等操作,大部分情况是由上层的容器服务商来提供的,通过调用Docker标准的API获取容器状态,出现问题可以通过镜像回滚、更新、扩容。
阿里云容器服务持续集成模组
下面来看一下阿里云是如何实习持续交付系统的,阿里云提供的是把我们底层的云资源整合起来的一个能力。
上图展示的是阿里云容器服务和周边的生态,图中左边和右边都是阿里云的IaaS层服务,如云数据库、缓存服务等,如果想在容器内使用数据库,是可以使用阿里云提供的云数据库去做;如果想用消息队列,也可以找到对应的云产品。
中间部分是阿里云容器服务提供的模组部分,阿里云容器服务和其他容器服务提供商不同,大多数容器服务商更倾向于采用pass去做这个场景,阿里云更侧重是将用户的能力进行整合,然后提供给用户更多的云资源。阿里云容器服务采用Docker Swarm编排系统,进行上层管理,完全兼容Docker社区;同时阿里云容器服务在Docker上进行了容器编排、弹性伸缩、集群管理;此外,在服务级别做了Discover Service,即服务的注册和发现,进行同步和异步通信,诸如不间断发布也是来源发布这一部分。再上一层支持两种路由:一是VM级的路由;另一种是阿里云内部的自定义的路由服务。
容器Hub持续交付模组
来讲一个很简单的交付案例:代码变更后,远程线上也随之更新的简单案例。这里主要运用了容器Hub的持续交付模组,该模组由三部分组成:容器Hub、代码仓库、容器服务触发器。运用这三个模组之后就可以实现本地提交代码,远程自动更新。
原理如下:开发者在本地提交代码之后,会将代码提交到代码仓库,目前容器Hub集成的代码仓库有阿里云Code、Github和Bitbucket;当源代码仓库发现开发者所提交的代码后,将该消息通知给阿里云容器Hub,容器Hub确定提交代码的分支,会根据该分支对应的代码Branch,将代码下拉,然后进行镜像的自动化构建;镜像自动化构建完成后会通过一个触发器的方式触发容器服务进行应用的自动部署;容器被触发后,下拉最新的镜像,重新运行应用。通过这个流程来实现最简单的交付系统。
Jenkins持续交付模组
下面来讲一个更复杂点的持续交付方式,是一种开源方案:Jenkins持续交付模组。该模组主要分为代码仓库、Jenkins Master、Jenkins Slaves、容器Hub、容器服务触发器或Jenkins部署插件五部分。
整个交付的大致流程如上图所示。Jenkins实际上是开源中常见的持续集成所用的Center,大家可能会把它作为持续集成或持续交付的Server。Jenkins实际上是开源的持续集成Server中功能最为强大,生态最为丰富的,包含各类所需的插件,如支持静态代码扫描的SoMa插件等等。
Jenkins内部的维度是一个Job维度,例如你想做一次持续集成,则相当于该Job执行之后便可以做完持续集成,但这个Job的运行并不是在Jenkins本机上运行的。Jenkins是主从结构,它是Master/Slave模式(在容器服务中,推荐使用该模式),在该模式下,一个Job会从Jenkins Master分发给Jenkins Slave,而在Jenkins Slave上完成Job的具体任务。
对于上述案例,开发者首先将代码提交到源代码管理仓库;该代码仓库会通知Jenkins Master,Jenkins Master会将相应的任务分发给对应的Jenkins Slave;在Jenkins Slave内,使用者可自定义流程、构建方式,同时在Jenkins Slave内,完成代码的编译和测试、代码的Image build and publish,以及最终部署的触发器。
在Jenkins Slave内可以完成很多事情,这里只是采用默认时限,来帮助大家简化开发。在Slave内,我们定义了四个流程,分别是:Build、Test、Image build and publish和Probe trigger。
这里以Java应用举例,Build流程类似于Maven Build功能,会打包成一个Jar包或War包、Tar包。Test流程是完成测试功能,可以完成单元测试、集成测试,也可以融合二者同时运行,不同的测试类型取决于对应的业务形态。Image build and publish流程相当于将build之后的结果根据Dockerfile构建成所需的镜像,再将该镜像推送到Hub仓库中。第四个步骤实际上是触发自动重新部署,或者使用插件重新部署。
在上述流程内,遇到了各种各样的问题,这里与大家进行一一分享。
首先第一个问题是:Jenkins官网的镜像大多跑不起来。经过排查后,发现问题出现在镜像内部,因为Jenkins默认执行的权限为Jenkins的user,挂载的Jenkins_home写权限不足。在阿里云容器服务中提供了自己的Jenkins镜像,在该镜像中采用先提权后解权的方式,实现了宿主机挂靠镜像运行权限的问题。此外,阿里云容器服务还打包了一些常见的开发插件,帮助大家减少下载插件的时间和下载失败的可能性。
第二个问题是:Jenkins Slave中构建镜像,怎么处理Docker in Docker。针对该问题,可以将/var/run/docker.sock传递进入Slave,这样Slave中的Docker相当于一个Client,而真正的Engine是宿主机的Docker,会使用宿主机的资源。
最后一个问题是:Jenkins持续交付中多套环境如何解决。最简单的方式是采用Git workflow的方式,实现不同分支不同权限、开发环境、预发环境自动发布,不同特定分支发布到特定的环境,线上分支采用手动发布,可以使用多种发布策略实现0宕机发布。
蓝绿发布模组
蓝绿发布是指应用版本进行蓝绿发布时,在其他几台机器上或环境中起一套类似的版本(新版本),两个版本之间共享一套路由;通过路由权重的切换方式来实现不同版本之间的切换。例如,先上线A版本,然后又上线了B版本,如果想查看B版本是否正常,可以吧线上路由从A切换到B,验证没问题时,进行Confirm,Confirm动作会吧原来的老应用删掉,新应用完全上线;如果应用B存在问题,则把路由切换到A,再进行回滚操作,将新发布的应用下线掉。
蓝绿发布是基于路由来实现的,因此要涉及容器服务里边网络状况。上图是容器服务的网络示意图。在容器服务*支持四种网络模型:None,即无网络,相当于容器里只有IP,对外无法访问;第二种是Bridge模式,bridge模式相当于把容器里边的端口映射到宿主机上的端口;第三模式是host模式,host模式就相当于完全共享宿主机的网络;第四种模式是容器服务现在默认提供的网络模型,叫only网络,only网络实际上是一种container的实现。它大致原理是:不同虚拟机、容器之间实际上有一个大的container网络,在这个网络内,所有容器时互通的。但从宿主机的网络访问二层网络还是不同,因为它们是不同的网络模型。阿里云容器服务提供的路由服务,是基于HA Proxy的实现,而HA Proxy的实现主要完成的工作就是将两层的网络模型进行一次打通,使用者可以通过HA Proxy来转发到二层网络内的任一容器。灾不同容器之间进行权重切换时,权重是在HA Proxy层完成的。目前对HA Proxy层支持两种路由方式:内部路由和外部路由。外部路由相当于是对外访问时用的,而内部路用于应用之间的相互调用,相当于是内部的负载均衡。
对于蓝绿发布,常见的应用场景包括:利用Jenkins模组进行自动集成,完成测试环境和预发环境的自动发布。在手动发布中,采用的是蓝绿发布0宕机发布。
蓝绿发布的大致策略是:两个版本并存,通过权重切换的方式来实现流量的上线和下线的切换;验证无误的时进行发布确认,删除老版本容器;验证无法通过的时候就发布回滚,这时新版本的容器就删除。