容器网络模式、容器的虚拟网络是如何实现的
Docker 网络模式
1. Bridge 网桥网络模式
Bridge 模式就是 Docker 给每个容器分配一个独立的内部 IP,再通过虚拟网桥将这些容器连接起来。
假设你有一台 Linux 服务器:
宿主机 IP:192.168.1.100
你在这台服务器上启动了两个 Docker 容器,分别运行 Nginx 和 Redis。
它们的网络结构如下:
外部网络
|
|
Linux 宿主机
192.168.1.100
|
|
docker0 网桥
172.17.0.1
|
+--------+--------+
| |
Nginx 容器 Redis 容器
172.17.0.2 172.17.0.3
端口:80 端口:6379
docker0 就是一台运行在 Linux 内核中的虚拟二层交换机,用来连接 Docker 容器。
这里实际上有两个网络:
1. 物理局域网:192.168.1.0/24
- 路由器:192.168.1.1
- Linux 宿主机:192.168.1.100
- 你的电脑:192.168.1.10
2. Docker 内部网络:172.17.0.0/16
- docker0:172.17.0.1
- 容器 A:172.17.0.2
- 容器 B:172.17.0.3
因此,宿主机和容器的 IP 不一样,是因为它们使用了不同的网络地址空间。
我们分别来看三个场景。
场景 1:Nginx 容器访问 Redis 容器
因为两个容器都连接到同一个 Bridge 网络,因此它们可以通过内部 IP 通信。
例如:
Nginx 容器
172.17.0.2
|
| 访问 172.17.0.3:6379
|
docker0 网桥
|
|
Redis 容器
172.17.0.3:6379
注意:容器之间通过同一个 Bridge 网络通信,不需要端口映射。
场景 2:外部电脑访问 Nginx 容器
假设你的电脑 IP 是:
192.168.1.10
现在你想通过浏览器访问 Nginx。
但是 Nginx 的 IP 是:
172.17.0.2
这个 IP 属于 Docker 内部网络,外部电脑通常无法直接访问。
怎么办?
使用端口映射。
启动 Nginx:
docker run -d -p 8080:80 nginx
其中:
8080:宿主机端口
80:容器端口
通信过程:
你的电脑
192.168.1.10
|
| 访问 192.168.1.100:8080
|
Linux 宿主机
192.168.1.100:8080
|
| 端口映射
|
Nginx 容器
172.17.0.2:80
这样就能访问 Nginx 了。
注意:在这个命令中,8080 端口默认发布到宿主机的所有网络接口,因此需要结合防火墙控制外部访问权限。
场景 3:Nginx 容器访问互联网
假设 Nginx 容器需要访问百度。
它可以直接发起网络请求:
Nginx 容器
172.17.0.2
|
|
docker0 网桥
172.17.0.1
|
| NAT 地址转换
|
Linux 宿主机
192.168.1.100
|
|
互联网
在默认配置下,Docker 会通过 NAT,让 Bridge 网络中的容器借助宿主机访问外部网络。
所以,Bridge 模式中的容器通常可以访问互联网。
场景四:宿主机访问 Nginx 容器
宿主机通常可以直接通过容器 IP 访问容器,不需要端口映射。
例如:
Linux 宿主机
192.168.1.100
|
| 直接访问 172.17.0.2:80
|
docker0(172.17.0.1)
|
|
Nginx 容器
172.17.0.2:80
在宿主机执行:
curl http://172.17.0.2:80
就可以访问容器中的 Nginx(假设 Nginx 正在监听该地址和端口)。
总结
网桥模式,就是 Docker 为每个容器分配独立 IP,通过虚拟网桥连接它们。宿主机可以直接访问容器 IP,而外部设备通常需要通过宿主机的端口映射访问容器。
2. Host 主机网络模式
Bridge 模式,是 Docker 给每个容器分配独立 IP,再通过网桥连接。
而 Host 模式最大的特点就是:容器不再拥有独立的网络,而是直接共享 Linux 宿主机的网络。
假设你的 Linux 宿主机 IP 为:
192.168.1.100
现在启动两个 Docker 容器:Nginx 和 Redis。
网络结构如下:
Linux 宿主机
IP:192.168.1.100
|
共享宿主机网络
|
+--------+--------+
| |
Nginx 容器 Redis 容器
端口 80 端口 6379
| |
+--------+--------+
|
宿主机网络
这里需要注意:
- Nginx 容器没有独立的容器 IP。
- Redis 容器也没有独立的容器 IP。
- 两个容器直接共享宿主机的 IP 地址和端口空间。
- 不需要通过 docker0 网桥通信。
举个实际例子
假设你想通过 Docker 部署一个 Nginx 网站。
执行:
docker run -d --network host --name nginx nginx
这里:
--network host
表示使用 Host 网络模式。
Nginx 默认监听 80 端口。
现在,你可以直接在另一台电脑的浏览器中输入:
http://192.168.1.100:80
访问 Nginx。
注意:整个过程不需要端口映射。
因为 Nginx 直接使用了宿主机的 80 端口。
Host 模式下,容器之间如何通信?
假设你又启动了一个 Redis 容器:
docker run -d --network host --name redis redis
现在:
Linux 宿主机(192.168.1.100)
|
+-------------------+
| |
Nginx 容器 Redis 容器
端口 80 端口 6379
| |
+-------------------+
共享网络
由于两个容器共享宿主机的网络命名空间,因此 Nginx 容器可以通过:
127.0.0.1:6379
访问 Redis。
Host 模式有什么问题?
主要问题是:端口冲突。
例如:
你的 Linux 宿主机已经运行了一个 Nginx 服务,占用了 80 端口。
现在,你又通过 Host 模式启动一个 Nginx 容器:
docker run -d --network host nginx
由于宿主机的 80 端口已经被占用,容器中的 Nginx 通常就无法再绑定该端口,导致服务启动失败。
但是在 Bridge 模式下,可以这样做:
docker run -d -p 8080:80 nginx
容器仍然监听自己的 80 端口,而宿主机使用 8080 端口对外提供访问,因此可以避开宿主机 80 端口的冲突。
总结
简而言之,Host 网络模式下,容器共享宿主机的 IP 和 Port 空间,我们是通过 Port 来区分容器和宿主机的,不需要映射 Port,但这也会带来一个问题,就是容器的 Port 会和宿主机的 Port 冲突。
3. None 无网络模式
简单来说,None 模式就是 Docker 不给容器配置外部网络连接,让容器处于网络隔离状态。
假设你的 Linux 宿主机 IP 为 192.168.1.100,现在启动一个 None 模式的容器。
Linux 宿主机
192.168.1.100
|
X 网络不连通
|
Docker 容器
network = none
与前面两种模式不同:
- Bridge: 容器有独立 IP,通过网桥通信。
- Host: 容器共享宿主机的网络。
- None: 容器没有外部网络连接。
举个实际例子
假设你有一个 Python 程序,只负责计算数据,不需要访问互联网或其他容器。
可以这样启动:
docker run -it --network none python:3.11 bash
进入容器后,查看网络:
ip addr
通常只能看到:
lo:127.0.0.1
这里的 lo 是回环网络接口。
它允许容器内部的程序通过 localhost 相互通信,但不能用来访问外部网络。
例如:
情况 1:容器访问互联网
curl https://www.baidu.com
❌ 无法访问,因为没有外部网络连接。
情况 2:容器访问宿主机
curl http://192.168.1.100:80
❌ 无法直接访问,因为没有连接宿主机的网络接口。
情况 3:容器访问自己的服务
假设容器内部有一个 Python 服务,监听 8000 端口:
curl http://127.0.0.1:8000
✅ 可以正常访问。
None 模式有什么用?
例如,你需要使用 Docker 运行一个离线数据处理程序:
Docker 容器
|
├── 读取本地数据
|
├── Python 数据计算
|
└── 保存计算结果
整个过程不需要联网,因此可以使用 None 模式,减少不必要的网络访问。
四、总结
| 网络模式 | 网络特点 |
|---|---|
| Bridge | 独立 IP,通过网桥通信 |
| Host | 共享宿主机 IP 和端口空间 |
| None | 不配置外部网络,只保留容器内部回环通信 |
一句话理解:None 模式就是容器拥有自己的网络命名空间,但不连接外部网络。
注意,None 只是网络隔离,并不代表容器获得了完整的安全隔离。
4. Container 容器共享网络模式
简单来说,Container 模式就是多个 Docker 容器共享同一个网络命名空间,也就是共享 IP 地址和端口空间。
它和 K8s Pod 网络机制非常相似。
假设 Linux 宿主机 IP 为 192.168.1.100,现在启动两个容器:
Linux 宿主机
192.168.1.100
|
docker0 网桥
|
172.17.0.2
|
+-------+-------+
| |
容器 A 容器 B
Nginx Python
端口 80 端口 5000
| |
+-------+-------+
|
共享 IP 和端口空间
这里需要注意:
- 容器 A 和容器 B 共享 IP:
172.17.0.2。 - 两个容器共享网络接口和端口空间。
- 两个容器可以通过
localhost相互通信。 - 两个容器不能同时监听相同的 IP、协议和端口组合。
举个实际例子
假设我们先启动一个 Nginx 容器:
docker run -d --name nginx nginx
假设 Docker 分配给它的 IP 为:
172.17.0.2
接下来启动 Python 容器,让它共享 Nginx 的网络:
docker run -it \
--network container:nginx \
python:3.11 bash
现在,两个容器共享同一个 IP。
假设你在 Python 容器中执行:
curl http://localhost:80
就可以访问 Nginx 容器的服务。
为什么?
因为两个容器共享网络命名空间,它们的 localhost 指向的是同一个网络环境。
Container 和 Host 模式有什么区别?
你可能会发现,Container 模式和 Host 模式都共享 IP 和端口空间。
但它们有一个关键区别:
Host 模式:
Linux 宿主机(192.168.1.100)
|
+------ 容器 A
|
+------ 容器 B
共享宿主机的网络
Container 模式:
Linux 宿主机(192.168.1.100)
|
docker0
|
172.17.0.2
|
+------ 容器 A
|
+------ 容器 B
共享指定容器的网络
也就是说:
- Host: 容器共享宿主机的网络。
- Container: 容器共享另一个容器的网络。
与 K8s Pod 有什么联系?
你之前问过:一个 Pod 中的多个 Container 是否具有相同的 IP?
答案就是:是的。
K8s Pod 内的容器网络机制与 Docker Container 模式非常相似。
例如:
K8s Pod
IP:10.244.1.5
|
+------+------+
| |
容器 A 容器 B
Nginx Python
端口 80 端口 5000
两个容器共享 Pod IP 和端口空间。
不过 Kubernetes 通常通过 Pod 的网络沙箱来实现共享,而不是简单地让一个业务容器共享另一个业务容器的网络。
总结
| 网络模式 | 共享的网络 |
|---|---|
| Bridge | 不共享,每个容器有独立 IP |
| Host | 共享宿主机网络 |
| None | 不连接外部网络 |
| Container | 共享指定容器的网络 |
一句话总结:Container 模式就是让多个容器共用同一个 IP 地址和端口空间,通过 localhost 就能相互通信。
你可以把它理解为:多个容器虽然是独立运行的程序,但它们共用一张网卡和一套网络配置。
5. Overlay 覆盖网络模式
前面介绍的 Bridge 模式,主要解决的是同一台宿主机上多个容器之间的通信问题。
而 Overlay 模式主要解决的是:不同宿主机上的 Docker 容器如何互相通信。
举个实际例子
假设现在有两台 Linux 服务器:
- 服务器 A:
192.168.1.100 - 服务器 B:
192.168.1.101
两台服务器分别运行一个 Docker 容器。
服务器 A 服务器 B
192.168.1.100 192.168.1.101
| |
Docker Docker
| |
容器 A 容器 B
10.0.0.2 10.0.0.3
| |
+------------------------------+
|
Overlay 网络
假设这两个容器都加入了同一个 Overlay 网络。
那么:
容器 A 可以直接通过 10.0.0.3 访问容器 B,即使它们运行在不同的物理服务器上。
例如,容器 B 运行 Nginx,监听 80 端口。
容器 A 就可以访问:
curl http://10.0.0.3:80
整个过程不需要为容器间通信配置端口映射。
Overlay 是如何实现的?
你可能会有一个疑问:
两台服务器明明是独立的,为什么它们的容器可以像在同一个局域网中一样通信?
这是因为 Overlay 在物理网络之上建立了一个虚拟网络。
Docker 通常使用 VXLAN 技术实现这个功能。
例如,容器 A 向容器 B 发送数据:
容器 A(10.0.0.2)
|
| 发送数据
↓
服务器 A(192.168.1.100)
|
| VXLAN 封装
↓
物理网络
|
↓
服务器 B(192.168.1.101)
|
| VXLAN 解封装
↓
容器 B(10.0.0.3)
简单理解:
Overlay 相当于在两台服务器之间建立了一条虚拟网络通道,让不同服务器上的容器看起来像连接在同一个虚拟局域网中。
实际应用场景
假设你部署一个分布式系统:
服务器 A
|
└── Nginx 容器
10.0.0.2
服务器 B
|
└── Python 容器
10.0.0.3
服务器 C
|
└── Redis 容器
10.0.0.4
如果三个容器加入同一个 Overlay 网络,它们就可以直接通过虚拟网络进行通信。
在 Docker 中,Overlay 网络主要配合 Docker Swarm 使用。即使连接的是普通独立容器,也需要让相关宿主机加入同一个 Swarm。
总结
| 网络模式 | 主要解决的问题 |
|---|---|
| Bridge | 同一台宿主机上的容器互联 |
| Host | 容器共享宿主机网络 |
| None | 容器不连接外部网络 |
| Container | 多个容器共享网络命名空间 |
| Overlay | 不同宿主机上的容器互联 |
一句话总结:Overlay 模式就是在多台宿主机之间建立虚拟网络,让不同服务器上的 Docker 容器能够直接通过容器 IP 通信。
你可以把它理解成:Bridge 主要连接一台服务器内的容器,而 Overlay 可以连接多台服务器上的容器。
6. Macvlan 物理网络模式
Macvlan = MAC + VLAN
- MAC(Media Access Control):媒体访问控制,通常指网卡的 MAC 地址。
- VLAN(Virtual Local Area Network):虚拟局域网。
因此,Macvlan 可以理解为一种基于 MAC 地址的虚拟网络技术。
它通过 Linux 内核的 Macvlan 技术,让一张物理网卡派生出多个虚拟网络接口,每个接口可以拥有独立的 MAC 地址和 IP 地址。
简单来说,Macvlan 模式就是让 Docker 容器直接接入宿主机所在的物理局域网,每个容器拥有独立的 IP 地址和 MAC 地址。
举个实际例子
假设你有一台 Linux 服务器,连接在公司的局域网中。
网络信息如下:
- 路由器:
192.168.1.1 - Linux 宿主机:
192.168.1.100 - 你的电脑:
192.168.1.10
现在,你在 Linux 服务器上创建两个 Macvlan 容器。
网络结构如下:
路由器
192.168.1.1
|
交换机
|
+----------+-----------+
| |
你的电脑 Linux 宿主机
192.168.1.10 192.168.1.100
|
Macvlan 网络
|
+------+------+
| |
容器 A 容器 B
192.168.1.101 192.168.1.102
Nginx Redis
注意,这里有一个重要特点:
容器 A、容器 B 和 Linux 宿主机都处于同一个物理局域网中。
因此,你可以直接在自己的电脑上访问:
192.168.1.101:80
从而访问容器 A 的 Nginx 服务。
不需要通过宿主机进行端口映射。
Macvlan 和 Bridge 有什么区别?
假设宿主机 IP 为 192.168.1.100。
Bridge 模式:
Linux 宿主机:192.168.1.100
|
docker0
|
容器 A:172.17.0.2
容器 B:172.17.0.3
容器使用 Docker 内部 IP。
你的电脑通常需要通过:
192.168.1.100:8080
访问容器中的 Nginx。
Macvlan 模式:
Linux 宿主机:192.168.1.100
|
Macvlan
|
容器 A:192.168.1.101
容器 B:192.168.1.102
容器直接使用局域网 IP。
你的电脑可以直接通过:
192.168.1.101:80
访问 Nginx。
为什么需要 Macvlan?
假设你的服务器上运行了三个 Nginx 容器,都监听 80 端口。
如果使用 Bridge 模式,通常需要分别映射到宿主机的不同端口:
192.168.1.100:8080 → 容器 A:80
192.168.1.100:8081 → 容器 B:80
192.168.1.100:8082 → 容器 C:80
而使用 Macvlan:
192.168.1.101:80 → 容器 A
192.168.1.102:80 → 容器 B
192.168.1.103:80 → 容器 C
每个容器有独立 IP,因此可以直接使用相同的服务端口。
需要注意,Macvlan 默认情况下,容器通常无法直接与宿主机通信,如果需要通信,需要额外配置。
五、总结
| 对比 | Bridge | Macvlan |
|---|---|---|
| 容器独立 IP | ✅ | ✅ |
| 容器 IP | Docker 内部网段 | 物理局域网网段 |
| 容器独立 MAC | ✅ | ✅ |
| 局域网电脑直接访问容器 IP | 通常不可以 | ✅ 通常可以 |
| 是否需要端口映射 | 外部访问通常需要 | 不需要 |
Macvlan 模式就是让每个 Docker 容器像一台真实的物理电脑一样,直接加入宿主机所在的局域网,拥有自己的 IP 和 MAC 地址。
你可以这样区分:
- Bridge: Docker 内部建立一个虚拟局域网。
- Macvlan: Docker 容器直接加入外部物理局域网。
7. IPvlan IP 网络模式
IPvlan 和刚才介绍的 Macvlan 非常相似。
它们的核心区别是:
- Macvlan: 每个容器拥有独立的 IP 和 MAC 地址。
- IPvlan: 每个容器拥有独立的 IP,但共享宿主机父网络接口的 MAC 地址。
举个实际例子
假设现在有一台 Linux 服务器:
宿主机 IP:192.168.1.100
宿主机 MAC:AA:BB:CC:DD:EE:FF
你在上面运行两个 Docker 容器,使用 IPvlan 的 L2 模式。
网络结构如下:
路由器
192.168.1.1
|
交换机
|
Linux 宿主机
192.168.1.100
|
eth0 网卡
MAC:AA:BB:CC:DD:EE:FF
|
IPvlan 网络
|
+-------+-------+
| |
容器 A 容器 B
192.168.1.101 192.168.1.102
| |
共享父接口 MAC 地址
此时,两个容器的信息如下:
| 设备 | IP 地址 | MAC 地址 |
|---|---|---|
| Linux 宿主机 | 192.168.1.100 | AA:BB:CC:DD:EE:FF |
| 容器 A | 192.168.1.101 | AA:BB:CC:DD:EE:FF |
| 容器 B | 192.168.1.102 | AA:BB:CC:DD:EE:FF |
可以看到:
虽然三个设备的 IP 地址不同,但对外使用的是同一个 MAC 地址。
那 IPvlan 如何区分不同的容器?
你可能会有疑问:
既然两个容器的 MAC 地址相同,数据到达宿主机后,怎么知道应该发给哪个容器?
答案是:通过 IP 地址进行区分。
例如,你的电脑想访问容器 A:
你的电脑
192.168.1.10
|
| 访问 192.168.1.101:80
|
交换机
|
|
Linux 宿主机 eth0
|
| 根据目标 IP 分发
|
容器 A
192.168.1.101:80
如果访问的是 192.168.1.102,数据就会被送到容器 B。
这里的关键是:
外部交换机看到的是同一个 MAC 地址,而 Linux 内核根据目标 IP 将数据分发给对应的容器。
IPvlan 和 Macvlan 有什么区别?
假设一台 Linux 服务器运行了 100 个 Docker 容器。
Macvlan 模式:
100 个容器
|
100 个独立 IP
|
100 个不同 MAC 地址
|
物理交换机
物理交换机需要学习和维护这些 MAC 地址。
IPvlan 模式:
100 个容器
|
100 个独立 IP
|
共享 1 个 MAC 地址
|
物理交换机
这样可以减少交换机需要维护的 MAC 地址数量。
因此,IPvlan 特别适合对 MAC 地址数量有限制的网络环境。
IPvlan 有哪些模式?
IPvlan 主要有两种常用工作模式:
| 模式 | 特点 |
|---|---|
| IPvlan L2 | 容器可以直接接入宿主机所在的二层局域网 |
| IPvlan L3 | 容器可以使用独立网段,通过三层路由实现通信 |
例如:
IPvlan L2:
宿主机:192.168.1.100
容器 A:192.168.1.101
容器 B:192.168.1.102
宿主机和容器可以处于同一个局域网网段。
IPvlan L3:
宿主机:192.168.1.100
容器 A:10.10.1.2
容器 B:10.10.2.2
容器可以处于不同网段,通过路由实现通信。外部网络访问这些容器时,还需要配置相应的路由。
另外,IPvlan L2 默认也存在宿主机父接口与容器之间的通信隔离,并不是共享 MAC 地址就可以直接互通。
总结
| 对比 | Macvlan | IPvlan L2 |
|---|---|---|
| 容器有独立 IP | ✅ | ✅ |
| 容器有独立 MAC | ✅ | ❌ |
| 可以直接接入物理局域网 | ✅ | ✅ |
| 通常需要端口映射 | ❌ | ❌ |
| 对交换机 MAC 表的占用 | 较多 | 较少 |
一句话总结:IPvlan 和 Macvlan 都可以让容器拥有独立 IP 并接入物理网络,但 Macvlan 给每个容器分配独立 MAC,而 IPvlan 让多个容器共享父接口 MAC,通过 IP 区分容器。
Docker 是如何实现虚拟网络的
1. Network Namespace
我们知道,正常情况下,一台 Linux 服务器有自己的网络环境,包括:
- IP 地址
- 网络接口(网卡)
- 路由表
- 网络端口
- 网络协议栈
例如:
Linux 宿主机
│
├── IP:192.168.1.100
├── 网卡:eth0
├── 端口:80、8080 等
└── 路由表
Network Namespace 的作用,就是让 Linux 系统能够创建多个相互隔离的网络环境。
简单理解:
一台 Linux 服务器,可以拥有多个相互独立的网络空间,每个空间都有自己的网卡、IP 地址、路由表和端口空间。
假设我们在 Linux 宿主机上创建两个 Docker 容器。
Linux 宿主机
192.168.1.100
|
+-------------+-------------+
| |
Container A Container B
| |
Network Namespace A Network Namespace B
| |
IP:172.17.0.2 IP:172.17.0.3
网卡:eth0 网卡:eth0
端口:80 端口:80
独立路由表 独立路由表
注意:
两个容器拥有独立的 Network Namespace,因此它们可以拥有自己的 IP、网卡和端口空间。
例如:
- 容器 A 的 Nginx 监听 80 端口。
- 容器 B 的 Nginx 也监听 80 端口。
这两个容器不会发生端口冲突。
为什么?
因为它们的网络端口空间是相互隔离的。
这也是为什么我们前面介绍的 Bridge 模式中,不同容器可以使用相同的端口。
需要注意,上图中的 IP 和 eth0 是 Docker 后续配置的。Network Namespace 本身只提供隔离环境,并不会自动为容器创建网卡或分配 IP。
2. Veth Pair
前面我们已经知道,Docker 通过 Network Namespace 为每个容器创建独立的网络环境。
但是,这就产生了一个问题:
既然容器的网络环境相互隔离,那么容器如何与宿主机通信呢?
答案就是:Veth Pair。
Veth Pair 的全称是 Virtual Ethernet Pair(虚拟以太网设备对)。
你可以把它理解成一根虚拟网线。
它有两个端点:
- 一端连接 Docker 容器。
- 另一端留在 Linux 宿主机中。
数据从一端发送,就可以从另一端接收。
举个实际例子
假设我们有一台 Linux 服务器:
宿主机 IP:192.168.1.100
现在启动一个 Docker 容器:
docker run -d --name nginx nginx
假设 Docker 给这个容器分配了 IP:
172.17.0.2
那么它的网络连接结构可以简化为:
Linux 宿主机
192.168.1.100
|
docker0 网桥
|
veth123
|
| Veth Pair
|
eth0
|
Nginx 容器
172.17.0.2
这里需要理解三个东西:
1. eth0:容器内部的虚拟网卡
进入 Nginx 容器,可以看到一张名为 eth0 的网卡。
它的 IP 地址是:
172.17.0.2
2. veth123:宿主机上的虚拟网卡
这张网卡位于 Linux 宿主机的网络命名空间中。
这里的 veth123 只是示例名称,实际名称由系统生成。
3. Veth Pair:连接两张虚拟网卡
eth0 和 veth123 实际上是一对相互连接的虚拟网络设备。
你可以把它理解成:
容器 eth0 ←→ 宿主机 veth123
当容器通过 eth0 发送数据时,数据就会从宿主机的 veth123 接收。
反过来也一样。
Veth Pair 如何实现容器之间的通信?
假设我们现在有两个 Docker 容器:
容器 A:172.17.0.2
容器 B:172.17.0.3
它们的网络结构如下:
Linux 宿主机
|
docker0 网桥
|
+--------+--------+
| |
veth-A veth-B
| |
| |
eth0 eth0
| |
容器 A 容器 B
172.17.0.2 172.17.0.3
假设容器 A 想访问容器 B:
ping 172.17.0.3
数据传输过程:
容器 A
172.17.0.2
|
↓
容器 A 的 eth0
|
↓
veth-A
|
↓
docker0 网桥
|
↓
veth-B
|
↓
容器 B 的 eth0
|
↓
容器 B
172.17.0.3
这里你应该能发现:
Veth Pair 负责连接容器与宿主机的网络,docker0 网桥负责将不同容器连接起来。
两者配合,就实现了 Bridge 模式下的容器通信。
为什么需要 Veth Pair?直接使用 docker0 不行吗?
不行,因为它们的作用不同。
你可以把 Docker 网络想象成一个真实的办公室局域网。
| 真实网络 | Docker 虚拟网络 |
|---|---|
| 电脑 | Docker 容器 |
| 电脑网卡 | 容器 eth0 |
| 网线 | Veth Pair |
| 交换机 | docker0 网桥 |
例如:
真实局域网:
电脑 A —— 网线 —— 交换机 —— 网线 —— 电脑 B
Docker 虚拟网络:
容器 A —— Veth Pair —— docker0 —— Veth Pair —— 容器 B
二者的工作原理非常相似。
Veth Pair 和 Network Namespace 有什么关系?
这是一个比较关键的知识点。
我们之前说过,Network Namespace 用于隔离网络环境。
而 Veth Pair 可以连接不同的 Network Namespace。
例如:
Linux 宿主机
Network Namespace
|
veth-A
|
| Veth Pair
|
eth0
|
Container A
Network Namespace A
也就是说:
- Network Namespace 负责将容器网络隔离。
- Veth Pair 负责在不同网络命名空间之间建立连接。
一个负责隔离,一个负责连接。
3. Linux Bridge
前面我们已经知道:
- Network Namespace:为容器创建独立的网络空间。
- Veth Pair:相当于虚拟网线,连接容器与宿主机。
- Linux Bridge:相当于虚拟交换机,连接多个容器。
这次我们重点理解:当容器 A 向容器 B 发送数据时,Linux Bridge 是怎么知道应该把数据转发给哪个容器的?
Linux Bridge 的工作原理
假设我们有一台 Linux 宿主机,运行着三个 Docker 容器:
Linux 宿主机
|
docker0 网桥
172.17.0.1
|
+----------+----------+
| | |
veth-A veth-B veth-C
| | |
eth0 eth0 eth0
| | |
容器 A 容器 B 容器 C
172.17.0.2 172.17.0.3 172.17.0.4
这里的 docker0 就像一台交换机。
那么问题来了:
当容器 A 向容器 B 发送数据时,docker0 怎么知道数据应该发给 B,而不是 C?
答案是:MAC 地址表(MAC Address Table)。
什么是 MAC 地址表?
我们知道,每张网卡都有 MAC 地址。
假设三个容器的网卡信息如下:
| 容器 | IP 地址 | MAC 地址 |
|---|---|---|
| 容器 A | 172.17.0.2 | AA:AA:AA:AA:AA:AA |
| 容器 B | 172.17.0.3 | BB:BB:BB:BB:BB:BB |
| 容器 C | 172.17.0.4 | CC:CC:CC:CC:CC:CC |
Linux Bridge 会维护一张 MAC 地址转发表,也叫 FDB(Forwarding Database,转发数据库)。
假设它已经学习到三个容器的 MAC 地址:
| MAC 地址 | 对应网桥端口 |
|---|---|
| AA:AA:AA:AA:AA:AA | veth-A |
| BB:BB:BB:BB:BB:BB | veth-B |
| CC:CC:CC:CC:CC:CC | veth-C |
这张表告诉 docker0:
- 要找容器 A,就从 veth-A 转发。
- 要找容器 B,就从 veth-B 转发。
- 要找容器 C,就从 veth-C 转发。
注意:Linux Bridge 主要根据 MAC 地址转发数据,而不是根据 IP 地址。
举个实际例子:容器 A 访问容器 B
假设:
容器 A:172.17.0.2
容器 B:172.17.0.3
现在容器 A 执行:
ping 172.17.0.3
我们看看数据是怎么传输的。
第一步:容器 A 确定容器 B 的 MAC 地址
容器 A 知道容器 B 的 IP 是:
172.17.0.3
但它还不知道容器 B 的 MAC 地址。
因此,容器 A 会发送一个 ARP 请求:
谁拥有 172.17.0.3?
请告诉 172.17.0.2。
这个请求通过:
容器 A → eth0 → veth-A → docker0
到达网桥。
因为 ARP 请求是广播帧,所以 docker0 会将它转发给同一网桥中的其他相关端口。
docker0
|
+---------+---------+
| |
容器 B 容器 C
| |
收到 ARP 请求 收到 ARP 请求
容器 B 发现请求的 IP 是自己的,于是回复:
我是 172.17.0.3。
我的 MAC 地址是 BB:BB:BB:BB:BB:BB。
容器 A 收到回复后,就知道容器 B 的 MAC 地址了。
第二步:容器 A 发送数据
现在容器 A 已经知道:
目标 IP:172.17.0.3
目标 MAC:BB:BB:BB:BB:BB:BB
于是,它发送以太网数据帧:
源 MAC:AA:AA:AA:AA:AA:AA
目标 MAC:BB:BB:BB:BB:BB:BB
数据经过 veth-A,到达 docker0。
第三步:docker0 查找 MAC 地址表
docker0 收到数据帧后,检查目标 MAC:
BB:BB:BB:BB:BB:BB
然后查询自己的 MAC 地址表:
BB:BB:BB:BB:BB:BB → veth-B
于是,docker0 就知道:
应该将数据从 veth-B 转发出去。
最终:
容器 A
|
↓
eth0
|
↓
veth-A
|
↓
docker0
|
| 查询 MAC 地址表
|
↓
veth-B
|
↓
eth0
|
↓
容器 B
整个通信过程就完成了。
需要补充的是,MAC 地址表并不是 Docker 预先把所有容器的 IP 和 MAC 填进去的。Linux Bridge 可以通过收到的数据帧中的源 MAC 地址,自动学习该 MAC 对应哪个端口。
如果 docker0 不知道目标 MAC 地址呢?
假设容器 A 发送了一个数据帧,但 docker0 的 MAC 地址表中没有对应记录。
这时,Linux Bridge 通常会采用一种叫 泛洪(Flooding) 的方式。
也就是:
把数据帧转发到除接收端口之外的其他相关网桥端口。
例如:
docker0
|
+---------+---------+
| |
veth-B veth-C
| |
容器 B 容器 C
容器 B 和容器 C 都可能收到这个数据帧。
但是:
- 容器 B 发现目标 MAC 是自己的,就接收并处理。
- 容器 C 发现目标 MAC 不是自己的,通常就丢弃。
之后,随着网桥学习到目标 MAC 对应的端口,就可以直接定向转发数据。
为什么说 Linux Bridge 是二层设备?
这里需要理解网络通信的两个概念:
| 网络层次 | 主要使用的地址 | 典型设备 |
|---|---|---|
| 第二层:数据链路层 | MAC 地址 | 交换机、Linux Bridge |
| 第三层:网络层 | IP 地址 | 路由器 |
例如:
容器 A 和容器 B 在同一个 Bridge 网络中:
172.17.0.2 → 172.17.0.3
它们位于同一网段,可以通过 Linux Bridge 进行二层通信。
但是,如果容器 A 想访问互联网:
172.17.0.2 → 8.8.8.8
这时候仅靠 Linux Bridge 就不够了。
因为目标 IP 不属于 Docker 内部网段,需要通过宿主机进行三层路由转发。
这就涉及我们下一步要介绍的 Routing(路由)和 NAT(网络地址转换)。
总结
到目前为止,我们已经学习了 Docker 虚拟网络的三个核心技术:
| 技术 | 核心作用 | 通俗理解 |
|---|---|---|
| Network Namespace | 网络隔离 | 每个容器拥有独立的网络空间 |
| Veth Pair | 连接网络空间 | 虚拟网线 |
| Linux Bridge | 根据 MAC 地址转发数据 | 虚拟交换机 |
一句话总结:Docker 通过 Network Namespace 隔离容器网络,通过 Veth Pair 连接容器与宿主机,再通过 Linux Bridge 根据 MAC 地址转发数据,实现同一 Bridge 网络内的容器通信。
接下来
我们已经解决了容器 A 如何访问容器 B的问题。
接下来还有两个问题:
- 容器 A 的 IP 是
172.17.0.2,它为什么能够访问互联网? - 外部电脑为什么能通过
192.168.1.100:8080访问容器的172.17.0.2:80?
这两个问题都与 Linux Routing 和 NAT 密切相关,也是理解 Docker 网络通信的最后一个重要环节。
4. Routing、NAT
前面我们已经知道,Docker 通过 Network Namespace、Veth Pair 和 Linux Bridge 实现了同一 Bridge 网络中容器之间的通信。
但现在有一个问题:
Docker 容器使用的是内部 IP(172.17.0.2),为什么它可以访问互联网?
这就需要 Routing 和 NAT 的配合。
Routing(路由)是什么?
简单来说,路由就是决定数据包应该从哪个网络接口发送出去,以及下一步应该到哪里。
举个实际例子
假设我们的网络结构如下:
互联网
|
路由器
192.168.1.1
|
Linux 宿主机
192.168.1.100
|
docker0 网桥
172.17.0.1
|
容器 A
172.17.0.2
现在容器 A 想访问:
ping 8.8.8.8
容器 A 会首先检查自己的路由表。
假设路由表中有这样一条记录:
default via 172.17.0.1 dev eth0
这句话的意思是:
如果目标 IP 不属于本地直接连接的网络,就把数据包发送给默认网关 172.17.0.1。
因此,通信过程为:
容器 A(172.17.0.2)
|
| 目标:8.8.8.8
↓
默认网关(172.17.0.1)
|
↓
Linux 宿主机
|
| 查询宿主机路由表
↓
宿主机 eth0(192.168.1.100)
|
↓
路由器(192.168.1.1)
|
↓
互联网
注意,这里的路由表在容器和宿主机中是分别存在的。
- 容器路由表: 决定将数据发送给哪个网关。
- 宿主机路由表: 决定如何将数据继续转发到外部网络。
不过,仅仅有路由还不够。
因为容器使用的是内部 IP:172.17.0.2。
这个地址不能直接在公共互联网中路由。
因此,我们需要 NAT。
NAT(网络地址转换)是什么?
NAT 的全称是:Network Address Translation(网络地址转换)。
简单理解:NAT 可以修改数据包中的源 IP 或目标 IP,让不同网络中的设备能够通信。
我们继续使用刚才的例子。
1. 容器访问互联网
假设:
宿主机 IP:192.168.1.100
容器 A IP:172.17.0.2
目标 IP:8.8.8.8
容器 A 发出数据包时:
源 IP:172.17.0.2
目标 IP:8.8.8.8
数据经过 docker0,到达 Linux 宿主机。
Docker 默认会通过 NAT 规则进行源地址转换。
例如:
转换前:
源 IP:172.17.0.2
目标 IP:8.8.8.8
转换后:
源 IP:192.168.1.100
目标 IP:8.8.8.8
这个过程叫作 SNAT(Source NAT,源地址转换)。
Docker 默认 Bridge 网络通常使用一种叫 MASQUERADE 的动态源地址转换方式。
这里的 192.168.1.100 仍然是局域网私有 IP,数据继续进入互联网时,家用或企业路由器通常还会进行一次 NAT。
2. 那返回的数据怎么办?
你可能会问:
既然容器 IP 被转换成了宿主机 IP,那么返回的数据怎么知道应该交给哪个容器?
这是因为 Linux 会通过连接跟踪(Conntrack)记录 NAT 连接信息。
例如:
容器 A:172.17.0.2
|
| SNAT
↓
宿主机:192.168.1.100
|
↓
互联网:8.8.8.8
当响应数据返回宿主机后,Linux 根据连接跟踪记录执行反向转换,再将数据交给容器 A。
因此,整个过程可以理解为:
容器借助宿主机的 IP 与外部网络进行通信。
外部电脑如何访问 Docker 容器?
前面我们讲的是:
容器 → 互联网
现在我们来看反方向:
外部电脑 → 容器
这时就涉及另一种 NAT:DNAT(Destination NAT,目标地址转换)。
举个例子
假设:
宿主机:192.168.1.100
Nginx 容器:172.17.0.2
Nginx 监听端口:80
我们启动容器:
docker run -d -p 8080:80 nginx
现在外部电脑访问:
192.168.1.100:8080
数据包到达宿主机时:
目标 IP:192.168.1.100
目标端口:8080
Docker 配置的端口发布规则会将目标地址转换为:
目标 IP:172.17.0.2
目标端口:80
这个过程就是 DNAT。
通信过程:
外部电脑
192.168.1.10
|
| 访问 192.168.1.100:8080
↓
Linux 宿主机
192.168.1.100
|
| DNAT
| 修改目标 IP 和端口
↓
docker0 网桥
|
↓
Nginx 容器
172.17.0.2:80
这样,外部电脑就成功访问到了容器中的 Nginx 服务。
需要注意,Docker 的端口发布具体实现可能还涉及用户态代理,但通过 DNAT 理解其主要工作机制是合适的。
SNAT 和 DNAT 有什么区别?
这是我们需要重点掌握的内容。
| 对比 | SNAT | DNAT |
|---|---|---|
| 中文名称 | 源地址转换 | 目标地址转换 |
| 修改内容 | 数据包源地址 | 数据包目标地址 |
| Docker 典型场景 | 容器访问互联网 | 外部访问容器 |
| 转换示例 | 172.17.0.2 → 192.168.1.100 | 192.168.1.100:8080 → 172.17.0.2:80 |
注意:这里描述的是两个典型场景,并不是说 SNAT 和 DNAT 只能分别用于这两种方向。
现在我们把 Docker 虚拟网络串起来
假设有两个 Docker 容器:
互联网
|
Linux 宿主机
192.168.1.100
|
Routing
NAT
|
docker0
172.17.0.1
|
+-------+-------+
| |
Veth A Veth B
| |
容器 A 容器 B
172.17.0.2 172.17.0.3
现在我们就能解释三种通信场景:
| 通信场景 | 主要使用的技术 |
|---|---|
| 容器 A → 容器 B | Veth Pair + Linux Bridge |
| 容器 A → 互联网 | Veth Pair + Bridge + Routing + SNAT |
| 外部电脑 → 容器 A | 端口映射(通常由 DNAT 实现)+ Routing + Bridge + Veth Pair |
其中:
- Network Namespace: 负责网络隔离。
- Veth Pair: 负责连接容器与宿主机网络。
- Linux Bridge: 负责同一二层网络内的数据转发。
- Routing: 负责跨网段的数据转发。
- NAT: 负责修改数据包的源地址或目标地址。
总结
到这里,我们已经基本理解了 Docker Bridge 虚拟网络的实现原理。
它实际上并不是 Docker 自己发明了一套全新的网络技术,而是利用了 Linux 内核已有的网络功能。
Docker 主要负责创建、配置和管理这些虚拟网络设备及相关规则,真正的数据转发工作主要由 Linux 内核完成。
5. iptables、Netfilter
前面我们已经知道,Docker 通过 Routing 和 NAT 实现了容器与外部网络的通信。
但是有一个问题:
Docker 是如何让 Linux 知道,应该把宿主机的 8080 端口映射到容器的 80 端口的?
答案就是:通过 Linux 的 Netfilter 网络框架和 iptables 规则。
iptables 是什么?
首先,我们需要区分两个概念:
- Netfilter:Linux 内核中的网络数据包处理框架,负责执行数据包过滤、NAT 等操作。
- iptables:用于配置 Netfilter 规则的工具。
简单理解:
iptables 负责制定规则,Linux 内核中的 Netfilter 负责执行规则。
例如,我们可以制定这样的规则:
如果数据包的目标是:
192.168.1.100:8080
就将它转发到:
172.17.0.2:80
这实际上就是 Docker 端口映射的核心原理。
需要说明的是,现代 Linux 还可以使用 nftables 管理网络规则。Docker Engine 默认使用 iptables 后端,也支持 nftables 后端。我们先以 iptables 为例理解。
举个实际例子
假设我们的网络环境如下:
外部电脑
192.168.1.10
|
|
Linux 宿主机
192.168.1.100
|
docker0
172.17.0.1
|
Nginx 容器
172.17.0.2
端口 80
现在,我们启动 Nginx:
docker run -d \
--name nginx \
-p 8080:80 \
nginx
注意:
-p 8080:80
表示将宿主机的 8080 端口映射到容器的 80 端口。
Docker 在后台做了什么?
在典型的 Linux Bridge 网络配置下,Docker 会自动创建相应的 iptables 规则。
这些规则可以简化理解为:
规则:
当数据包访问
192.168.1.100:8080
将目标地址修改为
172.17.0.2:80
这就是我们之前介绍的 DNAT(目标地址转换)。
数据包是如何被处理的?
现在外部电脑访问:
curl http://192.168.1.100:8080
数据传输过程:
外部电脑
192.168.1.10
|
| 目标:192.168.1.100:8080
|
↓
Linux 宿主机
|
↓
Netfilter
|
| 匹配 iptables NAT 规则
|
| DNAT:
| 192.168.1.100:8080
| ↓
| 172.17.0.2:80
|
↓
Linux 路由转发
|
↓
docker0 网桥
|
↓
Nginx 容器
172.17.0.2:80
这里要注意:
iptables 本身不负责传输数据,而是配置规则,由 Linux 内核按照规则处理数据包。
数据最终仍然需要通过路由、网桥和 Veth Pair 到达容器。
Docker 容器访问互联网也是通过 iptables 实现的吗?
是的,在默认 Bridge 网络中,Docker 通常也通过 iptables 配置 NAT 规则。
但这里使用的是 SNAT(源地址转换)。
例如:
Nginx 容器
172.17.0.2
|
| 访问 8.8.8.8
|
↓
Linux 宿主机
|
| Netfilter 执行 NAT 规则
|
| 源 IP 转换:
| 172.17.0.2
| ↓
| 192.168.1.100
|
↓
宿主机 eth0
|
↓
外部网络
Docker 默认使用的源地址转换方式通常叫 MASQUERADE(地址伪装)。
它是 SNAT 的一种实现方式,可以自动使用宿主机出口网络接口的 IP 地址。
因此,我们可以这样理解:
| 通信场景 | NAT 类型 | 主要修改内容 |
|---|---|---|
| 容器访问外部网络 | SNAT / MASQUERADE | 源 IP |
| 外部电脑访问容器映射端口 | DNAT | 目标 IP 和端口 |
我们能查看 Docker 创建的 iptables 规则吗?
当然可以。
假设我们在 Linux 服务器上执行:
docker run -d -p 8080:80 nginx
然后查看 NAT 规则:
sudo iptables -t nat -S DOCKER
在使用 iptables 后端的典型配置中,可以看到与 Docker 端口映射有关的规则。
你可能会看到类似的内容(示意):
-A DOCKER ... -p tcp --dport 8080
-j DNAT --to-destination 172.17.0.2:80
它表达的含义就是:
当数据包符合对应条件,并访问宿主机的 8080 端口时,将目标地址转换为容器的 172.17.0.2:80。
实际规则还包含接口、目标地址等匹配条件,具体内容会因 Docker 版本和配置而有所不同。
另外,也可以查看源地址转换规则:
sudo iptables -t nat -S POSTROUTING
其中可能看到 Docker 创建的 MASQUERADE 规则。
总结
到目前为止,Docker Bridge 网络的底层实现就比较清晰了。
| 核心技术 | 作用 |
|---|---|
| Network Namespace | 隔离容器网络 |
| Veth Pair | 连接容器与宿主机网络 |
| Linux Bridge | 实现容器之间的二层通信 |
| Routing | 实现跨网段通信 |
| NAT | 实现网络地址转换 |
| Netfilter / iptables | 执行和配置网络处理规则 |
一句话总结:Docker 利用 Linux 的 Network Namespace、Veth Pair 和 Bridge 构建容器虚拟网络,再利用 Routing、NAT 和 Netfilter 实现容器与外部网络之间的通信。

浙公网安备 33010602011771号