Docker 当容器在本地主机接口上侦听时端口转发不工作

Docker port forwarding not working when container is listening on localhost interface

我遇到了无法解决的端口转发问题。我在 VM 中 运行 Linux,我正在使用该 VM 中的 docker。当我尝试从 docker 设置端口转发时,例如:

ports:
   - "3080:3080"

它仅在容器中的应用程序 运行 正在侦听 0.0.0.0:3080 的情况下有效。问题是,我 docker 正在使用的大多数应用程序都在监听本地主机。除 0.0.0.0 之外的任何接口都会导致端口转发不起作用。您知道为什么会发生这种情况或如何解决这个问题吗?

我是 运行:

Docker version 17.05.0-ce, build 89658be

docker-compose version 1.17.1, build unknown

谢谢

P.S。我找到了一个临时解决方法。我为容器指定网络模式 "host",强制容器使用主机 OS 网络,但这种方法在 MacOS 上不起作用。

我建议在 docker-compose 文件中使用网络模式桥接

driver: bridge

"How to solve this"是设置应用监听0.0.0.0。对于简短的脚本,您经常会在主函数中看到这个硬编码(或者甚至被库隐含),但它是 "real" 服务器的一个非常常见的选项,以及您可能通过命令公开的那种东西 -行选项或环境变量。

就"why"而言:在Docker内,每个容器运行在一个独立的网络名称space中。例如,如果您尝试:

$ docker run --rm busybox ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:02  
          inet addr:172.17.0.2  Bcast:172.17.255.255  Mask:255.255.0.0

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0

这里重要的是 每个容器都有自己的 localhost,不同于每个其他容器的 localhost 和主机的 localhost .因此,如果您将容器设置为 bind(2) 到 127.0.0.1,它将只接受来自 127.0.0.1 同一容器 内的连接。

同时,Docker 运行 为您提供网络地址转换 (NAT) 层。如果您显示 运行 docker run -p 3080:3080,然后(从主机)运行 iptables -vL,您会发现其中一件事是端口转发规则将入站请求路由到主机上的端口 3080,容器 IP 地址(在我的示例中为 172.17.0.2),通过设备 docker0,路由到端口 3080。在容器的网络地址 space 中,它将接收人工容器本地 eth0 接口上的入站连接;如果您要在套接字上调用 getsockname(2),您会看到 172.17.0.2 地址。 您的进程必须接受容器本地 eth0 接口或所有接口 上的连接才能从容器外部访问。

所有这些都是实施细节;您几乎永远不需要真正担心其中的任何一个。例如,由于 172.17.0.0/16 地址是由 Docker 人为管理的,您无法从主机外访问它们,并且它们会在不同的 docker run 之间变化;对于容器之间的通信(在同一个 Docker-内部网络上),您确实间接使用它们,但通常是通过 Docker 提供的 DNS 服务(因此连接到 other-container-name 作为主机名,这将恰好解析为 172.17.0.3)。如果您查看一些特别涉及的服务器启动序列的详细输出,您会看到它们遍历接口并显式绑定到所有接口;但对于大多数应用程序,Docker space 中的正确答案只是始终绑定到 0.0.0.0.

理想情况下,您可以将容器内的服务配置为侦听 0.0.0.0:3080 而不是本地主机 127.0.0.1:3080

但是,如果像我最近一样,您正在使用硬编码的第三方服务来侦听本地主机端口(例如 127.0.0.1:3080),您可以使用 socat 实用程序在容器内部将容器的外部端口转发到内部服务。 例如,要使侦听本地主机端口 127.0.0.1:3080 的服务可从容器外部的端口 3081:

访问
apt-get update && apt-get install -y socat

然后

socat TCP-LISTEN:3081,fork TCP:127.0.0.1:3080

然后您可以通过docker端口转发从容器外部访问该服务,即docker run -p 3080:3081(或docker-compose.yml中的ports: ["3080:3081"]) , 用 3080 替换任何方便的端口号。