mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
3329 字
9 分钟
容器网络
2021-09-21

当你执行 docker run -p 8080:80 nginx 后,从浏览器访问 http://localhost:8080 就能看到 Nginx 欢迎页。但数据包是怎么从浏览器到达容器内的 Nginx 的?

它穿越了 iptables DNAT 规则(8080→172.17.0.2<80>),经过 docker0 bridge,通过 veth pair 进入容器的 Network Namespace,最终到达 Nginx 进程的 socket。这条链路涉及 Network Namespace、veth pair、bridge、iptables 四个核心组件。

下面逐一拆解容器网络的每个环节,追踪数据包从宿主机到容器的完整路径。

容器网络的演进从“简单连通”到“安全策略”。Docker 早期用 bridge + NAT,所有容器挂 docker0、通过 MASQUERADE 出网、通过 DNAT 暴露端口,只解决单机通信。多主机通信需要覆盖网络,CoreOS 在 2015 年提出 CNI 标准(spec 初版随 Kubernetes 1.0 发布,2016 年作为独立项目入 CNCF 托管),将网络配置从运行时解耦,flannel、Calico、Cilium 各自实现不同方案。2019 年基于 eBPF 的 Cilium 用内核态程序替代 iptables 做策略和转发,性能远超规则线性匹配。

前置知识#

Important
  • Ch02 Linux Namespace 深入:Network Namespace 是容器网络的基础,每个容器拥有独立的网络栈

  • Linux 网络基础:veth pair、bridge、iptables/NAT、路由表

  • 网络协议基础:TCP/IP、DNS、ARP

Note

本章需要 Linux 网络基础(veth pair、bridge、iptables/NAT、路由表)和 TCP/IP 协议基础。

一、容器网络基础#

1.1 Network Namespace 回顾#

每个容器有独立的 Network Namespace,拥有自己的网络栈(Network Namespace 的细节见 Ch02)。刚创建的 Namespace 只有一个 DOWN 状态的 loopback,Docker 会为其接入 eth0:

# 容器的 Network Namespace
PID=$(docker inspect -f '{{.State.Pid}}' mynginx)
sudo nsenter -t $PID -n ip link
# 1: lo: <LOOPBACK,UP,LOWER_UP>
# 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP>

1.2 容器网络的核心问题#

Network Namespace 隔离了网络栈,但也带来了核心问题:如何让隔离的 Namespace 与外部通信?

graph LR subgraph 容器NS["容器 Network Namespace"] ETH0["eth0<br/>172.17.0.2"] end subgraph 宿主机["宿主机"] BRIDGE["docker0 bridge<br/>172.17.0.1"] VETH_HOST["veth123"] ETH_HOST["eth0<br/>192.168.1.100"] end subgraph 外部["外部网络"] CLIENT["客户端<br/>192.168.1.200"] end ETH0 <-->|"veth pair"| VETH_HOST VETH_HOST <--> BRIDGE BRIDGE <--> ETH_HOST ETH_HOST <--> CLIENT style 容器NS fill:#e0f2f1,stroke:#00695c style 宿主机 fill:#e8eaf6,stroke:#283593 style 外部 fill:#fff3e0,stroke:#e65100

二、veth pair#

2.1 veth pair 原理#

veth pair 是一对虚拟网卡,像一根”虚拟网线”,从一端发送的数据包会从另一端接收:

# 创建 veth pair
sudo ip link add veth-host type veth peer name veth-container
# 将一端放入容器的 Network Namespace
sudo ip link set veth-container netns ns1
# 在容器内配置 IP
sudo ip netns exec ns1 ip addr add 10.0.0.2/24 dev veth-container
sudo ip netns exec ns1 ip link set veth-container up
sudo ip netns exec ns1 ip link set lo up
# 在宿主机配置 IP
sudo ip addr add 10.0.0.1/24 dev veth-host
sudo ip link set veth-host up
# 测试连通性
sudo ip netns exec ns1 ping -c 3 10.0.0.1

2.2 veth pair 的特性#

特性说明
成对出现创建时必须指定两端
跨 Namespace一端在宿主,一端在容器
MAC 地址每端有独立的 MAC 地址
MTU默认 1500 字节
性能数据包在内核空间传递,延迟极低

2.3 veth pair 在容器中的使用#

veth pair 的两端一端在容器命名空间里(通常叫 eth0),另一端在宿主机上(通常叫 vethXXX)。排查容器网络问题时,常要从容器内的 eth0 找到宿主机上的对端,或者反过来。靠的就是 interface index 的对应关系:

# 查看 Docker 容器的 veth pair
docker exec mynginx ip link show eth0
# 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
# link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
# 在宿主机上找到对应的另一端
ip link | grep -A1 "veth"
# 5: veth123@if2: <BROADCAST,MULTICAST,UP,LOWER_UP>
# link/ether 3a:2b:1c:4d:5e:6f brd ff:ff:ff:ff:ff:ff
# 注意:veth123 的 @if2 表示它的对端是 interface index 2
# 容器内 eth0 的 index 正是 2

三、Bridge#

3.1 Bridge 原理#

Bridge(网桥)是二层数据链路层设备,类似于物理交换机,它连接多个网络接口,根据 MAC 地址转发数据帧:

# 创建 bridge
sudo ip link add br0 type bridge
sudo ip link set br0 up
# 将 veth pair 的宿主端连接到 bridge
sudo ip link set veth-host master br0
# 配置 bridge 的 IP 地址(作为容器的网关)
sudo ip addr add 172.17.0.1/16 dev br0

3.2 Docker 的 bridge 网络模型#

graph TB subgraph 宿主机["宿主机"] DOCKER0["docker0 bridge<br/>172.17.0.1/16"] VETH1["veth-容器A"] VETH2["veth-容器B"] ETH0["eth0<br/>192.168.1.100"] IPTABLES["iptables NAT"] end subgraph 容器A["容器 A (Network NS)"] CA_ETH0["eth0<br/>172.17.0.2"] CA_APP["nginx"] end subgraph 容器B["容器 B (Network NS)"] CB_ETH0["eth0<br/>172.17.0.3"] CB_APP["redis"] end CA_ETH0 <-->|"veth pair"| VETH1 CB_ETH0 <-->|"veth pair"| VETH2 VETH1 <--> DOCKER0 VETH2 <--> DOCKER0 DOCKER0 <--> IPTABLES IPTABLES <--> ETH0 style 宿主机 fill:#e8eaf6,stroke:#283593 style 容器A fill:#e0f2f1,stroke:#00695c style 容器B fill:#fff3e0,stroke:#e65100

3.3 Bridge 的 MAC 学习#

Bridge 维护一张 MAC 地址转发表,记录每个端口对应的 MAC 地址:

# 查看 bridge 的 MAC 转发表
bridge fdb show br docker0
# 输出示例:
# 02:42:ac:11:00:02 dev veth123 master docker0
# 02:42:ac:11:00:03 dev veth456 master docker0
# 33:33:00:00:00:01 dev docker0 self permanent

四、iptables/NAT#

4.1 iptables 链遍历顺序#

容器网络中,数据包经过的 iptables 链有严格的先后顺序。理解这个顺序是排查 NAT 问题的前提。

需要先说明的是,本文用 iptables 命令演示,但现代发行版(Debian 12、RHEL 9 等)默认的 iptables 已经是 iptables-nft,背后走的是 nftables 后端;Docker 25+ 和 Kubernetes 1.29+ 也已原生支持 nftables 规则。链遍历的逻辑两者一致,只是查看命令不同:iptables -t nat -L -n -v 看到的是 iptables 视图,对应的 nftables 视图用 nft list ruleset 查看。排查时如果发现 iptables-save 输出和预期不符,先确认当前走的是哪个后端。

以入站流量(外部访问容器)为例,数据包进入宿主机后的链遍历路径:

graph LR IN["数据包到达 eth0"] --> PRE["PREROUTING"] PRE -->|"DNAT 规则<br/>8080 → 172.17.0.2:80"| DOCKER["DOCKER 链"] DOCKER --> FORWARD["FORWARD"] FORWARD --> POST["POSTROUTING"] POST --> OUT["离开宿主机"] style PRE fill:#e3f2fd,stroke:#1565c0 style DOCKER fill:#fff3e0,stroke:#e65100 style POST fill:#e8f5e9,stroke:#2e7d32

具体流程:

  1. PREROUTING 链:数据包刚进入网络栈,路由判断之前。Docker 在这里挂载 DNAT 规则,将目标地址从 宿主机IP:8080 改写为 容器IP:80
  2. DOCKER 链:Docker 自定义的子链,从 PREROUTING 跳入。所有端口映射的 DNAT 规则都在这条链上,方便集中管理
  3. FORWARD 链:DNAT 完成后,数据包的目标地址已经是容器 IP,不属于本机,所以走 FORWARD 而非 INPUT
  4. POSTROUTING 链:数据包离开宿主机前最后的机会做源地址转换。出站 NAT(MASQUERADE)在这里执行

出站流量(容器访问外网)的路径类似,但不经过 DOCKER 链,只在 POSTROUTING 做 MASQUERADE:

# 查看完整的链跳转关系
sudo iptables -t nat -L PREROUTING -n -v
# Chain PREROUTING (policy ACCEPT)
# DOCKER all -- 0.0.0.0/0 0.0.0.0/0 ← 所有流量跳到 DOCKER 链
sudo iptables -t nat -L DOCKER -n -v
# Chain DOCKER (2 references)
# DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80
sudo iptables -t nat -L POSTROUTING -n -v
# Chain POSTROUTING (policy ACCEPT)
# MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
Note

排查容器网络问题时,用 iptables -t nat -L <链名> -n -v 逐条链检查,看数据包到底在哪条链上被处理(或被漏掉)。-v 参数会显示每条规则的匹配计数,如果某条规则计数为 0,说明流量根本没走到这里。

4.2 容器的出站 NAT#

容器访问外网时,数据包的源地址需要从容器 IP(172.17.0.2)转换为宿主机 IP(192.168.1.100):

# Docker 创建的 NAT 规则
sudo iptables -t nat -L POSTROUTING -n -v
# Chain POSTROUTING (policy ACCEPT)
# MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
# ↑ 容器网段的所有出站流量做 MASQUERADE(动态 SNAT)

为什么用 MASQUERADE 而不是静态 SNAT?两者都能做源地址转换,但有关键区别:

对比项MASQUERADESNAT
源地址自动选取出口网卡的 IP手动指定固定 IP
接口变化立即适应,无需修改规则需要手动更新规则
性能略慢(每次查出口 IP)略快(IP 已固定)
适用场景DHCP 环境、多网卡固定 IP 环境

Docker 选择 MASQUERADE 的原因很实际:宿主机的出口 IP 可能变化。比如笔记本在 WiFi 和有线之间切换,云服务器弹性 IP 变更,或者多网卡场景下出口网卡不确定。MASQUERADE 每次发包时自动查找出口网卡的主 IP 做转换,不需要人工维护规则。如果用静态 SNAT(-j SNAT --to-source 192.168.1.100),IP 一变就得手动更新规则,否则容器立刻断网。

Tip

在生产环境中,如果宿主机 IP 确实固定不变,可以手动把 MASQUERADE 换成 SNAT 以获得微小的性能提升。但通常这个差距可以忽略不计,MASQUERADE 的自动适配能力更有价值。

4.3 容器的入站 DNAT#

外部访问容器的端口时,数据包需要做 DNAT(目标地址转换):

# docker run -p 8080:80 nginx 创建的 DNAT 规则
sudo iptables -t nat -L DOCKER -n -v
# Chain DOCKER (2 references)
# DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80
# ↑ 访问宿主机 8080 端口的流量转发到容器 80 端口

4.4 完整的数据包路径#

sequenceDiagram participant CLIENT as 客户端 participant ETH0 as 宿主机 eth0 participant IPT as iptables NAT participant BRIDGE as docker0 bridge participant VETH as veth pair participant CONTAINER as 容器 eth0 Note over CLIENT,CONTAINER: 入站:客户端 → 容器 CLIENT->>ETH0: SYN → 192.168.1.100:8080 ETH0->>IPT: DNAT: 192.168.1.100:8080 → 172.17.0.2:80 IPT->>BRIDGE: 转发到 172.17.0.2 BRIDGE->>VETH: MAC 查找 → veth123 VETH->>CONTAINER: SYN → 172.17.0.2:80 Note over CLIENT,CONTAINER: 出站:容器 → 客户端 CONTAINER->>VETH: SYN-ACK ← 172.17.0.2:80 VETH->>BRIDGE: 转发到网关 BRIDGE->>IPT: SNAT: 172.17.0.2 → 192.168.1.100 IPT->>ETH0: SYN-ACK ← 192.168.1.100:8080 ETH0->>CLIENT: SYN-ACK ← 192.168.1.100:8080

五、Docker 网络模式#

5.1 四种网络模式#

模式Namespace 策略特点性能
bridge独立 NS + veth + bridge默认模式,有 NAT中等
host共享宿主 NS无隔离,性能最好最好
none独立 NS,仅 loopback无网络
container共享另一容器 NSPod 内共享中等
# bridge 模式(默认)
docker run -d nginx
# host 模式
docker run --network=host -d nginx
# none 模式
docker run --network=none -d nginx
# container 模式(共享另一个容器的网络栈)
docker run --network=container:nginx_id -d myapp

5.2 自定义 bridge 网络#

# 创建自定义 bridge 网络
docker network create --driver bridge --subnet 10.0.0.0/24 mynet
# 在自定义网络中运行容器
docker run --network=mynet -d --name app1 nginx
docker run --network=mynet -d --name app2 redis
# 容器间可以通过名称通信(Docker DNS)
docker exec app1 ping -c 3 app2
# PING app2 (10.0.0.3): 56 data bytes

六、CNI:容器网络接口#

6.1 CNI 原理#

CNI(Container Network Interface)是 CNCF 主办的通用容器网络接口规范,定义了容器网络配置的通用接口。Kubernetes、containerd、Podman 等都遵循它,但 CNI 本身独立于 Kubernetes:

{
"cniVersion": "0.4.0",
"name": "bridge-network",
"type": "bridge",
"bridge": "cni0",
"ipam": {
"type": "host-local",
"subnet": "10.244.0.0/24",
"routes": [{"dst": "0.0.0.0/0", "gw": "10.244.0.1"}]
},
"dns": {"nameservers": ["8.8.8.8"]}
}

6.2 CNI 插件的工作流程#

sequenceDiagram participant KUBELET as Kubelet participant CRI as CRI Runtime participant CNI as CNI Plugin KUBELET->>CRI: 创建 Pod (pause 容器) CRI->>CNI: ADD 网络配置 Note over CNI: 1. 创建 veth pair Note over CNI: 2. 连接到 bridge Note over CNI: 3. 分配 IP 地址 Note over CNI: 4. 配置路由 Note over CNI: 5. 设置 iptables 规则 CNI-->>CRI: 返回 IP 地址和路由 KUBELET->>CRI: 启动应用容器 Note over CRI: 应用容器共享 pause 容器的 Network NS

6.3 常见 CNI 插件#

CNI 插件类型特点
BridgeL2 bridge最简单的 CNI 插件
FlannelOverlay (VXLAN)简单易用,跨主机通信
CalicoBGP(默认)/ eBPF(可选)高性能,网络策略;eBPF 数据平面需显式启用
CiliumeBPF高性能,可观测性
WeaveOverlay (加密)简单,加密通信

6.4 Pod 网络模型#

Kubernetes 的 Pod 网络模型:同一 Pod 内的所有容器共享 Network Namespace

# Pod 内的 pause 容器创建 Network Namespace
# 其他容器加入 pause 的 Network Namespace
# 效果:
# 1. 所有容器共享同一个 IP 地址
# 2. 所有容器共享同一个端口空间
# 3. 容器间可以通过 localhost 通信
# 4. 容器间可以通过 IPC 通信

七、动手实践#

7.1 手动创建容器网络#

#!/bin/bash
# 手动创建容器网络(模拟 Docker bridge 模式)
# 1. 创建 Network Namespace
sudo ip netns add container1
# 2. 创建 veth pair
sudo ip link add veth-host1 type veth peer name veth-container1
# 3. 将一端放入容器 NS
sudo ip link set veth-container1 netns container1
# 4. 创建 bridge
sudo ip link add br-container type bridge
sudo ip link set br-container up
sudo ip addr add 10.0.0.1/24 dev br-container
# 5. 连接 veth 到 bridge
sudo ip link set veth-host1 master br-container
sudo ip link set veth-host1 up
# 6. 配置容器内的网络
sudo ip netns exec container1 ip addr add 10.0.0.2/24 dev veth-container1
sudo ip netns exec container1 ip link set veth-container1 up
sudo ip netns exec container1 ip link set lo up
sudo ip netns exec container1 ip route add default via 10.0.0.1
# 7. 配置 NAT
sudo iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -j MASQUERADE
sudo sysctl -w net.ipv4.ip_forward=1
# 8. 测试连通性
sudo ip netns exec container1 ping -c 3 8.8.8.8
# 9. 清理
sudo ip netns delete container1
sudo ip link delete br-container
sudo iptables -t nat -D POSTROUTING -s 10.0.0.0/24 -j MASQUERADE

7.2 网络诊断脚本#

#!/bin/bash
# 容器网络诊断脚本
CONTAINER=$1
PID=$(docker inspect -f '{{.State.Pid}}' $CONTAINER 2>/dev/null)
if [ -z "$PID" ]; then
echo "Container not found"
exit 1
fi
echo "=== 容器网络配置 ==="
echo "Container: $CONTAINER (PID: $PID)"
echo ""
echo "1. 网络接口"
sudo nsenter -t $PID -n ip addr
echo ""
echo "2. 路由表"
sudo nsenter -t $PID -n ip route
echo ""
echo "3. DNS 配置"
sudo nsenter -t $PID -n cat /etc/resolv.conf
echo ""
echo "4. 连通性测试"
sudo nsenter -t $PID -n ping -c 2 -W 2 8.8.8.8 2>&1 | tail -3
echo ""
echo "5. iptables NAT 规则"
sudo iptables -t nat -L DOCKER -n -v 2>/dev/null | grep "$CONTAINER"
echo ""
echo "6. veth pair 对应"
sudo nsenter -t $PID -n ip link show eth0 2>/dev/null | grep "link/ether"

八、容器网络性能优化#

8.1 网络性能瓶颈#

容器网络的性能瓶颈主要在三个地方:

瓶颈原因优化方案
veth pair 开销每个包经过两次内核协议栈使用 host 网络或 SR-IOV
bridge 转发软件二层转发使用 OVS 硬件卸载
iptables NAT规则匹配开销使用 IPVS 或 eBPF 替代

8.2 网络模式性能对比#

# 性能基准测试
# 1. bridge 模式(默认)
docker run --network=bridge --rm alpine iperf3 -c 192.168.1.1
# 吞吐量: ~8 Gbps
# 2. host 模式
docker run --network=host --rm alpine iperf3 -c 192.168.1.1
# 吞吐量: ~10 Gbps (接近原生)
# 3. 性能差距来源
# bridge 模式: veth pair + bridge + iptables NAT
# host 模式: 直接使用宿主网络栈

8.3 Cilium eBPF 网络优化#

Cilium 用 eBPF 程序替代 iptables 做网络转发和策略执行。要理解它为什么快,需要先看 iptables 的性能瓶颈在哪里,再看 eBPF 在哪些钩子点介入、数据路径有什么不同。

iptables 的性能瓶颈#

iptables 的核心问题是规则线性匹配。每条规则按顺序逐条检查,直到命中或遍历完整个链。规则少的时候没问题,但 Kubernetes 集群中 Service 数量动辄上千,对应的 iptables 规则可能数万条:

# 一个中等规模集群的 iptables 规则数量
sudo iptables-save | wc -l
# 50000+ (kube-proxy iptables 模式下每个 Service 约产生 8 条规则,5 万条对应约 6000 个 Service)
# 每个包都要遍历这些规则,O(n) 复杂度
# 即使大部分规则与当前包无关,也必须逐条检查

此外,iptables 的连接跟踪(conntrack)和 NAT 处理都在内核协议栈的固定位置执行,数据包必须走完整个协议栈路径,无法跳过不必要的处理阶段。

eBPF 的三个钩子点#

Cilium 在三个关键位置挂载 eBPF 程序,分别拦截不同阶段的数据包:

钩子点挂载位置处理时机Cilium 用途
XDP网卡驱动层数据包刚从网卡收到,尚未创建 skbDDoS 防护、早期丢包
tc流量控制层(qdisc)skb 创建后,进入协议栈之前/之后服务负载均衡、NAT、策略执行
cgroupcgroup 钩子进程发起 socket 操作时socket 级别策略、连接跟踪
graph TB subgraph inbound["数据包入站路径"] NIC["网卡收到数据包"] --> XDP["XDP 钩子<br/>最早拦截点"] XDP --> SKB["创建 skb"] SKB --> TC_IN["tc ingress<br/>进入协议栈前"] TC_IN --> STACK["内核协议栈<br/>路由/conntrack/iptables"] STACK --> TC_EG["tc egress<br/>离开协议栈后"] TC_EG --> OUT["发送到网卡"] end subgraph socket_layer["Socket 层"] CG["cgroup connect<br/>应用发起连接时"] end style XDP fill:#ffcdd2,stroke:#c62828 style TC_IN fill:#fff9c4,stroke:#f57f17 style TC_EG fill:#fff9c4,stroke:#f57f17 style CG fill:#c8e6c9,stroke:#2e7d32 style STACK fill:#e0e0e0,stroke:#616161

数据路径对比:iptables vs eBPF#

iptables 模式下,一个数据包从网卡到容器要走的完整路径:

graph LR subgraph iptables_path["iptables 路径"] I1["网卡"] --> I2["netfilter PREROUTING"] I2 --> I3["conntrack 查找"] I3 --> I4["iptables NAT 规则<br/>线性遍历"] I4 --> I5["路由判断"] I5 --> I6["netfilter FORWARD"] I6 --> I7["iptables filter 规则<br/>线性遍历"] I7 --> I8["netfilter POSTROUTING"] I8 --> I9["bridge 转发"] I9 --> I10["veth pair"] end

Cilium eBPF 模式下,tc 钩子直接在数据包进入协议栈前完成转发决策,跳过了中间大量处理:

graph LR subgraph ebpf_path["eBPF 路径"] E1["网卡"] --> E2["tc ingress eBPF"] E2 --> E3["哈希查找 Service 后端<br/>O(1)"] E3 --> E4["直接修改目标 IP/端口"] E4 --> E5["重定向到 veth"] E5 --> E6["容器收到"] end

为什么 eBPF 更快#

性能差距的根源不是”eBPF 本身比 iptables 快”,而是数据路径的根本不同

  1. O(1) 查找 vs O(n) 遍历:eBPF 程序用哈希表存储 Service 后端映射,一次查找就能定位目标,而 iptables 必须逐条规则匹配。规则越多,差距越大

  2. 跳过协议栈中间层:tc eBPF 在数据包进入协议栈前就完成 DNAT 和转发决策,数据包不需要经过 conntrack 查找、iptables 规则遍历、FORWARD 链处理等阶段。这减少了多次上下文切换和表查找

  3. XDP 早期拦截:对于 DDoS 防护等场景,XDP 在网卡驱动层就丢弃恶意包,连 skb 都不创建。iptables 至少要等数据包进入协议栈才能处理

  4. socket 级短路:cgroup eBPF 可以在应用发起 connect() 时就确定后端 Pod,直接建立连接,跳过整个内核侧的 Service 负载均衡流程

# Cilium 完全替代 kube-proxy 的安装方式
helm install cilium cilium/cilium \
--namespace kube-system \
--set kubeProxyReplacement=strict
# 验证 eBPF 程序已挂载
cilium bpf lb list
# 展示 Service 后端的哈希表,查找复杂度 O(1)
# 查看 tc 钩子上的 eBPF 程序
tc filter show dev eth0 ingress
# filter protocol all pref 1 handle 0x1 bpf ...
Warning

eBPF 程序由内核验证器(verifier)检查安全性,保证不会死循环或越界访问。但这也意味着 eBPF 程序的复杂度有限制(指令数上限、栈大小限制),不能做太复杂的逻辑。Cilium 通过程序链(tail call)机制绕过单程序限制,将逻辑拆分到多个 eBPF 程序中串联执行。

附、实践:手工搭建容器网络#

Note

本节用 ip 命令手工搭建一个最小容器网络,理解 veth pair、Network Namespace、IP 配置的完整链路。所有命令需要 root 权限。

附.1 创建 Network Namespace#

ip netns add ns1
ip netns add ns2
ip netns list
ns2
ns1

Network Namespace 是容器网络的隔离基础,每个容器拥有独立的网络栈(接口、路由表、iptables 规则)。

附.2 创建 veth pair#

veth pair 是一对虚拟网卡,像一根网线的两端,从一端发送的数据包会从另一端收到:

ip link add veth1 type veth peer name veth2
ip link show veth1
27: veth1@veth2: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
link/ether 12:30:f5:ac:41:b2 brd ff:ff:ff:ff:ff:ff

附.3 将 veth2 移入 ns1#

ip link set veth2 netns ns1
# 确认 veth2 已在 ns1 中
ip netns exec ns1 ip link show
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
26: veth2@if27: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
link/ether 2e:a3:f7:94:33:a6 brd ff:ff:ff:ff:ff:ff link-netnsid 0

veth2 的 link-netnsid 0 表示它连接到对端(veth1)所在的 Network Namespace。

附.4 配置 IP 地址并启用接口#

# 宿主端
ip addr add 10.0.0.1/24 dev veth1
ip link set veth1 up
# ns1 端
ip netns exec ns1 ip addr add 10.0.0.2/24 dev veth2
ip netns exec ns1 ip link set veth2 up
ip netns exec ns1 ip link set lo up

附.5 测试连通性#

ping -c 2 10.0.0.2
--- 10.0.0.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 0.064/0.633/1.203/0.569 ms

宿主机通过 veth1 → veth2 与 ns1 中的网络栈通信,这就是容器网络的最小模型。Docker 的 bridge 网络模式在此基础上增加了 bridge(连接多个容器)和 iptables NAT(访问外网)。

Note

实验结束后清理:ip link del veth1; ip netns del ns1; ip netns del ns2

九、本章小结#

上一章讨论了容器的安全加固机制。

组件功能所在层
Network Namespace网络栈隔离内核
veth pair跨 Namespace 通信数据链路层
bridge二层转发数据链路层
iptables/NAT地址转换网络层
CNI网络配置接口插件层
Tip

容器网络问题排查时,先确认数据包走到了哪一步:tcpdump 抓宿主机接口确认 DNAT 是否命中,nsenter -n 进入容器 NS 确认 veth 对端是否收到,iptables -t nat -L -n -v 查看规则匹配计数。90% 的容器网络问题都是 iptables 规则缺失或 veth 未正确连接 bridge。

Note

容器网络的核心是 Network Namespace + veth pair + bridge + iptables 的组合。CNI 插件将这些组件的配置自动化,让你不需要手动创建 veth pair 和配置路由。

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

容器网络
https://blog.souloss.cn/posts/container-runtime/container-networking/
作者
Souloss
发布于
2021-09-21
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

相关文章 智能推荐
1
OverlayFS:容器文件系统
容器运行时 OverlayFS 是容器分层文件系统的核心。全面剖析 OverlayFS 的 lowerdir/upperdir/workdir 三层结构、whiteout 标记文件机制、copy-up 写时复制语义、多层叠加规则,以及容器运行时如何用 OverlayFS 实现镜像层复用和容器可写层,理解「为什么容器启动这么快」和「镜像层是怎么共享的」。
2
容器安全:seccomp/AppArmor/Capabilities
容器运行时 容器的安全边界在哪里?Namespace 提供视图隔离,但不阻止特权操作。从零讲透 Linux 安全模块在容器中的应用,Capabilities(权限细分)、seccomp(系统调用过滤)、AppArmor(文件访问控制),以及 rootless 容器的实现,让你理解容器的安全边界和加固方法。
3
容器完整流程:docker run 背后
容器运行时 当你执行 docker run nginx 时,背后发生了什么?本章完整追踪从 Docker CLI 到容器进程启动的每一步,镜像拉取、OCI Bundle 生成、shim 启动、runc 创建、Namespace/Cgroup/OverlayFS 配置、容器进程执行,让你对 docker run 的每一步都了如指掌。
4
容器全景:从 chroot 到 OCI
容器运行时 从 1979 年的 chroot 到 2015 年的 OCI 标准,容器技术走过了三十多年的演进之路。本章梳理这条脉络,并辨析容器与虚拟机、容器进程与普通进程、镜像与容器与 Bundle 几组核心概念,为后续深入打下认知基础。
5
容器运行时深入系列导读
容器运行时 从 Linux 内核的 Namespace、Cgroup、OverlayFS 出发,深入 OCI 规范、runc 源码、containerd 架构与 shim 机制,再覆盖容器安全、网络、镜像构建三个运行时核心维度,建立从内核机制到工业实现的完整认知链。