如何从 docker 容器访问主机端口

How to access host port from docker container

我有一个 docker 容器 运行ning jenkins。作为构建过程的一部分,我需要访问位于主机本地 运行 的 Web 服务器。有没有办法将主机 Web 服务器(可以在端口上配置为 运行)暴露给 jenkins 容器?

我在 Linux 机器上 运行ning docker。

更新:

除了下面的@larsks 回答之外,要从主机获取主机 IP 的 IP 地址,我执行以下操作:

ip addr show docker0 | grep -Po 'inet \K[\d.]+'

当运行在Linux本地Docker时,您可以使用docker0接口的IP地址访问主机服务。从容器内部,这将是您的默认路线。

例如,在我的系统上:

$ ip addr show docker0
7: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::f4d2:49ff:fedd:28a0/64 scope link 
       valid_lft forever preferred_lft forever

并在容器内:

# ip route show
default via 172.17.0.1 dev eth0 
172.17.0.0/16 dev eth0  src 172.17.0.4 

使用简单的 shell 提取此 IP 地址相当容易 脚本:

#!/bin/sh

hostip=$(ip route show | awk '/default/ {print }')
echo $hostip

您可能需要修改主机上的 iptables 规则以允许 来自 Docker 个容器的连接。这样的事情会做 技巧:

# iptables -A INPUT -i docker0 -j ACCEPT

这将允许从 Docker 访问主机上的任何端口 容器。注意:

  • iptables 规则是有序的,这条规则可能会也可能不会 正确的事情取决于它之前的其他规则。

  • 您将只能访问满足 (a) 监听 INADDR_ANY(又名 0.0.0.0)或明确 在 docker0 接口上监听。


如果您在 MacOS or Windows 18.03+ 上使用 Docker,您可以连接到魔术主机名 host.docker.internal


最后,在 Linux 下,您可以通过设置 --net=host 运行 主机网络命名空间中的容器;在这种情况下,主机上的 localhost 与容器内的 localhost 相同,因此容器化服务将像非容器化服务一样运行,无需任何额外配置即可访问。

当您“已经”创建了两个 docker 图像并且您想要放置两个容器以相互通信时。

为此,您可以方便地 运行 每个容器都有自己的 --name 并使用 --link 标志来启用它们之间的通信。不过在 docker 构建过程中你不会得到这个。

当你处于像我这样的场景中,并且是你的

docker build -t "centos7/someApp" someApp/ 

当您尝试时,它会中断

curl http://172.17.0.1:localPort/fileIWouldLikeToDownload.tar.gz > dump.tar.gz

并且您卡在“curl/wget”没有返回“到主机的路由”。

原因是 docker 设置的安全性,默认情况下禁止容器与主机或主机上的其他容器 运行 通信。 这让我非常惊讶,我必须说,你会期望 docker 机器的回声系统 运行 在本地机器上 ning 可以完美地相互访问而没有太多障碍。

对此的解释在以下文档中有详细描述。

http://www.dedoimedo.com/computers/docker-networking.html

提供了两个快速解决方法,可通过降低网络安全性帮助您开始行动。

The simplest alternative is just to turn the firewall off - or allow all. This means running the necessary command, which could be systemctl stop firewalld, iptables -F or equivalent.

对于所有平台

Docker v 20.10 及更高版本(自 2020 年 12 月 14 日起)

在 Linux 上,将 --add-host=host.docker.internal:host-gateway 添加到您的 Docker 命令以启用此功能。 (Docker Compose 配置见下文。)

使用您的内部 IP 地址或连接到特殊的 DNS 名称 host.docker.internal,它将解析为主机使用的内部 IP 地址。

To enable this in Docker Compose on Linux, add the following lines to the container definition:

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

对于 macOS 和 Windows

Docker v 18.03 及以上(自 2018 年 3 月 21 日起)

使用您的内部 IP 地址或连接到特殊的 DNS 名称 host.docker.internal,它将解析为主机使用的内部 IP 地址。

Linux 支持待定 https://github.com/docker/for-linux/issues/264

MacOS 与早期版本的 Docker

Docker 对于 Mac v 17.12 到 v 18.02

同上,但使用 docker.for.mac.host.internal 代替。

Docker 对于 Mac v 17.06 到 v 17.11

同上,但使用 docker.for.mac.localhost

Docker Mac 17.05 及以下

要从 docker 容器访问主机,您必须将 IP 别名附加到您的网络接口。您可以绑定任何您想要的 IP,只要确保您没有将它用于任何其他用途即可。

sudo ifconfig lo0 alias 123.123.123.123/24

然后确保您的服务器正在侦听上述 IP 或 0.0.0.0。如果它正在侦听本地主机 127.0.0.1,它将不会接受连接。

然后把你的docker容器指向这个IP就可以访问主机了!

要进行测试,您可以在容器内 运行 类似 curl -X GET 123.123.123.123:3000 的东西。

别名将在每次重新启动时重置,因此如有必要,请创建启动脚本。

解决方案和更多文档在这里:https://docs.docker.com/docker-for-mac/networking/#use-cases-and-workarounds

在您的 docker run 命令中使用 --net="host",然后 docker 容器中的 localhost 将指向您的 docker 主机。

目前在 Mac 和 Windows 上执行此操作的最简单方法是使用主机 host.docker.internal, that resolves to host machine's IP address. Unfortunately it does not work on linux yet(截至 2018 年 4 月)。

使用 docker-compose 的解决方案: 要访问基于主机的服务,您可以使用 network_mode 参数 https://docs.docker.com/compose/compose-file/#network_mode

version: '3'
services:
  jenkins:
    network_mode: host

编辑 2020-04-27:建议仅在本地开发环境中使用。

编辑 2021-09-21:IHaveHandedInMyResignation 写道它不适用于 Mac 和 Windows。仅 Linux

支持该选项

我创建了一个 docker 容器来完成 https://github.com/qoomon/docker-host

然后您可以简单地使用容器名称 dns 访问主机系统,例如 curl http://dockerhost:9200

我探索了各种解决方案,我发现这是最简单的解决方案:

  1. 为网桥网关 IP 定义一个静态 IP 地址。
  2. 将网关 IP 添加为 extra_hosts 指令中的额外条目。

唯一的缺点是如果您有多个网络或项目这样做,您必须确保它们的 IP 地址范围不冲突。

这是一个 Docker 撰写示例:

version: '2.3'

services:
  redis:
    image: "redis"
    extra_hosts:
      - "dockerhost:172.20.0.1"

networks:
  default:
    ipam:
      driver: default
      config:
      - subnet: 172.20.0.0/16
        gateway: 172.20.0.1

然后您可以使用主机名“dockerhost”从容器内部访问主机上的端口。

您可以通过两种方式访问​​主机中 运行ning 的本地网络服务器。

  1. 使用 public IP 的方法 1

    使用主机 public IP 地址访问 Jenkins docker 容器中的网络服务器。

  2. 使用主机网络的方法 2

    使用“--net host”在主机的网络堆栈上添加 Jenkins docker 容器。部署在主机堆栈上的容器可以完全访问主机接口。您可以使用主机的私有 IP 地址访问 docker 容器中的本地网络服务器。

NETWORK ID          NAME                      DRIVER              SCOPE
b3554ea51ca3        bridge                    bridge              local
2f0d6d6fdd88        host                      host                local
b9c2a4bc23b2        none                      null                local

使用主机网络启动容器 Eg: docker run --net host -it ubuntu 和 运行 ifconfig 列出可从 docker 容器访问的所有可用网络 IP 地址。

例如:我在本地主机上启动了一个 nginx 服务器,我能够从 Ubuntu docker 容器访问 nginx 网站 URL。

docker run --net host -it ubuntu

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
a604f7af5e36        ubuntu              "/bin/bash"         22 seconds ago      Up 20 seconds                           ubuntu_server

正在从 Ubuntu docker 具有私有网络 IP 地址的容器访问 Nginx Web 服务器(运行在本地主机上运行)。

root@linuxkit-025000000001:/# curl 192.168.x.x -I
HTTP/1.1 200 OK
Server: nginx/1.15.10
Date: Tue, 09 Apr 2019 05:12:12 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 26 Mar 2019 14:04:38 GMT
Connection: keep-alive
ETag: "5c9a3176-264"
Accept-Ranges: bytes

我们发现一个更简单的解决所有这些网络垃圾的方法是只使用服务的域套接字。如果您无论如何都想连接到主机,只需将套接字安装为一个卷,就可以了。对于 postgresql,这很简单:

docker run -v /var/run/postgresql:/var/run/postgresql

然后我们只需将数据库连接设置为使用套接字而不是网络。就这么简单。

对于 docker-compose 使用桥接网络在容器之间创建专用网络,使用 docker0 的公认解决方案不起作用,因为容器的出口接口不是 docker0,相反,它是一个随机生成的接口 ID,例如:

$ ifconfig

br-02d7f5ba5a51: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.32.1  netmask 255.255.240.0  broadcast 192.168.47.255

不幸的是,随机 id 是不可预测的,并且每次 compose 必须重新创建网络时都会改变(例如在主机重启时)。我的解决方案是在已知子网中创建专用网络并配置 iptables 以接受该范围:

撰写文件片段:

version: "3.7"

services:
  mongodb:
    image: mongo:4.2.2
    networks:
    - mynet
    # rest of service config and other services removed for clarity

networks:
  mynet:
    name: mynet
    ipam:
      driver: default
      config:
      - subnet: "192.168.32.0/20"

如果您的环境需要,您可以更改子网。我通过使用 docker network inspect 任意选择 192.168.32.0/20 来查看默认创建的内容。

在主机上配置 iptables 以允许私有子网作为源:

$ iptables -I INPUT 1 -s 192.168.32.0/20 -j ACCEPT

这是最简单的 iptables 规则。您可能希望添加其他限制,例如通过目标端口。不要忘记坚持你的 iptables 规则,当你很高兴他们正在工作时。

这种方法的优点是可重复,因此可以自动化。我使用 ansible 的 template 模块部署带有变量替换的撰写文件,然后使用 iptablesshell 模块分别配置和保留防火墙规则。

这是一个老问题,有很多答案,但其中 none 非常符合我的情况。就我而言,容器非常精简,不包含从容器中提取主机 IP 地址所需的任何网络工具。

此外,使用 --net="host" 方法是一种非常粗略的方法,当想要使用多个容器进行良好隔离的网络配置时不适用。

所以,我的方法是在主机端提取主机地址,然后通过--add-host参数将其传递给容器:

$ docker run --add-host=docker-host:`ip addr show docker0 | grep -Po 'inet \K[\d.]+'` image_name

或者,将主机的 IP 地址保存在环境变量中,稍后使用该变量:

$ DOCKERIP=`ip addr show docker0 | grep -Po 'inet \K[\d.]+'`
$ docker run --add-host=docker-host:$DOCKERIP image_name

然后 docker-host 添加到容器的主机文件中,您可以在数据库连接字符串或 API URL 中使用它。

对于 linux 系统,您可以 – 从 docker 引擎的主要版本 20.04 开始 – 现在也可以通过 host.docker.internal 与主机通信。这不会自动起作用,但您需要提供以下运行标志:

--add-host=host.docker.internal:host-gateway

对我来说(Windows 10,Docker 引擎 v19.03.8)它是 and 的混合。

  1. 将host/ip改为host.docker.internal
    例如:LOGGER_URL = "http://host.docker.internal:8085/log"
  2. 将network_mode设置为bridge(如果你想保持端口转发;如果不使用host):
    version: '3.7' services: server: build: . ports: - "5000:5000" network_mode: bridge 或者:如果您不使用 docker-compose(类似于
    ,请使用 --net="bridge" 正如之前的回答中所指出的:这应该仅在本地开发环境中使用
    更多信息请阅读:https://docs.docker.com/compose/compose-file/#network_mode and https://docs.docker.com/docker-for-windows/networking/#use-cases-and-workarounds

对我有用的最简单的选择是, 我用的是我机器在本地网络的IP地址(由路由器分配)

您可以使用 ifconfig 命令找到它

例如

ifconfig

en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
        options=400<CHANNEL_IO>
        ether f0:18:98:08:74:d4 
        inet 192.168.178.63 netmask 0xffffff00 broadcast 192.168.178.255
        media: autoselect
        status: active

然后使用了inet地址。这对我来说可以连接我机器上的任何端口。

答案是...

http://127.0.0.1http://localhost替换为http://host.docker.internal

为什么?

Source in the docs of Docker.

我的 google 搜索将我带到这里,在深入研究评论后我发现它与 From inside of a Docker container, how do I connect to the localhost of the machine? 重复。我投票赞成将此作为重复关闭,但由于人们(包括我自己!)经常向下滚动答案而不是仔细阅读评论,这里是一个简短的答案。

在将近 7 年的时间里,这个问题被问到,要么 docker 已经改变,要么没有人尝试过这种方式。所以我会附上我自己的答案。

我发现所有答案都使用了复杂的方法。今天正好需要这个,找到了两个很简单的方法:

  • 在您的主机上使用 ipconfigifconfig 并记下所有 IP 地址。容器至少可以使用其中两个。

    • 我在 WiFi LAN 适配器上有一个固定的本地网络地址:192.168.1.101。这可能是 10.0.1.101。结果将根据您的路由器而改变
    • 我在 windows 上使用 WSL,它有自己的 vEthernet 地址:172.19.192.1
  • 使用host.docker.internal。大多数答案都有这种或另一种形式,具体取决于 OS。顾名思义,它现在被 docker.

    全球使用

第三种选择是使用机器的 WAN 地址,或者换句话说,服务提供商提供的 IP。但是,如果 IP 不是静态的,这可能不起作用,并且需要路由和防火墙设置。


PS:虽然与 this question here 完全相同,而且我 post 在那里编辑了这个答案,我首先发现了这个 post,所以我 post它也在这里,因为可能会忘记我自己的答案。