docker 网络

1 none和host网络的适用场景

本章开始讨论 Docker 网络。

我们会首先学习 Docker 提供的几种原生网络,以及如何创建自定义网络。然后探讨容器之间如何通信,以及容器与外界如何交互。

Docker 网络从覆盖范围可分为单个 host 上的容器网络和跨多个 host 的网络,本章重点讨论前一种。对于更为复杂的多 host 容器网络,我们会在后面进阶技术章节单独讨论。

Docker 安装时会自动在 host 上创建三个网络,我们可用 docker network ls 命令查看:

[root@docker ~]# docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
a1b809382f0d   bridge    bridge    local
6ad21ed9ea1f   host      host      local
4e31521b27c6   none      null      local

下面我们分别讨论它们。

none 网络

故名思议,none 网络就是什么都没有的网络。挂在这个网络下的容器除了 lo,没有其他任何网卡。容器创建时,可以通过 --network=none 指定使用 none 网络。

[root@docker ~]# docker run -it --network=none busybox
/ #
/ # ifconfig
lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

/ #
/ # hostname
4027da12afaf

我们不禁会问,这样一个封闭的网络有什么用呢?

其实还真有应用场景。封闭意味着隔离,一些对安全性要求高并且不需要联网的应用可以使用 none 网络。

比如某个容器的唯一用途是生成随机密码,就可以放到 none 网络中避免密码被窃取。

当然大部分容器是需要网络的,我们接着看 host 网络。

host 网络

连接到 host 网络的容器共享 Docker host 的网络栈,容器的网络配置与 host 完全一样。可以通过 --network=host 指定使用 host 网络。

[root@docker ~]# docker run -it --network=host busybox
/ #
/ # ip l
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq qlen 1000
    link/ether 00:0c:29:33:15:f6 brd ff:ff:ff:ff:ff:ff
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue
    link/ether 02:42:02:fd:82:b3 brd ff:ff:ff:ff:ff:ff
/ #
/ # hostname
docker
/ #

在容器中可以看到 host 的所有网卡,并且连 hostname 也是 host 的。host 网络的使用场景又是什么呢?

直接使用 Docker host 的网络最大的好处就是性能,如果容器对网络传输效率有较高要求,则可以选择 host 网络。当然不便之处就是牺牲一些灵活性,比如要考虑端口冲突问题,Docker host 上已经使用的端口就不能再用了。

Docker host 的另一个用途是让容器可以直接配置 host 网路。比如某些跨 host 的网络解决方案,其本身也是以容器方式运行的,这些方案需要对网络进行配置,比如管理 iptables,大家将会在后面进阶技术章节看到。

下一节讨论应用更广的 bridge 网络。


2 学容器必须懂brige网络

上一节我们讨论了 none 和 host 类型的容器网络,本节学习应用最广泛也是默认的 bridge 网络。

Docker 安装时会创建一个 命名为 docker0 的 linux bridge,实际上它是 Linux 的一个 bridge (网桥),可以理解为一个软件交换机,它会在挂载到它的网口之间进行转发。如果不指定--network,创建的容器默认都会挂到 docker0 上。

Docker 就创建了在主机和所有容器之间一个虚拟共享网络 当创建一个 Docker 容器的时候,同时会创建了一对 veth pair 接口(当数据包发送到一个接口时,另外一个接口也可以收到相同的数据包), 这对接口

  • 一端在容器内即 eth0;
  • 另一端在本地并被挂载到 docker0 网桥,名称以 veth 开头(例如 vethAQI2QT)

通过这种方式,主机可以跟容器通信,容器之间也可以相互通信。

在这里插入图片描述

下面演示:

先配置yum源用于安装软件

[root@docker ~]# cd /etc/yum.repos.d/
[root@docker yum.repos.d]# vim cloud.repo
[centos-openstack-victoria]
name=CentOS 8 - OpenStack victoria
baseurl=https://mirrors.aliyun.com/centos-vault/8-stream/cloud/x86_64/openstack-victoria/
enabled=1
gpgcheck=0

[root@docker yum.repos.d]# yum install -y bridge-utils
[root@docker ~]# brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.02420be905a5       no

当前 docker0 上没有任何其他网络设备,我们创建一个容器看看有什么变化。

[root@docker ~]# docker run -itd --name busybox1 busybox
5225d246f751dbfc4cdf745675d9c79d3de1b91dd4174268c35e671b9f1b0c80

[root@docker ~]# brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.02420be905a5       no              vethddb2744

一个新的网络接口 vethddb2744 被挂到了 docker0 上,vethddb2744就是新创建容器的虚拟网卡。

下面看一下容器的网络配置。

[root@docker ~]# docker exec -it busybox1 sh
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
       
# 容器离得网卡是24号网卡名字叫eth0,对面是25号网卡
24: eth0@if25: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

容器有一个网卡 eth0@if25。大家可能会问了,为什么不是vethddb2744 呢?

实际上 eth0@if25vethddb2744 是一对 veth pair。veth pair 是一种成对出现的特殊网络设备,可以把它们想象成由一根虚拟网线连接起来的一对网卡,网卡的一头(eth0@if25)在容器中,另一头(vethddb2744)挂在网桥 docker0 上,其效果就是将 eth0@if25 也挂在了 docker0 上。

在宿主机上查看IP地址,可以证明Docker0上的vethddb2744和容器中的eth0@if25 是一对

在宿主机看到有块网卡:25: vethddb2744@if24

宿主机的25号网卡:25: vethddb2744@if24 含义就是宿主机25号网卡对面连接了一块24号网卡,25号网卡的名字叫vethddb2744

证明了容器busybox1里的eth0连接到了docker0网桥的vethddb2744

在这里插入图片描述

我们还看到eth0@if25 已经配置了 IP 172.17.0.2,为什么是这个网段呢?让我们通过 docker network inspect bridge 看一下 bridge 网络的配置信息:

[root@docker ~]# docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "9c8f4ff0c361c169536a912736d7a305b93abd9f3acc69fee066233260930a69",
        "Created": "2025-07-31T13:31:35.155868533+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "5225d246f751dbfc4cdf745675d9c79d3de1b91dd4174268c35e671b9f1b0c80": {
                "Name": "busybox1",
                "EndpointID": "ecc590ff010723831b97c0439ef9a9ca13fda695185d846d25662caecb066b7f",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]

原来 bridge 网络配置的 subnet 就是 172.17.0.0/16,并且网关是 172.17.0.1。这个网关在哪儿呢?大概你已经猜出来了,就是 docker0。

[root@docker ~]# ip a | grep docker0
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0

当前容器网络拓扑结构如图所示:

在这里插入图片描述

容器创建时,docker 会自动从 172.17.0.0/16 中分配一个 IP,这里 16 位的掩码保证有足够多的 IP 可以供容器使用。

除了 none, host, bridge 这三个自动创建的网络,用户也可以根据业务需要创建 user-defined 网络,下一节我们将详细讨论。


3 如何自定义容器网络?

除了 none, host, bridge 这三个自动创建的网络,用户也可以根据业务需要创建 user-defined 网络。

Docker 提供三种 user-defined 网络驱动:bridge, overlay 和 macvlan。overlay 和 macvlan 用于创建跨主机的网络,我们后面有章节单独讨论。

我们可通过 bridge 驱动创建类似前面默认的 bridge 网络,例如:

[root@docker ~]# docker network create --driver bridge my_net
89f7bc11b602e84452ae01786113ac196a535f7296b9989ad941b3a48a5e6d04

查看一下当前 host 的网络结构变化:

[root@docker ~]# brctl show
bridge name              bridge id               STP enabled     interfaces
br-89f7bc11b602         8000.0242194d039e         no                             #my_net                     
docker0                 8000.02420be905a5         no             vethddb2744

新增了一个网桥 br-89f7bc11b602,这里 br-89f7bc11b602 正好是新建 bridge 网络 my_net 的短 id。执行 docker network inspect 查看一下 my_net 的配置信息:

[root@docker ~]# docker network inspect my_net
[
    {
        "Name": "my_net",
        "Id": "89f7bc11b602e84452ae01786113ac196a535f7296b9989ad941b3a48a5e6d04",
        "Created": "2025-07-31T15:57:17.487644996+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]

这里 172.18.0.0/16 是 Docker 自动分配的 IP 网段。

我们可以自己指定 IP 网段吗?
答案是:可以。

只需在创建网段时指定 --subnet--gateway 参数:

[root@docker ~]# docker network create --driver bridge --subnet 172.22.16.0/24 --gateway 172.22.16.1 my_net2
ec761bc51778f67c2245af72fd969f00cd517ee617a10a70dc01904f9c10279d

[root@docker ~]# brctl show
bridge name     bridge id               STP enabled     interfaces
br-89f7bc11b602         8000.0242194d039e       no                     #my_net
br-ec761bc51778         8000.02427fa54b88       no                     #my_net2
docker0         8000.02420be905a5       no              vethddb2744

[root@docker ~]# docker network inspect my_net2
[
    {
        "Name": "my_net2",
        "Id": "ec761bc51778f67c2245af72fd969f00cd517ee617a10a70dc01904f9c10279d",
        "Created": "2025-07-31T16:00:06.021244096+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.22.16.0/24",                    #确实是指定的网段
                    "Gateway": "172.22.16.1"                      #确实是指定的网关地址
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]

这里我们创建了新的 bridge 网络 my_net2,网段为 172.22.16.0/24,网关为 172.22.16.1。与前面一样,网关在 my_net2 对应的网桥 br-ec761bc51778 上:

[root@docker ~]# brctl show
bridge name     bridge id               STP enabled     interfaces
br-89f7bc11b602         8000.0242194d039e       no                     #my_net
br-ec761bc51778         8000.02427fa54b88       no                     #my_net2
docker0         8000.02420be905a5       no              vethddb2744

#同时宿主机上出现了与网桥my_net,my_net2同名的网卡
#查看my_net2网卡
[root@docker ~]# ip a | grep br-ec761bc51778
27: br-ec761bc51778: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    inet 172.22.16.1/24 brd 172.22.16.255 scope global br-ec761bc51778

容器要使用新的网络,需要在启动时通过 --network 指定:

[root@docker ~]# docker run -it --network=my_net2 --name busybox2 busybox
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
28: eth0@if29: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 02:42:ac:16:10:02 brd ff:ff:ff:ff:ff:ff
    inet 172.22.16.2/24 brd 172.22.16.255 scope global eth0
       valid_lft forever preferred_lft forever
/ # ctrl+P,ctrl+q退出容器

[root@docker ~]# brctl show
bridge name     bridge id               STP enabled     interfaces
br-89f7bc11b602         8000.0242194d039e       no
br-ec761bc51778         8000.02427fa54b88       no      veth02dd704      #my_net2上新增的接口veth02dd704连接容器busybox2
docker0         8000.02420be905a5       no              vethddb2744

容器分配到的 IP 为 172.22.16.2。

到目前为止,容器的 IP 都是 docker 自动从 subnet 中分配,我们能否指定一个静态 IP 呢?

答案是:可以,通过--ip指定。

[root@docker ~]# docker run -it --network=my_net2 --ip 172.22.16.8 --name busybox3 busybox
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
30: eth0@if31: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 02:42:ac:16:10:08 brd ff:ff:ff:ff:ff:ff
    inet 172.22.16.8/24 brd 172.22.16.255 scope global eth0           #确实是指定的IP
       valid_lft forever preferred_lft forever
/ # ctrl+P,ctrl+q退出容器

[root@docker ~]# brctl show
bridge name     bridge id               STP enabled     interfaces
br-89f7bc11b602         8000.0242194d039e       no                        #my_net
br-ec761bc51778         8000.02427fa54b88       no      veth02dd704       #my_net2
                                                        veth5fbb7f6       #veth5fbb7f6是连接busybox3
docker0         8000.02420be905a5       no              vethddb2744

注:只有使用 --subnet 创建的网络才能指定静态 IP

my_net 创建时没有指定 --subnet,如果指定静态 IP 报错如下:

[root@docker ~]# docker run -it --network=my_net --ip 172.18.0.8 busybox
docker: Error response from daemon: invalid config for network my_net: invalid endpoint settings:
user specified IP address is supported only when connecting to networks with user configured subnets.
See 'docker run --help'.

好了,我们来看看当前 docker host 的网络拓扑结构。

在这里插入图片描述

下一节讨论这几个容器之间的连通性。


4 理解容器之间的连通性

通过前面小节的实践,当前 docker host 的网络拓扑结构如下图所示,今天我们将讨论这几个容器之间的连通性。

在这里插入图片描述

busybox2、busybox3 容器都挂在 my_net2 上,应该能够互通,我们验证一下:

# 登陆busybox2 ping busybox3
[root@docker ~]# docker exec -it busybox2 sh
/ # ifconfig eth0
eth0      Link encap:Ethernet  HWaddr 02:42:AC:16:10:02
          inet addr:172.22.16.2  Bcast:172.22.16.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:18 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:1436 (1.4 KiB)  TX bytes:0 (0.0 B)

/ # ping -c 3 172.22.16.8
PING 172.22.16.8 (172.22.16.8): 56 data bytes
64 bytes from 172.22.16.8: seq=0 ttl=64 time=0.197 ms
64 bytes from 172.22.16.8: seq=1 ttl=64 time=0.137 ms
64 bytes from 172.22.16.8: seq=2 ttl=64 time=0.158 ms

--- 172.22.16.8 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.137/0.164/0.197 ms
/ #

# ping my_net2网关地址
/ # ping -c 3 172.22.16.1
PING 172.22.16.1 (172.22.16.1): 56 data bytes
64 bytes from 172.22.16.1: seq=0 ttl=64 time=0.149 ms
64 bytes from 172.22.16.1: seq=1 ttl=64 time=0.116 ms
64 bytes from 172.22.16.1: seq=2 ttl=64 time=0.182 ms

--- 172.22.16.1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.116/0.149/0.182 ms

可见同一网络中的容器、网关之间都是可以通信的。

my_net2 与默认 bridge 网络(docker0)能通信吗?

从拓扑图可知,两个网络属于不同的网桥,应该不能通信,我们通过实验验证一下,让 busybox2 容器 ping buxybox1 容器:

/ # ping -c 3 172.17.0.2
PING 172.17.0.2 (172.17.0.2): 56 data bytes

--- 172.17.0.2 ping statistics ---
3 packets transmitted, 0 packets received, 100% packet loss
/ #

确实 ping 不通,符合预期。

“等等!不同的网络如果加上路由应该就可以通信了吧?”

这是一个非常非常好的想法。

确实,如果 host 上对每个网络的都有一条路由,同时操作系统上打开了 ip forwarding,host 就成了一个路由器,挂接在不同网桥上的网络就能够相互通信。下面我们来看看 docker host 满不满足这些条件呢?

ip r 查看 host 上的路由表:

[root@docker ~]# ip r
default via 192.168.108.2 dev ens160 proto static metric 100
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
172.18.0.0/16 dev br-a6eff42dc3c6 proto kernel scope link src 172.18.0.1 linkdown
172.22.16.0/24 dev br-c0fc7bdf8143 proto kernel scope link src 172.22.16.1
192.168.108.0/24 dev ens160 proto kernel scope link src 192.168.108.30 metric 100
[root@docker ~]#

172.17.0.0/16 和 172.22.16.0/24 两个网络的路由都定义好了。再看看 ip forwarding:

[root@docker ~]# sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1

ip forwarding 也已经启用了。

条件都满足,为什么不能通行呢?

我们还得看看 iptables:

[root@docker ~]# iptables-save
# Generated by iptables-save v1.8.5 on Thu Jul 31 17:09:34 2025
*filter
:INPUT ACCEPT [69184:9702316]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [113709:49882234]
:DOCKER - [0:0]
:DOCKER-ISOLATION-STAGE-1 - [0:0]
:DOCKER-ISOLATION-STAGE-2 - [0:0]
:DOCKER-USER - [0:0]
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o br-ec761bc51778 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o br-ec761bc51778 -j DOCKER
-A FORWARD -i br-ec761bc51778 ! -o br-ec761bc51778 -j ACCEPT
-A FORWARD -i br-ec761bc51778 -o br-ec761bc51778 -j ACCEPT
-A FORWARD -o br-89f7bc11b602 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o br-89f7bc11b602 -j DOCKER
-A FORWARD -i br-89f7bc11b602 ! -o br-89f7bc11b602 -j ACCEPT
-A FORWARD -i br-89f7bc11b602 -o br-89f7bc11b602 -j ACCEPT
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER-ISOLATION-STAGE-1 -i br-ec761bc51778 ! -o br-ec761bc51778 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -i br-89f7bc11b602 ! -o br-89f7bc11b602 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
-A DOCKER-ISOLATION-STAGE-2 -o br-ec761bc51778 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -o br-89f7bc11b602 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
-A DOCKER-USER -j RETURN
COMMIT
# Completed on Thu Jul 31 17:09:34 2025
# Generated by iptables-save v1.8.5 on Thu Jul 31 17:09:34 2025
*security
:INPUT ACCEPT [69166:9698194]
:FORWARD ACCEPT [34:2856]
:OUTPUT ACCEPT [113709:49882234]
COMMIT
# Completed on Thu Jul 31 17:09:34 2025
# Generated by iptables-save v1.8.5 on Thu Jul 31 17:09:34 2025
*raw
:PREROUTING ACCEPT [69554:9733396]
:OUTPUT ACCEPT [113709:49882234]
COMMIT
# Completed on Thu Jul 31 17:09:34 2025
# Generated by iptables-save v1.8.5 on Thu Jul 31 17:09:34 2025
*mangle
:PREROUTING ACCEPT [69554:9733396]
:INPUT ACCEPT [69184:9702316]
:FORWARD ACCEPT [370:31080]
:OUTPUT ACCEPT [113709:49882234]
:POSTROUTING ACCEPT [113743:49885090]
COMMIT
# Completed on Thu Jul 31 17:09:34 2025
# Generated by iptables-save v1.8.5 on Thu Jul 31 17:09:34 2025
*nat
:PREROUTING ACCEPT [367:33182]
:INPUT ACCEPT [9:500]
:POSTROUTING ACCEPT [294:23341]
:OUTPUT ACCEPT [291:23089]
:DOCKER - [0:0]
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.22.16.0/24 ! -o br-ec761bc51778 -j MASQUERADE
-A POSTROUTING -s 172.18.0.0/16 ! -o br-89f7bc11b602 -j MASQUERADE
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A DOCKER -i br-ec761bc51778 -j RETURN
-A DOCKER -i br-89f7bc11b602 -j RETURN
-A DOCKER -i docker0 -j RETURN
COMMIT
# Completed on Thu Jul 31 17:09:34 2025

原因就在这里了:iptables DROP 掉了网桥 docker0 与 br-ec761bc51778(my_net2) 之间双向的流量

[root@docker ~]# brctl show
bridge name     bridge id               STP enabled     interfaces
br-89f7bc11b602         8000.0242194d039e       no
br-ec761bc51778(mynet2)         8000.02427fa54b88       no      veth02dd704
                                                        veth5fbb7f6
docker0         8000.02420be905a5       no              vethddb2744

从规则的命名 DOCKER-ISOLATION 可知 docker 在设计上就是要隔离不同的 netwrok。

那么接下来的问题是:怎样才能让 busybox1与busybox2 通信呢?

答案是:为 busybox1 容器添加一块 my_net2的网卡。这个可以通过docker network connect 命令实现。

[root@docker ~]# docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED             STATUS             PORTS     NAMES
0899976a1c59   busybox   "sh"      About an hour ago   Up About an hour             busybox3
e9da18f962d6   busybox   "sh"      About an hour ago   Up About an hour             busybox2
5225d246f751   busybox   "sh"      About an hour ago   Up About an hour             busybox1

[root@docker ~]# docker network connect my_net2 busybox1

我们在 httpd 容器中查看一下网络配置:

[root@docker ~]# docker exec -it busybox1 sh
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
24: eth0@if25: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
32: eth1@if33: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 02:42:ac:16:10:03 brd ff:ff:ff:ff:ff:ff
    inet 172.22.16.3/24 brd 172.22.16.255 scope global eth1
       valid_lft forever preferred_lft forever


[root@docker ~]# brctl show
bridge name     bridge id               STP enabled     interfaces
br-89f7bc11b602         8000.0242194d039e       no
br-ec761bc51778         8000.02427fa54b88       no      veth02dd704       #busybox2
                                                        veth5fbb7f6       #busybox3
                                                        veth6642cc9       #busybox1
docker0         8000.02420be905a5       no              vethddb2744

容器中增加了一个网卡 eth1,分配了 my_net2 的 IP 172.22.16.3。现在 busybox2 应该能够访问busybox1 了,验证一下:

[root@docker ~]# docker exec -it busybox2 sh
/ # ping -c 3 172.22.16.3
PING 172.22.16.3 (172.22.16.3): 56 data bytes
64 bytes from 172.22.16.3: seq=0 ttl=64 time=0.199 ms
64 bytes from 172.22.16.3: seq=1 ttl=64 time=0.134 ms
64 bytes from 172.22.16.3: seq=2 ttl=64 time=0.141 ms

--- 172.22.16.3 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.134/0.158/0.199 ms
/ #

busybox 能够 ping 到 busybox2。当前网络结构如图所示:

在这里插入图片描述

下一节我们讨论容器间通信的三种方式。


5 容器通信的三种方式

容器之间可通过 IP,Docker DNS Server 或 joined 容器三种方式通信。

IP 通信

从上一节的例子可以得出这样一个结论:两个容器要能通信,必须要有属于同一个网络的网卡。

满足这个条件后,容器就可以通过 IP 交互了。具体做法是在容器创建时通过 --network 指定相应的网络,或者通过 docker network connect 将现有容器加入到指定网络。可参考上一节 busybox 的例子,这里不再赘述。

Docker DNS Server

通过 IP 访问容器虽然满足了通信的需求,但还是不够灵活。因为我们在部署应用之前可能无法确定 IP,部署之后再指定要访问的 IP 会比较麻烦。对于这个问题,可以通过 docker 自带的 DNS 服务解决。

从 Docker 1.10 版本开始,docker daemon 实现了一个内嵌的 DNS server,使容器可以直接通过“容器名”通信。方法很简单,只要在启动时用 --name 为容器命名就可以了。

下面启动两个容器 bbox1 和 bbox2:

[root@docker ~]# docker run -it --network my_net2 --name bbox1 busybox
/ # 
ctrl_p,ctrl_q退出容器

[root@docker ~]# docker run -it --network my_net2 --name bbox2 busybox
/ # 
ctrl_p,ctrl_q退出容器

然后,bbox2 就可以直接 ping 到 bbox1 了:

[root@docker ~]# docker exec -it bbox2 sh
/ #
/ # ping -c 3 bbox1
PING bbox1 (172.22.16.2): 56 data bytes
64 bytes from 172.22.16.2: seq=0 ttl=64 time=0.177 ms
64 bytes from 172.22.16.2: seq=1 ttl=64 time=0.161 ms
64 bytes from 172.22.16.2: seq=2 ttl=64 time=0.187 ms

--- bbox1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.161/0.175/0.187 ms

使用 docker DNS 有个限制:只能在 user-defined 网络中使用。也就是说,默认的 bridge 网络是无法使用 DNS 的。下面验证一下:

创建 bbox3 和 bbox4,均连接到 bridge 网络。

[root@docker ~]# docker run -it --name bbox3 busybox
/ # 
ctrl_p,ctrl_q退出容器

[root@docker ~]# docker run -it --name bbox4 busybox
/ # 
ctrl_p,ctrl_q退出容器

bbox4 无法 ping 到 bbox3。

[root@docker ~]# docker exec -it bbox4 sh
/ # ping -c 3 bbox3
ping: bad address 'bbox3'
/ #
joined 容器

joined 容器是另一种实现容器间通信的方式。

joined 容器非常特别,它可以使两个或多个容器共享一个网络栈,共享网卡和配置信息,joined 容器之间可以通过 127.0.0.1 直接通信。请看下面的例子:

先创建一个 httpd 容器,名字为 web1。

[root@docker ~]# docker run -d -it --name web1 httpd
f7641c43eb7021724e869f77bd4541e909ebf0968fc35e6a43e8e674d046ef3f

下面我们查看一下 web1 的网络:

[root@docker ~]# docker exec -it web1 bash

root@1d5181ce7a85:/usr/local/apache2# apt-get update

root@1d5181ce7a85:/usr/local/apache2# apt-get install iproute2 -y

root@1d5181ce7a85:/usr/local/apache2# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
18: eth0@if19: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

然后创建 busybox 容器并通过 --network=container:web1 指定 jointed 容器为 web1:

请注意 busybox 容器中的网络配置信息

[root@docker ~]# docker run -it --network container:web1 busybox
/ #
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
18: eth0@if19: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
/ #

看!busybox 和 web1 的网卡 mac 地址与 IP 完全一样,它们共享了相同的网络栈。busybox 可以直接用 127.0.0.1 访问 web1 的 http 服务。

/ # wget 127.0.0.1
Connecting to 127.0.0.1 (127.0.0.1:80)
saving to 'index.html'
index.html           100% |***************************|    45  0:00:00 ETA
'index.html' saved
/ #
/ # cat index.html
<html><body><h1>It works!</h1></body></html>
/ #

joined 容器非常适合以下场景:

  1. 不同容器中的程序希望通过 loopback 高效快速地通信,比如 web server 与 app server。
  2. 希望监控其他容器的网络流量,比如运行在独立容器中的网络监控程序。

容器之间的通信我们已经搞清楚了,接下来要考虑的是容器如何与外部世界通信?这将是下一节的主题。


6 容器如何访问外部世界

前面我们已经解决了容器间通信的问题,接下来讨论容器如何与外部世界通信。这里涉及两个方向:

  1. 容器访问外部世界
  2. 外部世界访问容器

容器访问外部世界

在我们当前的实验环境下,docker host 是可以访问外网的。

[root@docker ~]# ping -c 3 www.baidu.com
PING www.baidu.com (223.109.82.6) 56(84) bytes of data.
64 bytes from 223.109.82.6 (223.109.82.6): icmp_seq=1 ttl=128 time=67.8 ms
64 bytes from 223.109.82.6 (223.109.82.6): icmp_seq=2 ttl=128 time=13.0 ms
64 bytes from 223.109.82.6 (223.109.82.6): icmp_seq=3 ttl=128 time=12.5 ms

--- www.baidu.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 12.454/31.074/67.754/25.937 ms

我们看一下容器是否也能访问外网呢?

[root@docker ~]# docker run -it --name test1 busybox
/ #
/ # ping -c 3 www.baidu.com
PING www.baidu.com (223.109.82.6): 56 data bytes
64 bytes from 223.109.82.6: seq=0 ttl=127 time=21.240 ms
64 bytes from 223.109.82.6: seq=1 ttl=127 time=14.987 ms
64 bytes from 223.109.82.6: seq=2 ttl=127 time=14.651 ms

--- www.baidu.com ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 14.651/16.959/21.240 ms

可见,容器默认就能访问外网

请注意:这里外网指的是容器网络以外的网络环境,并非特指 internet。

现象很简单,但更重要的:我们应该理解现象下的本质。

在上面的例子中,busybox 位于 docker0 这个私有 bridge 网络中(172.17.0.0/16),当 busybox 从容器向外 ping 时,数据包是怎样到达 www.baidu.com 的呢?

这里的关键就是 NAT。我们查看一下 docker host 上的 iptables 规则:

[root@docker ~]# iptables -t nat -S
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P POSTROUTING ACCEPT
-P OUTPUT ACCEPT
-N DOCKER
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 172.19.0.0/16 ! -o br-6936dc39839e -j MASQUERADE
-A POSTROUTING -s 172.22.16.0/24 ! -o br-c0fc7bdf8143 -j MASQUERADE
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A DOCKER -i docker0 -j RETURN
-A DOCKER -i br-6936dc39839e -j RETURN
-A DOCKER -i br-c0fc7bdf8143 -j RETURN
[root@docker ~]#

在 NAT 表中,有这么一条规则:

-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE

其含义是:如果网桥 docker0 收到来自 172.17.0.0/16 网段的外出包,把它交给 MASQUERADE 处理。而 MASQUERADE 的处理方式是将包的源地址替换成 host 的地址发送出去,即做了一次网络地址转换(NAT)

下面我们通过 tcpdump 查看地址是如何转换的。先查看 docker host 的路由表:

[root@docker ~]# ip r
default via 192.168.108.2 dev ens160 proto static metric 100
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
172.19.0.0/16 dev br-6936dc39839e proto kernel scope link src 172.19.0.1 linkdown
172.22.16.0/24 dev br-c0fc7bdf8143 proto kernel scope link src 172.22.16.1 linkdown
192.168.108.0/24 dev ens160 proto kernel scope link src 192.168.108.30 metric 100

默认路由通过 ens160 发出去,所以我们要同时监控 ens160 和 docker0 上的 icmp(ping)数据包。

使用tcpdump观察现象

[root@docker ~]# yum install -y tcpdump

[root@docker ~]# tcpdump -i docker0 -n icmp
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on docker0, link-type EN10MB (Ethernet), capture size 262144 bytes

#再开一个窗口
[root@docker ~]# tcpdump -i ens160 -n icmp
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens160, link-type EN10MB (Ethernet), capture size 262144 bytes

再开一个窗口, busybox ping www.baidu.com

[root@docker ~]#  docker run -it busybox
/ #
/ # ping -c 3 www.baidu.com
PING www.baidu.com (223.109.82.41): 56 data bytes
64 bytes from 223.109.82.41: seq=0 ttl=127 time=12.503 ms
64 bytes from 223.109.82.41: seq=1 ttl=127 time=13.380 ms
64 bytes from 223.109.82.41: seq=2 ttl=127 time=11.507 ms

--- www.baidu.com ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 11.507/12.463/13.380 ms
/ #

tcpdump 输出如下:

[root@docker ~]# tcpdump -i docker0 -n icmp
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on docker0, link-type EN10MB (Ethernet), capture size 262144 bytes
15:23:42.854951 IP 172.17.0.2 > 223.109.82.41: ICMP echo request, id 7, seq 0, length 64
15:23:42.870473 IP 223.109.82.41 > 172.17.0.2: ICMP echo reply, id 7, seq 0, length 64
15:23:43.857551 IP 172.17.0.2 > 223.109.82.41: ICMP echo request, id 7, seq 1, length 64
15:23:43.880065 IP 223.109.82.41 > 172.17.0.2: ICMP echo reply, id 7, seq 1, length 64
15:23:44.869116 IP 172.17.0.2 > 223.109.82.41: ICMP echo request, id 7, seq 2, length 64
15:23:44.883777 IP 223.109.82.41 > 172.17.0.2: ICMP echo reply, id 7, seq 2, length 64

docker0 收到 busybox 的 ping 包,源地址为容器 IP 172.17.0.2,这没问题,交给 MASQUERADE 处理。这时,在 ens160 上我们看到了变化:

[root@docker ~]# tcpdump -i ens160 -n icmp
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens160, link-type EN10MB (Ethernet), capture size 262144 bytes
15:25:41.886472 IP 192.168.108.30 > 223.109.82.41: ICMP echo request, id 8, seq 0, length 64
15:25:41.906738 IP 223.109.82.41 > 192.168.108.30: ICMP echo reply, id 8, seq 0, length 64
15:25:42.887752 IP 192.168.108.30 > 223.109.82.41: ICMP echo request, id 8, seq 1, length 64
15:25:42.899613 IP 223.109.82.41 > 192.168.108.30: ICMP echo reply, id 8, seq 1, length 64
15:25:43.889467 IP 192.168.108.30 > 223.109.82.41: ICMP echo request, id 8, seq 2, length 64
15:25:43.904277 IP 223.109.82.41 > 192.168.108.30: ICMP echo reply, id 8, seq 2, length 64

ping 包的源地址变成了 ens160 的 IP 192.168.108.130

这就是 iptable NAT 规则处理的结果,从而保证数据包能够到达外网。下面用一张图来说明这个过程:

在这里插入图片描述

  1. busybox 发送 ping 包:172.17.0.2 > www.baidu.com。
  2. docker0 收到包,发现是发送到外网的,交给 NAT 处理。
  3. NAT 将源地址换成 ens160的 IP:192.168.108.30 > www.baidu.com。
  4. ping 包从 ens160 发送出去,到达 www.baidu.com。

通过 NAT,docker 实现了容器对外网的访问。

下一节我们讨论另一个方向的流量:外部世界如何访问容器。


7 外部世界如何访问容器

上节我们学习了容器如何访问外部网络,今天讨论另一个方向:外部网络如何访问到容器?

答案是:端口映射

docker 可将容器对外提供服务的端口映射到 host 的某个端口,外网通过该端口访问容器。容器启动时通过-p参数映射端口:

在这里插入图片描述

容器启动后,可通过 docker ps 或者 docker port 查看到 host 映射的端口。在上面的例子中,httpd 容器的 80 端口被映射到 host 32768 上,这样就可以通过 <host ip>:<32768> 访问容器的 web 服务了。

[root@docker ~]# curl 192.168.108.30:32768
<html><body><h1>It works!</h1></body></html>

在这里插入图片描述

除了映射动态端口,也可在 -p 中指定映射到 host 某个特定端口,例如可将 80 端口映射到 host 的 8080 端口:

[root@docker ~]# docker run -d -p 8080:80 httpd
25b04cb7f6d6c012c609251a425475fcf7da03c93feb20c1d951eb45cb1ef79b
[root@docker ~]#
[root@docker ~]# curl 192.168.108.30:8080
<html><body><h1>It works!</h1></body></html>

每一个映射的端口,host 都会启动一个 docker-proxy 进程来处理访问容器的流量:

![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.

以 0.0.0.0:8080->80/tcp 为例分析整个过程:

  1. docker-proxy 监听 host 的 8080 端口。
  2. 当 curl 访问 192.168.108.30:8080时,docker-proxy 转发给容器 172.17.0.3:80。
  3. httpd 容器响应请求并返回结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值