持续集成与持续部署宝典Part 2:创建持续集成流水线

577 阅读12分钟
原文链接: dockone.io
在本系列文章中,我们将探讨在容器时代如何在基于Docker的环境中创建连贯的工作流程和流水线来简化大规模项目的部署。另外,我们还将详细介绍如何利用Docker和Rancher自动化处理这些工作流。

在上文《将构建环境容器化》中,我们开始了构建持续集成流水线的第一步工作——构建系统(Build System)的创建。我们分析了【Build】这一环节的常见的三大挑战——依赖管理、管理环境依赖、复杂项目的漫长构建时间,以及如何用传统工具与方法解决这些问题。接着,我们分享了如何利用Docker创建容器化的构建系统以更轻松地解决那些传统挑战,包括如何将构建环境容器化、如何使用Docker打包应用程序、如何使用Docker Compose创建构建环境,最终创造一个可重复的、集中管理的、良好隔离的、并行化的构建系统。

现在我们已经将【Build】系统创建好了,那么在本文中,我们将为示例的应用创建一个持续集成流水线。这样我们既可以确保遵循最佳实践,又可以确保彼此冲突的那些变化不会相互作用、引发问题。不过,在我们为代码建立持续集成之前,我们先花一点时间讨论如何将代码划分到分支中。



分支模式

在我们实现持续集成流水线的自动化时,一个需要考虑的重点是团队遵循的开发模式。这个模式通常由团队如何使用版本控制系统来决定。由于我们的应用程序托管在git仓库中,因此我们使用git-flow模型进行分支、版本化以及发布我们的应用程序。它是基于git仓库上最常用的模型之一。简而言之,该模型的思想是维护两个分支:一个开发(者)分支,一个主分支。每当我们想开发新功能时,就会从开发分支创建出新的分支,并在功能开发完成时,将它合并回来。所有功能分支都有开发人员单独管理。一旦将代码提交到开发分支,CI服务器将负责确保分支始终能够编译、通过自动化测试并且可以在服务器上进行QA测试和评审。当我们准备进行发布时,可以从开发分支创建一个发布,并将其合并到主分支中。被发布的特定的commit hash也会使用版本号进行标记。被标记好的发布项接着就可以被推送到Staging/Beta或者生产环境中。

下面我们将使用git-flow工具来帮助管理我们的git分支。安装git-flow请参考这里的说明:github.com/nvie/gitflo…。安装好git-flow,你就可以通过下面所示的git flow init命令配置你的仓库。Git flow会问一些问题,我们建议你使用默认的设置即可。执行过git-flow命令后,它将创建一个开发分支(如果原先没有开发分支的话),并将其检出作为工作分支。



现在,我们使用git flow输入git flow feature start [feature-name]命令创建一个新功能。通常做法是用ticket/issue id用作功能的名称。比如,如果你使用的是Jira处理ticket,那么ticket id(例如,MSP-123)就可以作为功能名称。你还会发现当你使用git-flow创建新功能时,它将自动切换到功能分支。



到了这一步,你可以去完成该功能所需的全部内容,然后运行自动化测试套件以确保一切正常运转。一旦你准备好发布工作,只需告诉git-flow去完成这一功能即可。根据你对该功能的实际需要,你想要进行多少次提交都可以。在我的这个示例中,从我们的目的来看,我们只需更新README文件,并通过输入“git flow feature finish MSP-123”来完成更新。



需要注意的是,git flow合并了开发分支的功能,删除了功能分支并且返回到了开发分支。此时,你可以将开发分支推送到远程仓库(git push origin develop:develop)。 当你提交了开发分支,CI服务器就会接管持续集成流水线。对于更大的团队来说,一种更合适的模式是在完成功能之前将功能分支推送到远程,让它们经评审(review)后,使用pull request来合并到开发分支中。

使用Jenkins创建CI流水线

在这一节,我们假设你已启动并运行了一个Jenkins集群。如果没有的话,你可以在这里使用官方的Jenkins镜像:hub.docker.com/_/jenkins/ ,在这里可以看到更多关于建立可扩展Jenkins集群的内容:rancher.com/deploying- … 。在你有了运行的Jenkins集群后,我们需要在Jenkins服务器上安装下面的插件和依赖项:

Jenkins Plugins 2.32.2+
  • Git Parameter Plugin 0.8.0+
  • Parameterized Trigger Plugin 2.33+
  • Copy Artifact Plugin 1.38.1+
  • Build Pipeline Plugin 1.5.6+
  • Mask Passwords Plugin 2.9+

    Docker 1.13.1+

    Docker Compose 1.11.1+

安装好需要的插件后,我们就可以在【Build流水线】中创建最初的三个任务:编译、打包和集成测试。这些将作为我们持续集成和部署系统的起点。

构建应用

序列中的第一个任务将会在每次提交后从源码控制中检出最新的代码并且确保其可编译。它还会运行单元测试。如果要在我们的示例项目中设置第一个任务,选择New Item->Freestyle Project。进入项目配置视图中,选择General选项卡以及“The project is parameterized”选项。添加一个叫做GO_AUTH_VERSION的git参数,将参数类型设置为Branch(分支)或者Tag(标记)。接下来选择Advanced配置参数,使用Tag Filter设置获取匹配“v”的所有标记(比如v2.0)。将Default Value设置成develop(开发分支)。这将有助于从Git获取版本标签列表,并为该任务填充选项菜单。如果该任务要在没有给定值的情况下自动触发,那么GO_AUTH_VERSION默认设成develop(开发)分支。



接下来,在Source Code Management选项卡部分添加仓库url,指定分支为${GO_AUTH_VERSION},这样手动构建时将会使用git参数来选择分支或标记进行构建及设置轮询间隔。这样一来,Jenkins就会持续追踪我们开发分支的未来所有更改,在我们的CI(和CD)流水线中自动触发第一个任务。这里要注意的是,GO_AUTH_VERSION的默认值(比如开发分支)将用于自动检测到的更改。



现在在Build选项卡部分选择Add Build Step > Execute Shell并从本章前面部分复制docker run命令粘贴到这。这样可以从Github获得最新的代码,并将代码构建到go-auth可执行文件中。这里需要安装并运行docker。如果你使用的是Linux服务器,可能还需要在docker客户机命令中添加sudo以便能够访问docker守护进程。



在构建步骤之后,我们需要添加两个后续步骤,其中Archive the Artifacts(工件归档)将go-auth二进制文件和帮助脚本归档到项目中。我们在任务中需要指定下列的工件进行归档。



接着我们使用Trigger parameterized builds(触发器参数化构建)启动流水线中的下一个任务,如下图所示。在添加Trigger parameterized build时,请确保是从Add Parameters中添加的Current build parameters。这样可以让当前任务的全部参数(比如GO_AUTH_VERSION)用于下一个任务。请留意在Trigger parameterized build部分中用于下游任务的参数名称,我们将在下面的步骤中用到。



构建任务的日志输出应该像下面展示的这样。你可以看到我们使用了docker化的容器来执行构建。构建时将使用go fmt来修复代码中任何格式的不一致,并且执行我们的单元测试。如果出现测试失败或者编译不通过,Jenkins就会检测到失败。此外,你应该通过email或者chat integrations(例如Hipchat或者Slack)设置通知,这样在构建失败时就能通知到你的团队,快速地修复它。





打包应用

代码编译通过了,接下来我们就能将它打包进Docker容器中。创建打包任务的方式是,选择New Item > Freestyle Project并给你的第二个任务起一个名称,该名称对应于在先前任务中指定的内容。和之前一样,该任务也是一个带有GO_AUTH_VERSION参数的参数化构建。要注意的是,这里和所有后续的任务中,GO_AUTH_VERSION只是一个默认值为develop(开发分支)的string参数。我们希望它的值是从上游传过来的。



和之前一样,添加构建步骤来执行shell。注意这里你不需要指定SCM设置,因为我们将从前一个构建生成的工件中提取所需的二进制文件和脚本。注意一下,我们这里是先创建未标记的usman/go-auth,之后再重新标记,这样我们就可以在后面执行集成测试,搭建好未打标记的容器环境。



为了构建Docker容器,我们还需要前一步中构建的可执行文件。为此,我们增加一个构建步骤复制上游构建中的工件。这样可以保证我们有可用于Docker构建命令的可执行文件,该命令可以打包到Docker容器中。注意这里我们选择了flatten目录来保证所有工件都复制到当前项目的根目录中。



我们一直在使用GO_AUTH_VERSION变量标记我们正在构建的镜像。在默认情况下,开发分支的变更,总会构建usman/go-auth:develop并覆盖现有的镜像。在下一章中,我们会发布应用程序的新版本并重新审视这个流水线。

和之前相同,使用了Trigger parameterized builds下(包含了Current build parameters)的后构建(post-build)来触发流水线中的下一个任务,该任务将使用我们刚刚构建好的Docker容器以及在之前章节中详述的Docker Compose来执行集成测试。

执行集成测试

接下来是执行集成测试,先要创建一个新任务。和打包任务相同,新任务是一个使用GO_AUTH_VERSION string变量的参数化构建。然后从构建任务中复制工件。这一次我们将使用上面的Docker Compose模板来搭建一个多容器测试环境,并对我们的代码执行集成测试。集成测试(不同于单元测试)通常是与正在测试的代码完全隔离的。因此,我们会用到一个shell脚本,它可以针对我们的测试环境执行http查询。在执行shell命令中,将目录修改为go-auth并执行integrationtest.sh。



脚本的内容可以在这里找到:github.com/usmanismail…。我们用Docker Compose搭建我们的环境,然后使用curl发送http请求到搭建的容器中。这项任务的日志将会和下图显示的相似。Compose将会启动一个数据库容器,并将它连接到goauth容器。数据库连接之后你应该会看到一些列“Pass: …”信息,说明各项测试都在运行和验证。测试完成后,compose模板将会自行清理数据库和go-auth容器。





经过了三个任务的设置,你就可以在Jenkins视图中选择【+选项卡】,选择build pipeline view来创建一个新的构建流水线视图。在弹出的配置界面中,选择你编译/构建的任务作为初始任务,然后选择ok。现在你应该能看到CI流水线已经成型。这将给你一个可视化引导,展示每个提交是如何通过你的构建和部署流水线的。



当你更改开发分支时,你会注意到流水线是由Jenkins自动触发的。如果要手动触发流水线,选择你第一个(构建)任务,运行它。系统会要求你选择git参数的值(比如GO_AUTH_VERSION)。不指定的话会执行默认值,并且会运行针对开发分支中最新内容的CI流水线。当然,你可以直接在流水线视图中单击“Run”。

我们快速回顾一下到现在为止我们所做的工作。我们通过以下步骤为我们的应用程序创建了CI流水线:
  1. 使用git-flow添加新功能,并将他们合并到开发分支中。
  2. 跟踪开发分支的变化,在一个容器化环境中构建我们的应用程序
  3. 将我们的应用程序打包到docker容器中
  4. 使用Docker Compose搭建短生命周期环境
  5. 执行集成测试以及清理环境

通过上面的CI流水线,每当新功能(或者修复)合并到开发分支时,CI流水线就会执行上述所有的步骤,创建出“usman/go-auth:develop” Docker镜像。此外,我们在接下来的章节中将构建更深层的集成部署流水线。另外因为该视图有清晰的测试阶段,你还可以使用此视图将应用程序版本推广到各种部署环境中。

总 结

在本章中,我们分享了如何利用Docker为我们的项目创建一个持续集成流水线,该流水线是集中管理的、可测试的,并且在机器和时间上可重复。我们能够根据需要对各种组件的环境依赖进行隔离。这是我们未来部署一条更长的基于Docker构建和部署的流水线的起点,我们将在下一次的文章中继续构建这一流水线的余下部分并将过程经验记录下来。

我们流水线的下一步是创建持续部署,后续文章我们将展示如何使用Rancher部署整个服务器环境来运行代码,我们还将介绍如何为大型项目设置长期运行的测试环境和持续部署流水线的最佳实践。