Docker容器内通过docker.sock运行Spring集成测试连接MySQLContainer失败
解决Docker容器内运行Testcontainers连接Ryuk失败的问题
问题背景
本地执行gradle test时,使用Testcontainers的MySQLContainer运行Spring集成测试完全正常,但在Docker容器内执行测试时抛出异常:
Caused by: java.lang.IllegalStateException: Could not connect to Ryuk at 172.17.0.1:49157
已挂载/var/run/docker.sock卷,尝试过设置TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal、创建网络关联容器等操作,问题仍未解决。
相关配置
build.gradle
plugins { id 'java' id 'org.springframework.boot' version '3.0.5' id 'io.spring.dependency-management' version '1.1.0' } group = 'org.autovill' version = '0.0.1-SNAPSHOT' sourceCompatibility = '17' configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation group: 'org.springframework.boot', name: 'spring-boot-starter-webflux', version: '3.0.5' implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-r2dbc', version: '3.0.5' implementation 'org.springframework.boot:spring-boot-starter-security' implementation group: 'org.flywaydb', name: 'flyway-core', version: '9.16.3' implementation group: 'org.flywaydb', name: 'flyway-mysql', version: '9.16.3' implementation group: 'io.asyncer', name: 'r2dbc-mysql', version: '1.0.0' implementation group: 'com.mysql', name: 'mysql-connector-j', version: '8.0.32' implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5' runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5' runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: '3.0.5' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' testImplementation group: 'io.projectreactor', name: 'reactor-test', version: '3.5.5' testImplementation "org.testcontainers:testcontainers:1.18.0" implementation platform('org.testcontainers:testcontainers-bom:1.18.0') //import bom testImplementation('org.testcontainers:mysql') //no version specified } tasks.named('test') { useJUnitPlatform() } test { systemProperty 'spring.profiles.active', 'test' }
Dockerfile
FROM gradle:7.6.1-jdk17-jammy as base WORKDIR /app COPY ./ ./ RUN gradle dependencies FROM base as test CMD ["gradle", "test"]
测试代码
@SpringBootTest class AutovillApplicationStarterTest { @ClassRule public static MySQLContainer mysql = new MySQLContainer<>("mysql:8.0") .withExposedPorts(3306, 3306) .withEnv("MYSQL_ROOT_HOST", "%") .withUsername("azamat") .withPassword("123456") .withDatabaseName("autovill") .withEnv("MYSQL_HOST", "%"); static { mysql.start(); System.out.println("MYSQL_HOST: " + mysql.getHost()); System.out.println("MYSQL JDBC URL: " + mysql.getJdbcUrl()); System.setProperty("spring.r2dbc.url", mysql.getJdbcUrl().replace("jdbc:", "r2dbc:")); System.setProperty("spring.r2dbc.username", mysql.getUsername()); System.setProperty("spring.r2dbc.password", mysql.getPassword()); System.setProperty("spring.flyway.url", mysql.getJdbcUrl()); System.setProperty("spring.flyway.username", mysql.getUsername()); System.setProperty("spring.flyway.password", mysql.getPassword()); } @Test void contextLoads() { } }
构建与运行命令
docker build -t spring-test --target=test .
docker run \ --volume /var/run/docker.sock:/var/run/docker.sock \ -v $PWD:$PWD \ -w $PWD \ spring-test
解决方案
1. 固定Ryuk端口并映射
Ryuk默认使用随机端口,容器内测试进程无法直接访问宿主机的随机端口。可以强制指定固定端口,并在运行测试容器时映射该端口:
docker run \ --volume /var/run/docker.sock:/var/run/docker.sock \ -v $PWD:$PWD \ -w $PWD \ -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal \ -e TESTCONTAINERS_RYUK_PORT=49157 \ -p 49157:49157 \ spring-test
2. 临时禁用Ryuk
如果不需要Ryuk的资源自动清理功能,可以直接禁用它绕过连接问题:
docker run \ --volume /var/run/docker.sock:/var/run/docker.sock \ -v $PWD:$PWD \ -w $PWD \ -e TESTCONTAINERS_RYUK_DISABLED=true \ spring-test
3. 修正测试代码与版本
- 移除测试代码中手动调用的
mysql.start():@ClassRule会自动管理容器的启动与停止,手动调用可能导致冲突 - 升级Testcontainers版本到1.19.x及以上,新版本对Docker-in-Docker场景的支持更完善
修改后的测试代码片段:
@SpringBootTest class AutovillApplicationStarterTest { @ClassRule public static MySQLContainer mysql = new MySQLContainer<>("mysql:8.0") .withEnv("MYSQL_ROOT_HOST", "%") .withUsername("azamat") .withPassword("123456") .withDatabaseName("autovill"); static { // 移除mysql.start() System.out.println("MYSQL_HOST: " + mysql.getHost()); System.out.println("MYSQL JDBC URL: " + mysql.getJdbcUrl()); System.setProperty("spring.r2dbc.url", mysql.getJdbcUrl().replace("jdbc:", "r2dbc:")); System.setProperty("spring.r2dbc.username", mysql.getUsername()); System.setProperty("spring.r2dbc.password", mysql.getPassword()); System.setProperty("spring.flyway.url", mysql.getJdbcUrl()); System.setProperty("spring.flyway.username", mysql.getUsername()); System.setProperty("spring.flyway.password", mysql.getPassword()); } @Test void contextLoads() { } }
4. 使用宿主机网络模式
让测试容器直接使用宿主机网络,这样可以直接访问宿主机上的Ryuk容器:
docker run \ --volume /var/run/docker.sock:/var/run/docker.sock \ -v $PWD:$PWD \ -w $PWD \ --network host \ spring-test
注意:该模式在Windows和Mac的Docker Desktop下可能需要额外配置,且会失去容器网络隔离性。
内容的提问来源于stack exchange,提问作者Azamatik
相关产品推荐
相关产品推荐

