GitLab CI DinD环境下Kafka TestContainer advertised.listeners配置问题
问题背景
本地使用Kafka TestContainer(v1.19.8)编写的Spring Boot集成测试运行正常,但在GitLab CI流水线中执行时抛出以下错误:
Exception in thread "main" java.lang.IllegalArgumentException: requirement failed: advertised.listeners cannot use the nonroutable meta-address 0.0.0.0. Use a routable IP address.
GitLab CI Job配置如下:
# DinD service is required for Testcontainers services: - name: docker:dind # explicitly disable tls to avoid docker startup interruption command: ["--tls=false"] variables: # Instruct Testcontainers to use the daemon of DinD, use port 2375 for non-tls connections. DOCKER_HOST: "tcp://docker:2375" # Instruct Docker not to start over TLS. DOCKER_TLS_CERTDIR: "" # Improve performance with overlayfs. DOCKER_DRIVER: overlay2 test: image: gradle:5.0 stage: test script: ./gradlew test
当前未给测试容器设置KAFKA_ADVERTISED_LISTENERS,因为提前不知道映射端口,且容器启动后无法通过KafkaContainer的addEnv()方法重新定义该值。
疑问
- 是否可修改GitLab CI Job配置,让Kafka TestContainer不再使用0.0.0.0作为
KAFKA_ADVERTISED_LISTENERS? - 是否有基于
KafkaContainer的解决方案,还是必须改用已弃用的FixedHostPortGenericContainer?
解决方案
1. GitLab CI配置层面的调整
可以通过添加环境变量让Testcontainers自动识别正确的路由地址,无需修改代码。在CI的variables块中新增以下配置:
variables: # 保留原有变量... TESTCONTAINERS_HOST_OVERRIDE: docker
这个变量会告诉Testcontainers将容器的访问主机地址替换为DinD服务的别名docker,Kafka容器会自动把KAFKA_ADVERTISED_LISTENERS设置为可路由的docker主机地址,而非0.0.0.0。
2. 基于KafkaContainer的代码解决方案
不需要改用已弃用的FixedHostPortGenericContainer,有多种可靠的代码层面方案:
方案一:容器创建阶段动态配置
通过withCreateContainerCmdModifier在容器创建时注入正确的环境变量,利用DinD网络的主机别名:
KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.4.0")) .withNetworkAliases("kafka") .withCreateContainerCmdModifier(cmd -> { cmd.withEnv("KAFKA_ADVERTISED_LISTENERS", "PLAINTEXT://kafka:9092"); cmd.withEnv("KAFKA_LISTENERS", "PLAINTEXT://0.0.0.0:9092"); }); kafka.start(); // 测试代码中直接使用容器提供的bootstrap地址 String bootstrapServers = kafka.getBootstrapServers();
这种方式下,Testcontainers会自动将kafka这个网络别名转换为CI环境中可访问的地址,无需手动处理端口映射。
方案二:启动后动态修改配置
容器启动后,利用Testcontainers提供的方法获取容器IP和映射端口,执行命令修改Kafka配置并重启服务:
KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.4.0")); kafka.start(); // 获取容器IP和映射端口 String containerIp = kafka.getContainerIpAddress(); Integer mappedPort = kafka.getMappedPort(9092); // 修改server.properties配置 kafka.execInContainer( "sh", "-c", String.format("echo 'advertised.listeners=PLAINTEXT://%s:9092,PLAINTEXT_HOST://docker:%d' >> /etc/kafka/server.properties", containerIp, mappedPort) ); // 重启Kafka服务生效 kafka.execInContainer("sh", "-c", "kafka-server-stop.sh && kafka-server-start.sh -daemon /etc/kafka/server.properties");
方案三:使用镜像内置占位符
部分官方Kafka镜像支持容器主机名占位符,可直接在环境变量中使用:
KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.4.0")) .addEnv("KAFKA_ADVERTISED_LISTENERS", "PLAINTEXT://${HOSTNAME}:9092"); kafka.start();
${HOSTNAME}会自动替换为容器的主机名,在DinD环境中可通过网络直接访问。
总结
优先推荐调整GitLab CI的TESTCONTAINERS_HOST_OVERRIDE变量,无需修改代码即可解决问题。如果需要代码层面的控制,使用上述基于KafkaContainer的方案即可,完全不需要依赖已弃用的FixedHostPortGenericContainer。
内容的提问来源于stack exchange,提问作者Suren Aznauryan

