Testcontainers, Docker in Docker with custom network, 容器不属于网络

Posted

技术标签:

【中文标题】Testcontainers, Docker in Docker with custom network, 容器不属于网络【英文标题】:Testcontainers, Docker in Docker with custom network, Containers don't belong to network 【发布时间】:2020-08-13 12:17:48 【问题描述】:

我正在尝试使用 Docker 构建器映像让 Testcontainers 在 TeamCity 上运行。

测试在本地运行良好(不在构建器映像内)。并且仅部分位于 TeamCity 的构建器图像中。我关注了guide on DinD,但没有关于 docker 网络如何发挥作用的示例。

我们在 TeamCity 中开始构建的方式(注意 --network 参数,ryuk 因连接问题而被禁用):

docker network create --driver bridge custom_network

docker run --rm -it -v $PWD:$PWD -w $PWD \
  --privileged \
  --network=custom_network \
  -e TESTCONTAINERS_RYUK_DISABLED=true \
  -e _JAVA_OPTIONS="" \
  -e DOCKER_HOST="unix:///var/run/docker.sock" \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /home/teamcity/.docker:/home/java/.docker
  -v /local/maven/cache/repository:/opt/m2/repository \
  registry.ch/java:11-builder \
  mvn verify

构建运行非常正常:junit 测试开始,我们使用的自定义 oracle-xe 映像已下载,日志提示它已启动。但是在本地我可以看到 testcontainers 正在轮询以创建连接,在 TeamCity 上构建只是继续并遇到错误:

[14:01:07] :     [Step 3/3] 14:01:07.006 [tc-okhttp-stream-276714561] DEBUG com.github.dockerjava.core.command.PullImageResultCallback - ResponseItem(stream=null, status=Extracting, progressDetail=ResponseItem.ProgressDetail(current=625569807, total=625569807, start=null), progress=[==================================================>]  625.6MB/625.6MB, id=2538d1d7e815, from=null, time=null, errorDetail=null, error=null, aux=null)
[14:01:07] :     [Step 3/3] 14:01:07.211 [tc-okhttp-stream-276714561] DEBUG com.github.dockerjava.core.command.PullImageResultCallback - ResponseItem(stream=null, status=Pull complete, progressDetail=ResponseItem.ProgressDetail(current=null, total=null, start=null), progress=null, id=2538d1d7e815, from=null, time=null, errorDetail=null, error=null, aux=null)
...
[14:01:07] :     [Step 3/3] 14:01:07.228 [tc-okhttp-stream-276714561] INFO    [registry/private/oracle/database:18c_xe] - Pull complete. 2 layers, pulled in 46s (downloaded 637 MB at 13 MB/s)
[14:01:07] :     [Step 3/3] 14:01:07.228 [main] DEBUG com.github.dockerjava.core.command.AbstrDockerCmd - Cmd: registry/private/oracle/database:18c_xe
...
[14:01:07]i:     [Step 3/3] Docker event: "status":"pull","id":"registry/private/oracle/database:18c_xe","Type":"image","Action":"pull","Actor":"ID":"registry/private/oracle/database:18c_xe","Attributes":"name":"registry/private/oracle/database","scope":"local","time":1588075267,"timeNano":1588075267227817791
...
[14:01:08] :     [Step 3/3]  :: Spring Boot ::        (v2.2.6.RELEASE)
[14:01:08] :     [Step 3/3] 
[14:01:08] :     [Step 3/3] 2020-04-28 14:01:08.502 ERROR 47 --- [           main] o.s.boot.SpringApplication               : Application run failed
[14:01:08] :     [Step 3/3] 
[14:01:08] :     [Step 3/3] java.lang.IllegalStateException: Mapped port can only be obtained after the container is started
[14:01:08] :     [Step 3/3]     at org.testcontainers.shaded.com.google.common.base.Preconditions.checkState(Preconditions.java:174) ~[testcontainers-1.14.1.jar:na]
[14:01:08] :     [Step 3/3]     at org.testcontainers.containers.ContainerState.getMappedPort(ContainerState.java:129) ~[testcontainers-1.14.1.jar:na]
[14:01:08] :     [Step 3/3]     at org.testcontainers.containers.OracleContainer.getOraclePort(OracleContainer.java:95) ~[oracle-xe-1.14.1.jar:na]
[14:01:08] :     [Step 3/3]     at org.testcontainers.containers.OracleContainer.getJdbcUrl(OracleContainer.java:64) ~[oracle-xe-1.14.1.jar:na]
[14:01:08] :     [Step 3/3]     at ch.package.OracleFlywayDatabaseTest$Initializer.initialize(OracleFlywayDatabaseTest.java:35) ~[test-classes/:na]
[14:01:08] :     [Step 3/3]     at org.springframework.boot.SpringApplication.applyInitializers(SpringApplication.java:626) ~[spring-boot-2.2.6.RELEASE.jar:2.2.6.RELEASE]
...
[14:01:08] :     [Step 3/3]     at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128) ~[junit-platform-launcher-1.3.1.jar:1.3.1]
...
[14:01:08] :     [Step 3/3]     at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:418) ~[surefire-booter-2.22.2.jar:2.22.2]
[14:01:08] :     [Step 3/3] 
...
[14:01:08] :     [Step 3/3] org.testcontainers.containers.ContainerLaunchException: Container startup failed
[14:01:08] :     [Step 3/3] Caused by: org.testcontainers.containers.ContainerFetchException: Can't get Docker image: RemoteDockerImage(imageName=registry/private/oracle/database:18c_xe, imagePullPolicy=DefaultPullPolicy())
[14:01:08] :     [Step 3/3] Caused by: java.time.format.DateTimeParseException: Text '2020-03-04T15:17:25.025952651+01:00' could not be parsed at index 29
[14:01:08] :     [Step 3/3] 

我不确定最后一个异常,它似乎并不坏,问题似乎是我们启动的oracle容器'不可见'。 DateTimeParseException 中的日期是我们注册表中 oracle-xe 映像的创建日期。

我也尝试使用构建器上的withNetwork 选项创建容器:

@Testcontainers
public abstract class OracleFlywayDatabaseTest 

  @Container
  private static final OracleContainer oracle =
        new OracleContainer("registry/private/oracle/database:18c_xe")
                // .withNetwork(Network.builder().id("custom_network").build())
                .withUsername("TESTUSR")
                .withPassword("TESTPWD");

如果我使用 docker network inspect custom_network 在本地调查此问题,则由 Testcontainers 启动的数据库容器不在该网络中。

将容器放入该网络的正确方法是什么?意味着构建器图像最初开始的同一网络? id 真的是 docker 创建时分配给网络的 id 吗? (我试过了,但也许我做错了什么)。

【问题讨论】:

【参考方案1】:

我们找到了一种方法来完成这项工作。这不是人们所说的美丽......但这是目前有效的:

使用 docker 命令创建自定义网络(“custom_nework”是本示例中的网络名称):

docker network ls|grep custom_network > /dev/null || docker network create --driver bridge custom_network

然后确定那个网络的id:

network_id=`docker network inspect custom_network --format ".ID"`

并且设置是和环境变量。

在 Testcontainers 测试中,您现在可以通过以下方式引用此网络: (在IDE本地环境中只运行一次测试,我们要区分是否有自定义网络(CI服务器),或者没有(IDE))

import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.BeforeAll;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.testcontainers.containers.OracleContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@Testcontainers
@ContextConfiguration(initializers = OracleFlywayDatabaseTest.Initializer.class)
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
public abstract class OracleFlywayDatabaseTest 

    private static final Logger LOGGER = LoggerFactory.getLogger(OracleFlywayDatabaseTest.class);

    private static final String NETWORK_ID = "NETWORK_ID";

    @Container
    protected static final OracleContainer oracle;

    static 
        String networkId = System.getenv(NETWORK_ID);
        if (StringUtils.isBlank(networkId)) 
            oracle = new OracleContainer("diemobiliar/minimized-oraclexe-image:18.4.0-xe");
         else 
            oracle = new NetworkOracleContainer("diemobiliar/minimized-oraclexe-image:18.4.0-xe", networkId);
        
        oracle.withUsername("AOO_TESTS").withPassword("AOO_TESTS");
    

    @BeforeAll
    public static void setupOracle() 
        LOGGER.info("ORACLE 18 JDBC URL: " + oracle.getJdbcUrl());
    

    public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> 

        @Override
        public void initialize(ConfigurableApplicationContext configurableApplicationContext) 
            TestPropertyValues.of("spring.datasource.platform=" + "ORACLE", //
                    "spring.datasource.url=" + oracle.getJdbcUrl(), //
                    "spring.datasource.username=" + oracle.getUsername(), //
                    "spring.datasource.password=" + oracle.getPassword()) //
                    .applyTo(configurableApplicationContext.getEnvironment());
            LOGGER.info("spring.datasource. Properties set.");
        
    


以及帮助 NetworkOracleContainer 类:

import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.testcontainers.containers.Network;

public class NetworkOracleContainer extends LocalOracleContainer 

    private static final String CONTAINER_NAME = "oracle";

    public NetworkOracleContainer(String dockerImageName, String networkId) 
        super(dockerImageName);
        this.withNetwork(new ExistingNetwork(networkId))
                .withCreateContainerCmdModifier(cmd -> cmd.withName(CONTAINER_NAME));
    

    @Override
    public String getHost() 
        return CONTAINER_NAME;
    

    @Override
    public Integer getOraclePort() 
        return 1521;
    

    private static class ExistingNetwork implements Network 

        private final String networkId;

        ExistingNetwork(String networkId) 
            this.networkId = networkId;
        

        @Override
        public String getId() 
            return networkId;
        

        @Override
        public void close() 
            // noop
        

        @Override
        public Statement apply(Statement base, Description description) 
            return base;
        
    


我们还没有在 Testcontainers API 中找到更好的方法来执行此操作。也许在更新的版本中(目前为 1.14.3)

【讨论】:

以上是关于Testcontainers, Docker in Docker with custom network, 容器不属于网络的主要内容,如果未能解决你的问题,请参考以下文章

如何强制 Testcontainers 使用特定的 docker 镜像?

如何使用 Testcontainers 将可执行文件复制到 Docker 容器

如果不存在,Testcontainers 可以为我创建 docker 网络吗?

使用 testcontainers 使用自定义端口运行 ES docker 映像

使用 docker compose 运行 Testcontainers 时的静态容器名称

使用 org.testcontainers 时如何在 docker 容器中包含 postgresql.conf