网络命名空间与容器网络底层

系统讲解容器网络的底层构件:从 Linux 网络命名空间的隔离边界、veth pair 的成对转发、Linux bridge 的二层交换,到 iptables/NAT 与路由决策,并手工搭建一套最小容器网络,最后对接 CNI 插件的落地方式。

容器网络看起来复杂,但拆到底层只有四个构件:网络命名空间(netns)提供隔离,veth pair 提供连线,Linux bridge 提供二层交换,iptables/NAT 提供三层出网。Kubernetes 的 CNI 插件、Docker 的 bridge 网络、Podman 的 pod,无非是这四块积木的不同拼法。

本文先讲清每块积木的语义,再用手工命令从零搭出一个「容器能访问外网、外网能访问容器」的最小网络,最后说明 CNI 插件在这套机制上加了什么。理解了这些,再回头看 Calico、Flannel、Cilium 的差异就会清晰很多。

一、网络命名空间:隔离的边界

1.1 命名空间隔离了什么

网络命名空间(network namespace)是内核提供的一整套独立网络栈:每个命名空间有自己的网卡、路由表、ARP 表、iptables 规则、socket 与端口空间。默认情况下,进程运行在初始命名空间(root netns)里,也就是宿主机的网络栈。

每个 netns 独立拥有:
  - 网络设备(lo、eth0、veth 等)
  - IPv4/IPv6 路由表
  - ARP / 邻居表
  - iptables / nftables 规则
  - socket 与端口号空间(两个 netns 可各用 80 端口)
  - /proc/net 下的网络统计

不隔离的:文件系统、进程、用户(这些由其他命名空间负责)

1.2 用 ip netns 操作

ip netns 是操作命名空间的主力工具。它在 /var/run/netns/ 下为每个命名空间创建挂载点,使命名空间可以持久化并命名。

# 创建并命名一个网络命名空间
ip netns add ns1

# 进入命名空间执行命令
ip netns exec ns1 ip addr show
ip netns exec ns1 ip route

# 打开一个 shell 在里面操作
ip netns exec ns1 bash

# 列出所有命名空间,删除
ip netns list
ip netns delete ns1

# 查看进程所属的命名空间
ls -l /proc/<pid>/ns/net

1.3 命名空间的生命周期

命名空间本身没有名字,是 ip netns 用挂载点赋予了它名字。只要还有进程或挂载点引用它,命名空间就存活;全部引用消失后自动销毁,其中的网卡、规则一并消失。容器运行时正是靠「进程退出 → 命名空间引用归零 → 网络资源自动回收」来避免资源泄漏。

二、veth pair:成对的虚拟网线

2.1 veth 的语义

veth(virtual ethernet)总是成对出现,像一根双向水管:从一端进入的数据包,会从另一端出来。它本身不做任何转发决策,只是把两端连起来。把一端留在宿主机的 bridge 上,另一端塞进容器的命名空间,容器就「插」上了宿主机的虚拟交换机。

veth pair 语义:
  veth0 <=============> veth1
  从 veth0 发出的包 → 从 veth1 出来
  从 veth1 发出的包 → 从 veth0 出来
  两端可以位于不同命名空间 → 实现跨命名空间连线
# 创建一对 veth
ip link add veth0 type veth peer name veth1

# 把 veth1 移入 ns1 命名空间
ip link set veth1 netns ns1

# 在 ns1 内看到 veth1 并配 IP
ip netns exec ns1 ip addr add 10.200.0.2/24 dev veth1
ip netns exec ns1 ip link set veth1 up
ip netns exec ns1 ip link set lo up

# 宿主机侧配 IP 并启用
ip addr add 10.200.0.1/24 dev veth0
ip link set veth0 up

2.2 只靠 veth 的两点直连

仅用一对 veth 就能让宿主机与命名空间互通:宿主机在 veth0 配 10.200.0.1,命名空间在 veth1 配 10.200.0.2,双方互 ping 即通。但这只支持「一对一」,要让多个命名空间互访,就需要 bridge。

# 直连测试
ip netns exec ns1 ping -c 2 10.200.0.1
ping -c 2 10.200.0.2

三、Linux bridge:虚拟二层交换机

3.1 bridge 的作用

Linux bridge 是一个软件二层交换机:它按 MAC 地址转发帧,维护 MAC 地址表,支持泛洪。把多个 veth 的宿主机端都挂到同一个 bridge 上,所有命名空间就处于同一个二层广播域,可以互相通信。

bridge 转发逻辑:
  1. 收到帧 → 学习源 MAC 与入端口
  2. 查 MAC 表:命中则从对应端口转发(单播)
  3. 未命中或广播 → 从除入端口外的所有端口泛洪
  4. 表项老化(默认 300s)后重新学习
# 创建 bridge 并启用
ip link add br0 type bridge
ip link set br0 up
ip addr add 10.200.0.1/24 dev br0

# 把多个 veth 宿主机端挂到 bridge
ip link set veth0 master br0
ip link set vethA master br0
ip link set vethB master br0

# 查看 bridge 与 MAC 表
bridge link show
bridge fdb show br br0

3.2 为什么容器桥接后要设网关

容器(命名空间)里的默认路由要指向 bridge 的 IP(如 10.200.0.1)。这样,目标不在同网段的包会发给 bridge,由宿主机做三层转发。忘记设默认路由是「同网段能通、跨网段不通」的典型原因。

# 命名空间内设置默认路由指向 bridge
ip netns exec ns1 ip route add default via 10.200.0.1

# 同网段容器互访走二层(bridge 直接转发)
# 跨网段/出网走三层(宿主机转发 + NAT)

四、iptables/NAT:出网与端口映射

4.1 出网需要 SNAT/MASQUERADE

容器用的是私有网段(如 10.200.0.0/24),出公网时源地址必须被改写成宿主机 IP,否则回包找不到路。这就是 MASQUERADE:把容器源地址动态改写为出接口地址。

# 开启转发
sysctl -w net.ipv4.ip_forward=1

# 容器出网:对来自容器网段的流量做源地址伪装
iptables -t nat -A POSTROUTING -s 10.200.0.0/24 ! -o br0 -j MASQUERADE

# 放行转发
iptables -A FORWARD -i br0 -j ACCEPT
iptables -A FORWARD -o br0 -j ACCEPT

4.2 端口映射需要 DNAT

外网要访问容器里的服务,需要把宿主机某端口的流量改写目标为容器地址,这就是 DNAT(通常配合 -p tcp --dport)。

# 把宿主机 8080 映射到容器 10.200.0.2:80
iptables -t nat -A PREROUTING -p tcp --dport 8080 \
  -j DNAT --to-destination 10.200.0.2:80

# 注意:从容器自身所在宿主机访问时,还需 OUTPUT 链的 DNAT
iptables -t nat -A OUTPUT -p tcp --dport 8080 \
  -j DNAT --to-destination 10.200.0.2:80

4.3 报文路径与 conntrack

iptables 依赖连接跟踪(conntrack)记住每条流的 NAT 映射,回包才能被正确改写。排错时 conntrack -L 能看到活跃连接及其 NAT 状态,是定位「单向通」问题的关键。

阶段链典型动作
入站路由前PREROUTINGDNAT(端口映射)
转发决策FORWARD放行/丢弃
出站路由后POSTROUTINGSNAT/MASQUERADE
本机进程INPUT/OUTPUT本机访问策略
# 查看 NAT 连接跟踪表
conntrack -L | grep 10.200.0.2

# 统计某条链的命中
iptables -t nat -L POSTROUTING -v -n

五、CNI:把手工步骤自动化

5.1 CNI 规范做什么

容器编排系统不自己实现网络,而是调用 CNI(Container Network Interface)插件。CNI 定义了一个简单契约:容器运行时创建网络命名空间后,调用插件二进制,通过环境变量与 JSON 配置传入「容器 ID、netns 路径、要执行的命令(ADD/DEL/CHECK)」,插件负责把网卡、IP、路由、规则配好并返回结果。

CNI 调用约定:
  环境变量:
    CNI_COMMAND   = ADD | DEL | CHECK | VERSION
    CNI_CONTAINERID, CNI_NETNS, CNI_IFNAME
    CNI_ARGS, CNI_PATH
  stdin:网络配置 JSON(插件名、子网、网关等)
  stdout:结果 JSON(分配的 IP、网关、DNS)
{
  "cniVersion": "1.0.0",
  "name": "mynet",
  "type": "bridge",
  "bridge": "br0",
  "isGateway": true,
  "ipMasq": true,
  "subnet": "10.200.0.0/24",
  "ipam": {
    "type": "host-local",
    "ranges": [[{ "subnet": "10.200.0.0/24" }]]
  }
}

5.2 bridge 插件做了什么

标准 bridge 插件做的事,正是前面手工步骤的自动化:创建 bridge(若不存在)、创建 veth pair、把一端挂到 bridge、另一端放进容器 netns、配 IP 与路由、按需加 iptables 规则。host-local IPAM 插件负责从子网里分配不冲突的 IP。

5.3 不同 CNI 的差异在哪

CNI数据面特点
bridge + host-localbridge + veth + iptables最简单,单机模型
Calico路由/BGP 或 eBPF无 overlay,走三层路由
FlannelVXLAN overlay简单易用,跨节点隧道
CiliumeBPF高性能,替代 iptables

无论哪种,底层都离不开 netns、veth、路由与 NAT 这几块积木;差异在于「跨节点怎么连」——是路由、是隧道,还是 eBPF 重写数据面。想深入跨节点隧道可以看 Overlay 网络与 VXLAN/Geneve ,想了解 eBPF 重写数据面可以看 eBPF 与 XDP 数据面 。

六、手工搭一套最小容器网络

6.1 完整脚本

把前面的步骤串起来,就是一套能跑的最小容器网络:命名空间出网、外网访问容器、容器间互访。

set -e
# 1. 建 bridge
ip link add br0 type bridge
ip addr add 10.200.0.1/24 dev br0
ip link set br0 up

# 2. 建两个命名空间并连线
for i in 1 2; do
  ip netns add ns$i
  ip link add veth$i type veth peer name eth0 netns ns$i
  ip link set veth$i master br0
  ip link set veth$i up
  ip netns exec ns$i ip addr add 10.200.0.$((i+1))/24 dev eth0
  ip netns exec ns$i ip link set eth0 up
  ip netns exec ns$i ip link set lo up
  ip netns exec ns$i ip route add default via 10.200.0.1
done

# 3. 出网 NAT
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -s 10.200.0.0/24 ! -o br0 -j MASQUERADE
iptables -A FORWARD -i br0 -j ACCEPT
iptables -A FORWARD -o br0 -j ACCEPT

6.2 验证清单

验证项命令预期
容器互访ip netns exec ns1 ping 10.200.0.2通(二层经 bridge)
访问宿主机ip netns exec ns1 ping 10.200.0.1通
访问外网ip netns exec ns1 ping 1.1.1.1通(经 NAT)
端口映射宿主机 curl localhost:8080命中容器服务
命名空间内规则ip netns exec ns1 iptables -L独立于宿主机

6.3 常见故障对照

现象原因排查
容器间不通未挂到同一 bridgebridge link show
出网不通未开转发或 NAT 规则缺失sysctl net.ipv4.ip_forward、iptables -t nat -L
单向不通回包路径被 FORWARD 拦conntrack -L、FORWARD 计数
端口映射从宿主机访问失败缺 OUTPUT 链 DNAT补 -A OUTPUT 规则
命名空间残留引用未清理ip netns list、检查 /var/run/netns

6.4 veth 的性能特征与卸载

veth 是纯软件设备,转发要经过内核网络栈,因此开销高于物理网卡直连。但它支持 GRO/GSO/TSO 等卸载标志,能让大包在进入栈前被聚合,显著提升吞吐。排错时用 ethtool -k 确认卸载状态,用 ip -s link 看收发统计。

# 查看 veth 的卸载特性
ethtool -k veth0 | grep -E 'generic|tcp-segmentation'

# 查看收发字节、丢包、错误计数
ip -s link show veth0
ip -s link show br0

# 用 netns 内的命名空间做带宽测试
ip netns exec ns1 iperf3 -s &
iperf3 -c 10.200.0.2
现象可能原因排查
吞吐远低于预期卸载未生效ethtool -k 对比物理网卡
大量丢包队列溢出ip -s link 看 dropped
CPU 软中断高单队列、无 RPS/RSS配置 RPS 或改用多队列

6.5 CNI 的 IPAM 与多网卡

CNI 不只有「连线」,还负责地址分配(IPAM)。host-local 用本地文件记录已分配 IP,适合单机;跨节点需要 calico-ipam 等分布式方案,避免不同节点分到相同网段。多网卡场景(如 SR-IOV、Macvlan)则通过多个 CNI 配置串行调用,为容器挂上多张网卡。

IPAM 方案对比:
  host-local : 单机文件记录,简单,不跨节点
  dhcp       : 向 DHCP 服务器申请,适合传统网络
  calico-ipam: 从集群地址池分配,跨节点协调
  静态       : 配置里写死,适合固定拓扑
{
  "cniVersion": "1.0.0",
  "name": "multus-demo",
  "plugins": [
    { "type": "bridge", "bridge": "br0",
      "ipam": { "type": "host-local", "subnet": "10.200.0.0/24" } },
    { "type": "macvlan", "master": "eth1",
      "ipam": { "type": "host-local", "subnet": "10.201.0.0/24" } }
  ]
}

串行插件(如 Multus)把多张网卡依次挂到同一命名空间:第一张作主网卡承载默认路由,其余作辅助网卡承载存储或管理流量,是高性能场景的常见做法。

七、小结

构件职责关键命令
netns隔离网络栈ip netns add/exec
veth成对连线ip link add ... type veth
bridge二层交换ip link add ... type bridge
iptables三层转发与 NAT-t nat POSTROUTING
CNI自动化上述步骤插件二进制 + JSON 配置

容器网络没有魔法:它把「隔离、连线、交换、转发」四件事用 Linux 原语表达出来,再交给 CNI 插件自动化。理解这套底层,排错时就不必在抽象层里猜——可以逐层 ip link、bridge fdb、iptables -t nat -L 地验证。要把单机模型扩展到集群,可继续阅读 云原生网络与 CNI ;三层转发的完整决策过程可参考 IP 路由与转发 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「network」更多文章

  1. MPTCP 与多路径传输
  2. 流量整形与 TC/qdisc
  3. WireGuard 与现代 VPN