如何在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 工作流。

先决条件

在开始之前,您需要:

第 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 消息。

我们的应用程序有两个依赖项,FlaskRedis,您可以在前两行中看到它们。 必须先定义这些依赖项,然后才能执行应用程序。

打开一个新文件:

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”应用程序。

它定义了两个容器,webredis

  • web 使用 build 上下文的当前目录,并从我们刚刚创建的 Dockerfile 文件构建我们的 Python 应用程序。 这是我们为 Python 应用程序制作的本地 Docker 映像。 它定义了到 redis 容器的链接,以便访问 redis 容器 IP。 它还可以使用您的 Ubuntu 服务器的公共 IP 从 Internet 公开访问端口 80
  • redis 从一个名为 redis 的标准公共 Docker 映像执行。

完成后,保存并退出文件。

第 3 步 — 部署“Hello World”应用程序

在这一步中,我们将部署应用程序,到最后它可以通过 Internet 访问。 出于部署工作流程的目的,您可以将其视为开发、登台或生产环境,因为您可以多次以相同的方式部署应用程序。

docker-compose.ymlDockerfile 文件允许您通过执行以下命令自动部署本地环境:

docker-compose -f ~/hello_world/docker-compose.yml build
docker-compose -f ~/hello_world/docker-compose.yml up -d

第一行从 Dockerfile 文件构建我们的本地应用程序映像。 第二行在 docker-compose.yml 文件中指定的守护程序模式 (-d) 下运行 webredis 容器。

通过执行以下命令检查是否已创建应用程序容器:

docker ps

这应该显示两个正在运行的容器,名为 helloworld_web_1helloworld_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 依赖项。 这是文件中指定 webredis 容器的部分。 唯一的区别是 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 来在一个全新且不可变的环境中测试您的应用程序。