第14章 CNI模型与实现
学习目标
- 理解CNI(Container Network Interface)规范和工作原理
- 掌握主流CNI插件的配置和使用
- 能够开发自定义CNI插件
- 了解CNI在Kubernetes网络中的作用
前置知识
14.1 CNI概述
14.1.1 什么是CNI
CNI(Container Network Interface)是一个用于配置容器网络的标准接口规范,定义了容器运行时与网络插件之间的通信协议。
核心特点:
- 插件化架构,支持多种网络实现
- 标准化的配置接口
- 支持多网络接口
- 与容器运行时解耦
14.1.2 CNI架构
┌─────────────────────────────────────────────────────────────┐
│ CNI Architecture │
├─────────────────────────────────────────────────────────────┤
│ Container Runtime (containerd, CRI-O, Docker) │
├─────────────────────────────────────────────────────────────┤
│ CNI Plugin Interface │
├─────────────────────────────────────────────────────────────┤
│ CNI Plugins (bridge, host-local, flannel, calico, etc.) │
├─────────────────────────────────────────────────────────────┤
│ Network Implementation (Linux Bridge, VXLAN, etc.) │
└─────────────────────────────────────────────────────────────┘
14.1.3 CNI工作流程
- 容器创建:容器运行时创建网络命名空间
- 插件调用:运行时调用CNI插件配置网络
- 网络配置:插件创建网络接口和路由
- 容器销毁:运行时调用插件清理网络资源
14.2 CNI规范详解
14.2.1 CNI配置格式
基本配置结构:
{
"cniVersion": "0.4.0",
"name": "mynet",
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.244.0.0/16",
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
}
配置字段说明:
cniVersion:CNI规范版本name:网络名称type:插件类型bridge:网桥名称isGateway:是否作为网关ipMasq:是否启用IP伪装ipam:IP地址管理配置
14.2.2 CNI插件接口
ADD命令:
{
"cniVersion": "0.4.0",
"name": "mynet",
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.244.0.0/16"
}
}
DEL命令:
{
"cniVersion": "0.4.0",
"name": "mynet",
"type": "bridge",
"bridge": "cni0"
}
14.2.3 环境变量
CNI插件通过环境变量获取运行时信息:
# 容器网络命名空间路径
CNI_NETNS=/proc/12345/ns/net
# 容器网络接口名称
CNI_IFNAME=eth0
# 容器ID
CNI_CONTAINERID=container-123
# 配置数据
CNI_ARGS=key1=value1;key2=value2
# 配置路径
CNI_PATH=/opt/cni/bin
14.3 主流CNI插件
14.3.1 Bridge插件
功能: 创建Linux网桥,实现容器间通信
配置示例:
{
"cniVersion": "0.4.0",
"name": "bridge",
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.244.0.0/16",
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
}
工作原理:
- 创建Linux网桥
- 创建veth pair
- 将veth一端连接到网桥
- 将veth另一端移动到容器命名空间
- 配置IP地址和路由
14.3.2 Host-local IPAM
功能: 本地IP地址管理
配置示例:
{
"type": "host-local",
"subnet": "10.244.0.0/16",
"rangeStart": "10.244.1.0",
"rangeEnd": "10.244.1.255",
"gateway": "10.244.0.1",
"routes": [
{ "dst": "0.0.0.0/0", "gw": "10.244.0.1" }
]
}
14.3.3 Flannel插件
功能: 基于VXLAN的Overlay网络
配置示例:
{
"name": "cbr0",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
14.4 实验:使用CNI插件
14.4.1 环境准备
安装CNI工具:
# 下载CNI插件
wget https://github.com/containernetworking/plugins/releases/download/v1.1.1/cni-plugins-linux-amd64-v1.1.1.tgz
# 解压到CNI路径
sudo mkdir -p /opt/cni/bin
sudo tar -C /opt/cni/bin -xzf cni-plugins-linux-amd64-v1.1.1.tgz
# 验证安装
ls /opt/cni/bin/
14.4.2 实验1:Bridge插件
步骤1:创建网络命名空间
# 创建命名空间
sudo ip netns add test-ns
# 查看命名空间
ip netns list
步骤2:创建CNI配置
# 创建配置目录
sudo mkdir -p /etc/cni/net.d
# 创建Bridge配置
sudo cat > /etc/cni/net.d/10-bridge.conf << EOF
{
"cniVersion": "0.4.0",
"name": "bridge",
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.244.0.0/16",
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
}
EOF
步骤3:调用CNI插件
# 设置环境变量
export CNI_NETNS=/proc/$(ip netns exec test-ns sh -c 'echo $$')/ns/net
export CNI_IFNAME=eth0
export CNI_CONTAINERID=test-container
export CNI_PATH=/opt/cni/bin
# 调用ADD命令
echo '{"cniVersion": "0.4.0", "name": "bridge", "type": "bridge", "bridge": "cni0", "isGateway": true, "ipMasq": true, "ipam": {"type": "host-local", "subnet": "10.244.0.0/16", "routes": [{"dst": "0.0.0.0/0"}]}}' | sudo CNI_COMMAND=ADD CNI_NETNS=$CNI_NETNS CNI_IFNAME=$CNI_IFNAME CNI_CONTAINERID=$CNI_CONTAINERID CNI_PATH=$CNI_PATH /opt/cni/bin/bridge
步骤4:验证配置
# 查看网桥
ip link show cni0
# 查看命名空间中的接口
ip netns exec test-ns ip addr show
# 查看路由
ip netns exec test-ns ip route show
14.4.3 实验2:多网络接口
步骤1:创建第二个网络
# 创建第二个网络配置
sudo cat > /etc/cni/net.d/20-macvlan.conf << EOF
{
"cniVersion": "0.4.0",
"name": "macvlan",
"type": "macvlan",
"master": "eth0",
"mode": "bridge",
"ipam": {
"type": "host-local",
"subnet": "192.168.1.0/24",
"rangeStart": "192.168.1.100",
"rangeEnd": "192.168.1.200"
}
}
EOF
步骤2:添加第二个接口
# 调用macvlan插件
echo '{"cniVersion": "0.4.0", "name": "macvlan", "type": "macvlan", "master": "eth0", "mode": "bridge", "ipam": {"type": "host-local", "subnet": "192.168.1.0/24", "rangeStart": "192.168.1.100", "rangeEnd": "192.168.1.200"}}' | sudo CNI_COMMAND=ADD CNI_NETNS=$CNI_NETNS CNI_IFNAME=eth1 CNI_CONTAINERID=test-container CNI_PATH=$CNI_PATH /opt/cni/bin/macvlan
步骤3:验证多接口
# 查看所有接口
ip netns exec test-ns ip addr show
# 测试连通性
ip netns exec test-ns ping -c 3 192.168.1.1
14.5 开发自定义CNI插件
14.5.1 插件结构
基本插件结构:
package main
import (
"encoding/json"
"fmt"
"os"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/version"
)
type NetConf struct {
CNIVersion string `json:"cniVersion"`
Name string `json:"name"`
Type string `json:"type"`
Bridge string `json:"bridge"`
}
func cmdAdd(args *skel.CmdArgs) error {
// 解析配置
conf := NetConf{}
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
return err
}
// 实现ADD逻辑
fmt.Printf("Adding network %s\n", conf.Name)
return nil
}
func cmdDel(args *skel.CmdArgs) error {
// 实现DEL逻辑
fmt.Printf("Deleting network\n")
return nil
}
func main() {
skel.PluginMain(cmdAdd, cmdDel, version.All)
}
14.5.2 编译和测试
编译插件:
# 创建Go模块
go mod init my-cni-plugin
# 安装依赖
go get github.com/containernetworking/cni/pkg/skel
go get github.com/containernetworking/cni/pkg/version
# 编译
go build -o my-cni-plugin
# 安装到CNI路径
sudo cp my-cni-plugin /opt/cni/bin/
测试插件:
# 创建测试配置
sudo cat > /etc/cni/net.d/30-my-plugin.conf << EOF
{
"cniVersion": "0.4.0",
"name": "my-plugin",
"type": "my-cni-plugin",
"bridge": "my-bridge"
}
EOF
# 测试ADD命令
echo '{"cniVersion": "0.4.0", "name": "my-plugin", "type": "my-cni-plugin", "bridge": "my-bridge"}' | sudo CNI_COMMAND=ADD CNI_NETNS=$CNI_NETNS CNI_IFNAME=eth0 CNI_CONTAINERID=test-container CNI_PATH=$CNI_PATH /opt/cni/bin/my-cni-plugin
14.6 CNI在Kubernetes中的应用
14.6.1 Kubelet CNI配置
Kubelet配置:
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cniConfDir: /etc/cni/net.d
cniBinDir: /opt/cni/bin
cniCacheDir: /var/lib/cni/cache
14.6.2 多网络支持
NetworkAttachmentDefinition:
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: macvlan-conf
spec:
config: |
{
"cniVersion": "0.4.0",
"name": "macvlan",
"type": "macvlan",
"master": "eth0",
"mode": "bridge",
"ipam": {
"type": "host-local",
"subnet": "192.168.1.0/24"
}
}
Pod使用多网络:
apiVersion: v1
kind: Pod
metadata:
name: multi-net-pod
annotations:
k8s.v1.cni.cncf.io/networks: macvlan-conf
spec:
containers:
- name: app
image: nginx
14.7 故障排查
14.7.1 常见问题诊断
问题1:CNI插件调用失败
# 检查CNI配置
cat /etc/cni/net.d/*.conf
# 检查插件权限
ls -la /opt/cni/bin/
# 查看Kubelet日志
journalctl -u kubelet -f
问题2:网络接口创建失败
# 检查命名空间
ip netns list
# 检查网桥状态
ip link show cni0
# 查看CNI日志
tail -f /var/log/cni.log
问题3:IP地址分配失败
# 检查IPAM状态
cat /var/lib/cni/networks/bridge/last_reserved_ip.0
# 检查子网配置
ip route show
14.7.2 调试工具
# 使用CNI调试工具
CNI_COMMAND=ADD CNI_NETNS=/proc/12345/ns/net CNI_IFNAME=eth0 CNI_CONTAINERID=test CNI_PATH=/opt/cni/bin /opt/cni/bin/bridge < config.json
# 查看CNI缓存
ls -la /var/lib/cni/cache/
# 清理CNI状态
rm -rf /var/lib/cni/cache/*
14.8 排错清单
14.8.1 CNI配置检查
- [ ] CNI配置文件格式是否正确
- [ ] 插件是否存在于CNI路径
- [ ] 插件是否有执行权限
- [ ] 网络命名空间是否存在
- [ ] IPAM配置是否正确
14.8.2 网络连通性检查
- [ ] 网桥是否创建成功
- [ ] veth pair是否正确创建
- [ ] IP地址是否分配成功
- [ ] 路由表是否正确
- [ ] 防火墙规则是否阻止流量
14.8.3 性能问题检查
- [ ] 插件执行时间是否过长
- [ ] 网络接口数量是否过多
- [ ] IP地址池是否耗尽
- [ ] 网桥转发性能是否正常
- [ ] 内存使用是否正常
14.9 延伸阅读
返回目录:README