将 docker-compose 与 CI 结合使用 - 如何处理退出代码和守护程序链接容器?
Posted
技术标签:
【中文标题】将 docker-compose 与 CI 结合使用 - 如何处理退出代码和守护程序链接容器?【英文标题】:Using docker-compose with CI - how to deal with exit codes and daemonized linked containers? 【发布时间】:2015-06-16 13:56:43 【问题描述】:现在,我们的 Jenkins 代理为我们的每个 Rails 项目生成一个 docker-compose.yml,然后运行 docker-compose up。 docker-compose.yml 有一个主“web”容器,其中包含 rbenv 和我们所有其他 Rails 依赖项。它链接到包含测试 Postgres 数据库的数据库容器。
当我们需要实际运行测试并生成退出代码时,问题就出现了。仅当测试脚本返回出口 0 时,我们的 CI 服务器才会部署,但 docker-compose 始终返回 0,即使其中一个容器命令失败。
另一个问题是 DB 容器会无限期地运行,即使在 Web 容器运行完测试之后也是如此,所以 docker-compose up
永远不会返回。
有没有办法我们可以在这个过程中使用 docker-compose?我们需要能够运行容器,但是在 Web 容器完成后退出并返回它的退出代码。现在我们被困在手动使用 docker 来启动 DB 容器并使用 --link 选项运行 Web 容器。
【问题讨论】:
【参考方案1】:docker-compose run
是获得所需退出状态的简单方法。例如:
$ cat docker-compose.yml
roit:
image: busybox
command: 'true'
naw:
image: busybox
command: 'false'
$ docker-compose run --rm roit; echo $?
Removing test_roit_run_1...
0
$ docker-compose run --rm naw; echo $?
Removing test_naw_run_1...
1
或者,您可以选择inspect 死容器。您可以使用-f
标志来获取退出状态。
$ docker-compose up
Creating test_naw_1...
Creating test_roit_1...
Attaching to test_roit_1
test_roit_1 exited with code 0
Gracefully stopping... (press Ctrl+C again to force)
$ docker-compose ps -q | xargs docker inspect -f ' .Name exited with status .State.ExitCode '
/test_naw_1 exited with status 1
/test_roit_1 exited with status 0
至于永远不会返回的 db 容器,如果你使用 docker-compose up
那么你需要 sigkill 那个容器;这可能不是你想要的。相反,您可以使用docker-compose up -d
来运行您的容器,并在测试完成时手动终止容器。 docker-compose run
应该为你运行链接的容器,但我听说过关于 SO 的讨论,关于一个阻止它现在按预期工作的错误。
【讨论】:
docker run 的问题是它在使用 -T 运行时没有给出任何输出,我们想要输出以便我们可以检查失败的构建。 @LoganSerman 你可以用docker-compose logs
检查输出
有没有办法在运行期间不断地将这些日志通过管道传输到 STDOUT,以便我们可以在 CI 构建进行时看到它?
我想我不明白你为什么要使用-T
我们在容器内运行以运行测试的一些命令有可能要求输入,我们希望使用 -T 运行以避免这种情况。例如,Rbenv 会询问您是否要重新安装 Ruby 版本(如果它已经存在)。【参考方案2】:
docker-rails 允许您指定哪个容器的错误代码返回给主进程,以便您的 CI 服务器可以确定结果。这是一个很好的 CI 和使用 docker 开发 Rails 的解决方案。
例如
exit_code: web
在您的docker-rails.yml
中将产生web
容器退出代码作为命令docker-rails ci test
的结果。 docker-rails.yml
只是标准 docker-compose.yml
的元包装器,它使您可以为不同的环境(即开发、测试和并行测试)继承/重用相同的基本配置。
【讨论】:
【参考方案3】:基于小次郎的回答:
docker-compose ps -q | xargs docker inspect -f ' .State.ExitCode ' | grep -v '^0' | wc -l | tr -d ' '
-
获取容器 ID
获取每个容器 ID 的最后一次运行退出代码
仅限不以“0”开头的状态代码
非 0 状态码的计数
修剪空白
返回返回了多少个非 0 退出代码。如果一切都以代码 0 退出,则为 0。
【讨论】:
您还可以使用来自docker-compose ps
的非安静输出,例如:docker-compose ps | grep -c "Exit 1"
将为您提供来自docker-compose ps
的显示中匹配“Exit 1”的计数(它提供打印精美的结果汇总表)。退出代码列在“状态”列中。
这真是太棒了。在我的情况下,在容器中运行的测试套件失败不会使容器以 1 的代码退出。如果有任何以 1 的代码退出,我无法聚合,因为它们都没有......知道如何处理这个案例?【参考方案4】:
如果您愿意使用 docker-compose run
手动启动测试,那么奇怪的是,添加 --rm
标志会使 Compose 准确反映您的命令的退出状态。
这是我的例子:
$ docker-compose -v
docker-compose version 1.7.0, build 0d7bf73
$ (docker-compose run bash false) || echo 'Test failed!' # False negative.
$ (docker-compose run --rm bash false) || echo 'Test failed!' # True positive.
Test failed!
$ (docker-compose run --rm bash true) || echo 'Test failed!' # True negative.
【讨论】:
或(docker-compose run --rm ...) || exit $?
用于在出现错误时终止。在 bash 脚本中很有用。【参考方案5】:
使用docker wait
获取退出码:
$ docker-compose -p foo up -d
$ ret=$(docker wait foo_bar_1)
foo
是“项目名称”。在上面的示例中,我明确指定了它,但如果您不提供它,它就是目录名称。 bar
是您在 docker-compose.yml 中给被测系统的名称。
请注意,docker logs -f
也做了正确的事情,在容器停止时退出。所以你可以放
$ docker logs -f foo_bar_1
在docker-compose up
和docker wait
之间,这样您就可以看到您的测试运行了。
【讨论】:
【参考方案6】:从1.12.0
版本开始,您可以使用--exit-code-from
选项。
来自documentation:
--从服务退出代码
返回所选服务容器的退出代码。暗示 --abort-on-container-exit。
【讨论】:
如果您使用docker-compose
1.12.0 及更高版本,这应该是正确的做法。也许这也是你的情况。例如:docker-compose up --exit-code-from test-unit
。请注意,直到我在脚本开头添加了set -e
,它才对我有用。
--exit-code-from
不适用于 -d
。它会抛出这些错误:using --exit-code-from implies --abort-on-container-exit
和 --abort-on-container-exit and -d cannot be combined.
我能够在 Travis CI 上完成这项工作:travis-ci.org/coyote-team/coyote/builds/274582053 这是 travis.yml:github.com/coyote-team/coyote/blob/master/.travis.yml#L12
文档很糟糕。这与哪些标志兼容?是只有一项服务还是可以通过多项服务?
请注意,这些参数适用于 docker-compose up 而不是 docker-compose run【参考方案7】:
--exit-code-from SERVICE
和 --abort-on-container-exit
在需要运行所有容器以完成但其中一个提前退出的情况下不起作用。例如,如果在不同的容器中同时运行 2 个测试套件。
根据@spenthil 的建议,您可以将docker-compose
包装在一个脚本中,如果任何容器这样做都会失败。
#!/bin/bash
set -e
# Wrap docker-compose and return a non-zero exit code if any containers failed.
docker-compose "$@"
exit $(docker-compose -f docker-compose.ci.build.yml ps -q | tr -d '[:space:]' |
xargs docker inspect -f ' .State.ExitCode ' | grep -v 0 | wc -l | tr -d '[:space:]')
然后在您的 CI 服务器上只需将 docker-compose up
更改为 ./docker-compose.sh up
。
【讨论】:
此脚本永远不会到达退出部分,因为其他容器(例如数据库、Web 应用程序)会永久运行。以分离模式运行,一旦容器启动,它就会退出 没错,这仅在您想运行 all 容器完成时才有效。可能不是特别常见,但在撰写本文时它对我很有用,我想我会分享它。 无论如何都赞成你的答案,因为它让我大部分时间都在那里!在分离模式下在每个测试容器上添加 Docker 等待使其工作。感谢分享:)【参考方案8】:您可以通过以下方式查看退出状态:
echo $(docker-compose ps | grep "servicename" | awk 'print $4')
【讨论】:
感谢您开始。这是我的版本(对我来说效果更好 b/c 我认为命令输出格式在编写此答案后发生了变化)–docker-compose ps | grep servicename | grep -v 'Exit 0' && echo "Automation or integration tests failed." && exit 1
【参考方案9】:
如果您可能在一个 docker 引擎上运行多个具有相同名称的 docker-compose 服务,但您不知道确切的名称:
docker-compose up -d
(exit "$$(docker-compose logs -f test-chrome)##* ")
echo %?
- 从 test-chrome 服务返回退出代码
好处:
等待确切的服务退出 使用服务名称,而不是容器名称【讨论】:
以上是关于将 docker-compose 与 CI 结合使用 - 如何处理退出代码和守护程序链接容器?的主要内容,如果未能解决你的问题,请参考以下文章
在 GitLab CI 管道中使用 docker-compose
为啥我无法从本地主机上的 Gitlab CI 连接到我的 docker-compose 服务
在 Gitlab-ci 中使用带有 docker-compose 的 Testcontainers 运行端到端测试
如何使 Postman 集合和测试与 CI 流中的 swagger/open api 规范和 git 保持同步