You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JUnit5所有类测试完成后如何删除Docker中的Test Containers

解决JUnit5测试后Ryuk无法自动删除Testcontainers容器的方案

问题根源

  1. static实例脱离Spring生命周期:你代码里的ElasticsearchTestContainer等容器实例用了static变量,这玩意儿不会跟着Spring测试上下文销毁而回收,就算DisposableBean的destroy方法执行了,static引用还在,容器资源没彻底释放,Ryuk也追踪不到这类“游离”的容器。
  2. withReuse(true)的坑:开了容器复用模式后,Testcontainers默认不让Ryuk删容器——毕竟复用就是要留着给后续测试用,自然不会在单测结束就销毁。
  3. Spring销毁逻辑没触发:虽然实现了DisposableBean,但static实例可能让Spring没法正确触发销毁时机,或者注册单例的方式有问题,导致destroy根本没执行。

具体解决办法

1. 把static容器实例改成非static

把TestContainersSpringContextCustomizerFactory里的static变量全改成普通变量:

// 原来的static变量:
// private static SqlTestContainer prodTestContainer;
// private static S3TestContainer minioContainer;
// private static ElasticsearchTestContainer elasticsearchBean;
// private static RabbitMQTestContainer rabbitMQContainer;

// 改成这样:
private SqlTestContainer prodTestContainer;
private S3TestContainer minioContainer;
private ElasticsearchTestContainer elasticsearchBean;
private RabbitMQTestContainer rabbitMQContainer;

这样每个测试类的Spring上下文会自己创建容器实例,上下文销毁时destroy方法会被正常调用,容器关闭后Ryuk就能识别并清理了。

2. 调整容器复用设置

如果不需要复用容器,直接删掉withReuse(true):

elasticsearchContainer =
    new ElasticsearchContainer(
        DockerImageName.parse("docker-persistent.nexus.jaga.rt.ru/elasticsearch")
            .asCompatibleSubstituteFor("docker.elastic.co/elasticsearch/elasticsearch")
            .withTag("7.17.4"))
        // 删掉这行
        // .withReuse(true)
        .withStartupTimeout(Duration.of(CONTAINER_STARTUP_TIMEOUT_MINUTES, ChronoUnit.MINUTES))
        .withSharedMemorySize(256000000L)
        .withEnv("ES_JAVA_OPTS", "-Xms256m -Xmx256m")
        .withEnv("xpack.security.enabled", "false")
        .withLogConsumer(new Slf4jLogConsumer(log));

要是确实需要复用,又想所有测试跑完后清理,就加个JVM关闭钩子:

@Override
public void afterPropertiesSet() {
    if (null == elasticsearchContainer) {
        elasticsearchContainer = new ElasticsearchContainer(...)
            .withReuse(true)
            .withStartupTimeout(Duration.of(CONTAINER_STARTUP_TIMEOUT_MINUTES, ChronoUnit.MINUTES))
            ...;
    }
    if (!elasticsearchContainer.isRunning()) {
        elasticsearchContainer.start();
        // 加个钩子,JVM退出时关闭容器
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            if (elasticsearchContainer.isRunning()) {
                elasticsearchContainer.stop();
            }
        }));
    }
}

3. 让Spring正确管理容器销毁

在TestContainersSpringContextCustomizerFactory里注册单例时,显式注册DisposableBean,把注释掉的那行代码放开:

if (null == elasticsearchBean) {
    elasticsearchBean = beanFactory.createBean(ElasticsearchTestContainer.class);
    beanFactory.registerSingleton(ElasticsearchTestContainer.class.getName(), elasticsearchBean);
    // 放开这行,确保Spring调用destroy方法
    ((DefaultListableBeanFactory) beanFactory).registerDisposableBean(
        ElasticsearchTestContainer.class.getName(), elasticsearchBean);
}

4. 检查Ryuk是否正常运行

看看测试日志里有没有Ryuk started的记录,要是Ryuk没启动,排查下:

  • Docker能不能正常用,有没有权限创建容器
  • testcontainers.properties里的ryuk.container.image配置对不对,镜像能不能拉下来
  • 测试进程有没有和Docker daemon通信的权限

验证步骤

  1. 跑测试后看Docker容器状态,确认测试结束后容器是不是退出了
  2. 查测试日志,看看有没有容器关闭的输出(比如destroy里的日志)
  3. 看Ryuk的日志,确认它收到了清理容器的指令

内容的提问来源于stack exchange,提问作者DimaMsuVmk22

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 01:00:00