05-容器网络原理
学习目标
- 深入理解 Network Namespace 的工作原理
- 掌握 veth pair 虚拟网卡机制
- 理解 Linux Bridge 的工作原理
- 掌握 iptables NAT 配置
- 能够追踪数据包的完整路径
前置知识
- Linux 网络基础
- TCP/IP 协议栈
- 网络接口概念
- iptables 基础
一、容器网络概述
1.1 容器网络挑战
容器网络需要解决以下问题:
graph TD
A[容器网络需求] --> B[网络隔离]
A --> C[网络连通]
A --> D[网络管理]
A --> E[性能优化]
B --> B1[每个容器独立网络栈]
B --> B2[独立的IP地址空间]
B --> B3[独立的端口空间]
C --> C1[容器间通信]
C --> C2[容器与宿主机通信]
C --> C3[容器与外部网络通信]
D --> D1[IP地址分配]
D --> D2[路由配置]
D --> D3[防火墙规则]
E --> E1[低延迟]
E --> E2[高带宽]
E --> E3[低CPU开销]
1.2 容器网络解决方案
graph LR
A[容器网络] --> B[Network Namespace]
A --> C[veth pair]
A --> D[Linux Bridge]
A --> E[iptables NAT]
A --> F[路由表]
B --> B1[网络隔离]
C --> C1[虚拟网卡对]
D --> D1[二层转发]
E --> E1[地址转换]
F --> F1[三层路由]
二、Network Namespace 详解
2.1 Network Namespace 原理
Network Namespace 是 Linux 内核提供的网络协议栈隔离机制:
graph TD
A[Linux 内核] --> B[网络协议栈1]
A --> C[网络协议栈2]
A --> D[网络协议栈3]
B --> B1[网络接口]
B --> B2[IP地址]
B --> B3[路由表]
B --> B4[iptables规则]
C --> C1[网络接口]
C --> C2[IP地址]
C --> C3[路由表]
C --> C4[iptables规则]
D --> D1[网络接口]
D --> D2[IP地址]
D --> D3[路由表]
D --> D4[iptables规则]
2.2 Network Namespace 特性
特性 | 说明 | 示例 |
---|---|---|
独立网络接口 | 每个 namespace 有独立的网络接口列表 | ip link show |
独立IP地址 | 每个 namespace 有独立的IP地址空间 | ip addr show |
独立路由表 | 每个 namespace 有独立的路由表 | ip route show |
独立端口空间 | 每个 namespace 有独立的端口空间 | netstat -tlnp |
独立防火墙规则 | 每个 namespace 有独立的iptables规则 | iptables -L |
2.3 Network Namespace 实战演示
2.3.1 创建和查看 Network Namespace
# 1. 创建 Network Namespace
sudo ip netns add test-ns
# 2. 查看所有 Network Namespace
ip netns list
# 输出: test-ns
# 3. 在 Network Namespace 中执行命令
sudo ip netns exec test-ns ip link show
# 输出: 只有 lo 接口
# 4. 启动 lo 接口
sudo ip netns exec test-ns ip link set lo up
# 5. 查看网络接口状态
sudo ip netns exec test-ns ip addr show
# 输出: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
2.3.2 Network Namespace 隔离验证
# 在宿主机上查看网络接口
ip link show
# 输出: 包含 eth0, lo 等接口
# 在 test-ns 中查看网络接口
sudo ip netns exec test-ns ip link show
# 输出: 只有 lo 接口
# 在宿主机上查看路由表
ip route show
# 输出: 包含默认路由等
# 在 test-ns 中查看路由表
sudo ip netns exec test-ns ip route show
# 输出: 空(没有路由)
三、veth pair 虚拟网卡
3.1 veth pair 原理
veth pair 是 Linux 内核提供的虚拟以太网设备对:
graph LR
A[veth0] <--> B[veth1]
A --> C[宿主机网络栈]
B --> D[容器网络栈]
E[数据包] --> A
A --> F[转发到 veth1]
F --> B
B --> G[容器接收]
3.2 veth pair 特性
- 成对出现:veth0 和 veth1 总是成对创建
- 数据转发:从一端进入的数据会从另一端出来
- 双向通信:支持双向数据传输
- 命名空间隔离:两端可以分别放在不同的 Network Namespace 中
3.3 veth pair 实战演示
3.3.1 创建 veth pair
# 1. 创建 veth pair
sudo ip link add veth0 type veth peer name veth1
# 2. 查看创建的 veth pair
ip link show | grep veth
# 输出:
# 4: veth1@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN
# 5: veth0@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN
# 3. 启动 veth0
sudo ip link set veth0 up
# 4. 将 veth1 移到 test-ns
sudo ip link set veth1 netns test-ns
# 5. 在 test-ns 中启动 veth1
sudo ip netns exec test-ns ip link set veth1 up
3.3.2 配置 IP 地址
# 1. 给 veth0 配置 IP 地址
sudo ip addr add 192.168.1.1/24 dev veth0
# 2. 给 veth1 配置 IP 地址
sudo ip netns exec test-ns ip addr add 192.168.1.2/24 dev veth1
# 3. 验证配置
ip addr show veth0
# 输出: inet 192.168.1.1/24 scope global veth0
sudo ip netns exec test-ns ip addr show veth1
# 输出: inet 192.168.1.2/24 scope global veth1
3.3.3 测试连通性
# 1. 从宿主机 ping 容器
ping -c 3 192.168.1.2
# 输出: 64 bytes from 192.168.1.2: icmp_seq=1 ttl=64 time=0.123 ms
# 2. 从容器 ping 宿主机
sudo ip netns exec test-ns ping -c 3 192.168.1.1
# 输出: 64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=0.098 ms
四、Linux Bridge 详解
4.1 Linux Bridge 原理
Linux Bridge 是内核提供的二层虚拟交换机:
graph TD
A[Linux Bridge] --> B[学习表]
A --> C[转发规则]
D[veth0] --> A
E[veth1] --> A
F[veth2] --> A
G[eth0] --> A
A --> H[根据MAC地址转发]
H --> I[广播/单播/组播]
4.2 Bridge 特性
- MAC 学习:自动学习 MAC 地址与端口的对应关系
- 广播转发:向所有端口转发广播帧
- 单播转发:根据 MAC 地址表转发单播帧
- VLAN 支持:支持 VLAN 隔离
- STP 支持:支持生成树协议
4.3 Bridge 实战演示
4.3.1 创建和配置 Bridge
# 1. 创建 Bridge
sudo ip link add name br0 type bridge
# 2. 启动 Bridge
sudo ip link set br0 up
# 3. 给 Bridge 配置 IP 地址
sudo ip addr add 192.168.100.1/24 dev br0
# 4. 查看 Bridge 状态
ip link show br0
# 输出: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
4.3.2 连接 veth 到 Bridge
# 1. 创建多个 veth pair
sudo ip link add veth-host1 type veth peer name veth-cont1
sudo ip link add veth-host2 type veth peer name veth-cont2
# 2. 将 veth-host 端连接到 Bridge
sudo ip link set veth-host1 master br0
sudo ip link set veth-host2 master br0
# 3. 启动 veth-host 端
sudo ip link set veth-host1 up
sudo ip link set veth-host2 up
# 4. 将 veth-cont 端移到不同的 namespace
sudo ip link set veth-cont1 netns test-ns
sudo ip netns add test-ns2
sudo ip link set veth-cont2 netns test-ns2
# 5. 在 namespace 中配置 veth-cont 端
sudo ip netns exec test-ns ip link set veth-cont1 up
sudo ip netns exec test-ns ip addr add 192.168.100.2/24 dev veth-cont1
sudo ip netns exec test-ns2 ip link set veth-cont2 up
sudo ip netns exec test-ns2 ip addr add 192.168.100.3/24 dev veth-cont2
4.3.3 测试容器间通信
# 1. 从 test-ns ping test-ns2
sudo ip netns exec test-ns ping -c 3 192.168.100.3
# 输出: 64 bytes from 192.168.100.3: icmp_seq=1 ttl=64 time=0.123 ms
# 2. 从 test-ns2 ping test-ns
sudo ip netns exec test-ns2 ping -c 3 192.168.100.2
# 输出: 64 bytes from 192.168.100.2: icmp_seq=1 ttl=64 time=0.098 ms
# 3. 查看 Bridge 的 MAC 学习表
bridge fdb show br br0
# 输出: 显示 MAC 地址与端口的对应关系
五、iptables NAT 配置
5.1 NAT 原理
NAT (Network Address Translation) 用于实现地址转换:
graph TD
A[容器网络] --> B[192.168.100.0/24]
B --> C[SNAT 转换]
C --> D[宿主机IP]
D --> E[外部网络]
F[外部网络] --> G[宿主机IP]
G --> H[DNAT 转换]
H --> I[容器IP]
I --> J[容器网络]
5.2 NAT 类型
类型 | 全称 | 作用 | 使用场景 |
---|---|---|---|
SNAT | Source NAT | 源地址转换 | 容器访问外网 |
DNAT | Destination NAT | 目标地址转换 | 外网访问容器 |
MASQUERADE | 动态SNAT | 自动获取源IP | 动态IP环境 |
5.3 NAT 实战演示
5.3.1 配置 SNAT 出网
# 1. 启用 IP 转发
sudo sysctl -w net.ipv4.ip_forward=1
# 2. 配置 SNAT 规则
sudo iptables -t nat -A POSTROUTING -s 192.168.100.0/24 -j MASQUERADE
# 3. 查看 NAT 规则
sudo iptables -t nat -L POSTROUTING
# 输出:
# Chain POSTROUTING (policy ACCEPT)
# target prot opt source destination
# MASQUERADE all -- 192.168.100.0/24 anywhere
# 4. 在容器中测试出网
sudo ip netns exec test-ns ping -c 3 8.8.8.8
# 输出: 64 bytes from 8.8.8.8: icmp_seq=1 ttl=64 time=12.345 ms
5.3.2 配置 DNAT 入网
# 1. 配置 DNAT 规则(将宿主机8080端口映射到容器80端口)
sudo iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 192.168.100.2:80
# 2. 配置 FORWARD 规则
sudo iptables -A FORWARD -p tcp -d 192.168.100.2 --dport 80 -j ACCEPT
# 3. 查看 NAT 规则
sudo iptables -t nat -L PREROUTING
# 输出:
# Chain PREROUTING (policy ACCEPT)
# target prot opt source destination
# DNAT tcp -- anywhere anywhere tcp dpt:8080 to:192.168.100.2:80
六、数据包路径追踪
6.1 完整数据包路径
sequenceDiagram
participant C as 容器
participant V as veth pair
participant B as Bridge
participant I as iptables
participant H as 宿主机网卡
participant E as 外部网络
C->>V: 发送数据包
V->>B: 转发到 Bridge
B->>I: 经过 iptables
I->>H: SNAT 转换
H->>E: 发送到外部网络
E->>H: 返回数据包
H->>I: 经过 iptables
I->>B: DNAT 转换
B->>V: 转发到 veth
V->>C: 返回给容器
6.2 数据包处理流程
6.2.1 出网流程
- 容器发送:容器进程发送数据包
- veth 转发:veth pair 将数据包转发到 Bridge
- Bridge 学习:Bridge 学习 MAC 地址
- iptables 处理:经过 iptables 规则处理
- SNAT 转换:将源 IP 转换为宿主机 IP
- 路由转发:通过宿主机网卡发送到外部网络
6.2.2 入网流程
- 外部接收:宿主机网卡接收数据包
- iptables 处理:经过 iptables 规则处理
- DNAT 转换:将目标 IP 转换为容器 IP
- Bridge 转发:Bridge 根据 MAC 地址转发
- veth 转发:veth pair 将数据包转发到容器
- 容器接收:容器进程接收数据包
6.3 调试工具
6.3.1 使用 tcpdump 抓包
# 1. 在宿主机上抓包
sudo tcpdump -i br0 -n
# 2. 在容器中抓包
sudo ip netns exec test-ns tcpdump -i veth-cont1 -n
# 3. 抓取特定协议
sudo tcpdump -i br0 -n icmp
# 4. 抓取特定端口
sudo tcpdump -i br0 -n port 80
6.3.2 使用 iptables 日志
# 1. 添加日志规则
sudo iptables -A FORWARD -j LOG --log-prefix "FORWARD: "
# 2. 查看日志
sudo tail -f /var/log/kern.log | grep FORWARD
# 3. 删除日志规则
sudo iptables -D FORWARD -j LOG --log-prefix "FORWARD: "
️ 七、综合实战:搭建完整容器网络
7.1 自动化脚本
#!/bin/bash
# 搭建完整的容器网络环境
echo "=== 搭建容器网络环境 ==="
# 1. 创建 Network Namespace
sudo ip netns add container1
sudo ip netns add container2
# 2. 创建 Bridge
sudo ip link add name br0 type bridge
sudo ip link set br0 up
sudo ip addr add 192.168.100.1/24 dev br0
# 3. 创建 veth pair
sudo ip link add veth-host1 type veth peer name veth-cont1
sudo ip link add veth-host2 type veth peer name veth-cont2
# 4. 连接 veth 到 Bridge
sudo ip link set veth-host1 master br0
sudo ip link set veth-host2 master br0
sudo ip link set veth-host1 up
sudo ip link set veth-host2 up
# 5. 配置容器网络
sudo ip link set veth-cont1 netns container1
sudo ip link set veth-cont2 netns container2
sudo ip netns exec container1 ip link set veth-cont1 up
sudo ip netns exec container1 ip addr add 192.168.100.2/24 dev veth-cont1
sudo ip netns exec container1 ip route add default via 192.168.100.1
sudo ip netns exec container2 ip link set veth-cont2 up
sudo ip netns exec container2 ip addr add 192.168.100.3/24 dev veth-cont2
sudo ip netns exec container2 ip route add default via 192.168.100.1
# 6. 配置 NAT
sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -t nat -A POSTROUTING -s 192.168.100.0/24 -j MASQUERADE
# 7. 测试连通性
echo "=== 测试容器间通信 ==="
sudo ip netns exec container1 ping -c 3 192.168.100.3
echo "=== 测试出网 ==="
sudo ip netns exec container1 ping -c 3 8.8.8.8
echo "=== 网络环境搭建完成 ==="
echo "容器1: 192.168.100.2"
echo "容器2: 192.168.100.3"
echo "Bridge: 192.168.100.1"
7.2 Go 代码实现
package main
import (
"fmt"
"os"
"os/exec"
"syscall"
)
func createNetworkNamespace(name string) error {
// 创建 Network Namespace
cmd := exec.Command("ip", "netns", "add", name)
return cmd.Run()
}
func createBridge(name, cidr string) error {
// 创建 Bridge
if err := exec.Command("ip", "link", "add", "name", name, "type", "bridge").Run(); err != nil {
return err
}
// 启动 Bridge
if err := exec.Command("ip", "link", "set", name, "up").Run(); err != nil {
return err
}
// 配置 IP 地址
if err := exec.Command("ip", "addr", "add", cidr, "dev", name).Run(); err != nil {
return err
}
return nil
}
func createVethPair(hostIf, contIf string) error {
// 创建 veth pair
return exec.Command("ip", "link", "add", hostIf, "type", "veth", "peer", "name", contIf).Run()
}
func connectVethToBridge(hostIf, bridgeName string) error {
// 连接 veth 到 Bridge
if err := exec.Command("ip", "link", "set", hostIf, "master", bridgeName).Run(); err != nil {
return err
}
// 启动 veth
return exec.Command("ip", "link", "set", hostIf, "up").Run()
}
func configureContainerNetwork(nsName, contIf, ip, gateway string) error {
// 将 veth 移到 namespace
if err := exec.Command("ip", "link", "set", contIf, "netns", nsName).Run(); err != nil {
return err
}
// 在 namespace 中配置网络
commands := [][]string{
{"ip", "netns", "exec", nsName, "ip", "link", "set", contIf, "up"},
{"ip", "netns", "exec", nsName, "ip", "addr", "add", ip, "dev", contIf},
{"ip", "netns", "exec", nsName, "ip", "route", "add", "default", "via", gateway},
}
for _, cmd := range commands {
if err := exec.Command(cmd[0], cmd[1:]...).Run(); err != nil {
return err
}
}
return nil
}
func configureNAT(subnet string) error {
// 启用 IP 转发
if err := exec.Command("sysctl", "-w", "net.ipv4.ip_forward=1").Run(); err != nil {
return err
}
// 配置 SNAT
return exec.Command("iptables", "-t", "nat", "-A", "POSTROUTING", "-s", subnet, "-j", "MASQUERADE").Run()
}
func main() {
// 创建网络环境
if err := createNetworkNamespace("container1"); err != nil {
fmt.Printf("创建 namespace 失败: %v\n", err)
os.Exit(1)
}
if err := createNetworkNamespace("container2"); err != nil {
fmt.Printf("创建 namespace 失败: %v\n", err)
os.Exit(1)
}
if err := createBridge("br0", "192.168.100.1/24"); err != nil {
fmt.Printf("创建 Bridge 失败: %v\n", err)
os.Exit(1)
}
if err := createVethPair("veth-host1", "veth-cont1"); err != nil {
fmt.Printf("创建 veth pair 失败: %v\n", err)
os.Exit(1)
}
if err := createVethPair("veth-host2", "veth-cont2"); err != nil {
fmt.Printf("创建 veth pair 失败: %v\n", err)
os.Exit(1)
}
if err := connectVethToBridge("veth-host1", "br0"); err != nil {
fmt.Printf("连接 veth 到 Bridge 失败: %v\n", err)
os.Exit(1)
}
if err := connectVethToBridge("veth-host2", "br0"); err != nil {
fmt.Printf("连接 veth 到 Bridge 失败: %v\n", err)
os.Exit(1)
}
if err := configureContainerNetwork("container1", "veth-cont1", "192.168.100.2/24", "192.168.100.1"); err != nil {
fmt.Printf("配置容器网络失败: %v\n", err)
os.Exit(1)
}
if err := configureContainerNetwork("container2", "veth-cont2", "192.168.100.3/24", "192.168.100.1"); err != nil {
fmt.Printf("配置容器网络失败: %v\n", err)
os.Exit(1)
}
if err := configureNAT("192.168.100.0/24"); err != nil {
fmt.Printf("配置 NAT 失败: %v\n", err)
os.Exit(1)
}
fmt.Println("容器网络环境创建成功!")
fmt.Println("容器1: 192.168.100.2")
fmt.Println("容器2: 192.168.100.3")
fmt.Println("Bridge: 192.168.100.1")
}
八、验证检查清单
基础理解
- [ ] 理解 Network Namespace 的作用和原理
- [ ] 掌握 veth pair 的工作机制
- [ ] 理解 Linux Bridge 的工作原理
- [ ] 掌握 iptables NAT 配置
实践能力
- [ ] 能够创建和配置 Network Namespace
- [ ] 能够创建和配置 veth pair
- [ ] 能够创建和配置 Linux Bridge
- [ ] 能够配置 iptables NAT 规则
- [ ] 能够搭建完整的容器网络环境
调试技能
- [ ] 掌握网络调试工具的使用
- [ ] 能够追踪数据包路径
- [ ] 能够排查网络连通性问题
- [ ] 理解网络性能优化方法
相关链接
- 02-Namespace隔离机制 - 进程隔离技术
- 06-网络模式与实现 - 网络模式详解
- 07-CNI插件开发 - 网络插件开发
下一步:让我们学习不同的网络模式与实现,这是容器网络的高级应用!