使用Travis CI来测试Docker构建
在过去几个月的博客中我们讨论了如何 Docker化这个博客 。但是那边文章我们有提到的是我怎么使用Docker Hub的 自动构建 功能来自动在包含了本博客源码的Github仓库有改动的时候创建一个新的镜像。
自动构建非常有用因为我可以简单地在仓库中的代码或者文章然后一旦push,这些改动都会触发Docker Hub使用我们前一个文章中创建的 Dockerfile 来构建一个镜像。另外一个好处是Docker镜像也会在Docker Hub中获取到,这意味这人和安装了Docker的系统可以靠简单的执行 docker run -d madflojo/blog 来部署最新的版本。
唯一的问题在于,假如这些改动会破坏部署怎么办?假如一个构建让构建不在有用,或者更糟的情况让静态网站生成器不在正确的生成页面。我需要的是一个方法在他们被merge到 master 分支并部署到生产环境之前,提前知道是否一个改动将会造成问题。
要达到这一点,我们可以利用 持续集成 的原则和工具。
什么是持续集成
持续集成(Continuous Integration)或者CI,是一个已经在软件开发中已经出现好一阵子的东西了,但是最近逐渐在运维界中变得火了起来。CI提出来是为了解决多个开发者在同一个代码库造成集成的问题。基本上,两个开发者在同一样的代码上进行开发会制造冲突,并且只有在之后很久才会发现这些冲突。
基本的原则是,越往后发现代码中的问题,要修复这些问题就代价越昂贵(不管是时间还是金钱)。要解决这个问题让开发者更加频繁的将代码提交到版本控制中,一天平均提交多次。随着代码频繁的提交并推到代码库,这能减少代码集成的问题,并且即使在真正发生问题的时候,也能更加容易的被解决。
然后一天多次的提交代码自身无法解决集成问题。还需要一个方法来保证被提交的代码质量过关并且能运行良好。这就引出了CI的另外一个概念,每当代码提交的时候,代码就会进行构建并且自动的被测试。
在本博客的这个场景中,这个构建将会包含构建一个Docker镜像,并且测试将会包含多个我编写的测试来保证支撑本博客的代码是能正常工作的。要执行这些自动化的构建和测试,我们需要一个工具能检测更改发生,并且执行必要的操作;我们需要一个类似 Travis CI 的工具。
Travis CI
Travis CI是一个持续构建工具,与Github集成并且能执行自动化的构建和测试操作。它对于公共的Github仓库免费,比如本博客。
在这个文章中,我将会过一遍如何配置Travis CI来自动构建和测试为这个博客生成的Docker镜像,这会教会你如何使用Travis CI来测试你自己的Docker构建的基础知识。
使用Travis CI来自动化Docker构建
这个博客将会假设你已经注册了Travis CI并且将其连接到我们我们的公共仓库。这个过程十分的简单,这是一个Travis CI上手的一部分。如果你需要更加详细的教程,Travis CI有一个 新手上路 指南。
因为我们将会测试我们的构建,不希望影响主要的 master 分支,第一件事情要做的就是创建一个新的 git 分支然后在这个分支中做实验。
$ git checkout -b building-docker-with-travis
当我们想这个分支做改动的时候,我们可以将内容提交到Github中统一名称分支下,然后验证Travis CI的构建结果,而不需要将更改影响到 master 分支。
配置Travis CI
首先在我们新创建分支中,创建一个 .travis.yml 文件,这个文件主要包含Travis CI需要用到的配置和指令。在这个文件中我们能告诉Travis CI构建的环境需要使用什么语言,需要运行什么服务,以及进行构建需要需要执行什么指令。
定义构建环境
在开始任何的构建之前我们需要定义好构建环境是什么样子的。比如,因为 hamerkop 应用和相关的测试脚本是用Python编写的,我们需要在测试环境中安装Python。
尽管我们可以使用几个 apt-get 命令来安装好Python,但是因为Python是唯一我们在这个环境中需要的语言,将其在 .travis.yml 文件中用 language: python 参数来将其定义为基础语言是更好的做法。
language: python python: - 2.7 - 3.5
上面额配置让Travis CI来将构建环境设置为一个Python的环境;分别需要Python的2.7和3.5的版本安装好并且支持。
上面使用的语法是用YAML文件格式指定的,这是一种十分流行的配置格式。在上面我们主要将 language 参数定义为python并且将 python 参数设置为一个包含值2.7和3.5的列表。如果我们需要添加额外的版本,那就简单地在这个列表添加该版本,如下边这个例子:
language: python python: - 2.7 - 3.2 - 3.5
在上边这个例子我们仅仅通过将3.2添加到这个列表就添加这个版本支持。
必须的服务
因为我们将要构建一个Docker镜像我们也需要Docker安装好并且需要Doker服务在环境中运行,我们可以通过使用 services 参数来告诉Travis CI来安装Travis CI来安装Docker并且启动该服务。
services: - docker
与 python 参数类似 services 参数是一个需要在我们环境中启动的服务的列表。这意味着靠添加向这个列表就能包含额外的服务。加入我们需要Docker和Redis,我们仅仅需要在指定Docker服务下面那一行添加一行。
services: - docker - redis-server
在这个例子中我们不需要除Docker之外的任何服务,然后知道Travis CI 支持很多服务 是很有用处的。
进行构建
现在我们已经定义好的我们想要额构建环境,我们可以执行构建步骤了。因为我们想验证一个Docker的构建我们基本上需要执行两个步骤,构建一个Docker的镜像,并且启动一个基于该镜像的容器。
我们可以依靠指定在前一篇文章中中使用的同样的 docker 命名来一步步的执行这些步骤。
install: - docker build -t blog . - docker run -d -p 127.0.0.1:80:80 --name blog blog
在上面我们可以看到在 install 参数下面两个`docker命令。这个参数实际上是Travis CI的一定义好的构建步骤。
Taravis CI有多个在构建中预定义的步骤,在 .tarvis.yml 文件中可以指定这些步骤。在上面的例子中我们定义了那两个 docker 命名令安装这个英语哦你个必要的步骤。
测试构建
Travis CI不仅仅是一个简单的构建工具,它还是一个持续构建工具,这意味着它的主要功能是进行测试。这也意味着我们需要在构建步骤中添加一个测试;暂时我们可以简单地验证是否Docker容器是否能运行,这可以通过执行命令 docker ps 命令。
script: - docker ps | grep -q blog
在上面,我们通过使用 script 参数定义了我们基本的测。这是另外一个我们可uiuyonglai调用测试用例的构建步骤。 script 步骤是一个必须的步骤,如果省略了那构建就会失败。
Push到Github
在上面定义的步骤中,我们有了一个能发送给Travis CI最小化的构建;要达到这个目标我们可以简单的将我们的改动push到Github。
$ git add .travis.yml $ git commit -m "Adding docker build steps to Travis" [building-docker-with-travis 2ad7a43] Adding docker build steps to Travis 1 file changed, 10 insertions(+), 32 deletions(-) rewrite .travis.yml (72%) $ git push origin building-docker-with-travis
在Travis CI的注册过程中,你会被问到将你的仓库和Travis CI进行连接。这能让它监控仓库发生的任何改动。当改动发生的时候,Travis CI会自动的拉取这些改动并且在 .travis.yml 文件中定义好的步骤。在这个例子中,意味这执行我们的Docker构建并且验证是否一切工作正常。
就在我们将我们新的改动推到仓库中的时候,Travis CI应该已经检测到了这些更改。我们可以到Travis CI中验证时候这些改动的结果是否能带来一个成功的构建与否。
Travis CI,将会为每一个构建显示一个构建日志,在这个特定构建日志的末尾,我们可以看到构建成功了。
Removing intermediate container c991de57cced Successfully built 45e8fb68a440 $ docker run -d -p 127.0.0.1:80:80 --name blog blog 45fe9081a7af138da991bb9e52852feec414b8e33ba2007968853da9803b1d96 $ docker ps | grep -q blog The command "docker ps | grep -q blog" exited with 0. Done. Your build exited with 0.
要知道Travis CI的一个重要点是,绝大多的构建步骤需要命令按顺序地成功执行才能让构建被标记为成功。
script 和 install 步骤是这两个例子,如果任何命令失败了,没有返回0的 退出码 那么整个构建将被标记为失败。
如果这个发生在 install 步骤阶段,构建将会停止在该步骤发生的那一个点。然而对于 script 步骤,构建将不会停止。这个背后的想法是如果一个安装步骤失败了,构建无论如何也不会成功然后如果一个单个测试用例失败了,只有一部分功能失败。将所有的测试结构显示给用户,用户能自己辨别出什么是坏了,什么是如预期一样的工作正常。
添加额外的测试
尽管现在我们Travis CI能验证是否Docker构建成功与否,仍然后很多其他的途径我们可以不经意的破这个博客。比如我们做一个改动让网站静态生成无法正常生成页面,这会破坏容器中的网站但不一定会损坏容器自身。要防止像这样的情形发生,我们添加一些额外的测试。
在我们的仓库中,有一个目录叫做 tests ,这个目录包含三个其他的目录, unit , integration 和 functional 。这些目录包含多种这个环境的自动化测试。头两个测试类型 uint 和 intergration 是被设计来测试在 hamerkop.py 应用中的代码的。尽管其很有用但是这些测试对于测试Docker容器却帮不上忙。然后最后一个目录, functional ,包含了可以用来测试运行中的Docker容器的自动化测试。
$ ls -la tests/functional/ total 24 drwxr-xr-x 1 vagrant vagrant 272 Jan 1 03:22 . drwxr-xr-x 1 vagrant vagrant 170 Dec 31 22:11 .. -rw-r--r-- 1 vagrant vagrant 2236 Jan 1 03:02 test_broken_links.py -rw-r--r-- 1 vagrant vagrant 2155 Jan 1 03:22 test_content.py -rw-r--r-- 1 vagrant vagrant 1072 Jan 1 03:13 test_rss.py
这些测试是设计来连接到运行的Docker容器并且验证静态网站的内容。
比如, test_broken_links.py 将会爬取由Docker容器服务的网站然后检测每一个页面HTTP的返回状态码。如果状态码不是 200 OK 那么测试就会失败。 test_content.py 也会爬取网站并且验证返回的内容,看其是是否与一个特定的模式匹配。如果不匹配,那么这些测试还是会失败。
这些测试有用之处在于,即使静态网站在Docker容器中运行,我们仍然能够测试网站的功能性。如果我们能像Travis CI的配置添加这些测试,它们也会在每一次构建的时候运行;可以给我每一次发生的更改更多信心。
用 before_scriot 安装测试的必须条件
要通过Travis CI来运行这些测试,我们只需要简单地将他们添加到 script 部分正如我们添加 docker ps 命令一样。然而在它们可以被执行之前,这些测试需要安装好一些Python的库。要安装这些库我们可以将安装步骤放在 before_script 的构建步骤里面:
before_script: - pip install -r requirements.txt - pip install mock - pip install requests - pip install feedparser
before_script 构建步骤会在 script 步骤之前但是在 install 步骤之后执行。这让 before_script 是一个完美的放置 script 的预先条件,但是不属于整个安装过程的地方。因为 before_script 不像 install 步骤一样会执行安装操作,它也要求所有的命令都成功执行才会继续 script 构建步骤。如果 before_script 构建的一个命令失败了,那么构建将会失败。
运行额外的测试
在必须的Python库安装好我们可以添加测试添加到 script 步骤中:
script: - docker ps | grep -q blog - python tests.py
这些测试可以通过通过执行 tests.py 来启动,然后执行所有的是哪个自动化测试: unit , intergration 和 functional 。
再次进行测试
在测试添加好我们可以再一次将我们的更改提交到Github。
$ git add .travis.yml $ git commit -m "Adding tests.py execution" [building-docker-with-travis 99c4587] Adding tests.py execution 1 file changed, 14 insertions(+) $ git push origin building-docker-with-travis
当把更新提交到仓库之后我们可以坐下来然后静静等待Travis CI构建并测试我们的应用了。
#
Test Runner: Functional tests
#
runTest (test_rss.VerifyRSS)
Execute recursive request ... ok
runTest (test_broken_links.CrawlSite)
Execute recursive request ... ok
runTest (test_content.CrawlSite)
Execute recursive request ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.768s
OK
一旦构建完成我们可以在构建日志中看到如上的消息,显示Travis CI已经真正地运行了我们的测试。
总结
当我们我们构建成功处理完成,然后我们最后看看我们 .travis.yml 文件的样子:
language: python python: - 2.7 services: - docker install: - docker build -t blog . - docker run -d -p 127.0.0.1:80:80 --name blog blog before_script: - pip install -r requirements.txt - pip install mock - pip install requests - pip install feedparser script: - docker ps | grep -q blog - python tests.py
在上面我们可以看到Travis CI配置包含三个构建步骤: install , before_script 和 script 。 install 步骤是用来构建并且启动我们的Docker容器。 before_script 步骤只是用来安装测试脚本所必需库,然后 script 用来执行我们的测试脚本。
整体上,这个设置是非常简单的,即使不用Travis CI我们也可以手动的就行测试。然而,使用Travis C的好处在于所有这些步骤会在每一次改动的时候被执行,不管这些改动多么细微。
并且,因为我们使用Github将会将构建的状态通知附加到每一个拉取请求中, 比如这个 。有了这些提醒,在合并这种类型的拉取请求的时候我就有信心它们不会对生产环境造成破坏。
构建一个持续集成和部署的流水线
过去几个的博文里,我们探索了使用Docker来打包并发布运行本博客的应用。在这个文章中,我们讨论了利用Travis CI来自动ing构建Docker镜像并且对其做功能性测试。
在接下来几个月的的博文里,我们将会这个步骤改进,自动地将这些更改使用SaltSTack部署到多个服务器。在下一个文章结束的时候我们就有了一个完整的持续集成和部署工作流,这会让更改进过测试并且不需要人为的干预就能部署到身生产环境。