基于Docker的开发模式驱动持续集成落地实施
嘉宾简介
资深质量优化专家,12年软件测试与质量管理经验
《软件性能测试诊断分析与优化》等多本IT畅销书作者
演讲实录
今天主要交流的主题是基于Docker的开发模式如何驱动持续集成落地实施,这里会涉及两个主要的话题,一个是所谓Docker的开发模式是怎样的,与传统的开发模式有什么区别;另外一个是持续集成作为敏捷开发的最佳实践,结合Docker来实施会有什么样的效果,会不会有更好的促进作用,尤其是在传统企业中实施。
也希望跟大家一起交流一下关于Docker的一些具体实施问题、实施经验
首先我们来看看,在传统的开发运维模式下,会存在哪些问题。我稍微归纳总结了一下,大概会有以下三个问题:
- 从需求到版本上线中间是个黑箱子,风险不可控
- 开发设计时未过多考虑运维,导致后续部署及维护的困难
- 开发各自为政,烟囱式开发,未考虑共享重用、联调,开发的资产积累不能快速交移到运维手中
应对这样的问题,我们通常倡导的解决之道是:运维前移,统一运维,建立持续交付服务体系。
但是这样空喊是没用的,我们要来点实际的,尝试利用一些技术手段真正解决开发运维一体化的问题,具体推进DevOps的实施。
那好,我们看看最近1、2年非常火的Docker技术,看能否在解决上述问题上带来一些具体的突破,或者给我们带来一些解决具体问题的启发。
我们先从Docker的一些技术特性来看,这些新的技术可能带来的改变:
- Docker首先是一个容器级虚拟化技术,相比传统虚拟化技术,容器级的虚拟技术是操作系统内核层的虚拟,所以能节省更多资源、提升性能,意味着单位机器资源消耗下,能承载更复杂更庞大的应用系统架构。
- 启动速度更快,毫秒级的启动速度,这对于快速部署开发测试及运维环境非常有利。
- 镜像分层,部署时可以按需获取镜像生成容器并快速启动运行,因此有利于快速的部署扩容,解决运维中水平扩容的问题。
- 由于Docker镜像的天然可移植性,就像集装箱一样快速打包应用以及依赖项,在开发、测试、运维之间移动,所以可以推进 开发-测试-运维 环境的统一,持续集成(CI)能发挥更大的作用,例如通过CI构建应用、检查代码,打包到Docker、分发部署到测试和准生产环境,进行各类测试,都可以更方便快捷和统一。
既然Docker的一些技术特性看起来确实能给开发测试运维带来一些新的东西,那么我们接下来就具体看看,这些新的技术特性具体如何应用,以及应用过程中可能带来的问题。
容器可以把应用以及它的依赖项进行打包,这样带来的好处就是应用隔离,能否把这种隔离特性用于解决一些架构问题呢?因为架构设计不好的话,给开发、测试,尤其是运维以及后续的维护管理带来很多麻烦,例如随着业务复杂度越来越高,可维护性和敏捷程度随之变差。
传统的应用通常采用所谓的三层架构,例如:界面层 – 业务逻辑层 – 数据层,通常我们会把所有实现业务逻辑层的代码编译构建后部署到中间件,再通过负载均衡、集群等解决分流、灾备等问题。但是这种架构设计带来的问题是:
开发效率低
随着应用复杂度的增加,越来越少开发人员对应用能有全局性的深度理解。新功能开发和缺陷修复难度呈几何性增加。代码修改的正确性无法保障。而庞大的代码库需要更庞大的开发团队来维护,无形中又增添了管理、沟通和协调的成本。另外,新加入的团队成员需要花费大量的时间和精力来熟悉一个复杂的代码库。
交付周期长
在单一进程的单块架构下,任何微小的改动都需要重新编译、集成、测试和部署整个应用。随着应用体积的增大,交付流程和反馈周期都会相应变长,应用发布的代价也随之增加。于是应用交付周期变缓,交付间隙积累的代码变动增加,从而对于下次交付产生更大的压力,形成恶性循环。
技术转型难
单一进程、单块架构意味着中心化的技术选型。比如,应用的不同逻辑组建通常需要采用相对统一的编程语言、框架和技术栈。这些在项目初始阶段便已定型。之后,即便是应用中全新的逻辑组件,也很难采用不同的技术栈。而当应用达到一定规模后,全局化的技术栈更新会面临很高的风险。所以,单块架构应用一旦定型,就很难再享受行业技术变更、发展所带来的红利。
有了容器技术之后,微服务设计也被大家慢慢提到架构设计的前沿进行讨论了,这方面推荐大家看一本书《Building Microservices》。
微服务架构的诞生和容器技术的流行,几乎是同时发生的,这并非偶然,而是互联网时代倒逼传统技术和架构而产生的变革,而以Docker为代表的容器技术则为微服务理念提供了匹配的实现机制。
在微服务架构下,我们将原本单一的应用按照功能边界分解成一系列独立、专注的微服务。每个微服务对应传统应用中的一个组件,但是可以独立编译、部署和扩展。相对单块架构,微服务具备以下优势:
(1)复杂度可控:在将应用分解的同时,规避了原本复杂度无止境的积累。每一个微服务专注于单一功能,并通过定义良好的接口清晰表述服务边界。由于体积小、复杂度低,每个微服务可由一个小规模开发团队完全掌控,易于保持高可维护性和开发效率。
(2)独立部署:由于微服务具备独立的运行进程,所以每个微服务也可以独立部署。当某个微服务发生变更时无需编译、部署整个应用。由微服务组成的应用相当于具备一系列可并行的发布流程,使得发布更加高效,同时降低对生产环境所造成的风险,最终缩短应用交付周期。
(3)技术选型灵活:微服务架构下,技术选型是去中心化的。每个团队可以根据自身服务的需求和行业发展的现状,自由选择最适合的技术栈。由于每个微服务相对简单,当需要对技术栈进行升级时所面临的风险较低,甚至完全重构一个微服务也是可行的。
微服务架构中,可为每个服务选择一个新的适合业务逻辑的数据库系统,比如MongoDB、PostgreSQL。这样做的好处:首先我们可以根据业务类型(读多还是写多等)来决定使用哪种类型的数据库,其次这样可以减小单个数据库的负载。
(4)容错:当某一组建发生故障时,在单一进程的传统架构下,故障很有可能在进程内扩散,形成应用全局性的不可用。在微服务架构下,故障会被隔离在单个服务中。若设计良好,其他服务可通过重试、平稳退化等机制实现应用层面的容错。
(5)扩展:单块架构应用也可以实现横向扩展,就是将整个应用完整的复制到不同的节点。当应用的不同组件在扩展需求上存在差异时,微服务架构便体现出其灵活性,因为每个服务可以根据实际需求独立进行扩展。
看起来微服务的架构设计优势明显,也是未来架构设计的一大趋势方向,但是,如果采用微服务的架构设计,基于容器包装微服务进行部署,给运维会带来哪些新的挑战呢?我认为会从以下几个方面体现:
(1)运营开销:更多的服务也就意味着更多的运营,需要保证所有的相关服务都有完善的监控等基础设施,传统的架构开发者只需要保证一个应用正常运行,而现在却需要保证几十甚至上百道工序高效运转,这是一个艰巨的任务。
(2)DevOps要求:使用微服务架构后,团队需要高品质的DevOps和自动化技术,需要懂更多不同类型的技术栈。
(3)隐式接口:服务和服务之间通过接口来“联系”,当某一个服务更改接口格式时,可能涉及到此接口的所有服务都需要做调整。
(4)分布式系统的复杂性:微服务通过REST API或消息来将不同的服务联系起来,这在之前可能只是一个简单的远程过程调用。分布式系统也就意味着开发者需要考虑网络延迟、容错、消息序列化、不可靠的网络、异步、版本控制、负载等,而面对如此多的微服务都需要分布式时,整个产品需要有一整套完整的机制来保证各个服务可以正常运转。
幸好,业界已经从运维管理的角度出发,做了不少工作来尝试解决上述问题,例如Google开源的Kubernetes容器集群管理系统,提供应用部署、维护、 扩展机制等功能,利用Kubernetes能方便地管理跨机器运行容器化的应用,其主要功能如下:
- 使用Docker对应用程序包装(package)、实例化(instantiate)、运行(run)。
- 以集群的方式运行、管理跨机器的容器。
- 解决Docker跨机器容器之间的通讯问题。
- Kubernetes的自我修复机制使得容器集群总是运行在用户期望的状态。
前面我们探讨了一下基于容器的微服务架构设计给传统开发运维模式带来的改变,看起来更多的还是正面的改变。
容器化微服务解决的更多的是架构设计的问题,按软件工程来讲,设计之后的下一步就是开发实现的事情了,在这个阶段,传统的开发测试会有不少的问题,例如环境的问题:
- 软件安装麻烦、来源不一致、安装方式不一致、杂乱无章。
- 共用一个服务器开发环境,隔离性差,互相冲突。
- 可移植性差,例如和生产环境不一致,开发人员之间也无法共享;新人入职通常又折腾一遍开发环境,无法快速搭建。
那么,随着容器技术的引入,是否能带来一些改观呢?答案是明显的!开发测试环境的容器化带来的是标准化的开发测试环境,这对开发测试及运维的意义体现在:
(1)对开发测试的意义:测试环境搭建效率的提高,高效利用硬件资源同时又能敏捷轻便地搭建功能完备的开发测试环境,例如在一个资源有限的环境下面(比如开发人员的笔记本电脑)例如Chef 把系统的各个模块按照清晰的逻辑结构部署并运转起来,从而快速高效地进行开发与测试
(2)对运维部署的意义:开发测试(环境、依赖包) - Docker Hub - 运维(环境、依赖包) 的环境一致性
关于Docker在测试领域的一些应用,大家可以看看我写的一篇文章,《浅谈Docker在测试领域的应用》。
在传统模式下,开发自测通常没问题,但是到了测试或生产环境程序无法运行,让开发团队排查,经过长时间排查最后发现是测试环境的一个第三方库过时了。这样的现象在软件开发中很普遍,已经不适用如今的快速开发和部署。
而在Docker模式下,应用是以容器的形式存在,所有和该应用相关的依赖都会在容器中,因此移植非常方便,不会存在像传统模式那样的环境不一致。
在开发测试环境容器化的背景下,研发模式的改变:
项目开始,架构师根据项目预期创建好需要的基础镜像(Nginx、Tomcat、MySQL镜像),或者将Dockerfile分发给所有开发人员,开发人员根据Dockerfile创建的容器或从内部仓库下载的镜像来进行开发,如果开发过程中需要添加新的软件,则向架构师申请修改基础镜像的 Dockerfile;
开发任务结束后,架构师调整Dockerfile或image,分发给测试部门,测试部门马上就可以进行测试,消除了部署困难等难缠的问题。
在开发测试环境容器化的背景下,持续集成的模式也会发生一些必然的改变:持续集成各步骤围绕任务Docker模块进行工作。
开发人员的代码嵌入会触发开发环境中新的Docker镜像的构建(代码的编译、构建、检查、部署都基于Docker进行),测试人员发现有新的Docker 镜像构建出来,就会部署到测试环境中进行各类测试和验证,测试通过后,会把镜像放到公共镜像库,由运维人员进行生产环境的部署和发布。
好,那看完Docker在开发测试阶段的应用,尤其是对持续集成方式的改变后,我们来看看,Docker对于传统业务上线方式带来的改变。
传统业务上线存在的问题是:
- 上线环节多,跨越多个部门,沟通成本高;
- 涉及多个部门的审核,包括资源的申请、环境权限的批准、需求测试验收等,耗时长;
- 再加上上线部署过程涉及到的技术环节多,例如代码获取、编译构建、代码检查、软硬件安装配置、应用装载、启动,导致发布周期很长。
针对传统应用交付过程中的这些问题,Docker的引入带来了明显的改变,Docker把应用及相关依赖项打包成一个轻量、可移植、自包含的容器,让应用的部署和发布基于容器进行,而不是基于代码部署。由此,Docker重新定义了打包程序的方法:
Docker容器 + 用户应用 = 部署单位(构件)
容器级部署带来的最大的好处就是开发者本地测试、CI服务器测试、测试人员测试,以及生产环境运行的都可以是同一个Docker镜像。因此可以实现应用的自动化快速部署及上线发布。
由于Docker技术的标准化通用化,可以方便地实现云服务器之间的移植,不依赖具体的某一个云服务商,降低了云部署的风险,因此,从这个角度来看,Docker能极大地促进企业做应用云部署的积极性,难怪各大IaaS和PaaS供应商都热烈地拥抱Docker容器技术。
基于Docker的应用部署和发布,还可以方便地实施蓝-绿部署,例如:保持两套一样的生产环境,而实际上只有一套环境真正的对外提供服务(绿环境),而另一套环境则处于待机状态(蓝环境)。
先上线到蓝环境,如果测试没问题,再将路由切换到新的服务上。带来的好处是明显的:
- 最小化停机时间
- 快速回滚
- 热备份
但是具体实现蓝绿部署可能还会碰到更多细节的问题,例如数据的处理,这些都有待实践。
最后,我们来聊聊容器微服务运维,前面我们有聊到基于容器化微服务设计给运维带来的挑战以及一些应对措施。
在容器微服务的模式下,功能模块被隔离到一个个可单独开发、测试和部署的容器中,所以当某个功能出现问题的时候,可以单独把其对应的容器撤销,开发进行修复,再上线,在这个过程中其它的功能(容器)不受影响,仍然正常运行。
微服务带来的运维问题:
拆分成微服务后,部署变得复杂,监控的复杂度也随之提高,要理解在微服务之间产生的复杂交互,需要优秀的诊断与监控工具,同时对运维技能的要求也提高了,例如要了解多种技术栈。
我觉得可以从以下几个方面去考虑:
(1)自动化管理:自动构建、自动测试、自动部署、自动运维...,尽可能自动化一切,从而控制好复杂度、工作量、降低人为操作失误的风险;
(2)围绕业务能力建模服务:运维需要对开发提出需求,例如:服务部署的独立性、失败隔离性、可监控性,构建中心化的配置服务管理;
(3)充分利用容器技术实施服务流控,例如:降级、限流;
(4)充分利用容器技术实施服务恢复,多考虑故障快速恢复而非避免。