Java Testcontainers - 无法连接到暴露的端口

5

我使用javax.mail实现了一个POP3服务器和客户端,只是为了尝试使用Docker进行集成测试。因此,我基于openjdk:8-jre图像创建了两个Docker镜像,并将我的jars文件复制到它们中并启动了它们。根据我的配置(见下文),一切都正常。它们彼此通信。

但是,由于我想要进行多个集成测试,为每个测试构建一个镜像并启动它们将变得很繁琐。而且我不知道如何自动化结果。

但是,我偶然发现了TestContainers,似乎这会在实现这些测试时提供很大的帮助。

因此,我开始将这些测试移植到TestContainers中,使用我的POP3服务器映像作为GenericContainer,并在JUnit测试方法中启动我的POP3客户端类。我公开了端口24999,我的POP3服务器正在监听该端口。但是当我尝试连接服务器时,我遇到了以下错误:

com.sun.mail.util.MailConnectException: Couldn't connect to host, port: localhost, 32782; timeout -1;
  nested exception is:
    java.net.ConnectException: Connection refused
...

我可能在TestContainers中漏掉了一些设置。您能帮忙确认一下吗?

这是我正在使用的代码:

public class DockerPop3AutocryptKeyProvidingAndReceivingTest {
    @Test
    public void test() throws InterruptedException {
        GenericContainer container = new GenericContainer<>("immerfroehlich/emailfilter:latest")
                .withExposedPorts(24999);
        
        container.start();
        
        String host = container.getContainerIpAddress();
        String port = container.getFirstMappedPort().toString();

        //The following is simplified, but copied from the working jar used in the Docker Client image/container
        MyPOP3Client client = new MyPOP3Client(host, port);
        client.connect();
        
        container.stop();
    }
}

这是我创建Docker镜像的方法:
FROM openjdk:8-jre

ADD build/distributions/MyPOP3Server.tar . #This is where I have packed all the needed files to. It gets unpacked by Docker.
#EXPOSE 24999 #I tried both with and without this expose
WORKDIR /MyPOP3Server/bin
ENTRYPOINT ["sh","MyPOP3Server"] #Executes the shell script which runs java with my jar

这是运行在服务器Jar内的代码的简化版本:
MyPOP3Server server = new MyPOP3Server();
server.listenToPort(24999);

请告诉我错在哪里,我错过了什么?这儿有什么问题吗?
感谢您的关注和祝愿。

你可以在容器启动后设置一个断点并检查它,以查看映射到哪个端口。如果你像这样做:port = container.getMappedPort(24999) - MC Ninjava
好的,我已经更改为port = container.getMappedPort(24999),但仍然无法工作。我进行了调试,但不确定要查看哪里。container.exposedPorts是[24999];container.portBindings是[]。 - NoradX
5个回答

5

其他答案中已经有了一些好的建议,我会添加一些其他的提示:

如其他人建议:

  • 一定要添加LogConsumer,这样您就可以看到容器的日志输出 - 也许现在或将来会有一些有用的内容出现。 这总是很有用的。

  • 在容器启动后,在您启动客户端之前设置断点。

此外,我希望以下几点能够起到作用。当在断点处暂停时:

  • 在终端中运行docker ps -a
  • 首先,检查您的容器是否正在运行且没有退出。 如果已退出,请从终端查看容器的日志。
  • 其次,在docker ps输出中检查端口映射。 您应该看到类似于0.0.0.0:32768->24999/tcp(第一个端口号是随机的,不过)。
  • 在您的IDE中评估container.getFirstMappedPort()并检查您收到的端口号是否与随机公开端口相同。 除非您的本地计算机上拥有非常不寻常的Docker安装,否则应该可以在此端口处访问此容器的localhost:
  • 如果您走到了这一步,那么容器或客户端代码很可能出了问题。 您可以尝试连接不同的客户端到正在运行的容器 - 即使像nc这样的工具也能帮助您快速排除 POP3 客户端问题。

另一个尝试的方法是手动运行容器,以减少间接性的操作。 您提供的Testcontainers代码片段相当于:

docker run -p 24999 immerfroehlich/emailfilter:latest

你可能会发现这有助于将问题空间划分为更小的部分。

1
谢谢您的建议。它对于找到正确的解决方案非常有帮助。我已将其发布为新答案。谢谢。 - NoradX
这种方法对我也很有帮助。我退后一步,注释掉其他容器,并阅读了每个使用的testcontainers方法。问题是,我映射到了错误的容器端口(.withExposedPorts())。 - Oskar Westmeijer

3
感谢您的所有帮助。这引导我走向解决方案。问题在于缺少WaitStrategy和端口映射问题的组合。
这是我所做的: 1)在MyPop3Server.listenToPort(String port)方法中,我添加了一个System.out.println语句:
public class MyPop3Server {
  public void listenToPort(String port) {
     //simplified: do initialization and listenToPort
     System.out.println("Awaiting Connection...");
  }
}

在我的测试中,我添加了一个 LogMessageWaitStrategy,它监听 "Awaiting Connection"。
GenericContainer container = new GenericContainer<>("immerfroehlich/emailfilter:latest")
   .waitingFor(Wait.forLogMessage("Awaiting Connection.*", 1))
   .withExposedPorts(24999);

2) 我从 container.getFirstMappedPort() 切换到

container.getMappedPort(24999);

这是完整的已更改并运行的测试代码:

public class DockerPop3AutocryptKeyProvidingAndReceivingTest {
    @Test
    public void test() throws InterruptedException {
        GenericContainer container = new GenericContainer<>("immerfroehlich/emailfilter:latest")
                .waitingFor(Wait.forLogMessage("Awaiting Connection.*", 1))
                .withExposedPorts(24999);

        container.start();

        String host = container.getContainerIpAddress();
        String port = container.getMappedPort(24999).toString();

        //The following is simplified, but copied from the working jar used in the Docker Client image/container
        MyPOP3Client client = new MyPOP3Client(host, port);
        client.connect();

        container.stop();
    }
}

谢谢大家。

3
尝试添加HTTP检查。
 new GenericContainer<>("immerfroehlich/emailfilter:latest")
 .withExposedPorts(24999)
 .waitingFor(new HttpWaitStrategy().forPort(24999)
 .withStartupTimeout(Duration.ofMinutes(5)));

有可能你的容器已经启动,但在服务器初始化之前你尝试连接。

另外,注册日志附加程序以查看容器内部服务器的情况。

 .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger(
              DockerPop3AutocryptKeyProvidingAndReceivingTest.class)))

我绝对支持使用LogConsumer,但考虑到这是一个POP3服务器,我怀疑HttpWaitStrategy不会有帮助,很遗憾。 - Richard North
1
嗨,我使用了LogMessageWaitStrategy而不是HttpWaitStrategy,并在服务器中添加了System.out.println。结合switch container.getMappedPort(24999),这已经是解决方案了。谢谢。 - NoradX

0

既然您的邮件服务器和客户端都在容器中运行,我认为您应该连接到端口24999,而不是映射的端口


不,这是我最初所做的。我只是想提一下,以免有人问你的服务器是否工作正常。目前,服务器正在容器内运行,客户端在 JUnit 测试中本地运行。 - NoradX

0
尝试使用container.getMappedPort(24999)代替getFirstMappedPort。很可能你的Docker镜像暴露了多个端口。

正如我已经向MC Ninjava提到的那样,我尝试过了,但它并没有起作用。 - NoradX
如果在客户端连接之前设置断点,您能否使用telnet或其他邮件客户端连接到映射的端口? - ygun
不,这也没有起作用。但后来我在命令行上使用Docker启动了容器,但仍然没有成功。然后我阅读了Docker手册中的网络章节,并得到了一个想法。所以两个容器可以互相通信,但主机无法与容器通信。在手册中,它说桥接网络是默认网络。它允许两个容器互相通信(可能还有更多)。所以我学习了一下主机网络。基本上,它在Linux上与主机处于同一个网络中... - NoradX
我直接使用“docker run -i -t --network host immerfroehlich/emailfilter:latest”启动了容器,然后用JUnit Test连接上了容器。所以现在我唯一需要了解的是如何在TestContainers中设置主机网络。我尝试用“Network network = Network.builder().driver("host").build(); container.withNetwork(network)”来实现,但是我遇到了“ContainerLaunchException:container startup failed”的问题,并且下方提示“DockerException:仅允许一个主机网络实例。” 那么有人知道如何在TestContainers中实现吗? - NoradX
实际上,TestContainers是设计为在桥接模式下工作的。它使用随机映射端口来确保您的Docker化测试可以同时运行。您可以尝试的另一件事是插入一些等待条件(例如new GenericContainer("blah").waitingFor(Wait.forHttp("/some/url"))),甚至使用超时。 - ygun
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接