从主机 ip 访问具有随机端口的 docker 容器

Access a docker container with random ports from host ip

场景是这样的:

在 Linux 机器的容器内有一个应用程序 运行ning。此应用程序内部有多个服务 运行ning,每个服务都有自己的端口号。端口号是随机的。

现在我想在 Mac 笔记本电脑上访问此应用程序。这台 Mac 笔记本电脑能够 ping Linux 机器 ip。

我目前正在做的一种方法是将所有端口从 docker 容器映射到 Linux 机器,然后我可以使用 [=24= 从 Mac 访问] 机器IP.

但是这种方法无法随着更多服务的加入而扩展。我想知道是否有人遇到过同样的问题并且有更好的方法来处理这个问题?我们不在这些 Linux 机器上 运行 Kubernetes,因为它们不是服务器。这些机器是为了个人发展。

谢谢!

Docker 没有在容器创建后映射端口或修改现有端口映射的功能。

通常的解决方案是将您的应用程序配置为使用一组端口或一组端口。如果那不可能,那么有几个选项可以解决这个问题。

主机网络

使用docker run --network=host。容器共享主机网络堆栈,因此在主机 IP 上可用。请注意,这使容器可以访问主机网络,因此可能会干扰主机服务或将比正常情况下更多的主机暴露给容器。

可路由,用户定义的网络。

为您的容器创建一个用户定义的网络,并为其分配一个 IP 范围,该网络可以路由到 Docker 主机。然后可以直接寻址在容器中侦听端口的服务。

docker network create \
    --subnet=10.1.3.0/24 \
    -o com.docker.network.bridge.enable_ip_masquerade=false \
    routable

需要将新 docker 网络的路由添加到您的网络网关,以便它们可以通过您的 Docker 主机路由流量。在 Linux 这将是这样的:

ip route add 10.1.3.0/24 via $DOCKER_HOST_IP

那么您应该可以正常传输数据了

docker run --net=routable --rm -it alpine ping $DOCKER_HOST_GATEWAY_IP

Macvlan 网桥

Docker 有一个 macvlan network driver 允许您将主机接口映射到容器中,有点像 VM 中的桥接接口。然后容器可以在与主机相同的网络上有一个接口。

docker network create -d macvlan \
    --subnet=10.1.2.0/24 \
    -o macvlan_mode=bridge \
    -o parent=enp3s0 macvlan
docker run --net=macvlan --ip=10.1.2.128 --rm -it alpine ping 10.1.2.1

请注意,您无法通过此网桥与 docker 主机 IP 地址通信。您可以向主机添加一个 macvlan 子接口,并将主机 IP 地址移动到它上面以允许流量。

一些 VM 和虚拟网络对拥有额外的 MAC 地址很挑剔,他们不知道生成数据。例如,AWS EC2 将拒绝容器流量。

容器iptables

可以在容器命名空间中创建 iptables NAT 规则。要在容器内执行此操作,容器需要 NET_ADMIN 功能。某种形式的脚本可以在启动后查找应用程序端口,并使用 DNAT 规则将流量从静态外部映射端口转发到动态应用程序端口。

docker run -p 5000:5000 --cap-add=NET_ADMIN debian:9
# port=$(ss -lntpH | awk '/"app-bin"/ { split(,a,":"); print a[length(a)]; exit}')
# iptables -t nat -A -p tcp -m tcp --dport 5000 -j DNAT --to-destination 127.0.0.1:$port

如果您不想向容器添加 NET_ADMIN 功能,则可以类似地从 docker 主机为容器网络命名空间添加 iptables 规则。 The host needs a little help to use container name spaces

pid=$(docker inspect -f '{{.State.Pid}}' ${container_id})
mkdir -p /var/run/netns/
ln -sfT /proc/$pid/ns/net /var/run/netns/$container_id
ip netns exec "${container_id}" iptables -t nat -vnL