将主机端口转发到Docker容器

252
有没有可能让Docker容器访问主机开放的端口?具体地,我在主机上运行MongoDB和RabbitMQ,并希望在Docker容器中运行一个进程来监听队列并(可选)写入数据库。
我知道可以通过-p选项将端口从容器转发到主机,并且可以从Docker容器内部连接到外部世界(即互联网),但我不想将RabbitMQ和MongoDB端口从主机暴露给外部世界。
编辑:一些澄清:
Starting Nmap 5.21 ( http://nmap.org ) at 2013-07-22 22:39 CEST
Nmap scan report for localhost (127.0.0.1)
Host is up (0.00027s latency).
PORT     STATE SERVICE
6311/tcp open  unknown

joelkuiper@vps20528 ~ % docker run -i -t base /bin/bash
root@f043b4b235a7:/# apt-get install nmap
root@f043b4b235a7:/# nmap 172.16.42.1 -p 6311 # IP found via docker inspect -> gateway

Starting Nmap 6.00 ( http://nmap.org ) at 2013-07-22 20:43 UTC
Nmap scan report for 172.16.42.1
Host is up (0.000060s latency).
PORT     STATE    SERVICE
6311/tcp filtered unknown
MAC Address: E2:69:9C:11:42:65 (Unknown)

Nmap done: 1 IP address (1 host up) scanned in 13.31 seconds

我需要在容器内实现任何互联网连接,所以我采用了以下方法:My firewall is blocking network connections from the docker container to outside 编辑:最终我选择使用pipework创建自定义桥接,并让服务听取桥接IP。我选择这种方法而不是让MongoDB和RabbitMQ监听docker bridge,因为它更加灵活。

1
我希望有一个解决方案,支持服务监听在127.0.0.1上(而不是0.0.0.0或Docker网络的主机IP),并将端口转发(特定端口)到容器中。我很惊讶Docker支持从容器向主机进行转发,但反之则不行... - Alex Dehnert
11个回答

164
一种简单但相对不安全的方法是使用--net=host选项来运行docker run命令。
此选项使容器使用主机的网络堆栈。然后,您可以通过将"localhost"用作主机名来连接到在主机上运行的服务。
这样更易于配置,因为您不必配置服务以接受来自docker容器的IP地址的连接,并且您不必告诉docker容器要连接到特定的IP地址或主机名,只需一个端口即可。
例如,您可以通过运行以下命令来测试它,该命令假定您的镜像名为my_image,您的镜像包括telnet实用程序,并且您要连接的服务在25端口上:
docker run --rm -i -t --net=host my_image telnet localhost 25

如果您考虑以这种方式进行,请查看此页面上有关安全性的警告:

https://docs.docker.com/articles/networking/

它说:
--net=host -- 告诉Docker跳过将容器放置在单独的网络堆栈中。实际上,这个选择告诉Docker不要容器化容器的网络!虽然容器进程仍将被限制在自己的文件系统、进程列表和资源限制中,但快速执行ip addr命令将向您展示,从网络角度来看,它们“外部”存在于主Docker主机中,并且可以完全访问其网络接口。请注意,这并不允许容器重新配置主机网络堆栈 - 这需要--privileged=true - 但它确实允许容器进程像任何其他root进程一样打开低编号端口。它还允许容器访问本地网络服务,如D-bus。这可能导致容器中的进程能够做出意外的事情,例如重新启动计算机。您应该谨慎使用此选项。

21
对于没有在 Linux 上使用 Docker(例如使用某些虚拟化)的用户,这个方法行不通,因为主机将是包含虚拟机,而不是实际的主机操作系统。 - Sebastian Graf
18
特别是在 MacOS 系统上,这是不可能的(除非使用一些解决方法): https://docs.docker.com/docker-for-mac/networking/#i-want-to-connect-from-a-container-to-a-service-on-the-host - pje
34
在 MacOS 上,--net=host 参数无法使用,以便您的容器进程可以使用 localhost 连接到主机。相反,您可以让容器使用专用于 MacOS 的主机名 docker.for.mac.host.internal 来连接主机。在 docker run 中不需要任何额外的参数即可实现此功能。如果您想使容器平台无关,可以使用 -e 将其作为环境变量传递。这样,您就可以连接到在环境变量中命名的主机,并在 MacOS 上传递 docker.for.mac.host.internal,在 Linux 上传递 localhost - tul
34
жњЂж–°зљ„Macдё»жњєеђЌдёєhost.docker.internalпјЊиЇ¦и§Ѓж–‡жЎЈгЂ‚ - xysun
2
在Windows系统中同样适用的命令: docker run --rm -it --net=host postgres bash,然后执行 psql -h host.docker.internal -U postgres - Leo Cavalcante
不要使用这个。它确实是不安全的。而且在使用 Docker 的命名空间重映射 (userns-remap) 时也不可用。请参见下面的答案:https://dev59.com/d2Mm5IYBdhLWcg3wO9Aq#75120449 - Melroy van den Berg

78

您的Docker主机向所有容器公开适配器。假设您正在使用最新版的Ubuntu,可以运行以下命令:

ip addr

这将为您提供网络适配器列表,其中一个看起来会像这样

3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
link/ether 22:23:6b:28:6b:e0 brd ff:ff:ff:ff:ff:ff
inet 172.17.42.1/16 scope global docker0
inet6 fe80::a402:65ff:fe86:bba6/64 scope link
   valid_lft forever preferred_lft forever

您需要告诉 Rabbit/Mongo 绑定到该 IP(172.17.42.1)。之后,您应该能够从容器内部打开到 172.17.42.1 的连接。


47
容器如何知道要发送请求的IP地址?我可以硬编码该值(这里是172.17.42.1,在我的测试环境中也是如此),但那似乎违背了Docker与任何主机协作的原则! - JP.
3
需要配置才能显示吗?我正在使用docker 1.7.1,只有“lo”和“eth0”。 - mknecht
8
如果主机只监听 127.0.0.1,是否有办法进行此操作? - HansHarhoff
6
你需要告诉 Rabbit/Mongo 绑定到那个 IP 地址 (172.17.42.1)。之后,你就可以在容器内打开到 172.17.42.1 的连接了。如何进行这些操作会更好。 - Novaterata
1
一个值得记住的事情是运行 ip -br -c a。需要一些时间来适应它,但对于日常使用来说非常方便。 - exhuma
显示剩余3条评论

19
如评论中所述,这适用于Mac(可能也适用于Windows/Linux):
想要从容器连接到主机上的服务。主机具有动态IP地址(如果您没有网络访问权限,则没有IP地址)。我们建议您连接到特殊的DNS名称host.docker.internal,它将解析为主机使用的内部IP地址。此方法仅用于开发目的,在Docker Desktop for Mac之外的生产环境中将不起作用。
您还可以使用gateway.docker.internal来访问网关。
引自https://docs.docker.com/docker-for-mac/networking/ 没有使用--net=host,对我有效。

1
这是Mac最简单的解决方案。 - Yuchen
4
这在Windows上可行,但在Linux上不行(这个问题特别是关于Linux的)。在Linux上,如果使用足够新的Docker版本,则有一种解决方法:https://dev59.com/O1YM5IYBdhLWcg3wpRfa#62431165 - jlh
2
好的 Windows 解决方案。正在运作中。 - Justin Godesky

14

您还可以创建一个ssh隧道。

docker-compose.yml:

---

version: '2'

services:
  kibana:
    image: "kibana:4.5.1"
    links:
      - elasticsearch
    volumes:
      - ./config/kibana:/opt/kibana/config:ro

  elasticsearch:
    build:
      context: .
      dockerfile: ./docker/Dockerfile.tunnel
    entrypoint: ssh
    command: "-N elasticsearch -L 0.0.0.0:9200:localhost:9200"

docker/Dockerfile.tunnel:

FROM buildpack-deps:jessie

RUN apt-get update && \
    DEBIAN_FRONTEND=noninteractive \
    apt-get -y install ssh && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

COPY ./config/ssh/id_rsa /root/.ssh/id_rsa
COPY ./config/ssh/config /root/.ssh/config
COPY ./config/ssh/known_hosts /root/.ssh/known_hosts
RUN chmod 600 /root/.ssh/id_rsa && \
    chmod 600 /root/.ssh/config && \
    chown $USER:$USER -R /root/.ssh

配置文件/ssh/config:

# Elasticsearch Server
Host elasticsearch
    HostName jump.host.czerasz.com
    User czerasz
    ForwardAgent yes
    IdentityFile ~/.ssh/id_rsa

这样,elasticsearch就可以与运行服务(Elasticsearch、MongoDB、PostgreSQL)的服务器建立隧道,并通过该服务公开端口9200。


18
你基本上是将私钥放在 Docker 镜像中。机密信息不应该出现在 Docker 镜像中。 - Teoh Han Hui
3
到目前为止,这是唯一可用的明智解决方案。 - helvete
在早期的集成测试或本地开发中,@TeohHanHui认为这个解决方案非常完美。我们可以模拟那些服务,以后在生产环境中也会可用。我想在Cosmos DB模拟器中这样做,因为我们只能获得localhost证书,但是通过端口转发,我们也可以在“远程”主机上使用它。 - minus one

13

简而言之;

仅在本地开发时,请执行以下操作:

  1. 在您的笔记本电脑/台式电脑/个人电脑/ Mac 上启动服务或SSH隧道。
  2. 构建/运行Docker镜像/容器以连接到主机名 host.docker.internal:<hostPort>

注意:还有一个名为gateway.docker.internal,我没有尝试过。

END_TLDR;

例如,如果您在容器中使用此内容:

PGPASSWORD=password psql -h localhost -p 5432 -d mydb -U myuser

改为:

PGPASSWORD=password psql -h host.docker.internal -p 5432 -d mydb -U myuser

这种方法可以神奇地连接到我主机上运行的服务,您无需使用 --net=host-p "hostPort:ContainerPort"-P

背景

详情请见:https://docs.docker.com/docker-for-mac/networking/#use-cases-and-workarounds

我在 Windows 10 上使用 SSH 隧道连接到 AWS RDS Postgres 实例。我只需要将容器中的 localhost:containerPort 更改为 host.docker.internal:hostPort


9

我曾遇到过类似的问题,即从 Docker 容器访问 LDAP 服务器困难。 我为容器设置了一个固定的 IP,并添加了防火墙规则。

docker-compose.yml:

version: '2'
services:
  containerName:
    image: dockerImageName:latest
    extra_hosts:
      - "dockerhost:192.168.50.1"
    networks:
      my_net:
        ipv4_address: 192.168.50.2
networks:
  my_net:
    ipam:
      config:
      - subnet: 192.168.50.0/24

iptables规则:

iptables -A INPUT -j ACCEPT -p tcp -s 192.168.50.2 -d $192.168.50.1 --dport portnumberOnHost

在容器内访问dockerhost:portnumberOnHost


2

我不确定我是否回答了正确的问题,但如果您需要允许容器内部通过ssh隧道访问远程服务,可以这样做:

使用ssh连接并创建一个隧道,如下所示(注意开头的0.0.0.0),假设远程主机上的服务可在端口8081上访问:

ssh ubuntu@remoteIp -L 0.0.0.0:8080:localhost:8081

这将允许任何有权访问您的计算机的人连接到端口8080,从而访问已连接服务器上的端口8081。

然后,在容器内部使用“host.docker.internal”,例如:

curl host.docker.internal:8081

你能否给出更完整的回答?你传递 -L 给了什么?这似乎不是 docker -- 我认为这是在详细说明各种基于 ssh 的答案(例如 https://dev59.com/d2Mm5IYBdhLWcg3wO9Aq#68277488)? - Alex Dehnert
希望修改有所帮助。 - amcastror

2
如果MongoDB和RabbitMQ正在主机上运行,则端口应该已经暴露,因为它不在Docker内部。
您不需要使用-p选项将容器中的端口暴露到主机。默认情况下,所有端口都被暴露。 -p选项允许您将容器中的端口暴露到主机外部。
所以,我猜您根本不需要-p,它应该可以正常工作 :)

1
我知道这一点,但似乎我缺少一些信息:请查看最近的编辑,因为我无法访问主机上的端口。 - JoelKuiper
2
你需要设置RabbitMQ和MongoDB,使其不仅在主网络接口上监听,还要在桥接上进行监听。 - creack
14
你想知道如何让RabbitMQ和MongoDB监听桥接器吗? - Ryan Walls
4
尝试使用命令 docker inspect network bridge。查看 IPAM -> Config -> Gateway。在我的情况下,它是 172.17.0.1 - Yolo Voe

2

host.docker.internalgateway.docker.internal通常非常适用于Docker Mac桌面容器,但是当涉及到超级吝啬的kafka所设置的接受侦听器时,这也会失败。

我通过运行socat将容器内的9092端口转发到主机来绕过此问题。

apt-get install -y socat && socat tcp-l:9092,fork tcp:host.docker.internal:9092 &

基本上我有一个运行kafka的容器,还有另一个需要连接到kafka的应用程序容器。将应用程序容器kafka代理设置为["host.docker.internal:9092"]是不起作用的。所以我将kafka代理设置为["localhost:9092"],并使用socat将端口转发到主机,该主机与kafka容器具有端口转发。

在经过多次尝试后,我得出了这个解决方案。希望这能帮助到某些人!


1
现在在所有平台下更容易的方法是使用host.docker.internal。让我们首先从Docker运行命令开始:
docker run --add-host=host.docker.internal:host-gateway [....]

如果使用Docker Compose,请将以下内容添加到您的服务中:

extra_hosts:
  - "host.docker.internal:host-gateway"

这样一个 Docker Compose 文件的完整示例应该如下所示:

version: "3"
services:
  your_service:
    image: username/docker_image_name
    restart: always
    networks:
      - your_bridge_network
    volumes:
      - /home/user/test.json:/app/test.json
    ports:
      - "8080:80"
    extra_hosts:
      - "host.docker.internal:host-gateway"

networks:
  your_bridge_network:

这只是一个例子。但是如果这个Docker镜像将在80端口启动服务,那么它将在主机的8080端口上可用。

对于您的用例更重要的是; 如果Docker容器想要使用来自您的主机系统的服务,现在可以使用特殊的host.docker.internal名称。该名称将自动解析为内部Docker IP地址(docker0接口的IP地址)。

无论如何,假设...您还在主机上运行Web服务(端口80)。您现在应该能够在Docker容器中访问该服务。尝试一下:nc -vz host.docker.internal 80

所有这些都不需要使用network_mode: "host"


在什么情况下,你认为 '--add-host=host.docker.internal:host-gateway' 是必要的? - undefined
如果你想从Docker容器中访问运行在主机上的本地服务,可能只有在使用root Docker设置(非root可能会有所不同)的情况下才能实现。无论如何,假设你的Debian服务器上正在运行一个Postfix服务,并且你希望你的Docker容器能够连接到这个Postfix服务器(而不是为Postfix设置一个单独的容器),这就是方法。 - undefined
我知道,但在我尝试的几个镜像中,它们自动解析了 host.docker.internal,而不需要我传递该参数。 - undefined

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