Docker 容器测试全探索

当我们构建好Docker镜像并利用多套容器共同组合成应用程序,建立起持续交付通道,了解了如何将新创建的镜像纳入到生产或者测试环境当中之后,新的问题来了——我们该如何测试自己的Docker容器?

测试的策略多种多样,反映了各种各样的测试性格:天真型,懒人省事型,超前理想主义型,完美主义处女座型……那么你是哪一型?

下面我们就对其各自的方案利弊进行逐一分析。

“天真”型方案

大多数人会将此作为默认方案。其利用CI服务器实现任务执行。在这项方案中,开发人员利用Docker作为软件包管理器,其实际效果优于jar/rpm/deb方案。CI服务器对应用程序代码进行编译,而后执行测试(包括单元、服务及功能等)。Docker中的build可复用以生成新的镜像,由此生成的镜像不仅包含应用程序的“二进制代码”,同时亦拥有运行时所必需的依赖性及配置。

不过为了实现应用程序的可移植性,我们需要放弃开发与测试的可移植能力。在这种情况下,我们无法在CI之外重新建立同样的开发与测试环境。为了创始这样一套新的测试环境,我们需要设置测试工具(正确版本与插件)、配置运行时与操作系统设定,同时获取相同版本的测试脚本与测试数据。

为了解决上述难题,我们需要考虑以下方案。

应用&测试容器方案

现在我们尝试创建单一捆绑包,其中应用程序“二进制代码”中包含全部必需的软件包、测试工具(包括对应版本)、测试工具插件、测试脚本以及其它各类测试环境元素。

这套方案的优势包括:

  • 测试环境自身拥有可重复性——我们能够在CI、开发、分段或者生产环境当中使用完全相同的测试工具实现完全相同的测试效果。
  • 我们能够立足特定时间点捕捉测试脚本,所以可将其复用于任意环境。
  • 我们不需要对测试工具进行二次设置与配置——其已经成为镜像中的组成部分。

但这种方式也存在着显著弊端:

  • 镜像体积直线增长——这是因为其中包含有测试工具、必要软件包、测试脚本甚至是测试数据。
  • 特定测试配置可能对镜像运行时环境造成污染,甚至引入不必要的依赖性(集成测试中需要用到)。
  • 我们还需要考虑如何处理测试结果与记录日志;如何将其导出以及向哪里导出。 通过以下经过简化的 Dockerfile,我们可以了解上述方案的整个流程。

  1. FROM "<bases image>":"<version>"
  2. WORKDIR "<path>"
  3. # install packages required to run app and tests
  4. RUN apt-get update && apt-get install -y \
  5. "<app runtime> and <dependencies>" \ # add app runtime and required packages
  6. "<test tools> and <dependencies>" \ # add testing tools and required packages
  7. && rm -rf /var/lib/apt/lists/*
  8. # copy app files
  9. COPY app app
  10. COPY run.sh run.sh
  11. # copy test scripts
  12. COPY tests tests
  13. # copy "main" test command
  14. COPY test.sh test.sh
  15. # ... EXPOSE, RUN, ADD ... for app and test environment
  16. # main app command
  17. CMD [run.sh, "<app arguments>"]
  18. # it's not possible to have multiple CMD commands, but this is the "main" test command
  19. # CMD [/test.sh, "<test arguments>"]

点此下载 Dockerfile

毫无疑问,应该有更好的容器内测试方案可供选择。

测试感知型容器方案

目前,Docker承诺以“Build -> Ship -> Run”这一简单操作完成镜像构建、发布至注册表并在其它位置运行等任务。不过恕我直言,其中还缺少了Test这一重要环节。正确且完整的流程应该是Build -> Test -> Ship -> Run。 ​ 下面让我们看看能够为Docker命令提供“测试友好”型语法与扩展的Dockerfile是如何建立而成的。当然,其中并不涉及真正的语法,只是我个人更偏好这样表述。我定义出了自己的“理想”版本(真的只是理想版本,仅供大家借鉴其中的思想,小数注),大家应该能够看出其中可用于实践的指导思路。

ONTEST [INSTRUCTION]

首先定义一条特殊的ONTEST指令,其与现有ONBUILD指令非常相似。ONTEST指令会向镜像添加一条触发指令,其在随后镜像接受测试时自动执行。任意build指令都可被注册为触发条件。

ONTEST指令可由一条新的docker test命令进行识别。 

docker test [OPTIONS] IMAGE [COMMAND] [ARG...]

事实上,docker test命令的语法与docker run命令非常相似,二者只存在一项区别:前者会自动生成一套新的“可测试”镜像,甚至为其提供:-test 标签(向原始镜像标签中添加‘test’标签)。此可测试镜像将由初始应用镜像生成,执行全部build操作,于ONTEST命令后进行定义并执行ONTEST CMD(或者ONTEST ENTRYPOINT)。其中若测试发生错误,docker test命令应当返回一段非零代码。此测试结果应当被写入至自动生成且指向/var/tests/results文件夹的VOLUME。

下面我们来看看经过修改的Dockerfile——其中包含新的ONTEST指令。


  1. FROM "<base image>":"<version>"
  2. WORKDIR "<path>"
  3. # install packages required to run app
  4. RUN apt-get update && apt-get install -y \
  5. "<app runtime> and <dependencies>" \ # add app runtime and required packages
  6. && rm -rf /var/lib/apt/lists/*
  7. # install packages required to run tests
  8. ONTEST RUN apt-get update && apt-get install -y \
  9. "<test tools> and <dependencies>" \ # add testing tools and required packages
  10. && rm -rf /var/lib/apt/lists/*
  11. # copy app files
  12. COPY app app
  13. COPY run.sh run.sh
  14. # copy test scripts
  15. ONTEST COPY tests tests
  16. # copy "main" test command
  17. ONTEST COPY test.sh test.sh
  18. # auto-generated volume for test results
  19. # ONTEST VOLUME "/var/tests/results"
  20. # ... EXPOSE, RUN, ADD ... for app and test environment
  21. # main app command
  22. CMD [run.sh, "<app arguments>"]
  23. # main test command
  24. ONTEST CMD [/test.sh, "<test arguments>"]

点此下载 Dockerfile

如何实现“测试感知容器”

我们相信,Docker应该会将docker-test作为容器管理生命周期中的组成部分。当下我们都需要更为简便的解决方案,因此我在这里提出一套接近于理想情况的实现办法。

如之前所提到,Docker拥有ONBUILD这样一条非常实用的指令。该指令允许我们在已经成功的build之上触发另一build指令。其基本思路是在运行docker-test命令的同时,使用ONBUILD指令。

以下为docker-test命令的执行流程:

  1. docker-test将在应用程序Dockerfile当中搜索ONBUILD指令,而后……
  2. 利用初始Dockerfile生成一条临时的Dockerfile.test
  3. 执行docker build -f Dockerfile.test [OPTIONS] PATH,其中包含受docker build命令支持的其它选项:-test将自动被添加至tag选项当中。
  4. 如果构建成功,则执行 docker run -v ./tests/results:/var/tests/results [OPTIONS] IMAGE:TAG-test [COMMAND] [ARG...]
  5. 移除Dockerfile.test文件

那么,为什么不创建一个无需配合ONBUILD指令的Dockerfile.test文件?

这是因为,为了测试正确的镜像(及标签),我们需要保证FROM始终在测试目标的image:tag中得到更新。

不过前面提到的方案仍然存在局限——其不适用于“onbuild”镜像(即用于自动化构建应用的镜像),例如Maven:onbuild。

下面来看一条简单的docker-test命令实现流程。其中强调了一大重要概念:docker-test命令应当能够处理build与run命令选项,同时能够妥善处理错误状况。


  1. #!/bin/bash
  2. image="app"
  3. tag="latest"
  4. echo "FROM ${image}:${tag}" > Dockerfile.test &&
  5. docker build -t "${image}:${tag}-test" -f Dockerfile.test . &&
  6. docker run -it --rm -v $(pwd)/tests/results:/var/tests/results "${image}:${tag}-test" &&
  7. rm Dockerfile.test

让我们把注意力集中在最值得关注的重要部分。

集成测试型容器方案

假设我们的应用程序由数十甚至数百项微服务构建而成,同时假设我们已经拥有一套自动化CI/CD通道,其中每项微服务都由CI进行构建与测试,并在之后被部署到某种环境当中(例如测试、分段或者生产环境)。听起来不错,对吧?我们的CI会对各项微服务进行分别测试——运行单元与服务测试(或者API合同测试)。甚至有可能进行微集成测试——即将测试运行在特设的子系统之上(例如使用docker compose)。

但这又会带来一些新问题:

• 实际集成测试或者长期运行测试该如何完成(例如性能与压力测试)? • 弹性测试该如何实现(例如‘混乱猴子’测试)? • 如何实现安全扫描? • 那些需要耗费较长时间且运行在完整操作系统之上的测试与扫描要如何完成?

应该有一种更好的办法来替代这种直接将新的微服务版本交付至生产环境的作法,我们也需要更为密切的监控手段。

应当存在一类特殊的集成测试容器。这些容器将仅包含测试工具与测试元素:测试脚本、测试数据、测试环境配置等等。为了简化此类容器的编排与自动化流程,我们应当定义并遵循某些约定并使用元数据标签(Dockerfile中的LABEL指令)。

集成测试标签

  • test.type – 测试类型,负责定义integration; 可属于 integration, performance, security, chaos 或者其它任意文本之一; 此标签代表其属于一套集成测试容器
  • test.results – 用于存放测试结果的VOLUME ; 默认位置为 /var/tests/results
  • test.XXX -任何其它相关元数据,仅使用test.后缀名作为标签名称

集成测试容器

集成测试容器其实就是一种常规Docker容器,其中不包含任何应用程序逻辑及代码。它的惟一用途就是创建可重复且可移植的测试流程。以下为建议纳入集成测试容器的内容:

  • 测试工具 - Phantom.js, Selenium, Chakram, Gatling, …
  • 测试工具运行时 - Node.js, JVM, Python, Ruby, …
  • 测试管理配置 – 环境变量, 配置文件, 引导脚本, …
  • 测试 -作为经过编译的软件包或者脚本文件存在
  • 测试数据 – 任何用于测试的数据文件类型: json, csv, txt, xml, …
  • 测试启动脚本 -用于运行测试的部分“main”启动脚本,仅负责创建test.sh并借此启动该测试工具。

集成测试容器应当运行在全部微服务都已经部署到位的运营环境之下,包括测试、分段或者生产环境。这些容器可与其它服务采取完全一致的部署方式。其利用同样的网络层,因此能够访问多项其它服务;使用选定的服务发现方法(通常为DNS)。在实际集成测试当中,我们必须对多项服务进行访问——旨在模拟并验证自身系统是否能够在多种不同环境下正常运行。将集成测试纳入应用服务容器不仅会增加容器自身体积,同时亦会在各服务之间带来不必要的依赖性。因此,我们将所有依赖性都限制在集成测试容器当中。一旦测试(以及相关测试工具)被打包在该容器内,我们亦可在包括开发者设备在内的任意环境下重复运行同样的测试流程。大家还能够实现回滚,即根据需要运行任意集成测试容器版本。

 原文发布时间为:2016-08-17

本文来自合作伙伴“Linux中国”

时间: 2024-09-20 14:45:53

Docker 容器测试全探索的相关文章

Docker容器测试全探索

当我们构建好Docker镜像并利用多套容器共同组合成应用程序,建立起持续交付通道,了解了如何将新创建的镜像纳入到生产或者测试环境当中之后,新的问题来了--我们该如何测试自己的Docker容器? 测试的策略多种多样,反映了各种各样的测试性格:天真型,懒人省事型,超前理想主义型,完美主义处女座型--那么你是哪一型? 下面我们就对其各自的方案利弊进行逐一分析. "天真"型方案 大多数人会将此作为默认方案.其利用CI服务器实现任务执行.在这项方案中,开发人员利用Docker作为软件包管理器,其

【干货】全自主研发Docker容器调度引擎——Newbon

背景:       大家所熟知的Docker容器调度引擎包括,K8S, Swarm, Mesos和Rancher,这些调度引擎都是开源的国外引擎,各有各的特点.在同客户和圈内人士沟通中,很多人直言国内容器创业公司大多只是将各种开源组件集成在一起,同质化严重,没有核心竞争力.作为国内第一批的容器创业公司--Ghostcloud精灵云,深知国内需要在容器的诸多领域拥有真正完全可控,同时具有核心竞争力的产品.因此,容器云平台最核心的调度引擎迫切需要一个完全可控,自主的产品,在这种背景下Newbon应运

Docker容器互联方法-篇一

本文讲的是Docker容器互联方法-篇一,[编者的话]本文为Eddy Mavungu博士于DEIS官方博客中发布的系列文章的第一部分,Eddy博士在本篇系列文章中分享了Docker容器间互联的方法,并且做了演示.Eddy博士是DEIS公司的创始人,同时也是一位高级研究顾问.本文根据他于DEIS官方博客上发布的文章翻译而成. Docker容器都是独立的,互相隔离的环境.然而,它们通常只有互相通信时才能发挥作用. 虽然有许多方法可以连接容器们,可是我将并不会试着去将其全部讨论在内.但是在这一系列的方

Docker容器互联方法--篇二

本文讲的是Docker容器互联方法--篇二,[编者的话]本文为Eddy Mavungu博士于DEIS官方博客中发布的系列文章的第二部分,Eddy博士在本篇系列文章中分享了Docker容器间互联的方法,并且做了演示.Eddy博士是DEIS公司的创始人,同时也是一位高级研究顾问.本文根据他于DEIS官方博客上发布的文章翻译而成. 这篇系列文章的第二部分会看一下如何连接Docker各容器. 我们在第一节谈及了Docker的bridge接口,它可以让我们连接所有在相同Docker宿主机上的容器.特别需要

《Docker容器:利用Kubernetes、Flannel、Cockpit和Atomic构建和部署》——第2章 搭建容器运行时环境 2.1标准Linux系统的Docker配置

第2章 搭建容器运行时环境 自构建之初,Docker就运行在Linux之上.但与虚拟机不同,虚拟机包含了整个操作系统,而容器本身只是依靠单独的操作系统为其提供运行环境.这就是说,我们有诸多可选的方式来创建Docker的工作环境.具体方式包括以下这些. 标准Linux:多个主要的Linux发行版已将Docker打包在其中.如需最新的Docker特性,只要安装相应Linux发行版的最新版本即可.切记,Docker与操作系统有着密切的联系,因此,Linux的早期版本可能并不包含Docker运行所需的全

关于Docker在测试方面的应用

Docker 火了很长一段时间了,前段时间简单的学习和试玩了一下子,发现他对测试很有价值,觉得有必要再次深入研究. 这里标记一些较好的学习网址,用作参考: InfoQ上面有系列的文章: 深入浅出Docker在InfoQ上的内容: http://www.infoq.com/cn/dockerdeep/ 深入浅出Docker(一):Docker核心技术预览:http://www.infoq.com/cn/dockerdeep/ 深入浅出Docker(二):Docker命令行探秘:http://www

Docker 容器互联方法

Docker 容器互联方法 Docker容器都是独立的,互相隔离的环境.然而,它们通常只有互相通信时才能发挥作用. 虽然有许多方法可以连接容器们,可是我将并不会试着去将其全部讨论在内.但是在这一系列的方法中,我们将看看那些常用的做法. 虽然看起来是很浅显,但是这对于与Docker成天打交道的朋友来说,理解这些技术及底层的设计理念就显得非常地重要了. 理解这些主题将会: 帮助开发和运维人员探索广泛的容器部署的选择. 让开发和运维人员更自信的着手于微服务microservice架构设计. 让开发和运

3亿Docker容器部署的挑战及应对方案

编者按:IronWorker是一个面向开发者的任务队列服务,开发人员可以在不设置和管理任何基础设施的基础上,调度执行大规模的任务.几个月前,Iron开始试用Docker,如今其内部已经部署了3亿多个Docker容器,本文中分享了IronWorker在使用基于Docker的基础架构时,遇到的挑战.解决方法,以及其中的收获.以下为原文: IronWorker是一个任务队列服务,他让开发人员在不用设置和管理任何基础设施的基础上,调度执行大规模的任务.我们3年多前推出这项服务时,使用了包含所有的语言和代

使用IPython来操作Docker容器的入门指引_python

现在Docker是地球上最炙手可热的项目之一,就意味着人民实际上不仅仅是因为这个才喜欢它. 话虽如此,我非常喜欢使用容器,服务发现以及所有被创造出的新趣的点子和领域来切换工作作为范例. 这个文章中我会简要介绍使用python中的docker-py模块来操作Docker 容器,这里会使用我喜爱的编程工具IPython. 安装docker-py 首先需要docker-py.注意这里的案例中我将会使用Ubuntu Trusty 14.04版本. $ pip install docker-py IPyh