如何通过Java TestContainers配置Docker容器间通信端口转发
我正在使用Java TestContainers运行两个容器:一个简单的OpenLiberty服务器和MySQL数据库,通过network将它们置于同一网络中,创建代码如下:
@ClassRule public static Network network = Network.newNetwork(); @Container public static MySQLContainer<?> mysqlDb = new MySQLContainer<>() .withDatabaseName("DBNAME") .withUsername("user") .withPassword("pswd") .withExposedPorts(3306) .withCreateContainerCmdModifier(cmd -> cmd.withPortBindings(new PortBinding(Ports.Binding.bindPort(443), new ExposedPort(3306)))) .withNetwork(network) .withNetworkAliases("testDb"); @Container public static ApplicationContainer app = new ApplicationContainer("test") .withNetwork(network) .dependsOn(mysqlDb) .withAppContextRoot("/backend") .withReadinessPath("/") .withHttpPort(9080) .withStartupTimeout(Duration.ofMinutes(5)) .withFileSystemBind("/root/.m2", "/root/.m2") .withEnv(envVariables);
创建完成后,执行docker container ls确认配置正确,也通过docker container inspect 'id'确认它们处于同一网络且别名配置正确:
sudo docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a540e22c9543 test:latest "/usr/local/bin/mvn-…" 25 minutes ago Up 25 minutes 0.0.0.0:32772->9080/tcp, :::32772->9080/tcp sweet_goldstine 73a2ff3460c4 mysql:5.7.34 "docker-entrypoint.s…" 25 minutes ago Up 25 minutes 33060/tcp, 0.0.0.0:443->3306/tcp, :::443->3306/tcp flamboyant_tu 89993b13a2c5 testcontainers/ryuk:0.4.0 "/bin/ryuk" 25 minutes ago Up 25 minutes 0.0.0.0:32770->8080/tcp, :::32770->8080/tcp testcontainers-ryuk-c7ccfb4f-cc3b-42d2-b602-fe7f62472a9d
我原本以为容器间通信使用与主机相同的端口,所以将MySQL容器的内部端口3306绑定到主机端口443,但实际测试发现容器间通信直接使用暴露的内部端口(9080和3306)而非绑定到主机的端口(443和32772):
~$ sudo docker container exec -it 73a2ff3460c4 telnet a540e22c9543 9080 Trying 172.19.0.3... Connected to a540e22c9543. ~$ sudo docker container exec -it 73a2ff3460c4 telnet a540e22c9543 32772 Trying 172.19.0.3... telnet: Unable to connect to remote host: Connection refused ~$ sudo docker container exec -it a540e22c9543 telnet 73a2ff3460c4 443 telnet: Unable to connect to remote host: Connection refused ~$ sudo docker container exec -it a540e22c9543 telnet 73a2ff3460c4 3306 Connected to 73a2ff3460c4
我的需求是:让app容器能通过mysqlDb:443访问MySQL服务,请求自动转发到容器内部的3306端口,能否通过TestContainers实现这个需求?
可以实现,核心是给MySQL容器在内部网络中配置端口映射,让容器本身监听443端口并转发到3306,而不是只做主机端口绑定。具体有两种可行方式:
方式一:通过TestContainers内置配置添加内部端口转发
修改MySQL容器的配置,同时暴露443端口,并添加内部网络的端口映射规则:
@Container public static MySQLContainer<?> mysqlDb = new MySQLContainer<>() .withDatabaseName("DBNAME") .withUsername("user") .withPassword("pswd") .withExposedPorts(3306, 443) // 同时暴露内部的3306和443端口 .withCreateContainerCmdModifier(cmd -> { // 保留原有的主机端口绑定(如果需要从主机访问MySQL可保留,不需要则可删除) cmd.withPortBindings( new PortBinding(Ports.Binding.bindPort(443), new ExposedPort(3306)), // 关键:配置内部网络的端口转发,容器的443端口映射到自身的3306端口 new PortBinding(Ports.Binding.empty(), new ExposedPort(443, 3306)) ); }) .withNetwork(network) .withNetworkAliases("testDb");
这里的new PortBinding(Ports.Binding.empty(), new ExposedPort(443, 3306))是核心,它告诉Docker在容器所在的内部网络中,把443端口的流量转发到容器自身的3306端口。
方式二:自定义MySQL镜像实现端口转发
如果上述方式不生效,可以预先构建一个自定义MySQL镜像,在镜像内添加端口转发逻辑:
FROM mysql:5.7.34 EXPOSE 443 # 安装socat工具实现端口转发 RUN apt-get update && apt-get install -y socat && rm -rf /var/lib/apt/lists/* # 启动时先启动socat转发443到3306,再启动MySQL服务 CMD ["sh", "-c", "socat TCP-LISTEN:443,fork TCP:localhost:3306 & exec docker-entrypoint.sh mysqld"]
构建镜像后,在TestContainers中使用这个自定义镜像:
@Container public static MySQLContainer<?> mysqlDb = new MySQLContainer<>("your-custom-mysql-image:tag") .withDatabaseName("DBNAME") .withUsername("user") .withPassword("pswd") .withNetwork(network) .withNetworkAliases("testDb");
验证配置
修改完成后重新启动容器,进入app容器执行telnet testDb 443,应该能成功连接到MySQL服务。
内容的提问来源于stack exchange,提问作者Some_Person

