如何在Ubuntu16.04上使用Docker和DockerCompose配置持续集成测试环境
介绍
持续集成 (CI) 是指开发人员 尽可能频繁地集成 代码,并在通过 自动构建合并到共享存储库之前和之后测试每个提交的实践。
CI 加快了您的开发过程,并将生产中关键问题的风险降至最低,但设置起来并非易事; 自动构建在不同的环境中运行,其中 运行时依赖项 的安装和 外部服务 的配置可能与本地和开发环境中的不同。
Docker是一个容器化平台,旨在简化环境标准化问题,使应用程序的部署也可以标准化(了解更多关于Docker)。 对于开发人员,Docker 允许您通过在本地容器中运行应用程序组件来模拟本地机器上的生产环境。 这些容器可以使用 Docker Compose 轻松实现自动化,独立于应用程序和底层操作系统。
本教程使用 Docker Compose 来演示 CI 工作流的自动化。
我们将创建一个 Dockerized “Hello world” 类型的 Python 应用程序和一个 Bash 测试脚本。 Python 应用程序将需要两个容器来运行:一个用于应用程序本身,一个用于存储的 Redis 容器,该容器需要作为应用程序的依赖项。
然后,测试脚本将在自己的容器中被 Docker 化,整个测试环境移动到一个 docker-compose.test.yml 文件,这样我们就可以确保我们以全新和统一的方式运行每个测试执行应用环境。
这种方法展示了如何在每次测试时为您的应用程序构建一个相同的、全新的测试环境,包括它的依赖项。
因此,我们独立于被测应用程序和底层基础设施来自动化 CI 工作流。
先决条件
在开始之前,您需要:
- 具有 非 root 用户和 sudo 权限 的 Ubuntu 16.04 服务器。 Initial Server Setup with Ubuntu 16.04 解释了如何设置。
- Docker,按照如何在Ubuntu 16.04上安装和使用Docker的步骤1和2安装。
- Docker Compose,按照 How to Install Docker Compose on Ubuntu 16.04 中的步骤 1 安装
第 1 步——创建“Hello World”Python 应用程序
在此步骤中,我们将创建一个简单的 Python 应用程序作为您可以使用此设置测试的应用程序类型的示例。
通过执行以下命令为我们的应用程序创建一个新目录:
cd ~ mkdir hello_world cd hello_world
用 nano 编辑一个新文件 app.py
:
nano app.py
添加以下内容:
应用程序.py
from flask import Flask from redis import Redis app = Flask(__name__) redis = Redis(host="redis") @app.route("/") def hello(): visits = redis.incr('counter') html = "<h3>Hello World!</h3>" \ "<b>Visits:</b> {visits}" \ "<br/>" return html.format(visits=visits) if __name__ == "__main__": app.run(host="0.0.0.0", port=80)
完成后,保存并退出文件。
app.py
是一个基于 Flask 的 Web 应用程序,它连接到 Redis 数据服务。 visits = redis.incr('counter')
行增加了访问次数,并将这个值保存在 Redis 中。 最后,在 HTML 中返回带有访问次数的 Hello World
消息。
我们的应用程序有两个依赖项,Flask
和 Redis
,您可以在前两行中看到它们。 必须先定义这些依赖项,然后才能执行应用程序。
打开一个新文件:
nano requirements.txt
添加内容:
要求.txt
Flask Redis
完成后,保存并退出文件。 现在我们已经定义了我们的需求,稍后我们将在 docker-compose.yml
中实施这些需求,我们已经准备好进行下一步了。
第 2 步 — Docker 化“Hello World”应用程序
Docker 使用一个名为 Dockerfile
的文件来指示为给定应用程序构建 Docker 映像所需的步骤。 编辑一个新文件:
nano Dockerfile
添加以下内容:
Dockerfile
FROM python:2.7 WORKDIR /app ADD requirements.txt /app/requirements.txt RUN pip install -r requirements.txt ADD app.py /app/app.py EXPOSE 80 CMD ["python", "app.py"]
我们来分析一下每一行的含义:
FROM python:2.7
:表示我们的“Hello World”应用程序镜像是从官方的python:2.7
Docker镜像构建的WORKDIR /app
:将 Docker 镜像内部的工作目录设置为/app
ADD requirements.txt /app/requirements.txt
:将文件requirements.txt
添加到我们的 Docker 映像中RUN pip install -r requirements.txt
:安装应用程序的pip
依赖项ADD app.py /app/app.py
:将我们的应用程序源代码添加到 Docker 镜像中EXPOSE 80
:表示我们的应用可以通过80端口(标准的公共web端口)访问CMD ["python", "app.py"]
:启动我们应用程序的命令
保存并退出文件。 这个 Dockerfile
文件包含构建我们的“Hello World”应用程序的主要组件所需的所有信息。
依赖关系
现在我们进入示例中更复杂的部分。 我们的应用程序需要 Redis 作为外部服务。 这种依赖类型在传统的 Linux 环境中每次都很难以相同的方式设置,但使用 Docker Compose,我们可以每次都以可重复的方式设置它。
让我们创建一个 docker-compose.yml
文件来开始使用 Docker Compose。
编辑一个新文件:
nano docker-compose.yml
添加以下内容:
码头工人-compose.yml
web: build: . dockerfile: Dockerfile links: - redis ports: - "80:80" redis: image: redis
此 Docker Compose 文件指示如何在两个 Docker 容器中本地启动“Hello World”应用程序。
它定义了两个容器,web
和 redis
。
web
使用build
上下文的当前目录,并从我们刚刚创建的Dockerfile
文件构建我们的 Python 应用程序。 这是我们为 Python 应用程序制作的本地 Docker 映像。 它定义了到redis
容器的链接,以便访问redis
容器 IP。 它还可以使用您的 Ubuntu 服务器的公共 IP 从 Internet 公开访问端口 80redis
从一个名为redis
的标准公共 Docker 映像执行。
完成后,保存并退出文件。
第 3 步 — 部署“Hello World”应用程序
在这一步中,我们将部署应用程序,到最后它可以通过 Internet 访问。 出于部署工作流程的目的,您可以将其视为开发、登台或生产环境,因为您可以多次以相同的方式部署应用程序。
docker-compose.yml
和 Dockerfile
文件允许您通过执行以下命令自动部署本地环境:
docker-compose -f ~/hello_world/docker-compose.yml build docker-compose -f ~/hello_world/docker-compose.yml up -d
第一行从 Dockerfile
文件构建我们的本地应用程序映像。 第二行在 docker-compose.yml
文件中指定的守护程序模式 (-d
) 下运行 web
和 redis
容器。
通过执行以下命令检查是否已创建应用程序容器:
docker ps
这应该显示两个正在运行的容器,名为 helloworld_web_1
和 helloworld_redis_1
。
让我们检查一下应用程序是否已启动。 我们可以通过执行以下命令获取 helloworld_web_1
容器的 IP:
WEB_APP_IP=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' helloworld_web_1) echo $WEB_APP_IP
检查 Web 应用程序是否返回正确的消息:
curl http://${WEB_APP_IP}:80
这应该返回类似:
输出
<h3>Hello World!</h3><b>Visits:</b> 2<br/>
每次点击此端点时,访问次数都会增加。 您还可以通过访问 Ubuntu 服务器的公共 IP 地址从浏览器访问“Hello World”应用程序。
如何为您自己的应用程序定制
设置您自己的应用程序的关键是将您的应用程序放在自己的 Docker 容器中,并从其自己的容器中运行每个依赖项。 然后,您可以使用 Docker Compose 定义容器之间的关系,如示例中所示。 这篇 Docker Compose 文章 更详细地介绍了 Docker Compose。
有关如何让应用程序跨多个容器运行的另一个示例,请阅读这篇关于使用 Docker Compose 运行 WordPress 和 phpMyAdmin 的文章。
第 4 步 — 创建测试脚本
现在我们将为我们的 Python 应用程序创建一个测试脚本。 这将是一个检查应用程序 HTTP 输出的简单脚本。 该脚本是您可能希望在持续集成部署过程中运行的测试类型示例。
编辑一个新文件:
nano test.sh
添加以下内容:
测试.sh
sleep 5 if curl web | grep -q '<b>Visits:</b> '; then echo "Tests passed!" exit 0 else echo "Tests failed!" exit 1 fi
test.sh
测试我们的“Hello World”应用程序的基本网络连接。 它使用 cURL 来检索访问次数并报告测试是否通过。
第 5 步 — 创建测试环境
为了测试我们的应用程序,我们需要部署一个测试环境。 而且,我们要确保它与我们在 Step 3 中创建的实时应用程序环境相同。
首先,我们需要通过创建一个新的 Dockerfile 文件来对我们的测试脚本进行 Docker 化。 编辑一个新文件:
nano Dockerfile.test
添加以下内容:
Dockerfile.test
FROM ubuntu:xenial RUN apt-get update && apt-get install -yq curl && apt-get clean WORKDIR /app ADD test.sh /app/test.sh CMD ["bash", "test.sh"]
Dockerfile.test
扩展官方ubuntu:xenial
镜像安装curl
依赖,在镜像文件系统中添加tests.sh
,并表示CMD
命令使用 Bash 执行测试脚本。
一旦我们的测试被 Docker 化,它们就可以以可复制和不可知的方式执行。
下一步是将我们的测试容器链接到我们的“Hello World”应用程序。 这就是 Docker Compose 再次提供救援的地方。 编辑一个新文件:
nano docker-compose.test.yml
添加以下内容:
docker-compose.test.yml
sut: build: . dockerfile: Dockerfile.test links: - web web: build: . dockerfile: Dockerfile links: - redis redis: image: redis
Docker Compose 文件的后半部分以与之前的 docker-compose.yml
文件相同的方式部署主 web
应用程序及其 redis
依赖项。 这是文件中指定 web
和 redis
容器的部分。 唯一的区别是 web
容器不再暴露端口 80,因此在测试期间应用程序将无法通过公共 Internet 访问。 因此,您可以看到我们构建应用程序及其依赖项的方式与它们在实时部署中的方式完全相同。
docker-compose.test.yml
文件还定义了一个 sut
容器(命名为 system under testing),负责执行我们的集成测试。 sut
容器将当前目录指定为我们的 build
目录并指定我们的 Dockerfile.test
文件。 它链接到 web
容器,因此我们的 test.sh
脚本可以访问应用程序容器的 IP 地址。
如何为您自己的应用程序定制
请注意,docker-compose.test.yml
可能包含数十个外部服务和多个测试容器。 Docker 将能够在单个主机上运行所有这些依赖项,因为每个容器都共享底层操作系统。
如果您有更多测试要在您的应用程序上运行,您可以为它们创建额外的 Dockerfile,类似于上面显示的 Dockerfile.test
文件。
然后,您可以在 docker-compose.test.yml
文件中的 sut
容器下方添加其他容器,并引用其他 Dockerfile。
第 6 步 — 测试“Hello World”应用程序
最后,将 Docker 的想法从本地环境扩展到测试环境,我们有一种使用 Docker 自动测试应用程序的方法,方法是执行:
docker-compose -f ~/hello_world/docker-compose.test.yml -p ci build
此命令构建 docker-compose.test.yml
所需的本地图像。 请注意,我们使用 -f
指向 docker-compose.test.yml
和 -p
来指示特定的项目名称。
现在通过执行以下命令启动新的测试环境:
docker-compose -f ~/hello_world/docker-compose.test.yml -p ci up -d
OutputCreating ci_redis_1 Creating ci_web_1 Creating ci_sut_1
通过执行以下命令检查 sut
容器的输出:
docker logs -f ci_sut_1
输出
% Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 42 100 42 0 0 3902 0 --:--:-- --:--:-- --:--:-- 4200 Tests passed!
最后,检查 sut
容器的退出代码以验证您的测试是否通过:
docker wait ci_sut_1
输出
0
执行此命令后,如果测试通过,$?
的值为 0
。 否则,我们的应用程序测试将失败。
请注意,其他 CI 工具可以克隆我们的代码存储库并执行这几个命令来验证测试是否使用应用程序的最新位通过,而无需担心运行时依赖关系或外部服务配置。
就是这样! 我们已经在与我们的生产环境相同的新建环境中成功运行了我们的测试。
结论
感谢 Docker 和 Docker Compose,我们已经能够自动构建应用程序 (Dockerfile
)、部署本地环境 (docker-compose.yml
)、构建测试映像 (Dockerfile.test
) ,并为任何应用程序执行(集成)测试(docker-compose.test.yml
)。
特别是使用docker-compose.test.yml
文件进行测试的好处是测试过程是:
- Automatable:工具执行
docker-compose.test.yml
的方式独立于被测应用程序 - 轻量级:单台主机可部署数百个外部服务,模拟复杂(集成)测试环境
- Agnostic:避免 CI 提供者锁定,您的测试可以在任何基础设施和任何支持 Docker 的操作系统上运行
- Immutable:在本地机器上通过的测试将在你的 CI 工具中通过
本教程展示了如何测试简单的“Hello World”应用程序的示例。
现在是时候使用您自己的应用程序文件,Dockerize 您自己的应用程序测试脚本,并创建您自己的 docker-compose.test.yml
来在一个全新且不可变的环境中测试您的应用程序。