前言
随着微服务架构与容器虚拟化技术的发展,持续集成与持续交付的概念又重新回到了大家的视野,越来越多的公司开始使用持续集成的系统来解决频繁发布带来的质量问题;使用持续交付的工具来实现代码在不同环境上的自动部署。原本有些学院派乌托邦式的思想正被千千万万次的集成与部署证明着它应有的价值。那么究竟是因为什么让持续集成与持续交付这个已经不再年轻的软件开发与交付的思想重新焕发绽放迷人的光彩呢?
传统软件交付之殇
传统软件的开发与交付的周期都很漫长,一款普通的企业软件通常需要十几个开发人员,几个月的时间来完成,从需求的分析、系统的设计、编写测试用例、系统开发、单元测试、组装测试到交付调试。有条不紊的流程与规范像一辆绿皮火车下的枕木,稳定而可靠的保证整个系统缓慢的推进,每一次交付、升级,都需要提供基础的硬件、软件的环境、软件的代码、软件的文档与手册。还记得刚刚迈入软件开发行业的时候,跟随公司的服务团队,驻场交付产品,每一个驻场工程师都按照之前预演过好多遍的流程,对照着系统的部署手册,一步一步的组装硬件,安装软件,稍有差池,就要按照对应的应急预案进行回滚。开始的时候觉得交付像一个神圣的仪式,将用智慧和汗水构建成的软件交付给客户使用,是一种非常荣耀和值得骄傲的事情;后来越来越多次的产品交付让我深深的感觉每一次交付都像分娩一样痛苦,扪心自问是否有简单更舒畅的流程可以将软件交付给客户呢?
传统模式的反思与CI/CD概念的提出
通常来讲,一个软件的生命周期分为问题的定义、可行性的分析、系统设计、系统编写、系统测试与调试、系统部署与交付、维护与升级等步骤。在传统软件的生命周期中,更倾向于使用瀑布流的模式来去有条不紊的规范整个流程,每一个阶段都期望遵循“活动-结果-审核-再活动-直至正确”的流程来保证系统稳定。整个软件的生命周期就变成了一个很长的二维线性的流程。这也制约了软件的开发迭代与交付的速度,前辈们想了非常多的办法来提高整体的开发速度,比如将一个单体的系统系统设计成为服务化的分布式的子系统,这样可以让一个大型的单体软件的开发变成多个小的独立系统的并行开发;使用组件化的方式组建系统,在不同的系统间复用模块加速开发;通过自动化工具或者脚本进行自动化部署与交付等等。
诚然,这些都解决了软件交付过程中的一些问题与难点,但是这些方式都像西医一样,治标不治本,因为要想快速的交付,首先要明白软件交付过程中遇到的核心问题是什么。总结成两个词“自动”与“可靠”。自动是一个很宽泛的词汇,在软件交付中代表着测试自动化、交付自动化、运维自动化等等,而可靠讲的是每一次交付要保证是当前的交付是稳定的或可回滚到稳定版本的。
为了解决“自动”与“可靠”的问题,敏捷开发鼻祖Martin Fowler提出了持续集成与持续交付的概念,它所描述的软件开发,是从原始需求识别到最终产品部署到生产环境这个过程中,需求以小批量形式在团队的各个角色间顺畅流动,能够以较短地周期完成需求的小粒度频繁交付。频繁的交付周期带来了更迅速的对软件的反馈,并且在这个过程中,需求分析、产品的用户体验和交互 设计、开发、测试、运维等角色密切协作,相比于传统的瀑布式软件团队,更少浪费。通过这种小步快跑的方式,将小功能快速迭代、验证、交付,通过自动化的工具,将测试、部署、运维自动化,减少需求在软件生命周期中流动的时间。但是为什么看上去可以奉为圭臬的持续集成与持续交付的思想却在相当长的时间被开发者束之高阁呢?
实现持续集成持续交付的难点
对持续集成持续交付有一些理解与体会的开发者会经常看到类似下面这张图的持续交付流程。
在这张图中我们讲述了一个持续集成与持续交付的流程,从代码的提交、构建与编译、单元测试到部署环境、集成测试与发布。软件交付本身就是一件复杂的事情,不同的产品、不同的架构、不同的业务形态会导致持续集成与持续交付的实现上有非常大的不同。还记得很久以前流行一个关于哲学的笑话,当你问十个哲学家什么是哲学的时候,你会得到十一种答案,因为每个人都有对哲学不同的理解。对于持续交付也一样,Martin Fowler讲述了一个乌托邦式的软件开发与交付的模式-持续集成与持续交付,但是前辈只给了我们先进的思想,并没有给出默认的实现。不同的公司、不同的产品、不同的技术栈、不同的开发与部署形态决定了持续集成与持续交付注定是因人而异的,在大家不断摸索什么样的方式是持续集成与持续交付的最佳实践的过程中。有的人做少了,只实践了其中的一部分,导致基本的交付能力上有缺欠;有的人做多了,引入了更多复杂的流程,导致原本应该提速的交付流程,像穿着名牌高跟鞋参加跨栏一样,怎么也快不起来。
如果将上面的流程具象化一个LNMP(Linux、Nginx、MySQL、PHP)的例子,就变成了如下的过程。
我们会发现当整个持续交付的流程流转到了持续交付系统的时候,流程开始和具体的环境与编程框架开始耦合,比如单元测试在这个例子中需要运行PHPUnit相关的命令去实现;准备环境需要根据具体的部署环境是KVM的虚拟机还是物理机或者是云服务器区别实现;配置环境需要根据具体的编程模型来准备在本例中会通过自动化配管工具例如Ansible来验证与准备不同环境中的代码运行时环境;分发代码后的流程在本例中是通过重启Nginx实现。其实这就是持续集成与持续交付真正难的部分,它并没有特定的要求规定什么流程该用什么方式做什么,就像大型软件系统的架构设计,只有“法”没有“型”,这也就是为什么程序员有很多,但架构师少的可怜的道理。
归根结底,持续集成与持续交付的难点在于如何屏蔽不同语言、不同框架、不同系统之间的持续集成与持续交付流程的差异性。曾经幻想过是否能有一种方式可以归约软件的交付,而这就是Martin Fowler留给我们的课后思考题-论如何实现持续集成与持续交付的流程标准化。
新的交付之道——容器标准化交付
容器虚拟化这几年随着Docker的推出,也逐渐进入到开发者的视野中。刚刚接触Docker的时候,按照学习新知识的习惯,我将Docker和虚拟机或者虚拟化进行了等同,认为他们是一个领域的不同实现,通过类比的方式来学习Docker。但是后来我发现,Docker的意义不在于解决软件底层的环境定义的问题,更多的是在解决交付的问题。
在上文中我们讨论了为什么持续集成与持续交付对于很多公司来讲是有难度的。而解决这个问题的最理想的办法就是是否有一种方式可以将交付的流程编程一个标准化,这样进行持续集成与持续交付的开发者可以很快的像读说明书一样,一步一步完成自己的持续集成与持续交付流程。
容器给交付带来最大的变革就是标准化。
Dockerfile:将代码和环境打包成了镜像,将原来系统中分发的最小单元由代码变成了镜像,不同的环境、不同的软件、不同的配置都可以通过Dockerfile的配置来实现,标准化了交付的最小单元,让交付的最小单元不再和环境、编程框架耦合。
Docker API:将软件的生命周期管理从原来的不同框架不同实现变成了统一标准的命令。启动软件变成了docker run,停止软件变成了docker stop,重启软件变成了docker restart。
Docker Compose:将软件交付的方式进行了标准化,大型的软件是由很多不同的部分组成的,而Docker Compose就是将软件之间的关联关系用标准话的方式进行了描述,并通过分发Docker Comopse的配置文件即可将软件进行交付。
如上图,我们将刚才的例子用Docker的方式进行推演,发现原本和编程框架或者环境耦合的部分现在通过Docker进行了标准化,这样不同语言不同框架不同业务场景的开发者就可以快速的时间自己的持续集成与持续交付了。
软件定义交付时代已到来?
云计算的兴起,让越来越多的IT基础设施变成虚拟的、软件定义的,比如软件定义存储、软件定义网络等等。通过编程的方式可以将原本的IT基础设施进行创建、分配、管理,屏蔽掉了底层的硬件的异构。反过来看云端交付的场景,是否可以变成软件定义的。我们可以通过资源编排(例如阿里云的Ros或者AWS CloudFormation)定义基础的设施;可以通过Docker软件定义部署;云资源和容器化的应用都可以用API的方式来实现软件定义运维。
软件定义基础设施+软件定义部署+软件定义运维=软件定义交付?这个问题留给大家更多的思考。
阿里云容器服务是阿里云2015年12月推出的基于容器的CaaS产品,集成了阿里云OSS、ECS、SLB、SLS、VPC、RDS等多款IaaS云产品的能力。容器的交付方式虽然给交付带来了很多的便利,但是还远远不够,阿里云容器服务为微服务、持续交付提供了大量云交付的能力,可以让Docker的交付过程更顺畅。在下一篇文章中,我们将讨论下阿里云容器服务提供了什么样的功能特性满足大家的云端交付场景。