VDS上Spring Boot+Testcontainers反复创建数据库容器无法连接
Testcontainers在VDS上无法连接MySQL容器的问题
问题概述
测试时使用Testcontainers启动应用和数据库实例,本地Mac、Windows及Ubuntu电脑上均正常运行,但在VDS(虚拟专用服务器)上执行gradlew test clean命令时,Spring无法连接到启动的MySQL容器,重复创建多个容器后仍失败,最终测试全部报错。
预期行为
- 执行
gradlew test clean - Spring启动应用容器和数据库容器
实际在VDS上的行为
- 执行
gradlew test clean - Spring启动应用容器和数据库容器
- Spring无法连接数据库容器,超时2分钟后启动新的数据库容器
- 测试过程中Spring创建4-5个数据库容器,均无法连接,最终所有测试失败
测试文件的数据库配置
@SpringBootTest @AutoConfigureMockMvc @Testcontainers @ContextConfiguration(initializers = {UserDataControllerTest.Initializer.class}) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class UserDataControllerTest { @Autowired private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; @ClassRule @Container public final static MySQLContainer mysqlContainer = new MySQLContainer("mysql:8.0"); // ... some tests static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { public void initialize(ConfigurableApplicationContext configurableApplicationContext) { TestPropertyValues.of( "spring.datasource.url=" + mysqlContainer.getJdbcUrl(), "spring.datasource.username=" + mysqlContainer.getUsername(), "spring.datasource.password=" + mysqlContainer.getPassword(), "spring.jpa.generate-ddl=true", "spring.jpa.hibernate.ddl-auto=create-drop" ).applyTo(configurableApplicationContext.getEnvironment()); } } }
堆栈报错信息
org.testcontainers.containers.ContainerLaunchException: Container startup failed at app//org.testcontainers.containers.GenericContainer.doStart(GenericContainer.java:336) at app//org.testcontainers.containers.GenericContainer.start(GenericContainer.java:317) at app//org.testcontainers.junit.jupiter.TestcontainersExtension$StoreAdapter.start(TestcontainersExtension.java:242) at app//org.testcontainers.junit.jupiter.TestcontainersExtension$StoreAdapter.access$200(TestcontainersExtension.java:229) at app//org.testcontainers.junit.jupiter.TestcontainersExtension.lambda$null$1(TestcontainersExtension.java:59) at app//org.junit.jupiter.engine.execution.ExtensionValuesStore.lambda$getOrComputeIfAbsent$4(ExtensionValuesStore.java:86) at app//org.junit.jupiter.engine.execution.ExtensionValuesStore$MemoizingSupplier.computeValue(ExtensionValuesStore.java:223) at app//org.junit.jupiter.engine.execution.ExtensionValuesStore$MemoizingSupplier.get(ExtensionValuesStore.java:211) at app//org.junit.jupiter.engine.execution.ExtensionValuesStore$StoredValue.evaluate(ExtensionValuesStore.java:191) at app//org.junit.jupiter.engine.execution.ExtensionValuesStore$StoredValue.access$100(ExtensionValuesStore.java:171) at app//org.junit.jupiter.engine.execution.ExtensionValuesStore.getOrComputeIfAbsent(ExtensionValuesStore.java:89) at app//org.junit.jupiter.engine.execution.NamespaceAwareStore.getOrComputeIfAbsent(NamespaceAwareStore.java:53) at app//org.testcontainers.junit.jupiter.TestcontainersExtension.lambda$beforeAll$2(TestcontainersExtension.java:59) at java.base@17.0.4/java.util.ArrayList.forEach(ArrayList.java:1511) at app//org.testcontainers.junit.jupiter.TestcontainersExtension.beforeAll(TestcontainersExtension.java:59) at app//org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeBeforeAllCallbacks$10(ClassBasedTestDescriptor.java:381) at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at app//org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeBeforeAllCallbacks(ClassBasedTestDescriptor.java:381) at app//org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:205) at app//org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:80) at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:148) at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) at app//org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) at java.base@17.0.4/java.util.ArrayList.forEach(ArrayList.java:1511) at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) at app//org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35) at app//org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) at app//org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75) at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99) at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79) at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75) at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61) at java.base@17.0.4/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base@17.0.4/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base@17.0.4/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base@17.0.4/java.lang.reflect.Method.invoke(Method.java:568) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94) at jdk.proxy1/jdk.proxy1.$Proxy2.stop(Unknown Source) at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193) at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129) at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100) at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60) at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:133) at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71) at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) Caused by: org.rnorth.ducttape.RetryCountExceededException: Retry limit hit with exception at app//org.rnorth.ducttape.unreliables.Unreliables.retryUntilSuccess(Unreliables.java:88) at app//org.testcontainers.containers.GenericContainer.doStart(GenericContainer.java:329) ... 69 more Caused by: org.testcontainers.containers.ContainerLaunchException: Could not create/start container at app//org.testcontainers.containers.GenericContainer.tryStart(GenericContainer.java:525) at app//org.testcontainers.containers.GenericContainer.lambda$doStart$0(GenericContainer.java:331) at app//org.rnorth.ducttape.unreliables.Unreliables.retryUntilSuccess(Unreliables.java:81) ... 70 more Caused by: java.lang.IllegalStateException: Container is started, but cannot be accessed by (JDBC URL: jdbc:mysql://localhost:49156/test), please check container logs at org.testcontainers.containers.JdbcDatabaseContainer.waitUntilContainerStarted(JdbcDatabaseContainer.java:165) at org.testcontainers.containers.GenericContainer.tryStart(GenericContainer.java:468) ... 72 more
补充说明
已多次重装VDS系统(从Debian换成Ubuntu),Docker按官方文档安装,执行测试的用户已加入docker组,即使以root用户执行也无法解决问题。
解决方法
1. 排查MySQL容器日志
执行命令查看容器启动日志,确认具体报错:
docker logs <container-id>
常见问题包括:初始化超时、权限不足、存储卷挂载失败等。
2. 调整Testcontainers等待超时时间
MySQL在资源有限的VDS上启动较慢,增加超时时间:
public final static MySQLContainer mysqlContainer = new MySQLContainer("mysql:8.0") .withStartupTimeout(Duration.ofMinutes(5));
3. 修改MySQL认证插件
MySQL 8.0默认的caching_sha2_password可能导致JDBC连接失败,切换为兼容的mysql_native_password:
public final static MySQLContainer mysqlContainer = new MySQLContainer("mysql:8.0") .withCommand("--default-authentication-plugin=mysql_native_password");
4. 检查VDS网络与防火墙
- 确保VDS防火墙允许本地回环(localhost/127.0.0.1)的端口访问,或者对外开放Testcontainers映射的随机端口。
- 尝试使用host网络模式启动容器,避免端口映射问题:
public final static MySQLContainer mysqlContainer = new MySQLContainer("mysql:8.0") .withNetworkMode("host");
此时JDBC URL需要调整为VDS的内网IP或0.0.0.0,例如:jdbc:mysql://0.0.0.0:3306/test
5. 配置Testcontainers Docker环境
在src/test/resources/testcontainers.properties中添加Docker主机配置,确保Testcontainers能正确识别Docker环境:
testcontainers.docker.host=unix:///var/run/docker.sock
内容的提问来源于stack exchange,提问作者Aleksei .C
相关产品推荐
相关产品推荐

