将 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 updocker 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 保持同步

在我的 CI 管道中使用 docker-compose vs codeship-services

在 .gitlab-ci.yml 中运行 docker-compose build