一个在工作流中自动运行的管道,通过不同的阶段,将早期发现问题,并帮助您的团队以最有效的方式协作。
在本章中,我们将遵循持续的集成实践,在每次变更时自动运行管道,以确保我们的所有代码都遵循高质量标准,并且它运行并通过所有测试。我们还将准备一个容器投入生产。
我们将看到如何利用 GitHub 和 Travis CI 等工具,以最少的干预创建映像。
在本章中,我们将涵盖以下主题:
- 理解持续集成实践
- 配置特拉维斯配置项
- 配置 GitHub
- 从特拉维斯 CI 推送 Docker 映像
到本章结束时,您将知道如何在每次代码更改时自动运行测试,以及如何创建一个安全网来允许您更快、更高效地开发。
您需要一个 GitHub 帐户,并且需要成为您将为持续集成而设置的项目的所有者。作为本章的一部分,我们将创建一个 Travis CI 帐户。
您可以从 GitHub(https://GitHub . com/PacktPublishing/hand-On-Docker-for-micro-service-with-Python/tree/master/chapter 04)的Chapter04
子目录中检出本章中提到的完整代码。以.travis.yml
结尾的文件在根目录中。
持续集成(通常缩写为 CI )是一系列确保代码始终处于工作状态的软件工程实践。
术语持续集成来自于历史上必须频繁集成软件,通常是一天多次。这是因为开发人员使用的本地代码不一定会自动与其他人的代码结合。如今,使用一个源代码管理版本控制软件,比如 Git,使得一些元素自动可用。
持续集成强调始终拥有潜在的可发布代码。这使得发布经常成为可能,只需要少量的代码增量。
Making more releases more often actually generates an increase in the quality of each release. More deployments also mean that each deployment is smaller, reducing the possibility of a big problem. Even if it sounds counterintuitive, faster deployment is highly correlated with higher quality in deployments and fewer production problems.
The objective here is to be able to increase the deployment speed. But for that, we need to be sure to build a good safety net that checks (automatically) that what we're doing is safe to release. That's where all the CI practices come into play.
在设置好所有的流程和基础设施之后,很有可能一天实现多次发布(假设代码生成得足够快)。到达那里可能需要一段时间,但一定要花时间去理解这个过程,并制作所有必要的工具,以确保在不牺牲稳定性的情况下获得速度。相信我,这是完全可以实现的!
CI 的核心元素是生成与源代码控制系统集成的自动化构建。软件构建是(从源代码开始)执行一系列操作并产生输出的过程。如果项目是用编译语言编写的,输出通常是编译后的程序。
如果我们想要高质量的软件,那么构建的一部分包括检查生成的代码是否符合代码标准。如果代码没有遵循这些标准,那么构建将返回一个错误。
A common way of describing errors on a build is to say that the build is broken. A build can break in different ways, and some kinds of error may stop it early (such as a compilation error before running tests) or we can continue to detect further issues (such as running all tests to return all possible errors).
可以作为构建一部分的步骤示例如下:
- 编译代码。
Python usually doesn't need to be compiled, but it might be required if you use C extensions (modules written in C and imported from Python: https://docs.python.org/3/extending/) or tools such as Cython (https://cython.org/).
- 运行单元测试
- 运行静态代码分析工具
- 构建一个或多个容器
- 使用安全(https://pyup.io/safety/)等工具检查已知漏洞的依赖关系
- 生成用于分发的二进制或源包。比如 RPM(https://rpm.org/)、德比安套餐(https://www.debian.org/doc/manuals/debian-faq/ch-pkg_basics)等等
- 运行其他类型的测试
- 从代码生成报告、图表或其他资产
任何可以自动运行的东西都可以成为构建的一部分。本地构建可以随时生成,即使代码仍在进行中。这对调试和解决问题很重要。但是自动化构建将针对每个单独的提交运行,而不是在任何中间阶段。这使得检查哪些代码将在生产中运行以及哪些代码仍在运行变得非常明确。
Note that a single commit may still be work in progress, but it will be worth committing anyway. Maybe it's a single step toward a feature, more than one person is working on the same part of the code, or it's work spread over several days and the code gets pushed at the end of the day. No matter, each commit is a reproducible step that can be built and checked whether the build is successful or not.
为每个提交运行构建可以非常快速地检测到问题。如果提交量很小,一个突破性的变化就很容易确定。它还使得恢复破坏构建的更改并返回到已知的工作代码变得容易。
构建的主要传统问题之一是拥有一个足够的构建环境,其中包含运行完整构建所需的所有依赖项。这可能包括编译器、运行测试的测试框架、任何静态分析工具以及包管理器。版本的差异也会产生错误。
正如我们之前看到的,Docker 是封装我们软件的一种非常棒的方式。它允许我们创建一个包含我们的代码和所有能够完成所有步骤的工具的映像。
在前一章中,我们看到了如何基于构建映像在单个命令中运行单元测试。映像本身可以运行自己的单元测试。这抽象了测试环境并明确定义了它。这里唯一需要的依赖是安装 Docker。
请记住,一个构建可以生成多个映像,并使它们协同工作。在上一章中,我们看到了如何通过生成服务映像和数据库映像来运行单元测试,但是还有更多可能的用途。例如,您可以检查在两个不同的操作系统上运行的测试,从每个操作系统或不同的 Python 解释器版本创建两个映像,并检查测试是否全部通过。
Docker 映像的使用允许在所有环境中实现标准化。我们可以在开发环境中本地运行映像,使用与自动化环境中相同的命令。这简化了错误和问题的查找,因为它在构建运行的任何地方都创建了相同的环境,包括封装的操作系统。
Do not underestimate this element. Before that, a developer working on a laptop running Ubuntu and keen to run code to be deployed in CentOS needed to install a Virtual Machine (VM) and follow steps to have an environment similar to the one in production. But invariably, the local VM would deviate as it was difficult to keep every developer's local VM in sync with the one in production; also, any automated-build tool might also have requirements, such as not supporting an old version of CentOS running in production.
To make things worse, sometimes different projects were installed on the same VM, to avoid having one VM per project, and that may cause compatibility problems.
Docker massively simplifies this problem, in part forcing you to explicitly declare what the dependencies are, and reducing the surface actually required to run our code.
请注意,我们不一定需要创建运行整个构建的单一步骤;它可能是几个 Docker 命令,甚至使用不同的映像。但要求是它们都包含在 Docker 中,Docker 是运行它所需的唯一软件。
使用 Docker 构建的主要产品是 Docker 映像。我们需要正确地标记它们,但前提是构建成功。
配置项工具有助于阐明构建应该如何进行,并围绕管道的概念进行工作。管道是阶段的集合。如果其中任何一个不成功,管道就会停止。
管道中的每个阶段都可以产生可以在后期使用的元素,或者作为完整构建的最终产品。这些最终的元素被称为人工制品。
让我们看一个管道的例子:
第一阶段从源代码管理系统中提取最新的提交。然后,我们构建所有的容器,运行测试和静态分析。如果一切都成功了,我们标记得到的server
容器,并将其推送到注册表。
The order in which these stages run should be oriented at detecting problems as quickly as possible to give quick feedback. For example, if the static-analysis
stage is much faster than the test
stage, putting the analysis stage first will make a failing build finish earlier. Be aware of which parts can be executed earlier to reduce the feedback time.
配置项工具通常允许在管道中进行大量配置,包括并行运行不同阶段的可能性。为了能够并行运行阶段,它们需要能够并行,这意味着它们不应该改变相同的元素。
如果选择的配置项工具允许并行运行阶段,管道可以定义如下:
请注意,我们并行构建数据库和测试映像。下一阶段构建其余的元素,这些元素已经在缓存中可用,所以它会非常快。测试和静态分析可以在两个不同的容器中并行运行。
这可能会加速复杂的构建。
Be sure to validate that the amount of time taken reduces. There are cases where the time taken will be very similar. For example, static analysis could be very fast or the hardware you run it on may be not powerful enough to build things in parallel, making the time taken to build in parallel and sequentially very similar. So, always validate your assumptions.
管道在专门针对特拉维斯配置项工具的脚本中进行了描述。稍后我们将看一个 Travis CI 的例子。
我们什么时候运行构建?每次推送提交时。但每个结果都不一样。当处理像 Git 这样的源代码控制系统时,我们通常有两种分支:
- 一个主要分支
- 特征分支
它们实现了一个特定的特性或错误修复,当准备好时,它将被合并到主分支中,如下图所示:
在这个例子中,我们看到主分支(大师)是如何分支发展特色 A 的。功能 接下来简单介绍一下。有一个特性 B 还没有合并,因为还没有准备好。有了关于哪些构建成功或不成功的额外信息,我们就可以知道什么时候将功能分支合并到主分支是安全的:
尚未合并的特征分支中的断裂并不严重,但是当它正在进行工作时,预计会发生。同时,主要分支的破损是应该尽快修复的事件。如果主分支状态良好,这意味着它可能是可释放的。
GitHub 对此有一个模型:拉请求。我们将配置拉请求来自动检查构建是否已经通过并避免合并。如果我们强制任何特性分支在合并回来之前也与主分支保持同步,那么主分支最终会非常稳定。
For dealing with branches in Git to define releases, the most popular model is Git-flow, defined in this influential blog post (https://nvie.com/posts/a-successful-git-branching-model/). The following CI practices allow simplify things a bit and don't deal with elements such as release branches. This blog post is a highly recommended read.
在主要分支中拥有连续的成功构建也非常有助于培养项目的稳定性和质量感。如果主分支损坏非常罕见,那么用最新的主分支创建新版本的信心非常高。
Travis CI(https://travis-ci.com/)是一种流行的持续集成服务,可免费用于公共 GitHub 项目。与 GitHub 的集成非常简单,它允许您配置运行它的平台,例如 macOS、Linux,甚至 iOS。
Travis CI 与 GitHub 紧密集成,所以只需要登录 GitHub 就可以访问。我们将看看如何将我们的项目与它联系起来。
For clarity, only the code in this chapter will be hooked up to Travis.
Travis 的工作方式与其他 CI 工具有些不同,它通过启动一个新的虚拟机来创建独立的作业。这意味着为前一阶段创建的任何工件都需要复制到其他地方,以便在下一阶段开始时下载。
这使得事情有时有点不切实际,一个简单的解决方案是为每个单独的工作建立多次。
Configuring a remote system such as Travis CI can be a little frustrating sometimes, as it requires you to push a commit to be built to see if the configuration is correct. Also, it gets configured with a YAML file, which can be a bit temperamental in terms of syntax. It will take you a few attempts to get something stable, but don't worry. Once it is set up, you can change it only via a specific pull request as the configuration file is also under source control.
You can also check the requests in the Travis CI configuration to see if a .yml
file creates a parse error.
You can check full Travis CI documentation here: https://docs.travis-ci.com/.
要配置 Travis CI,让我们从从 GitHub 添加一个存储库开始。
要向 Travis CI 添加回购,我们需要采取以下步骤:
- 第一个阶段是转到 Travis CI 网页,并使用您的 GitHub 凭据登录。
- 然后,您需要通过激活它来授予 Travis 对 GitHub 的访问权限。
- 然后,选择要构建的回购。
The easiest starting point is to fork the repo with the examples from this book in https://github.com/PacktPublishing/Hands-On-Docker-for-Microservices-with-Python. Feel free to do so!
But remember to change the usernames, credentials, and registry information to match your own.
您需要拥有 GitHub 转帖的所有者权限,然后就可以开始了!
特拉维斯 CI 的主要元素是.travis.yml
文件的创建。
Be sure to name it exactly like this (including the initial dot and the .yml
extension) and include it in the root directory of your GitHub repo. If not, Travis CI builds won't start. Please note that, in the example repo, the file is in the root directory and not under the Chapter04
subdirectory.
.travis.yml
描述构建及其不同的步骤。构建在一个或多个虚拟机中执行。可以通过指定通用操作系统和特定版本来配置这些虚拟机。默认情况下,它们运行在 Ubuntu Linux 14.04 Trusty 中。您可以在这里找到更多关于可用操作系统的信息:https://docs.travis-ci.com/user/reference/overview/。
使用 Docker 可以让我们抽象出大部分的操作系统差异,但是需要保证我们使用的具体docker
和docker-compose
版本是正确的。
我们将使用以下代码启动.travis.yml
,确保存在有效的docker-compose
版本(1.23.2):
services:
- docker
env:
- DOCKER_COMPOSE_VERSION=1.23.2
before_install:
- sudo rm /usr/local/bin/docker-compose
- curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
- chmod +x docker-compose
- sudo mv docker-compose /usr/local/bin
- docker --version
- docker-compose version
before_install
块将在我们所有的虚拟机中执行。现在,为了运行测试,我们添加了一个script
块:
script:
- cd ch4
- docker-compose build db
- docker-compose build static-analysis
- docker-compose build test-postgresql
- docker-compose run test-postgresql
- docker-compose run static-analysis
我们构建所有要使用的映像,然后运行测试。请注意,使用 PostgreSQL 数据库运行测试需要您构建db
容器。
There's a small detail about the db
container: the Travis VM doesn't allow us to open port 5432
. We removed ports
in docker-compose
for that. Note that this only makes PostgreSQL available externally for debugging purposes; internally, the containers can talk to each other through their internal network.
We created a db-debug
service that's a copy of db
but it exposes the port for local development. You can check it in the docker-compose.yaml
file at https://github.com/PacktPublishing/Hands-On-Docker-for-Microservices-with-Python/blob/master/Chapter04/docker-compose.yaml.
这会运行所有测试。推进回购后,我们可以看到构建从 Travis CI 开始:
一旦完成,我们可以通过它被标记为绿色的事实来判断构建是成功的。然后可以查看日志以了解更多信息:
现在,您可以看到日志末尾的测试:
这对于检测问题和构建中断非常有用。现在,让我们看看工作是如何在特拉维斯工作的。
Travis 将整个构建分成一个接一个运行的阶段集合。在每个阶段,可以有几个工作。同一版本中的所有作业将并行运行。
正如我们之前看到的,我们可以将测试和静态分析配置为并行运行,方法是将script
部分替换为jobs
部分:
jobs:
include:
- stage: tests
name: "Unit Tests"
script:
- cd ch4
- docker-compose build db
- docker-compose build test-postgresql
- docker-compose run test-postgresql
- stage: tests
name: "Static Analysis"
script:
- cd ch4
- docker-compose build static-analysis
- docker-compose run static-analysis
这就在一个阶段中隐含地创造了两个工作岗位。舞台名为tests
,工作名为"Unit Tests"
和"Static Analysis"
。
结果显示在特拉维斯页面上:
请注意,在这两种情况下,由于作业是独立的,它们需要构建所需的映像。由于单元测试作业需要构建db
映像,这需要几分钟,因此比静态分析要慢。
您可以查看每个作业的详细日志。注意环境设置和before_install
动作在所有作业中是如何执行的。
这种划分不仅可以大大加快构建速度,还可以明确问题所在。简单地看一下,您可以看到中断因素要么是单元测试,要么是静态分析。这消除了混乱。
默认情况下,Travis CI 会发送一封电子邮件来通知构建的结果,但仅当构建被破坏或修复了被破坏的构建时。这避免了不断发送成功电子邮件,并且仅在需要采取行动时才采取行动。默认情况下,电子邮件只发送给提交者(和提交作者,如果不同的话)。
Note that there's a difference between failed builds and errored builds. The latter are failures in the job setup, which means that there's a problem in the before_install
, install
, or before_script
sections, while failed builds arise because the script part returned a non-zero result. Errored builds are common while changing Travis configuration.
Travis 允许我们配置通知电子邮件,并连接更多的通知系统,包括 Slack、IRC,甚至 OpsGenie,它能够根据通话时间表发送短信。查看此处的完整文档了解更多信息:https://docs.travis-ci.com/user/notifications/。
为了充分利用我们配置的配置项系统,我们需要确保在将其合并到主分支之前检查构建。为此,我们可以将 GitHub 中的master
配置为主分支,并在合并到其中之前添加需求:
Be sure that the .travis.yaml
file contains the proper credentials if you fork the repo. You'll need to update them with your own.
- 转到我们的 GitHub 报告中的设置和分支,然后单击添加规则。
- 然后,我们启用“在合并选项之前要求状态检查通过”选项和来自
travis-ci
的状态检查:
- 我们还选择了“要求分支在合并前是最新的”选项。这确保了没有之前没有运行过的合并到
master
中。
Take a look at the other possibilities that GitHub offers. In particular, enforcing code reviews is advisable to make code to be reviewed before being merged and disseminating knowledge.
- 在创建了一个新的分支和一个设计为静态测试失败的新拉请求之后,我们可以看到测试是如何被添加到 GitHub 的:
详细信息链接将带您进入特拉维斯配置项和特定的构建。您还可以看到构建的历史:
当构建完成时,GitHub 不会让您合并请求:
详细信息可在 Travis CI 的构建页面上找到:
修复问题并推送代码将触发另一个构建。这次会成功,拉取请求会合并成功。您可以看到每个提交都有自己的构建信息,无论它是正确的还是不正确的:
我们现在可以合并到主分支中,确信master
分支在运行测试时不会中断。
Note that there are two builds in the pull request: one for the branch and another for the pull request. By default, Travis CI has that configuration. If you force it to always create a pull request before merging, the request will be redundant, though it can help in some cases when the branch gets pushed before creating a pull request. You can enable or disable it in the Travis project configuration.
Another interesting feature that can be configured is automatically canceling builds if a newer commit is pushed. This helps to reduce the total number of builds in the system.
构建结果也可以在 GitHub 的提交视图中检查。
在我们的构建创建了 Docker 映像之后,我们需要能够与团队的其他成员共享或部署它。我们将使用 Docker Hub 中的 Docker 注册表来推送映像,如前一章所述。
让我们从设置机密变量开始。
为了能够推送至 Docker repo,我们首先需要配置一个密码来登录 Docker 注册表。这需要通过 Travis CI 中的机密配置来完成,以避免在 GitHub repo 中提交敏感信息:
It's worth repeating: do not commit secrets in your GitHub repo. These techniques can be used for any other required secret.
- 使用
gem
安装travis
命令行。这假设您的系统上安装了gem
(Ruby 1.93 或更高版本)。如果没有,请查看安装说明(https://github.com/travis-ci/travis.rb#installation):
$ gem install travis
- 登录特拉维斯:
travis login --pro
- 使用 Docker 集线器用户名创建一个安全变量:
$ travis encrypt --com DOCKER_USERNAME="<your user name>"
- 您将看到类似如下的输出:
secure: ".... encrypted data ...."
- 然后,您需要将加密数据添加到环境变量中,如下所示:
env:
global:
- DOCKER_COMPOSE_VERSION=1.23.2
- secure: ".... encrypted data ...."
- 现在,记下新的
global
部分,并使用 Docker Hub 密码重复步骤 3:
$ travis encrypt --com DOCKER_PASSWORD="<password>"
- 在第一个变量之后添加另一个安全变量:
env:
global:
- DOCKER_COMPOSE_VERSION=1.23.2
- secure: ".... encrypted data ...."
- secure: ".... encrypted data ...."
此操作创建两个环境变量,在构建过程中可用。不要担心,日志中不会显示它们:
Setting environment variables from .travis.yml
$ export DOCKER_COMPOSE_VERSION=1.23.2
$ export DOCKER_PASSWORD=[secure]
$ export DOCKER_USERNAME=[secure]
我们现在可以在before_install
部分添加正确的登录命令,以便 Docker 服务可以连接和推送映像:
before_install:
...
- echo "Login into Docker Hub"
- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
下一步是构建并标记生成的映像。
下面的代码将添加一个新的阶段,该阶段将构建、标记并最终将结果推送到 Docker 注册表:
jobs:
include:
...
- stage: push
script:
- cd Chapter04
- docker-compose build server
- docker tag thoughts_server:latest <registry>/thoughts-backend:$TRAVIS_BRANCH
第一部分为服务器构建最终映像,并用分支的名称对其进行标记。为了部署它,我们将添加一个deploy
部分:
- stage: push
script:
...
- docker tag thoughts_server:latest <registry>/thoughts-backend:$TRAVIS_BRANCH
deploy:
- provider: script
script: docker push <registry>/thoughts-backend:$TRAVIS_BRANCH
on:
branch: master
当分支为master
时,deploy
部分将执行script
命令。现在,我们的构建还将生成一个最终映像并推送它。这将确保我们的注册表在我们的主分支获得最新版本。
我们可以添加更多的deploy
条件来推送标签;例如,如果我们创建一个新的 Git 标签,我们可以用适当的标签推送结果映像。
Remember that tags, as discussed in the previous chapter, are a way to mark an image as significant. Normally, this will mean it's ready for some to be used outside automatic tests, for example, in deployment.
我们可以将标签添加到deploy
部分:
deploy:
- provider: script
script: docker push <registry>/thoughts-backend:$TRAVIS_BRANCH
on:
branch: master
- provider: script
script: docker push <registry>/thoughts-backend:$TRAVIS_TAG
on:
tags: True
Note that here we push whether the branch is the master or there's a defined tag, as both conditions won't be matched.
您可以在此查看完整的部署文档:https://docs.travis-ci.com/user/deployment。我们已经介绍了script
提供程序,这是一种创建我们自己的命令的方式,但是提供对诸如 Heroku、PyPI(在创建 Python 包的情况下)和 AWS S3 等提供程序的支持。
可以将每个单独构建的映像推送到注册表,由其 Git SHA 标识。当正在进行的工作可以共享用于演示目的、测试等时,这可能会很有用。
为此,我们需要在before_install
部分用 Git SHA 创建一个环境变量:
before_install:
...
- export GIT_SHA=`git rev-parse --short HEAD`
- echo "Building commit $GIT_SHA"
push
部分然后添加映像的标签和推送:
- stage: push
script:
- cd Chapter04
- docker-compose build server
- docker tag thoughts_server:latest <registry>/thoughts-backend:$GIT_SHA
- docker push <registry>/thoughts-backend:$GIT_SHA
- docker tag thoughts_server:latest <registry>/thoughts-backend:$TRAVIS_BRANCH
由于该动作发生在deploy
部分之前,因此将在到达该部分的每个构建上产生。
This method will produce a lot of tags. Depending on how your registry manages them, that may be costly. Be sure that it is a sensible thing to do.
Keep in mind that this same approach can be used for other conditional pushes.
请注意,注册表需要根据您自己的注册表详细信息进行调整。如果克隆示例 repo,则需要更改后者。
在本章中,我们介绍了持续集成实践,并探讨了 Docker 如何帮助实现它们。我们还研究了如何设计一个管道,确保我们的代码始终遵循高标准,并尽快检测出偏差。在 GitHub 中使用 Git 分支和拉请求与此同时进行,因为我们可以确定代码何时准备好合并到主分支并部署。
然后,我们介绍了 Travis CI,它是与 GitHub 一起工作以实现持续集成的一个很好的工具,并讨论了它的特性。我们学习了如何在 Travis CI 中创建管道,从创建.travis.yml
文件开始,如何配置作业,如何让构建将一个经过验证的 Docker 映像推送到我们的 Docker 注册表,以及如何得到通知。
我们描述了如何加速并行运行部分,以及如何将值设置为机密。我们还配置了 GitHub,以确保在将新代码合并到我们的主分支之前,Travis CI 管道已经成功运行。
在下一章中,我们将学习基本的 Kubernetes 操作和概念。
- 增加部署数量会降低部署质量吗?
- 描述什么是管道。
- 我们如何知道我们的主要分支是否可以部署?
- Travis CI 的主要配置来源是什么?
- 默认情况下,Travis CI 何时发送通知电子邮件?
- 我们如何避免将一个断了的分支合并到我们的主分支中?
- 为什么我们要避免在 Git 回购中存储机密?
要了解更多关于持续集成和其他工具的信息,您可以查看书籍动手持续集成和交付(https://www . packtpub . com/eu/virtualization-and-cloud/Hands-持续集成和交付),该书不仅涵盖了 Travis CI,还涵盖了 Jenkins 和 CircleCI 等其他工具。如果你想更深入地挖掘 GitHub 及其所有可能性,包括如何有效协作以及它所支持的不同工作流,请在GitHub Essentials(https://www . packtpub . com/eu/web-development/GitHub-Essentials-第二版)中了解更多信息。