Skip to content
60.Docker»20.网络模式»LV005-Bridge桥接模式.md

LV005-Bridge桥接模式

Bridge桥接模式是docker run 默认模式,最为常用。所以 --stat = bridge 可以省略不写。

一、Bridge 模式的拓扑

当 Docker server 启动时,会在主机上创建一个名为 docker0 的虚拟网桥,此主机上启动的 Docker 容器会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。

接下来就要为容器分配 IP 了,Docker 会从 RFC1918 所定义的私有 IP 网段中,选择一个和宿主机不同的 IP 地址和子网分配给 docker0,连接到 docker0 的容器就从这个子网中选择一个未占用的 IP 使用。如一般 Docker 会使用 172.17.0.0/16 这个网段,并将 172.17.0.1/16 分配给 docker0 网桥(在主机上使用 ifconfig 命令是可以看到 docker0 的,可以认为它是网桥的管理接口,在宿主机上作为一块虚拟网卡使用)。单机环境下的网络拓扑如下,主机地址为 10.10.0.186/24。

img

通过示意图,可以清晰的看到,docker 容器在桥接模式下,会为每个容器分配单独的虚拟网卡、虚拟 IP。然后再统一通过 docker0 虚拟网卡,统一和宿主机的 eth0 交互。

二、有什么特点

(1)默认为每个容器分配单独的网络空间,彼此相互隔离。

(2)每个容器都单独的网卡、路由、IP 等一些列基本的网络设施。

(3)每个容器启动后,都会被分配一个独立的虚拟 IP。

(4)该模式会自动将宿主机上的所有容器,都链接到 docker0 的虚拟网卡上。

(5)外界主机不能直接访问宿主机内的容器服务,需要宿主机通过-p 做端口映射后访问宿主的映射端口。

三、网络配置过程

Docker 完成以上网络配置的过程大致是这样的:

(1)在主机上创建一对虚拟网卡 veth pair 设备。veth 设备总是成对出现的,它们组成了一个数据的通道,数据从一个设备进入,就会从另一个设备出来。因此,veth 设备常用来连接两个网络设备。

(2)Docker 将 veth pair 设备的一端放在新创建的容器中,并命名为 eth0。另一端放在主机中,以 veth65f9 这样类似的名字命名,并将这个网络设备加入到 docker0 网桥中,可以通过 brctl show 命令查看。

shell
apt-get install bridge-utils # 安装 brctl
brctl show

【例】

shell
  /workspace git:(main) brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.0242d270dc72       no

(3)从 docker0 子网中分配一个 IP 给容器使用,并设置 docker0 的 IP 地址为容器的默认网关。

四、容器通信

在 bridge 模式下,连在同一网桥上的容器可以相互通信(若出于安全考虑,也可以禁止它们之间通信,方法是在 DOCKER_OPTS 变量中设置–icc = false,这样只有使用–link 才能使两个容器通信)。

Docker 可以开启容器间通信(意味着默认配置--icc = true),也就是说,宿主机上的所有容器可以不受任何限制地相互通信,这可能导致拒绝服务攻击。进一步地,Docker 可以通过--ip_forward 和--iptables 两个选项控制容器间、容器和外部世界的通信。

容器也可以与外部通信,我们看一下主机上的 Iptable 规则,可以看到这么一条:

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

这条规则会将源地址为 172.17.0.0/16 的包(也就是从 Docker 容器产生的包),并且不是从 docker0 网卡发出的,进行源地址转换,转换成主机网卡的地址。这么说可能不太好理解,举一个例子说明一下。假设主机有一块网卡为 eth0,IP 地址为 10.10.101.105/24,网关为 10.10.101.254。从主机上一个 IP 为 172.17.0.1/16 的容器中 ping 百度(180.76.3.151)。IP 包首先从容器发往自己的默认网关 docker0,包到达 docker0 后,也就到达了主机上。然后会查询主机的路由表,发现包应该从主机的 eth0 发往主机的网关 10.10.105.254/24。接着包会转发给 eth0,并从 eth0 发出去(主机的 ip_forward 转发应该已经打开)。这时候,上面的 Iptable 规则就会起作用,对包做 SNAT 转换,将源地址换为 eth0 的地址。这样,在外界看来,这个包就是从 10.10.101.105 上发出来的,Docker 容器对外是不可见的。

那么,外面的机器是如何访问 Docker 容器的服务呢?我们首先用下面命令创建一个含有 web 应用的容器,将容器的 80 端口映射到主机的 80 端口。

shell
docker run --name=nginx_bridge --net=bridge -p 80:80 -d nginx

然后查看 Iptable 规则的变化,发现多了这样一条规则:

shell
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.17.0.2:80

此条规则就是对主机 eth0 收到的目的端口为 80 的 tcp 流量进行 DNAT 转换,将流量发往 172.17.0.2:80,也就是我们上面创建的 Docker 容器。所以,外界只需访问 10.10.101.105:80 就可以访问到容器中的服务。

除此之外,我们还可以自定义 Docker 使用的 IP 地址、DNS 等信息,甚至使用自己定义的网桥,但是其工作方式还是一样的。

五、bridge 模式示例

这部分在 CNB 的默认云原生开发环境中进行,开始之前先删除之前的所有容器。

shell
docker ps -a  #列出所有容器列表
docker rm -f $(docker ps -qa)  #强制移除所有容器
docker ps -a

1. 未启动容器

1.1宿主机网关信息

我们执行以下命令查看:

shell
docker inspect bridge    # 查看桥接网络模式 docker0 虚拟网卡的详细信息(子网网段、网关等)

【例】

image-20251101232144162

可以看到默认的bridge网络的子网网段为172.17.0.0/16,这是一个CIDR(无类别域间路由)表示法,这种表示法用于指定IP地址的范围和子网掩码。在这里就表示:

shell
网络地址:172.17.0.0
子网掩码:255.255.0.0,即前16位是网络部分,后16位是主机部分。

可用的IP地址范围:从172.17.0.1到172.17.255.254(因为172.17.0.0是网络地址,172.17.255.255是广播地址,这两个地址不能分配给主机)。总IP数量:65,534个可用地址。后续在此宿主机中创建的bridge模式的容器都会在这个网段中。

在CIDR表示法中,IP地址后跟一个斜杠和一个数字,这个数字表示网络前缀的位数。

1.2 宿主机网卡信息

shell
ifconfig

【例】

shell
  /workspace git:(main) ifconfig
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        inet6 fe80::42:d2ff:fe70:dc72  prefixlen 64  scopeid 0x20<link>
        ether 02:42:d2:70:dc:72  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 5  bytes 526 (526.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.27.247.2  netmask 255.255.255.0  broadcast 172.27.247.255
        ether 02:42:ac:1b:f7:02  txqueuelen 0  (Ethernet)
        RX packets 13005  bytes 27367117 (26.0 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 11948  bytes 10653991 (10.1 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 522  bytes 52721 (51.4 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX pacets 522  bytes 52721 (51.4 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

1.3 宿主机 80 端口

我们后面要用 nginx 来测试,nginx 服务启动后占用的端口为 80,这里我们先看一下宿主机 80 端口的情况:

shell
docker ps -a   # 实验前,查看是否有启动的容器,如果有暂时先停掉,特别是 nginx 容器
netstat -nalpt # 实验前,先查看宿主机的(80)端口占用情况

查看目的:可以验证在桥接模式下,容器自始至终使用的都是容器自身的虚拟网卡和虚拟 IP,并不会占用宿主机的(80)端口和 IP。桥接模式实验结束后,会再次查看宿主机的 80 端口有没被占用(实际情况下,不会被占用的)

【例】

image-20251101200032288

会发现这里并没有任何服务占用 80 端口。

2. 启动 nginx 容器

2.1 启动容器

shell
docker images                               # 查看镜像列表
docker run -d --name nginx_V1 nginx:alpine  # 默认就是桥接,不用--net 单独指定 bridge
docker ps                                   # 查看已启动的容器

【例】

shell
  /workspace git:(main) docker run -d --name nginx_V1 nginx:alpine
Unable to find image 'nginx:alpine' locally
alpine: Pulling from library/nginx
2d35ebdb57d9: Pull complete
8f6a6833e95d: Pull complete
194fa24e147d: Pull complete
3eaba6cd10a3: Pull complete
df413d6ebdc8: Pull complete
d9a55dab5954: Pull complete
ff8a36d5502a: Pull complete
bdabb0d44271: Pull complete
Digest: sha256:b3c656d55d7ad751196f21b7fd2e8d4da9cb430e32f646adcf92441b72f82b14
Status: Downloaded newer image for nginx:alpine
bb02924f3afb203f775be5e49f3a94e7bc1191fd8b4e647af40dd74a4d1748ae
  /workspace git:(main) docker ps -a
CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS          PORTS     NAMES
bb02924f3afb   nginx:alpine   "/docker-entrypoint.…"   29 seconds ago   Up 29 seconds   80/tcp    nginx_V1

从这里可以看出使用的端口信息是 80/tcp。

2.2 查看宿主机网卡信息

shell
  /workspace git:(main) ifconfig
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        #...

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.27.247.2  netmask 255.255.255.0  broadcast 172.27.247.255
        #...

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        #...

vethca790d0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet6 fe80::3c60:9aff:fe32:b54c  prefixlen 64  scopeid 0x20<link>
        ether 3e:60:9a:32:b5:4c  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 15  bytes 1322 (1.2 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

会发现多了一个 vethca790d0 的网卡,这个网卡就是桥接模式下 nginx 容器的虚拟网卡名称。

2.3 容器的网络配置信息

通过以下命令查看容器的详细网络配置信息:

shell
docker ps
docker inspect nginx_V1 #通过 nginx 容器别名查看容器的详细信息,可以看到容器的网关和 IP 172.17.0.2

【例】

image-20251101201019148

可以看到独立的 IP、Port、GateWay、Mac 等资源。

2.4 访问容器的 nginx 服务

在宿主机下执行:

shell
curl 172.17.0.2  #桥接模式下,系统自动为 nginx 容器分配的虚拟 IP

【例】

shell
  /workspace git:(main) curl 172.17.0.2
#...
<h1>Welcome to nginx!</h1>
# ...

表示可以正常访问。

2.5 再开一个 Tomcat 容器

Tips:Tomcat 是一个常见的免费的 web 服务器。

我们执行下面的命令再启动一个 Tomcat 容器

shell
docker run -it -p 8080:8080 tomcat:8.5.46-jdk8-openjdk /bin/bash  # 进入其他容器内部

然后在这个容器内部使用 curl 命令访问 nginx 容器的 ip:

shell
curl 172.17.0.2  # 在 Tomcat 容器中,访问 nginx 容器

【例】

shell
  /workspace git:(main) docker run -it -p 8080:8080 tomcat:8.5.46-jdk8-openjdk /bin/bash
Unable to find image 'tomcat:8.5.46-jdk8-openjdk' locally
8.5.46-jdk8-openjdk: Pulling from library/tomcat
092586df9206: Pull complete
ef599477fae0: Pull complete
4530c6472b5d: Pull complete
d34d61487075: Pull complete
272f46008219: Pull complete
12ff6ccfe7a6: Pull complete
f26b99e1adb1: Pull complete
21bec9c8ea28: Pull complete
b8a32f28e27c: Pull complete
94fdd0ba0430: Pull complete
Digest: sha256:bb4ceffaf5aa2eba6c3ee0db46d863c8b23b263cb547dec0942e757598fd0c24
Status: Downloaded newer image for tomcat:8.5.46-jdk8-openjdk

root@3816fbd634a6:/usr/local/tomcat# curl 172.17.0.2
#...
<h1>Welcome to nginx!</h1>
#...

会发现也是可以正常访问的。此时,先不退出 tomcat 容器时,在新窗口用 ifconfig 命令,可以看到宿主机两块虚拟网卡(分别对应 nginx 容器和 tomcat 容器的)。

shell
  /workspace git:(main) ifconfig
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        #...

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        #...

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        #...

veth53406c3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet6 fe80::1426:60ff:fe86:3f9e  prefixlen 64  scopeid 0x20<link>
        ether 16:26:60:86:3f:9e  txqueuelen 0  (Ethernet)
        RX packets 9  bytes 628 (628.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 17  bytes 2071 (2.0 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

vethca790d0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet6 fe80::3c60:9aff:fe32:b54c  prefixlen 64  scopeid 0x20<link>
        ether 3e:60:9a:32:b5:4c  txqueuelen 0  (Ethernet)
        RX packets 14  bytes 2550 (2.4 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 36  bytes 2788 (2.7 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

2.6 再次查看宿主机 80 端口

shell
netstat -nalpt # 查看端口占用情况
curl 172.17.0.2 # 访问 nginx_V1 容器的 nginx 服务

【例】

shell
  /workspace git:(main) netstat -nalpt
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:8686            0.0.0.0:*               LISTEN      71/node
tcp        0      0 127.0.0.11:34341        0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:36000           0.0.0.0:*               LISTEN      87/sshd: /usr/sbin/
tcp      141      0 172.27.247.2:33066      120.53.74.30:443        ESTABLISHED 259/node
tcp        0      0 172.27.247.2:8686       172.27.247.1:46526      ESTABLISHED 71/node
tcp       21      0 172.27.247.2:8686       172.27.247.1:46534      ESTABLISHED 259/node
tcp6       0      0 :::8080                 :::*                    LISTEN      -
  /workspace git:(main) curl 172.17.0.2
#...
<h1>Welcome to nginx!</h1>
# ...

发现宿主机的 80 端口,并没有被占用,curl 也正常访问了 nginx 服务。为什么在宿主机还能通过 curl 172.17.0.2 访问 nginx 容器呢?实际情况该 curl 命令此时访问的是容器内部的 80 端口。桥接模式下,宿主机和容器之间、同一宿主机下容器和容器之间的网络是互联互通的。非宿主机的其他主机,即使和宿主机是同一网段的局域网,也不能直接访问宿主内的容器!

2.7 查看 nginx 容器端口

shell
docker ps  # 查看正在运行的容器列表
docker exec -it 3cae7605916d /bin/sh  # 进入 nginx 容器内部(注:这里使用/bin/bash 会报错)
netstat -nalpt

【例】

image-20251101202903706

注意:此处,为什么不是/bin/bash,为什么用/bin/bash 无法进入容器内部?点击这里 Docker 报错:OCI runtime exec failed: exec failed: container_linux.go:380: starting container process 详解

2.8 同网段的其他宿主机

shell
curl 172.17.0.2  #打开其他虚拟主机,访问虚拟主机 nginx 容器服务,发现不能访问

img

这里照搬一张图,因为我是在 CNB 云原生开发中验证,暂时还不知道怎么搞一个同网段的其他主机,这里就没有尝试了。

该结论在上面已经提及过,桥接模式下,非宿主机的其他主机,即使和宿主机是同一网段的局域网也无法直接访问容器内部, 但 并不意味着没有办法间接访问。这里简单科普一下,同一网段的其他非宿主机,可以访问宿主机 docker run -p 8000:8080, 前面的映射端口,比如 8000。

六、总结

  • docker run 命令不带--net 参数时,默认就是桥接模式。
  • 桥接模式下,同一个宿主机内的容器,彼此的网络是互通的。
  • 桥接模式下容器的虚拟 IP,容器内部彼此之间可以访问,非宿主机的其他其他主机无法直接访问容器内部的服务。

注意:桥接模式下,虽然外界主机不能直接访问宿主机容器内部的服务,但是可以通过间接方式访问宿主机 docker run -p 时,对外暴露的映射端口。比如:docker run -p 8000:8080,其他主机可访问宿主机 IP: 8000 端口,而不能直接访问宿主机内容器的 8080 端口。

参考资料:

Docker:网络模式详解 - Gringer - 博客园

Docker 四种网络模式(Bridge,Host,Container,None) - wq9 - 博客园

Docker 学习:容器五种(3+2)网络模式 | bridge 模式 | host 模式 | none 模式 | container 模式 | 自定义网络模式详解-CSDN 博客

莫道桑榆晚 为霞尚满天.