如何在Ubuntu 14.04上使用Docker和Docker Compose配置持续集成测试环境

Docker 技术文章

介绍

持续集成(CI)是指开发人员尽可能频繁地集成代码,并且每次提交都经过自动化构建之前和之后的测试,然后才合并到共享存储库中的一种实践。

CI加速了您的开发过程,并最大程度地减少了生产中的关键问题的风险,但设置起来并不是一件简单的事情;自动化构建在不同的环境中运行,其中运行时依赖项的安装和外部服务的配置可能与您的本地和开发环境不同。

Docker是一个容器化平台,旨在简化环境标准化的问题,以便应用程序的部署也可以标准化(了解更多关于Docker的信息)。对于开发人员来说,Docker允许您通过在本地容器中运行应用程序组件来模拟生产环境。这些容器可以使用Docker Compose轻松自动化,而与应用程序和底层操作系统无关。

本教程使用Docker Compose演示了CI工作流程的自动化。

我们将创建一个基于Docker的“Hello world”类型的Python应用程序和一个Bash测试脚本。Python应用程序将需要两个容器来运行:一个用于应用程序本身,另一个用于存储的Redis容器,这是应用程序所需的一个依赖项。

然后,测试脚本将被Docker化到自己的容器中,并且整个测试环境将移动到一个名为__docker-compose.test.yml__的文件中,以便我们可以确保每次执行测试时都在一个新的统一的应用程序环境中运行。

这种方法展示了如何为您的应用程序构建一个相同的、新鲜的测试环境,包括其依赖项,每次测试它时。

因此,我们可以独立于正在测试的应用程序和底层基础架构自动化CI工作流程。

要求

开始之前,您需要:

  • 一个Ubuntu 14.04服务器

  • 一个具有sudo特权的非root用户。您可以按照本教程进行设置

  • 对Docker和Docker Compose有一些了解;这些链接仅供参考,因为我们将在本教程中安装软件

  • 测试、测试和更多(自动化)测试! 您应该已经对测试的优势以及将CI应用到您的开发工作流程中有所了解

步骤1 — 安装Docker

如果Docker尚未在您的服务器上可用,最简单的方法是下载并执行官方Docker安装脚本,该脚本会提示输入sudo密码(在此处找到更多有关安装Docker的信息):

wget -qO- https://get.docker.com/ | sh

为了更轻松地使用Docker,使用以下命令将您的用户添加到docker组中:

sudo usermod -aG docker $(whoami)

注销然后重新登录到服务器以激活docker组对您的用户的权限。

步骤2 — 安装Docker Compose

Docker Compose是一个使用声明性方法定义和运行多容器应用程序的开源工具。为了安装Docker Compose,执行以下命令:

sudo apt-get update
sudo apt-get -y install python-pip
sudo pip install docker-compose

通过执行以下命令验证docker-compose是否正确安装:

docker-compose --version

您应该看到类似以下内容:


docker-compose version 1.6.2, build 4d72027

这应该告诉您已安装的docker-compose版本。

步骤3 — 创建“Hello World” Python应用程序

在这一步中,我们将创建一个简单的Python应用程序,作为您可以使用此设置进行测试的应用程序的示例。

通过执行以下命令为我们的应用程序创建一个新的文件夹:

cd ~
mkdir hello_world
cd hello_world

使用_nano_编辑一个名为app.py的新文件:

nano app.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

添加以下内容:


Flask
Redis

步骤 4 —— 为 “Hello World” 应用创建 Docker 镜像

Docker 使用一个名为 Dockerfile 的文件来指示构建给定应用的 Docker 镜像所需的步骤。编辑一个新文件:

nano 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

添加以下内容:


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,使端口 80 可以从互联网公开访问

  • redis 是从一个标准的公共 Docker 镜像 redis 中执行的

步骤 5 —— 部署 “Hello World” 应用

在这一步中,我们将部署应用程序,最终它将可以通过互联网访问。对于您的部署工作流程,您可以考虑这要么是一个开发、预发布或生产环境,因为您可以以相同的方式多次部署应用程序。

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

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

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

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

docker ps

这应该显示两个正在运行的容器,名称分别为 helloworld_web_1helloworld_redis_1

让我们检查一下应用程序是否已启动。我们可以通过执行以下命令获取 helloworld_web_1 容器的 IP:

WEB_APP_IP=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' helloworld_web_1)
echo $WEB_APP_IP

检查 Web 应用程序是否返回正确的消息:

curl http://${WEB_APP_IP}:80

这应该返回类似以下内容:


<h3>Hello World!</h3><b>Visits:</b> 1<br/>

每次访问此端点时,访问次数都会增加。您还可以通过访问您的 Ubuntu 服务器的公共 IP 地址,从浏览器访问 “Hello World” 应用程序。

如何为您自己的应用程序定制

设置您自己的应用程序的关键是将您的应用程序放在自己的 Docker 容器中,并从自己的容器中运行每个依赖项。然后,您可以使用 Docker Compose 定义容器之间的关系,就像示例中演示的那样。Docker Compose 在这篇 Docker Compose 文章中有更详细的介绍。

要了解如何在多个容器中运行应用程序的另一个示例,请阅读这篇关于使用 Docker Compose 运行 WordPress 和 phpMyAdmin 的文章。

步骤 6 — 创建测试脚本

现在我们将为我们的 Python 应用程序创建一个测试脚本。这将是一个简单的脚本,用于检查应用程序的 HTTP 输出。该脚本是您可能希望作为持续集成部署过程的一部分运行的测试类型的示例。

编辑一个新文件:

nano test.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”应用程序的基本 web 连通性。它使用 cURL 来检索访问次数,并报告测试是否通过。

步骤 7 — 创建测试环境

为了测试我们的应用程序,我们需要部署一个测试环境。而且,我们希望确保它与我们在步骤 5中创建的生产应用程序环境完全相同。

首先,我们需要通过创建一个新的 Dockerfile 文件来将我们的测试脚本 Docker 化。编辑一个新文件:

nano Dockerfile.test

添加以下内容:


FROM ubuntu:trusty

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:trusty 镜像,以安装 curl 依赖项,将 tests.sh 添加到镜像文件系统,并指示使用 Bash 执行测试脚本的 CMD 命令。

一旦我们的测试被 Docker 化,它们就可以以可复制和不可知的方式执行。

下一步是将我们的测试容器链接到我们的“Hello World”应用程序。这里 Docker Compose 再次发挥了作用。编辑一个新文件:

nano 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,因此在测试期间应用程序将无法通过公共互联网访问。因此,您可以看到我们正在以与生产部署相同的方式构建应用程序及其依赖项。

docker-compose.test.yml 文件还定义了一个名为 system under tests(sut)的 sut 容器,负责执行我们的集成测试。sut 容器指定当前目录作为我们的 build 目录,并指定我们的 Dockerfile.test 文件。它链接到 web 容器,因此应用程序容器的 IP 地址对我们的 test.sh 脚本是可访问的。

如何为您自己的应用程序定制

请注意,docker-compose.test.yml 可能包括数十个外部服务和多个测试容器。Docker 将能够在单个主机上运行所有这些依赖项,因为每个容器共享底层操作系统。

如果您有更多的测试要在您的应用程序上运行,您可以为它们创建类似于上面显示的 Dockerfile.test 文件的其他 Dockerfiles。然后,您可以在 docker-compose.test.yml 文件的 sut 容器下面添加其他容器,引用其他 Dockerfiles。

步骤 8 — 测试“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

通过执行以下命令检查 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 文件进行测试的优势在于测试过程是:

  • 可自动化的:工具执行 docker-compose.test.yml 的方式与被测试的应用程序无关
  • 轻量级的:可以在单个主机上部署数百个外部服务,模拟复杂的(集成)测试环境
  • 不可知的:避免 CI 提供商的锁定,并且您的测试可以在任何支持 Docker 的基础设施和任何操作系统上运行
  • 不可变的:在本地机器上通过的测试将在 CI 工具上通过

本教程展示了如何测试一个简单的“Hello World”应用程序的示例。

现在是时候使用您自己的应用程序文件,将您自己的应用程序测试脚本 Docker 化,并创建您自己的 docker-compose.test.yml 来在一个新的不可变环境中测试您的应用程序了。

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-14 06:12:04       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-14 06:12:04       100 阅读
  3. 在Django里面运行非项目文件

    2024-04-14 06:12:04       82 阅读
  4. Python语言-面向对象

    2024-04-14 06:12:04       91 阅读

热门阅读

  1. 打不动的蓝桥杯

    2024-04-14 06:12:04       34 阅读
  2. 使用低空无人机图像对树种进行实例分割

    2024-04-14 06:12:04       36 阅读
  3. git 如何合并两个分支中的某些文件

    2024-04-14 06:12:04       38 阅读
  4. python 今日小知识1——parser

    2024-04-14 06:12:04       39 阅读
  5. tomcat按顺序启动应用

    2024-04-14 06:12:04       32 阅读
  6. xxl-job调度任务原理解析

    2024-04-14 06:12:04       32 阅读
  7. Qt | .pro开发经验笔记

    2024-04-14 06:12:04       37 阅读
  8. uniapp——长按识别二维码

    2024-04-14 06:12:04       39 阅读
  9. c#raft算法实现

    2024-04-14 06:12:04       33 阅读
  10. 蓝桥杯3527 阶乘的和 Python

    2024-04-14 06:12:04       38 阅读
  11. 使用Spring Cloud构建微服务时的一些经验

    2024-04-14 06:12:04       29 阅读
  12. React中常见的Hook

    2024-04-14 06:12:04       43 阅读
  13. 如何在seata中编写测试用例

    2024-04-14 06:12:04       36 阅读