↓ 跳过正文

显示设置

330
Docker 网络模式梳理:从 Bridge 到 IPvlan

Docker 网络模式梳理:从 Bridge 到 IPvlan

·4177 字·9 分钟
目录

参考视频:Docker networking is CRAZY!! — NetworkChuck

最近在学习、折腾 Docker,除了把容器跑起来,也想弄清楚它背后的网络:容器为什么能访问外网?两个容器应该通过什么地址通信?为什么有时需要映射端口,有时又可以直接用容器 IP 访问?

跟着 NetworkChuck 的视频研究了一遍,把几种网络模式和容易混淆的地方整理在这里,方便以后配置和排查时查阅。

下面主要以 Linux 上的 Docker Engine 和 IPv4 网络为背景。Docker Desktop 中还有一层虚拟机,不能把原生 Linux 上的网络行为直接套用过去。

一、先理清:Docker 网络模式决定了什么?
#

容器中的进程需要网卡、IP、路由和端口,才能与其他进程通信。

在常见的独立容器网络中,Docker 通过 Linux 的 Network Namespace,也就是网络命名空间,为容器提供独立的网络环境,再通过不同的网络驱动,把它连接到宿主机或外部网络。

理解各种模式,可以先抓住三个问题:

  • 容器拥有独立的网络栈,还是共享宿主机的网络栈?
  • 容器 IP 属于 Docker 内部子网,还是现有局域网?
  • 数据包通过网桥、路由,还是跨主机隧道到达目标?

另外,“网络驱动”和“网络实例”需要分开看:

docker network ls

通常会看到:

NAME      DRIVER
bridge    bridge
host      host
none      null

NAME 是网络名称,DRIVER 是底层驱动。默认的 bridge 网络和自己创建的 Bridge 网络,都使用 bridge 驱动;IPvlan L2 和 L3,则是 ipvlan 驱动的不同工作模式。

二、Bridge:在宿主机内部连接容器
#

Bridge 是最常见的 Docker 网络驱动。

不指定网络运行普通容器时,默认会连接名为 bridge 的网络。在 Linux 上,它通常对应宿主机上的 docker0 网桥。

Docker 通过一对虚拟网卡,也就是 veth pair,将容器连接到网桥上。可以把这个网桥理解为宿主机里的一台虚拟交换机:

容器 A:eth0 ── veth ──┐
                      ├── docker0 ── 宿主机网络 ── 外部网络
容器 B:eth0 ── veth ──┘

每个容器拥有自己的网络命名空间,使用独立的 IP、接口和路由。同一 Bridge 网络中的容器,默认可以互相通信。

1. 容器如何访问外网?
#

默认情况下,Docker 会通过 NAT,让容器借助宿主机访问外网:

容器 IP → 宿主机进行源地址转换 → 外部网络

因此,外部服务器看到的通常是宿主机的地址,而不是容器内部的地址。

容器 IP 由 Docker 的 IPAM 管理和分配,并不是向家里的路由器申请 DHCP 租约。

2. 外部如何访问容器?
#

容器能访问外网,不代表局域网中的设备就能主动访问容器。

最常见的处理方式是发布端口:

docker run -d \
  --name web \
  -p 8080:80 \
  nginx:alpine

-p 8080:80 表示:

宿主机 8080 端口 → 容器 80 端口

局域网设备访问:

http://宿主机IP:8080

这里有几个容易混淆的点:

  • 同一 Bridge 网络中的容器互访,通常不需要 -p。
  • 镜像中的 EXPOSE 是端口声明,不等于发布端口。
  • 原生 Linux 宿主机通常可以直接访问 Bridge 容器 IP,不能将它与局域网中的其他设备混为一谈。
  • 应用需要监听容器可访问的接口;如果只监听容器内的 127.0.0.1,发布端口也不能让请求正常进入应用。

以上讨论的是默认配置;另外配置直接路由、防火墙规则时,访问方式可以不同。Bridge 官方文档

3. 为什么更适合使用自定义 Bridge?
#

默认 Bridge 能用,但实际组织应用时,我会优先创建自定义网络:

docker network create app-net

启动 Web 容器:

docker run -d \
  --name web \
  --network app-net \
  -p 8080:80 \
  nginx:alpine

从同一网络中的临时容器访问它:

docker run --rm \
  --network app-net \
  busybox wget -qO- http://web

这里直接用 web 作为地址,因为自定义 Bridge 提供容器名称和网络别名的自动解析。

默认 bridge 不具备同样的自动容器名解析能力,通常只能使用 IP,或依赖旧的 --link 等方式。

自定义网络还可以把不同项目分开。Web、API、数据库属于同一个项目,就放进同一个网络;不相关的应用使用各自的网络。

对普通单机应用,自定义 Bridge 是一个很合适的起点。默认与自定义 Bridge 的区别

三、Host:直接共享宿主机网络
#

Host 模式的思路更直接:

docker run -d \
  --name web-host \
  --network host \
  nginx:alpine

容器共享宿主机的网络命名空间,不再拥有独立的容器网络 IP。

容器中的 Nginx 监听 80 端口,相当于直接在宿主机网络中监听 80。因此不需要端口映射,-p 也不会起作用。

这减少了 Bridge 和 NAT 带来的网络处理,但也有两个直接影响:

  • 网络隔离减少,容器可以使用宿主机的网络环境。
  • 端口需要统一管理,不能与宿主机上的其他服务冲突。

例如,宿主机上已有服务绑定同一地址的 80 端口,再启动这个 Nginx 就可能失败。

这里共享的是网络命名空间,不代表文件系统、进程等其他隔离也全部取消。

本文中的 Host 用法以原生 Linux 为前提,不直接适用于当时的 Docker Desktop for Mac、Windows 环境。Host 官方文档

四、Macvlan:让容器像一台局域网设备
#

Bridge 模式通常通过“宿主机 IP + 映射端口”提供服务。

Macvlan 则可以让每个容器拥有自己的 MAC 地址和局域网 IP。从其他设备的视角看,它就像一台直接接入物理网络的设备。

例如:

路由器:    192.168.1.1
宿主机:    192.168.1.10
容器 A:    192.168.1.201
容器 B:    192.168.1.202

局域网设备可以直接访问容器 IP,不需要通过宿主机发布端口。

1. 创建一个 Macvlan 网络
#

假设物理网卡为 eth0,局域网为 192.168.1.0/24:

docker network create -d macvlan \
  --subnet=192.168.1.0/24 \
  --gateway=192.168.1.1 \
  --ip-range=192.168.1.192/27 \
  -o parent=eth0 \
  lan-macvlan

几个参数的含义:

参数作用
--subnet容器接入的子网
--gateway该子网的网关
--ip-rangeDocker 自动分配地址的范围
parent承载这个网络的宿主机接口

启动容器:

docker run -d \
  --name web-lan \
  --network lan-macvlan \
  --ip 192.168.1.201 \
  nginx:alpine

配置正确后,局域网设备可以直接访问:

http://192.168.1.201

示例中的网卡名和地址需要按实际环境修改。用于容器的地址范围,也要从局域网 DHCP 地址池中排除,并确认没有其他设备使用。

2. Macvlan 容易踩的几个坑
#

上游网络需要允许一个端口出现多个 MAC。

多个容器的 MAC 会通过同一个物理接口进入交换机。端口安全策略、虚拟交换机和某些云网络可能限制这种行为。在虚拟机里实验时,还可能需要调整混杂模式等设置。

宿主机默认不能直接访问自己的 Macvlan 容器。

这是 Linux 内核机制带来的限制。出现“其他电脑能访问,宿主机自己却访问不了”的情况,不一定是应用有问题。

需要互访时,可以增加宿主机侧的 Macvlan 接口并配置路由,或者给容器再连接一个 Bridge 网络。

Docker 不会自动与路由器协调地址。

--ip-range 只约束 Docker 的地址分配,不会通知路由器预留地址。地址冲突仍然需要自己避免。Macvlan 官方文档

3. Macvlan 与 802.1Q VLAN
#

视频还介绍了通过 VLAN 子接口连接 Macvlan 的方式。

例如,父接口使用:

eth0.20

对应 VLAN 20;使用 eth0.30,则对应 VLAN 30。

这允许多个容器网络通过一条物理链路接入不同 VLAN。不过,宿主机配置只是其中一环,上游交换机端口也必须配置 Trunk,并允许对应 VLAN 通过。

另外,Macvlan 的 bridge 工作模式,与 Docker 的 bridge 网络驱动是两个不同概念。Macvlan 802.1Q 配置说明

五、IPvlan L2:保留独立 IP,共享 MAC
#

IPvlan L2 和 Macvlan 的使用目标比较接近,都可以让容器直接接入现有网络。

最直观的区别是 MAC 地址:

对比项MacvlanIPvlan L2
容器 IP各自独立各自独立
对外使用的 MAC每个容器不同共享父接口的 MAC
上游交换机看到的情况一个端口后有多个 MAC多个 IP 对应同一个 MAC

如果交换机限制单个端口后面的 MAC 数量,IPvlan L2 就值得考虑。

创建示例:

docker network create -d ipvlan \
  --subnet=192.168.1.0/24 \
  --gateway=192.168.1.1 \
  --ip-range=192.168.1.224/27 \
  -o parent=eth0 \
  -o ipvlan_mode=l2 \
  lan-ipvlan

这里同样要提前预留地址范围。

共享 MAC 并不代表共享 IP,也不能省去网段、VLAN 和地址规划。IPvlan 同样存在容器与宿主机默认隔离的行为,不能假定它会自动解决宿主机互访的问题。IPvlan 官方文档

六、IPvlan L3:通过路由连接容器子网
#

IPvlan L3 把关注点从二层接入转向了三层路由。

容器可以使用独立于物理局域网的子网,由宿主机承担连接容器网络与外部网络的路由角色。

假设:

物理局域网:    192.168.1.0/24
Docker 宿主机: 192.168.1.10
容器子网:      10.20.0.0/24

创建网络:

docker network create -d ipvlan \
  --subnet=10.20.0.0/24 \
  -o parent=eth0 \
  -o ipvlan_mode=l3 \
  routed-net

启动容器:

docker run -d \
  --name web-routed \
  --network routed-net \
  --ip 10.20.0.5 \
  nginx:alpine

网络虽然创建好了,局域网中的其他设备却不一定知道如何到达 10.20.0.5。

通常需要在上游路由器中增加:

目标网段:10.20.0.0/24
下一跳:  192.168.1.10

它表达的意思是:访问这个容器子网的流量,交给 Docker 宿主机。

IPvlan L3 不在容器网络中转发二层广播和组播,也不会自动提供 Bridge 默认出网时的 NAT。因此,访问互联网还需要考虑回程路由,以及由哪个上游设备执行必要的 NAT。

这一模式下,“创建成功”和“网络连通”之间,还差完整的路由设计。排查时必须同时看数据包怎么出去、怎么回来。IPvlan L3 官方说明

七、Overlay:连接不同宿主机上的容器
#

前面的讨论主要围绕单台 Docker 宿主机。Overlay 解决的是跨宿主机通信。

它在底层网络之上建立逻辑网络,让分布在不同宿主机上的容器能够通过这个网络通信。

Docker 内置的 Overlay 网络依赖 Swarm。创建允许独立容器加入的网络,可以使用:

docker network create \
  -d overlay \
  --attachable \
  app-overlay

这需要在已经初始化的 Swarm 管理节点上执行。相关宿主机也要加入同一个 Swarm,并满足节点之间的端口和网络要求。

暂时只有单机部署需求时,先把 Bridge 理解清楚就够了。Overlay 可以等到需要多机部署时再深入。Overlay 官方文档

八、None:不连接外部网络
#

None 模式很简单:

docker run --rm \
  --network none \
  alpine ip addr

容器保留独立的网络命名空间,但默认只有回环接口 lo,没有正常连接外部网络的接口。

它适合不需要联网的任务,也可以作为手动配置容器网络的起点。这里限制的是网络连接,不能直接等同于完整的安全沙箱。None 官方文档

九、选型时先看需求
#

需求优先考虑
单机部署 Web、API、数据库等应用自定义 Bridge
需要直接共享宿主机网络Host
容器需要独立局域网 IP 和 MACMacvlan
容器需要独立局域网 IP,希望减少 MAC 数量IPvlan L2
容器使用独立子网,通过路由接入IPvlan L3
Swarm 环境中的跨主机通信Overlay
不需要外部网络连接None

对我目前学习和单机部署的需求,自定义 Bridge 可以作为默认选择。有了独立局域网 IP、共享宿主机网络或者跨主机通信的明确需求,再考虑其他模式。

十、留作回查的命令和排查顺序
#

查看网络:

docker network ls
docker network inspect app-net

查看容器使用的网络模式和地址:

docker inspect -f '{{.HostConfig.NetworkMode}}' web
docker inspect -f '{{json .NetworkSettings.Networks}}' web

查看发布的端口:

docker port web

给运行中的容器连接或断开自定义网络:

docker network connect app-net 容器名
docker network disconnect app-net 容器名

在 Linux 宿主机中查看接口和路由:

ip addr
ip route

如果容器镜像中带有相应工具,也可以进入容器检查:

docker exec 容器名 ip addr
docker exec 容器名 ip route

遇到网络不通,按下面的顺序检查:

  1. 确认访问路径。 访问的是容器 IP,还是宿主机发布的端口?
  2. 确认网络归属。 两个容器是否在同一网络?跨网络通信是否有相应配置?
  3. 区分 DNS 与连通性。 名称解析失败和 IP 不通是不同的问题。
  4. 确认应用监听。 服务是否启动?监听的是哪个地址和端口?
  5. 检查转发路径。 端口发布、防火墙、去程和回程路由是否完整?
  6. 检查物理网络。 使用 Macvlan、IPvlan 时,再看父接口、VLAN、地址冲突及上游设备限制。

这次学习最值得记下来的,是沿着数据包的路径理解配置。只要能说清楚一个请求从哪里出发、经过哪些接口、目标又如何返回,Docker 网络里的这些名称就容易对应到实际问题上。