容器网络模式、容器的虚拟网络是如何实现的

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的问题。

接下来还有两个问题:

  1. 容器 A 的 IP 是 172.17.0.2,它为什么能够访问互联网?
  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 实现容器与外部网络之间的通信。

posted @ 2026-11-02 21:13  光風霽月  阅读(6)  评论(0)    收藏  举报