快手、孩子王、华为等专家分享大模型在电商运营、母婴消费、翻译等行业场景的实际应用 了解详情
写点什么

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

  • 2020-04-15
  • 本文字数:5801 字

    阅读完需:约 19 分钟

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

随着 Docker 项目及其相关生态系统逐渐成熟,容器已经开始被更多企业用在了更大规模的项目中。因此,我们需要一套连贯的工作流程和流水线来简化大规模项目的部署。


Rancher Labs 准备了此持续集成与持续部署系列文章,共两万余字,希望能供企业参考如何利用诸如 Docker 和 Rancher 这类工具来创建属于企业的持续集成和持续部署流水线,并根据自己的实际情况和需求在这 CI/CD 流水线中也加入自定义的流程。


本文是此系列文章的最后一篇,我们将在本文中完成创建持续部署流水线的最后工作。本文内容包括创建持续部署流水线(发布 Docker 镜像、部署到集成环境、发布和部署一个新的版本)和部署策略(就地更新、蓝绿部署)。

创建持续部署流水线

我们已经创建好了测试环境,在前文中我们也构建好了 CI 流水线,由它来创建应用、将应用打包进容器、进行集成测试。现在我们将来到本系列文章的最终章,拓展 CI 流水线来创建一个持续部署流水线。

发布 Docker 镜像

我们首先将打包的镜像发布到 Docker 存储库中。方便起见,我们使用了一个公共 DockerHub 仓库,不过,对于实际的开发项目,我们还是建议将 Docker 镜像 push 到私有库中。下面我们在 Jenkins 中创建一个新的 Free Style Project 任务,点击 New Item 按钮,把任务命名为 push-go-auth-image。完成了这些步骤后,你会进入到 Jenkins 任务配置页面,在这里你可以定义 push 你的 go-auth 镜像到 Dockerhub 所需要的步骤。


因为这是我们对前一章中构建的流水线的后续,因此该作业会有和 go-auth-integration-test 作业类似的配置。你需要首先设置的是 parameterized build 并且添加 GO_AUTH_VERSION 变量。



要 push 镜像,我们选择 Add build step 下拉菜单,然后选择 Execute shell 选项。在结果文本框中添加下面的命令。在这些命令中,我们将登陆到 DockerHub,并且 push 我们之前构建好的镜像。这里我们准备将其 push 到 usman/go-auth 仓库中,而你则需要把它推送到自己的 DockerHub 库中。


上一章提到,我们使用的是 git-flow 分支模型,其所有的功能分支都合并到“开发”分支中。为了能够不断地将发生的变更部署到集成环境中,我们需要一个简单的机制来生成基于开发分支的最新镜像。在打包过程中,我们使用 GO_AUTH_VERSION 来标记 docker 容器(例如,docker build -t usman/go-auth:${GO_AUTH_VERSION} …)。在默认情况下,这个版本将会成为开发分支,但是本章后面我们将为我们的应用程序创建新发布,使用 CI/CD 流水线来构建、打包、测试,并把它们部署到我们的集成环境中。要注意的是,在这个方案中,我们总会覆盖我们开发分支的镜像(usman/go-auth:develop),这限制了我们引用历史构建以及执行回滚。你可以对流水线做一个简单的更改,把 Jenkins 构建编号添加到自身的版本名称上,比如 usman/go-auth:develop-14。


你需要指定你的 DockerHub 用户名、密码和电子邮件地址。你可以在每次运行时用参数构建的方式指定它们,也可以使用 Jenkins Mask Passwords Plugin 在主要的 Jenkins 配置中安全地定义它们,并将它们添加到构建中。要确保 Build Environment 中为你的作业启用了“Mask passwords(并且启用全局密码)”。



现在我们需要确保这个作业是在集成测试作业之后才触发的。要做到这一点,我们需要更新集成测试作业,以便使用当前的构建参数触发参数化构建。这意味着在每次成功运行集成测试作业后,我们会把测试好的镜像 push 到 DockerHub 上。



最后,我们需要在镜像成功 push 到 DockerHub 之后触发部署作业。就像我们为其他作业所做的一样,我们可以通过添加构建后操作来实现这一点。

部署到集成环境

我们将使用 Rancher Compose CLI 来停止运行的环境,从 DockerHub 获取最新的镜像,并重新启动环境(提醒一下,Updates API 还在发展,可能会发生变化。未来几周或者几个月内肯定会增加新的功能,因此请随时查看文档是否有更新项)。在我们创建 Jenkins 作业来实现持续部署之前,我们先手动完成这些步骤。


我们可以使用最简单的方法——停止所有服务(auth 服务、负载均衡器以及 mysql),提取最新的镜像并启动所有服务。然而,对于我们来说,我们只想更新应用程序,而并不想停止长时间运行的环境,这样就不太理想了。要更新我们的应用程序,我们首先要停止 auth-service。你可以在 Rancher Compose 使用 stop 命令完成操作。



这会停止运行 goauth 服务的所有容器,你可以在 Rancher UI 中打开堆栈来验证该服务的状态是否设置为 Inactive(不活跃)。接下来,我们要让 Rancher 拉取我们想要部署的镜像版本。现在,我们已经可以动态指定想要运行的版本,而无需每次都要更新模板了。如果你需要多次 push 相同的镜像版本,请添加 pull 开关,确保我们使用的是镜像版本的最新副本。



我们还可以通过下面的命令使用升级功能,零停机地实现环境的滚动更新。下一节中我们将进一步讨论滚动更新。在升级完成后,你可以使–rollbach 命令或–confirm-upgrade 来确认更改或者回滚到预览状态。



现在我们已经知道该如何运行我们的更新,我们在流水线中创建一个 Jenkins 作业来执行此操作。和之前一样,创建一个新的 freestyle 项目,命名为 deploy-integration。与其他的作业一样,它也是一个参数化构建,用 GO_AUTH_VERSION 作为字符串参数。接下来,我们要从上游的 build-go-auth 作业中复制工件。



最后,我们需要向 Execute Sheell 构建步骤中添加 Rancher Compose up 命令,这是我们在之前指定好的。需要注意,你还需要提前在 Jenkins 上设置 Rancher CLI,让它可以用于你在系统路径上的构建。执行 shell 步骤的内容和下面的代码片段相似。如果你有多个 Rancher Compose 节点,负载均衡器容器可能会在不同的主机上启动,你的 Route 53 记录集合就可能需要更新。



有了我们两个新的 Jenkins 作业,我们从上一章就开始构建的流水线现在看起来就像下图所示。每次对我们示例应用程序的 check-in 都会被编译,确保其没有出现语法错误并且能够通过自动化测试。然后,将这些变更打包,通过集成测试,最后再部署到手动测试中。下面的五个步骤为构建流水线提供了一个良好的基线模板,并且有助于将代码从开发阶段转移到测试和部署阶段上。拥有一个连续的部署流水线确保了代码不仅可以进过自动化系统的测试,而且还能快速提供给测试人员使用。除此之外它还能够作为生产部署自动化的模型,测试操作工具和代码,持续部署应用程序。



发布和部署一个新的版本


在我们将代码部署到持续的可测试环境中后,我们就会让 QA 团队测试对这些变更进行一段时间的测试。在他们确定代码已经就绪后,我们可以创建一个发布,随后将它部署到生产环境中。用 git-flow 发布的方式类似于特征分支(我们在前一章讨论过的工作方式),我们使用 gitflow release start [Release Name]命令(如下所示)进行发布。这将创建一个新的名称来发布分支。在这个分支中,我们将执行一些内部的操作,比如增加版本号,做最后的修改。



完成这些后,我们运行 releasefinish 命令将发布分支合并到主分支中。这样,主分支总能反映出最新发布的代码。另外,每个发布都会加上标签,对每个发布的内容都能够有历史记录。如果我们不需要再进行其他的更改,我们就可以确定最终的发布了。



最后一步就是把发布 push 到远端仓库中


git push origin master


git push --tags //pushes the v1 tag to remote repository


如果你使用的是 Github 来托管 git 库,现在应该会发现有一个新的版本了。我们还可以将与发布名称相匹配的版本镜像 push 到 DockerHub,这也是一个不错的选择。我们先运行第一项作业来触发我们的 CD 流水线。可能你还记得,我们为 CI 流水线设置了 GitParameter 插件,以便能够从 git 中获取与过滤器相匹配的所有标记。不过,这是对于默认的开发分支而言,当我们手动触发流水线时,我们可以从 git 标签中进行选择。例如,在下面我们为应用程序提供了两个版本。我们选择其中一个,启动集成和部署流水线。



然后,经过以下步骤,我们的应用程序 1.1 版本将会被部署到长时间运行的集成环境中,只需要点击几下就能实现。


  1. 从 git 中获取所选的发布

  2. 构建应用程序,运行单元测试

  3. 创建一个带有标签 v1.1 的新镜像(比如 usman/go-auth:v1.1)

  4. 运行集成测试

  5. Push 镜像(usman/go-auth:v1.1)到 DockerHub

  6. 将该版本部署到我们的集成环境

部署策略

管理长时间运行的环境时会遇到很多挑战,其中之一就是尽可能在发布期间的停机时间要降至最短,最好为零。为了让这个过程可预测并且安全,需要做相当多的工作。自动化和质量保证确实可以大大提高发布的可预测性和安全性。不过即使是这样,失败也会发生,而且对任何优秀的运维团队来说,他们的目标都是在最小化影响的同时快速恢复。在本节中,我们将介绍一些部署长时间运行环境的策略以及它们的优缺点。

就地更新

第一个策略称为就地更新(In-placeupdates),顾名思义,它的思想就是复用应用程序环境,并且就地更新应用程序。这有时也称作是滚动部署。我们将使用我们目前讨论的示例应用程序(go-auth)。此外,我们假定你有用 Rancher 运行的服务。如果要就地更新,你可以使用下面的升级命令:



在用户看不到的地方,Rancher agent 会在每个运行 auth 服务容器的主机上获取新的镜像(–pull 会重新下载,尽管镜像已经存在)。之后代理会停止旧的容器,批量启动新的容器。你可以使用–batch-size 标志来控制批处理的大小。此外,你还可以指定批更新之间的暂停时间间隔(–interval),使用足够大的时间间隔来验证新容器的行为是否按照预期运行,并且总体而言,服务是健康的。在默认情况下,旧的容器终止后,新的容器会在它们的位置上启动。或者你可以在 rancher-compose.yml 中设置 start_first 标志,告诉 Rancher 在启动新容器之前先停止旧容器。



如果对当前的更新不满意想要回滚,可以使用回滚标志来完成回滚。或者你想要继续进行更新,只需告诉 rancher 指定 confirm-update 标志完成更新即可。你也可以在原始的 up 命令中指定 confirm-update 标志一步完成这些操作。你还可以在 RancherUI 执行更新,从服务菜单(下图所示)中选择“upgrade”。



就地更新非常简单,不需要额外的资源来管理多个堆栈。然而,这种生产方式是存在缺点的。首先,对回滚更新进行细粒度控制大多很困难,就是说在故障发生的情况下它们往往是难以估计的。例如,处理部分故障和滚动更新会变得非常混乱。你需要知道在哪些节点上部署了更改,哪些没能部署,还有哪些仍在运行之前的版本。其次,你还需要保证所有的更新不仅是向后兼容,还能向前兼容,因为旧版本和新版本都是需要在相同的环境中同时运行的。最后,根据使用的情况,就地更新可能不实用。比如,如果老的客户端需要继续使用旧环境,而新的客户端需要向前滚。在这种情况下,使用我们今天要列出的方法分离客户端会更加容易。

蓝绿部署

就地更新的一个问题是缺少可预测性。为了克服这一问题,另一个部署策略是针对应用程序使用两个并行堆栈:一个处于活跃状态,另一个处于待机状态。要运行新版本,部署应用程序的最新版本到待机堆栈。当验证到新版本需要工作,将会从活动堆栈切换流量到备用堆栈上。这时,先前活跃的堆栈称为备用堆栈,反之亦然。该策略允许验证已经部署的代码、快速回滚(切换备用和重新激活),并还可以在需要时扩展两个堆栈的并发操作。这种策略通常被称为蓝绿部署。用我们的示例应用程序完成这样的部署,可以简单地在 Rancher 中创建两个堆栈:go-auth-blue 和 go-auth-green。此外,我们假设数据库不是这些堆栈的一部分,而是独立管理的。每个堆栈都会运行 goauth 和 auth-lb 服务。如果假设 go-auth-green 堆栈式活跃的,要执行更新,我们要做的就是将最新版本部署到蓝色堆栈中,执行验证并将流量切换到它这。

流量切换

有两个方法可用于执行流量切换,更改 DNS 记录来指向新堆栈,或者使用代理或负载均衡器,将流量路由到活动堆栈。下面我们会详细介绍这两个方法。


记录更新


一个简单的方法是更新 DNS 记录指向活动堆栈。这种方法的一个优点是,我们可以使用加权 DNS 记录将流量慢慢转换到新版本中。这也是执行 canary releases 的简单方法,对安全地在实时环境中进行更新或者做 A/B 测试非常有用。例如,我们可以将实验性的功能更部署到自己的功能堆栈(或活动堆栈),然后更新 DNS,仅将一小部分流量转发到新版本。如果新更新出现问题,我们可以逆转 DNS 记录回滚回来。此外,他比将所有流量从一个堆栈切换到另一个堆栈要安全得多,因为这样会覆盖掉新的堆栈。虽然简单,如果你希望所有流量能一次切换到新版本,DNS 记录更新就把 u 事最简洁的方法。根据 DNS 客户端不同,这些更改可能需要很长时间才能传播,从而导致和旧版本之间的大量通信,而不是直接了断切换。


使用反向代理


使用代理或负载均衡器,只需要将其更新成指向新堆栈就可以一次切换整个流量。这种方法在各种场景下都非常有用,例如非向后兼容的更新。要使用 Rancher 完成这一操作,我们首先要创建一个仅包含负载均衡器的堆栈。


接着,我们为负载均衡器指定一个端口,配置 SSL 并从下拉菜单中选择活动堆栈的负载均衡器作为 target service 来完成创建。本质上来说,我们将负载放到了负载均衡器上,它会在之后将流量路由到实际的服务节点。借助外部负载均衡器,你不需要更新每个版本的 DNS 记录。相反,你可以简单地更新外部负载均衡器指向已更新的堆栈。

总结

在本章中,我们介绍了如何创建一个持续部署流水线,可以将我们的示例应用程序放在集成环境中。我们还研究了如何集成 DNS 和 HTTPs 支持来创建一个用户能够进行集成的更加安全可用的环境。在后续的工作中,我们着眼于运行中的生产环境。部署到生产环境会带来一些列的挑战,我们希望部署是满足负载,并且有很少停机时间的(理想下为零)。此外,生产环境也面临着挑战,因为它们必须能向外扩展以满足负载,同时还要减少控制成本。最后,为了提供自动故障转移以及高可用性,我们对 DNS 管理进行了更全面地研究。我们还将研究生产中 Docker 环境的操作管理以及不同类型的工作负载,比如 state-full 连接服务。

结论

这一系列文章介绍了使用容器实现完整的 CI/CD 流水线的几种方法。我们尝试涵盖常见的使用案例,提供详细的示例,并且分享了我们在 Web 服务公司工作多年的 DevOps 中学到的一些最佳实践。未来我们还会继续将内容进一步深化,发布一份子妹篇,深入介绍如何在生产中使用容器运行服务,敬请保持关注!


2020-04-15 23:04701

评论

发布
暂无评论
发现更多内容

Flutter-vs-React-Native,谁才是跨平台应用开发的最佳利器?

android 程序员 移动开发

Dagger2在SystemUI中的应用

android 程序员 移动开发

Flutter Interact 的 Flutter 1

android 程序员 移动开发

Flutter 中动画的使用

android 程序员 移动开发

Flutter与Dart-入门

android 程序员 移动开发

EventBus 发送的消息,如何做到线程切换?

android 程序员 移动开发

FFmpeg 之I、B、P帧的基本编码原理(三

android 程序员 移动开发

Flutter - 路由管理 - 02 - Fluro

android 程序员 移动开发

Dalvik 和 ART 有什么区别?深扒 Android 虚拟机发展史,真相却出乎意料!(1)

android 程序员 移动开发

Flutter-vs-React-Native,谁才是跨平台应用开发的最佳利器?(1)

android 程序员 移动开发

Flutter_bloc框架使用笔记,后续估计都不太会用了(1)

android 程序员 移动开发

Flutter_bloc框架使用笔记,后续估计都不太会用了

android 程序员 移动开发

Flutter 扩展NestedScrollView (二)列表滚动同步解决

android 程序员 移动开发

Flutter 核心原理与混合开发模式

android 程序员 移动开发

Flutter40

android 程序员 移动开发

DatePickerDialog时间选择器+MVPPlugin开发插件的使用

android 程序员 移动开发

Flutter-VS-React-Native-VS-Native,谁才是性能之王

android 程序员 移动开发

ClassLoader在热修复中的应用

android 程序员 移动开发

Cocos2d-x 3(1)

android 程序员 移动开发

Cocos2d-x 3

android 程序员 移动开发

Flutter33

android 程序员 移动开发

Flutter40(1)

android 程序员 移动开发

C语言(二):指针基础

android 程序员 移动开发

Flutter 底部浮动按钮(模仿咸鱼APP底部)

android 程序员 移动开发

Flutter32

android 程序员 移动开发

Dalvik 和 ART 有什么区别?深扒 Android 虚拟机发展史,真相却出乎意料!

android 程序员 移动开发

Dart _ 浅析dart中库的导入与拆分

android 程序员 移动开发

DateUtils(一个日期工具类)

android 程序员 移动开发

cmake使用教程(九)-关于安卓的交叉编译

android 程序员 移动开发

FFmpeg-之音视频解码与音视频同步(二)

android 程序员 移动开发

Flutter 制作一个抽屉菜单

android 程序员 移动开发

持续集成与持续部署宝典Part 4:创建持续部署流水线_文化 & 方法_Rancher_InfoQ精选文章