一、从一个神奇的现象说起
不知道你是否有过这样的疑问:为什么在同一台宿主机上,两个容器可以各自拥有自己的 IP 地址 172.17.0.2 和 172.17.0.3,而且互不冲突? 更神奇的是,宿主机自己也有一个 IP 地址(比如 192.168.1.100),容器之间、容器与宿主机之间的网络竟然还能正常通信。
这个看似简单的现象背后,其实隐藏着 Linux 内核一个强大的机制——Network Namespace(网络命名空间)。它就像是一个个"网络层面的平行宇宙",每个宇宙都有自己独立的网络设备、IP 地址、路由表、防火墙规则。理解了 Network Namespace,你就掌握了理解 Docker 容器网络、Kubernetes CNI 插件的钥匙。
┌─────────────────────────────────────────────────────────────────┐│ 宿主机 (Root Namespace) ││ ┌───────────────────────────────────────────────────────────┐ ││ │ eth0: 192.168.1.100/24 │ ││ │ lo: 127.0.0.1/8 │ ││ │ docker0: 172.17.0.1/24 │ ││ └───────────────────────────────────────────────────────────┘ ││ │ ││ ┌───────────────┼───────────────┐ ││ ▼ ▼ ▼ ││ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││ │ Namespace A │ │ Namespace B │ │ Namespace C │ ││ │ eth0@if123 │ │ eth0@if127 │ │ eth0@if131 │ ││ │ 172.17.0.2 │ │ 172.17.0.3 │ │ 172.17.0.4 │ ││ │ lo:127.0.0.1│ │ lo:127.0.0.1│ │ lo:127.0.0.1│ ││ └─────────────┘ └─────────────┘ └─────────────┘ │└─────────────────────────────────────────────────────────────────┘
图示说明:宿主机与多个 Network Namespace 共存,每个 Namespace 拥有独立的网络栈
二、Network Namespace 到底是什么?
2.1 核心定义
Network Namespace 是 Linux 内核提供的一种资源隔离机制,它将系统的网络资源划分为多个独立的逻辑空间。 在一个 Network Namespace 中创建的网卡、配置的 IP 地址、设置的路由规则,只在该 Namespace 内部生效,对其他 Namespace 完全透明。
我们可以把 Network Namespace 想象成一栋大楼里的独立房间。每个房间都有自己的:
- 电话(网卡):可以接听和拨打电话
- 通讯录(路由表):记录着打给谁应该怎么走
- 门禁规则(防火墙):决定谁可以进来,谁可以出去
虽然大家都在同一栋大楼里,但每个房间内部的事情,外面的人看不到、也管不着。这就是"隔离"的含义。
2.2 Network Namespace 隔离了什么?
一个 Network Namespace 会独立维护以下网络资源:
资源类型 | 隔离内容 |
网络设备 | eth0、veth、br0 等所有网络接口 |
回环设备 | 独立的 lo 接口 |
IP 地址 | 可以在同一网段重复,不会冲突 |
端口号 | 同一端口可以在不同 Namespace 中被不同进程绑定 |
路由表 | 独立的转发规则和默认网关 |
防火墙规则 | 独立的 iptables/nftables 规则 |
ARP 表 | 独立的地址解析协议缓存 |
⚠️重要提醒:物理网卡(如 eth0)默认属于 Root Namespace(宿主机默认的 Namespace),不能直接"移动"到其他 Namespace。但可以通过 "物理网卡 + veth pair" 的方式让其他 Namespace 访问外部网络,这点我们后面会详细讲解。
三、实战演练:从零开始创建 Network Namespace
说了这么多,是时候动手试试了!下面我们使用 ip netns 命令来创建和管理 Network Namespace。整个操作流程非常简单,保证你一看就会。
3.1 环境准备
首先确认你的系统安装了 iproute2 包(大多数 Linux 发行版默认已安装):
# 检查 ip 命令是否支持 netns 子命令$ ip netns list# 如果没有任何输出,说明当前没有自定义 Namespace,这是正常的
3.2 创建第一个 Namespace
# 创建名为 "ns1" 的 Network Namespace$ sudo ip netns add ns1# 查看当前所有的 Network Namespace$ sudo ip netns listns1# 再创建一个,用于后续实验$ sudo ip netns add ns2$ sudo ip netns listns2ns1
📝命名空间列表顺序:注意 ip netns list 显示的顺序可能与你创建的顺序不同,这是正常现象,列表按字典序排序。
3.3 查看 Namespace 中的网络设备
默认情况下,每个新创建的 Namespace 只包含一个回环设备(lo),而且默认是 DOWN 状态:
# 查看 ns1 中的网络设备$ sudo ip netns exec ns1 ip link listlo: <LOOPBACK> mtu 65536 qdisc noqueue state DOWN mode DEFAULT link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
你会看到:
- lo:回环设备
- state DOWN:默认是关闭状态
- mtu 65536:最大传输单元 64KB
3.4 启动回环设备并配置 IP
# 启动 ns1 中的回环设备$ sudo ip netns exec ns1 ip link set lo up# 查看状态,确认 UP$ sudo ip netns exec ns1 ip link listlo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UP mode DEFAULT link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00# 注意:现在有 <LOOPBACK,UP,LOWER_UP> 标识了# 配置 IP 地址$ sudo ip netns exec ns1 ip addr add 192.168.100.10/24 dev lo# 查看 IP 地址配置$ sudo ip netns exec ns1 ip addrlo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UP inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet 192.168.100.10/24 scope global lo valid_lft forever preferred_lft forever
🎯关键点:我们给回环设备 lo 添加了第二个 IP 地址 192.168.100.10/24。在真实场景中,通常不会这么做(应该给专门创建的 veth 设备配 IP),但这个例子很好地展示了 Namespace 的隔离性——在 ns1 里,192.168.100.10 是"合法"的 IP地址。
3.5 在 Namespace 中执行任意命令
ip netns exec 不仅可以执行网络命令,还可以执行任意 shell 命令:
# 查看 ns1 中的主机名$ sudo ip netns exec ns1 hostnamens1# 查看 ns1 中的进程空间$ sudo ip netns exec ns1 ps aux# 注意:ps 命令可能需要额外配置才能看到完整进程列表$ sudo ip netns exec ns1 ping -c 2 127.0.0.1PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.045 ms64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.058 ms
四、进阶操作:打通网络孤岛
现在我们遇到了一个问题:创建的 Namespace 就像一个"信息孤岛",里面的进程只能自己和自己玩。要让不同 Namespace 之间通信,我们需要虚拟网线(veth pair)。
4.1 什么是 veth pair?
veth(Virtual Ethernet)pair 是一对虚拟的网络设备,就像一根网线的两端。从一端发送的数据包,会从另一端立即收到。形象地说,它就是连接两个 Namespace 的"虚拟网线"。
┌─────────────┐ veth pair ┌─────────────┐│ Namespace A │ ←──────────────────────→ │ Namespace B ││ veth0 │ ←───── "虚拟网线" ─────→ │ veth1 │└─────────────┘ └─────────────┘
图示说明:veth pair 就像一根虚拟的网线,连接两个 Namespace
4.2 创建 veth pair 并连接两个 Namespace
# 创建 veth pair,分别命名为 veth-ns1 和 veth-ns2$ sudo ip link add veth-ns1 type veth peer name veth-ns2# 查看创建的网络设备(此时还没分配到 Namespace)$ ip link show | grep vethveth-ns2@veth-ns1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWNveth-ns1@veth-ns2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN
🔍命名规则:veth pair 的两个端点互为"对端"(peer)。如果你把 veth-ns1 移到 Namespace A,那么从 Namespace A 内部看到的设备名还是 veth-ns1,但外部(宿主机)看到的设备名会变成 veth-ns2。
4.3 将 veth pair 分别加入不同 Namespace
# 将 veth-ns1 加入 ns1,veth-ns2 加入 ns2$ sudo ip link set veth-ns1 netns ns1$ sudo ip link set veth-ns2 netns ns2# 验证分配结果(现在宿主机上看不到这两个设备了)$ ip link show | grep veth# (无输出,因为已经移到 Namespace 中了)# 在 ns1 中查看$ sudo ip netns exec ns1 ip linklo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UP veth-ns1@if5: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN link/ether aa:bb:cc:dd:ee:ff brd ff:ff:ff:ff:ff:ff link-netnsid 0# 在 ns2 中查看$ sudo ip netns exec ns2 ip linklo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UP veth-ns2@if6: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN link/ether aa:bb:cc:dd:ee:00 brd ff:ff:ff:ff:ff:ff link-netnsid 0
4.4 配置 IP 地址并启动设备
# 配置 ns1 中的 veth-ns1$ sudo ip netns exec ns1 ip addr add 192.168.100.1/24 dev veth-ns1$ sudo ip netns exec ns1 ip link set veth-ns1 up# 配置 ns2 中的 veth-ns2$ sudo ip netns exec ns2 ip addr add 192.168.100.2/24 dev veth-ns2$ sudo ip netns exec ns2 ip link set veth-ns2 up
4.5 测试连通性
# 从 ns1 ping ns2$ sudo ip netns exec ns1 ping -c 2 192.168.100.2PING 192.168.100.2 (192.168.100.2) 56(84) bytes of data.64 bytes from 192.168.100.2: icmp_seq=1 ttl=64 time=0.123 ms64 bytes from 192.168.100.2: icmp_seq=2 ttl=64 time=0.088 ms# 从 ns2 ping ns1$ sudo ip netns exec ns2 ping -c 2 192.168.100.1PING 192.168.100.1 (192.168.100.1) 56(84) bytes of data.64 bytes from 192.168.100.1: icmp_seq=1 ttl=64 time=0.098 ms64 bytes from 192.168.100.1: ip netns exec ns1 ping 192.168.100.2
🎉恭喜!你已经成功实现了两个 Namespace 之间的网络通信! 这就是容器网络的基础模型。
五、网桥模式:构建更复杂的网络拓扑
上面的点对点连接只适合两个 Namespace 通信。在实际场景中(如 Docker 容器网络),我们需要让多个容器互通。这时候就需要网桥(Bridge)。
┌──────────────────────────────────────────────────────────┐ │ 宿主机 │ │ ┌─────────────────────────────────────────────────┐ │ │ │ br0 网桥 (172.17.0.1/24) │ │ │ └─────────────────────────────────────────────────┘ │ │ │ │ │ │ ┌─────┴─────┐ ┌─────┴─────┐ │ │ ▼ ▼ ▼ ▼ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ ns1 │ │ ns2 │ │ ns3 │ │ ns4 │ │ │ │veth-ns1 │ │veth-ns2 │ │veth-ns3 │ │veth-ns4 │ │ │ │.2/24 │ │.3/24 │ │.4/24 │ │.5/24 │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ └──────────────────────────────────────────────────────────┘
图示说明:通过网桥连接多个 Namespace,形成类似 Docker 容器网络的结构
5.1 创建网桥并连接 Namespace
# 创建名为 br0 的网桥$ sudo brctl addbr br0# 或使用 ip 命令$ sudo ip link add br0 type bridge# 启动网桥$ sudo ip link set br0 up# 为网桥配置 IP(作为网关)$ sudo ip addr add 172.17.0.1/24 dev br0
5.2 创建多个 veth pair 并接入网桥
# 创建 4 对 vethfor i in {1..4}; do sudo ip link add veth-host${i} type veth peer name veth-ns${i}done# 将 veth-host 留在宿主机,veth-ns 移到对应的 Namespacefor i in {1..4}; do sudo ip link set veth-host${i} master br0 sudo ip link set veth-host${i} up sudo ip link set veth-ns${i} netns ns${i}done
5.3 配置各 Namespace 的网络
# 为每个 Namespace 配置 IPfor i in {1..4}; do sudo ip netns exec ns${i} ip addr add 172.17.0.${i}/24 dev veth-ns${i} sudo ip netns exec ns${i} ip link set veth-ns${i} up # 开启回环设备 sudo ip netns exec ns${i} ip link set lo updone
现在,所有 Namespace 都可以通过网桥互相通信了!
六、Network Namespace 与 Docker 的关系
说了这么多,我们来看看实际应用。Docker 就是利用 Network Namespace 来实现容器网络隔离的。
当你启动一个 Docker 容器时:
$ docker run -d --name web nginx
Docker 会在后台:
- 创建一个新的 Network Namespace
- 创建一对 veth pair,一端放在容器内(通常命名为 eth0),一端接在 docker0 网桥上
- 从网桥的地址池中分配一个 IP 给容器
- 配置容器的路由,让容器可以访问外部网络
你可以用 docker network inspect bridge 查看 Docker 的网络配置,或者用 ip netns list 看到 Docker 创建的 Namespace(需要一些额外操作才能访问)。
七、常见问题与注意事项
7.1 如何查看宿主机上的物理 Namespace?
Root Namespace(宿主机默认的 Namespace)没有名字,可以用以下方式验证:
# 查看宿主机网络设备$ ip linklo: <LOOPBACK,UP,LOWER_UP>eth0: <BROADCAST,MULTICAST,UP,LOWER_UP>docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP>
7.2 物理网卡能直接移入 Namespace 吗?
不能。物理网卡(如 eth0)直接属于宿主机内核,不能直接"移动"到用户创建的 Namespace。如果需要让 Namespace 访问物理网络,需要通过路由 +NAT 或建立映射关系的方式实现。
7.3 如何删除不再使用的 Namespace?
# 删除指定 Namespace(会自动清理其中的设备)$ sudo ip netns del ns1# 清空所有自定义 Namespace$ sudo ip netns list | xargs -r sudo ip netns del
⚠️注意:删除 Namespace 前,确保没有进程在使用它,否则可能导致系统不稳定。
7.4 Namespace 中的进程能看到宿主机上的进程吗?
不能。PID Namespace(进程命名空间)是另一种隔离机制。默认情况下,每个 Network Namespace 没有独立的 PID Namespace。但 Docker 等容器运行时通常会结合使用多种 Namespace(Network、PID、Mount、UTS 等)来实现完整的容器隔离。
八、总结
Network Namespace 是 Linux 内核提供的强大网络隔离机制,它让不同的进程组可以拥有独立的网络环境,互不干扰。从 Docker 容器到 Kubernetes Pod,从网络测试到安全隔离,Network Namespace 的应用无处不在。
核心要点回顾:
- 创建:
ip netns add <name> - 执行:
ip netns exec <name> <command> - 连接:使用 veth pair 打通不同 Namespace
- 扩展:使用网桥连接多个 Namespace
理解 Network Namespace,不仅能帮你更好地理解容器网络原理,还能让你在网络调试、安全隔离等场景中游刃有余。