端口发布与映射

默认情况下,对于 IPv4 和 IPv6,Docker 守护进程会阻止访问未发布的端口。发布的容器端口会映射到主机 IP 地址。为此,它使用防火墙规则来执行网络地址转换 (NAT)、端口地址转换 (PAT) 和伪装。

例如,docker run -p 8080:80 [...] 会在 Docker 主机上任何地址的端口 8080 与容器的端口 80 之间创建映射。来自容器的出站连接将使用 Docker 主机的 IP 地址进行伪装。

发布端口

当使用 docker createdocker run 创建或运行容器时,桥接网络上的所有容器端口都可以从 Docker 主机和连接到同一网络的其他容器访问。端口无法从主机外部访问,或者在默认配置下,无法从其他网络中的容器访问。

使用 --publish-p 标志使端口在主机外部以及在其他桥接网络中的容器可用。

这会在主机中创建一个防火墙规则,将容器端口映射到 Docker 主机上对外部世界的端口。以下是一些示例:

标志值 描述
-p 8080:80 将 Docker 主机上的端口 8080 映射到容器中的 TCP 端口 80
-p 192.168.1.100:8080:80 将 Docker 主机 IP 192.168.1.100 上的端口 8080 映射到容器中的 TCP 端口 80
-p 8080:80/udp 将 Docker 主机上的端口 8080 映射到容器中的 UDP 端口 80
-p 8080:80/tcp -p 8080:80/udp 将 Docker 主机上的 TCP 端口 8080 映射到容器中的 TCP 端口 80,并将 Docker 主机上的 UDP 端口 8080 映射到容器中的 UDP 端口 80
Important

默认情况下,发布容器端口是不安全的。这意味着,当你发布容器的端口时,它不仅对 Docker 主机可用,对外部世界也是如此。

如果你在发布标志中包含本地主机 IP 地址 (127.0.0.1::1),则只有 Docker 主机可以访问发布的容器端口。

$ docker run -p 127.0.0.1:8080:80 -p '[::1]:8080:80' nginx
Warning

在 28.0.0 之前的版本中,同一 L2 网段(例如,连接到同一网络交换机的主机)内的主机可以访问发布到本地主机的端口。 更多信息,请参阅 moby/moby#45610

如果在端口映射中未给定主机 IP,且桥接网络仅为 IPv4,并且 --userland-proxy=true(默认),则主机 IPv6 地址上的端口将映射到容器的 IPv4 地址。

直接路由

端口映射确保发布的端口在主机的网络地址上可访问,这些地址很可能对外部客户端是可路由的。通常不会在主机网络中为存在于主机内的容器地址设置路由。

但是,特别是对于 IPv6,你可能更倾向于避免使用 NAT,而是安排外部路由到容器地址(“直接路由”)。

要从 Docker 主机外部访问桥接网络上的容器,必须首先通过 Docker 主机上的地址设置到桥接网络的路由。这可以使用静态路由、边界网关协议 (BGP) 或任何其他适合你网络的方法来实现。例如,在本地二层网络中,远程主机可以通过 Docker 守护进程主机在本地网络上的地址设置到容器网络的静态路由。

直接路由到桥接网络中的容器

默认情况下,不允许远程主机直接访问 Docker Linux 桥接网络中的容器 IP 地址。它们只能访问发布到主机 IP 地址的端口。

要允许直接访问任何 Linux 桥接网络中任何容器的任何已发布端口,请在 /etc/docker/daemon.json 中使用守护进程选项 "allow-direct-routing": true,或使用等效的 --allow-direct-routing

要允许从任何地方直接路由到特定桥接网络中的容器,请参阅网关模式

或者,要允许通过特定主机接口直接路由到特定桥接网络,请在创建网络时使用以下选项:

  • com.docker.network.bridge.trusted_host_interfaces

示例

创建一个网络,其中容器 IP 地址上的已发布端口可以直接从接口 vxlan.1eth3 访问:

$ docker network create --subnet 192.0.2.0/24 --ip-range 192.0.2.0/29 -o com.docker.network.bridge.trusted_host_interfaces="vxlan.1:eth3" mynet

在该网络中运行一个容器,将其端口 80 发布到主机环回接口的端口 8080:

$ docker run -d --ip 192.0.2.100 -p 127.0.0.1:8080:80 nginx

现在可以通过 Docker 主机上的 http://127.0.0.1:8080 访问容器端口 80 上运行的 Web 服务器,或者直接通过 http://192.0.2.100:80 访问。如果连接到接口 vxlan.1eth3 的网络上的远程主机有一条到 Docker 主机内 192.0.2.0/24 网络的路由,它们也可以通过 http://192.0.2.100:80 访问该 Web 服务器。

网关模式

桥接网络驱动具有以下选项:

  • com.docker.network.bridge.gateway_mode_ipv6
  • com.docker.network.bridge.gateway_mode_ipv4

每个选项都可以设置为以下网关模式之一:

  • nat
  • nat-unprotected
  • routed
  • isolated

默认为 nat,为每个发布的容器端口设置 NAT 和伪装规则。离开主机的数据包将使用主机地址。

routed 模式下,不设置 NAT 或伪装规则,但仍会设置防火墙规则,以便只有已发布的容器端口可访问。来自容器的出站数据包将使用容器的地址,而不是主机地址。

要访问 routed 网络中的已发布端口,远程主机必须有一条通过 Docker 主机上外部地址到容器网络的路由(“直接路由”)。本地二层网络上的主机可以在不需要任何额外网络配置的情况下设置直接路由。本地网络之外的主机只有在网络路由器配置为启用时才能使用直接路由到容器。

nat 模式网络中,将端口发布到环回接口上的地址意味着远程主机无法访问它。routednat 网络中的其他已发布容器端口始终可以通过直接路由从远程主机访问,除非 Docker 主机的防火墙有额外的限制。

Note

当端口在 nat 模式下发布到特定主机地址时,如果 Docker 主机上启用了 IP 转发,则可以通过其他主机接口使用直接路由到该主机地址来访问已发布的端口。

例如,一台启用了 IP 转发的 Docker 主机有两个 NIC,地址分别为 192.168.100.10/2410.0.0.10/24。 当端口发布到 192.168.100.10 时,10.0.0.0/24 子网中的主机可以通过路由到 10.0.0.10 来访问该端口。

nat-unprotected 模式下,未发布的容器端口也可以使用直接路由访问,不设置任何端口过滤规则。此模式包含在内是为了与旧的默认行为兼容。

网关模式还影响连接到同一主机上不同 Docker 网络的容器之间的通信。

  • natnat-unprotected 模式下,其他桥接网络中的容器只能通过它们发布到的主机地址访问已发布的端口。不允许来自其他网络的直接路由。
  • routed 模式下,其他网络中的容器可以使用直接路由访问端口,而无需经过主机地址。

routed 模式下,-p--publish 端口映射中的主机端口不被使用,主机地址仅用于决定是否将映射应用于 IPv4 或 IPv6。因此,当映射仅适用于 routed 模式时,应仅使用地址 0.0.0.0::,并且不应给定主机端口。如果给定了特定地址或端口,它将对已发布的端口没有效果,并且会记录警告消息。

isolated 模式只能在网络同时使用 CLI 标志 --internal 或等效方式创建时使用。在 internal 网络中,通常会为桥接设备分配一个地址。因此,Docker 主机上的进程可以访问该网络,并且网络中的容器可以访问侦听该桥接地址的主机服务(包括侦听“任何”主机地址 0.0.0.0:: 的服务)。当使用网关模式 isolated 创建网络时,不会为桥接分配地址。

示例

创建一个适用于 IPv6 直接路由的网络,并为 IPv4 启用 NAT:

$ docker network create --ipv6 --subnet 2001:db8::/64 -o com.docker.network.bridge.gateway_mode_ipv6=routed mynet

创建一个带有已发布端口的容器:

$ docker run --network=mynet -p 8080:80 myimage

然后:

  • 仅容器端口 80 将对 IPv4 和 IPv6 开放。
  • 对于 IPv6,使用 routed 模式,端口 80 将在容器的 IP 地址上开放。端口 8080 不会在主机的 IP 地址上打开,并且出站数据包将使用容器的 IP 地址。
  • 对于 IPv4,使用默认的 nat 模式,容器的端口 80 将通过主机 IP 地址上的端口 8080 访问,也可以直接从 Docker 主机内部访问。但是,容器端口 80 无法直接从主机外部访问。 来自容器的连接将使用主机的 IP 地址进行伪装。

docker inspect 中,此端口映射将显示如下。请注意,IPv6 没有 HostPort,因为它使用的是 routed 模式:

$ docker container inspect <id> --format "{{json .NetworkSettings.Ports}}"
{"80/tcp":[{"HostIp":"0.0.0.0","HostPort":"8080"},{"HostIp":"::","HostPort":""}]}

或者,要使映射仅限 IPv6,禁用对容器端口 80 的 IPv4 访问,请使用未指定的 IPv6 地址 [::] 并且不包含主机端口号:

$ docker run --network mynet -p '[::]::80'

设置容器的默认绑定地址

默认情况下,当容器的端口在没有指定任何主机地址的情况下映射时,Docker 守护进程会将端口发布到所有主机地址 (0.0.0.0[::])。

例如,以下命令将端口 8080 发布到主机上的所有网络接口,包括 IPv4 和 IPv6 地址,可能使其对外部世界可用。

docker run -p 8080:80 nginx

你可以更改已发布容器端口的默认绑定地址,使其默认仅对 Docker 主机可访问。为此,你可以将守护进程配置为使用环回地址 (127.0.0.1) 代替。

Warning

在 28.0.0 之前的版本中,同一 L2 网段(例如,连接到同一网络交换机的主机)内的主机可以访问发布到本地主机的端口。更多信息,请参阅 moby/moby#45610

要为用户定义的桥接网络配置此设置,请在创建网络时使用 com.docker.network.bridge.host_binding_ipv4 驱动选项。尽管选项名称如此,但可以指定 IPv6 地址。

$ docker network create mybridge \
  -o "com.docker.network.bridge.host_binding_ipv4=127.0.0.1"

或者,要为所有用户定义的桥接网络中的容器设置默认绑定地址,请使用守护进程配置选项 default-network-opts。例如:

{
  "default-network-opts": {
    "bridge": {
      "com.docker.network.bridge.host_binding_ipv4": "127.0.0.1"
    }
  }
}
Note

将默认绑定地址设置为 :: 意味着未指定主机地址的端口绑定将适用于主机上的任何 IPv6 地址。但是,0.0.0.0 意味着任何 IPv4 或 IPv6 地址。

更改默认绑定地址对 Swarm 服务没有任何影响。Swarm 服务始终在 0.0.0.0 网络接口上公开。

出站数据包的伪装或 SNAT

桥接网络默认启用 NAT,这意味着来自容器的出站数据包会被伪装。离开 Docker 主机的数据包的源地址会被更改为数据包发送出去的主机接口上的地址。

可以通过在创建网络时使用 com.docker.network.bridge.enable_ip_masquerade 驱动选项来为用户定义的桥接网络禁用伪装。例如:

$ docker network create mybridge \
  -o com.docker.network.bridge.enable_ip_masquerade=false ...

要为用户定义的网络的出站数据包使用特定的源地址,而不是让伪装选择地址,请使用选项 com.docker.network.host_ipv4com.docker.network.host_ipv6 来指定要使用的源 NAT (SNAT) 地址。com.docker.network.bridge.enable_ip_masquerade 选项必须为 true(默认值),这些选项才会生效。

默认桥接

要为默认桥接网络设置默认绑定,请在 daemon.json 配置文件中配置 "ip" 键:

{
  "ip": "127.0.0.1"
}

这会将默认桥接网络上已发布容器端口的默认绑定地址更改为 127.0.0.1。 重新启动守护进程以使此更改生效。 或者,你可以在启动守护进程时使用 dockerd --ip 标志。