测试容器:忽略来自 Dockerfile 的父“EXPOSE”指令

Posted

技术标签:

【中文标题】测试容器:忽略来自 Dockerfile 的父“EXPOSE”指令【英文标题】:Test containers: ignore parent `EXPOSE` instruction from Dockerfile 【发布时间】:2021-06-23 13:04:31 【问题描述】:

我正在尝试通过具有固定暴露端口的测试容器运行 Couchbase v.5.1.1 docker 容器进行测试,例如:

trait CouchbaseTestEnvironment extends ForAllTestContainer 
  this: Suite =>

  def couchbaseContainer: FixedHostPortGenericContainer = 
    val consumer = new Slf4jLogConsumer(LoggerFactory.getLogger(getClass))

    /*
     * Couchbase should know which ports are exposed for client, because this is how it exposes services.
     * E.g. client ask only for on port - say 8091. And query service port is 8093. So client, won't ask for every port,
     * instead CB will tell client on which port query service exposed, that's why CB should be aware about port mapping.
     * That's why we need to give CB port mappings
     *
     * See for more details:
     * https://***.com/questions/59277436/couchbase-in-docker-for-integration-tests-make-the-ports-8092-8093-8094-and-8
     */

    def randomPort: Int = 
      val (from, to) = (32768, 35000) //linux private port range
      from + Random.nextInt(to - from)
    

    val random8091Port = randomPort
    val random8092Port = randomPort
    val random8093Port = randomPort
    val random8094Port = randomPort
    val random11210Port = randomPort

    val container = FixedHostPortGenericContainer(
      imageName = "couchbase:community-5.0.1",
      exposedHostPort = random8091Port,
      exposedContainerPort = random8091Port,
      env = Map(
        "COUCHBASE_RANDOM_PORT_8091" -> random8091Port.toString,
        "COUCHBASE_RANDOM_PORT_8092" -> random8092Port.toString,
        "COUCHBASE_RANDOM_PORT_8093" -> random8093Port.toString,
        "COUCHBASE_RANDOM_PORT_8094" -> random8094Port.toString,
        "COUCHBASE_RANDOM_PORT_11210" -> random11210Port.toString
      )
    )

    container.container.withFixedExposedPort(random8092Port, random8092Port)
    container.container.withFixedExposedPort(random8093Port, random8093Port)
    container.container.withFixedExposedPort(random8094Port, random8094Port)
    container.container.withFixedExposedPort(random11210Port, random11210Port)

    container.container.withLogConsumer(consumer)

    container
  

如您所见,应该公开 5 个 FIXED 端口。 但是,当我运行测试时,我实际上可以看到,其他端口被随机端口暴露:

docker ps

f4fc1ce06544   couchbase:community-5.0.1   "/entrypoint.sh /opt…"   59 seconds ago   Up 1 second    0.0.0.0:55264->8091/tcp, 0.0.0.0:55263->8092/tcp, 0.0.0.0:55262->8093/tcp, 0.0.0.0:55261->8094/tcp, 0.0.0.0:55260->11207/tcp, 0.0.0.0:55259->11210/tcp, 0.0.0.0:55258->11211/tcp, 0.0.0.0:55257->18091/tcp, 0.0.0.0:55256->18092/tcp, 0.0.0.0:55255->18093/tcp, 0.0.0.0:55254->18094/tcp   unruffled_mendel
03b491ac2ea8   testcontainersofficial/ryuk:0.3.0

所以你可以看到另一个端口被暴露,并映射到随机端口而不是固定的。

据我所知,测试容器,忽略我提供的端口,而是从 Couchbase Dockerfile 公开端口:https://github.com/couchbase/docker/blob/master/community/couchbase-server/5.1.1/Dockerfile#L74

EXPOSE 8091 8092 8093 8094 8095 8096 11207 11210 11211 18091 18092 18093 18094 18095 18096

我能以某种方式强制测试容器忽略EXPOSE 指令吗?

部分帮助的问题:Couchbase in docker for integration tests: Make the ports 8092, 8093, 8094 and 8095 configurable to be able to use docker’s random ports

【问题讨论】:

【参考方案1】:

我能以某种方式强制测试容器忽略 EXPOSE 指令吗?

我不知道是否有一个简单的配置选项,但我发现一个解决方法是使用the docker-java create container command customization 的高级功能。我提供了一个 Java 示例,请您自己将其翻译成 Scala。在从函数返回容器对象之前将其作为最后一个命令应用:

container.withCreateContainerCmdModifier(
    cmd -> cmd.getHostConfig().withPublishAllPorts(false)
);

这里的重点是.withPublishAllPorts(false)的用法。据我了解,这与docker run 命令的--publish-all(或-P)参数相同。 Testcontainers 库sets 这个值默认为true。此修改将其覆盖为 false

使用此配置,您的示例根本不会发布任何端口,而不是按预期修复的 5 个:

CONTAINER ID   IMAGE                       COMMAND                  CREATED          STATUS          PORTS                                                        NAMES
2ee4fb91b97c   couchbase:community-5.0.1   "/entrypoint.sh couc…"   33 seconds ago   Up 32 seconds   8091-8094/tcp, 11207/tcp, 11210-11211/tcp, 18091-18094/tcp   trusting_keldysh

这是因为在您提供的答案中,作者创建了一个特殊的自定义 docker 镜像,它可以“理解”COUCHBASE_RANDOM_PORT_8091 等环境变量。您的代码使用标准 couchbase 映像 couchbase:community-5.0.1,它基本上只是忽略了这些环境变量。因此,为了在非标准内部端口上运行 counchbase,您需要使用“神奇”configure-node.sh 脚本构建自定义映像,该脚本使用环境变量中提供的值调整 counchbase 配置。

希望对你有帮助:)

【讨论】:

是的,这绝对有帮助!非常感谢您提供如此描述性的答案。 快速提问:custom image with the "magic" 你能建议我应该实施什么样的魔法吗?我可以覆盖子 Dokcerfile 公开指令吗?谢谢 您需要基于环境变量覆盖容器内 couchbase 的默认内部端口的东西。喜欢sed -i "s|8092|$COUCHBASE_RANDOM_PORT_8092|g" /opt/couchbase/etc/couchdb/default.d/capi.ini。这应该在启动 couchbase 服务器之前完成。 好的,我明白了。老实说,我这样做了,但无论如何这并没有帮助,测试容器仍然暴露来自父 Dockerfile 的端口。如果可能的话,我会尝试以某种方式覆盖来自父 Dockerfile 的 EXPOSE 指令。再次感谢您的帮助! 我想说EXPOSE 没那么重要。它更像是文档:docs.docker.com/engine/reference/builder/#expose 我建议通过 ssh 进入您的自定义容器,并确保 coouchbase 确实在内部的自定义端口上运行,而不是在默认端口上运行。祝你好运。

以上是关于测试容器:忽略来自 Dockerfile 的父“EXPOSE”指令的主要内容,如果未能解决你的问题,请参考以下文章

使用Docker测试静态网站

使用Dockerfile部署springboot打包jar包

5.Dockerfile

Dockerfile自作镜像

来自带有 Gunicorn 的 Docker 容器的 ModuleNotFoundError

Docker虚拟化技术系列之-DockerFile配置