CNI 网络插件深度解析

CNI 网络插件深度解析

CNI 概述

CNI (Container Network Interface) 是 Kubernetes 用于配置容器网络的标准接口规范。

CNI 架构

┌────────────────────────────────────────┐
│           kubelet                      │
├────────────────────────────────────────┤
│  1. 创建 Pod                            │
│  2. 调用 CRI 创建容器                   │
│  3. 调用 CNI 配置网络                   │
│     - ADD: 添加网络接口                 │
│     - DEL: 删除网络接口                 │
│     - CHECK: 检查网络状态               │
│     - VERSION: 查询版本                 │
└────────┬───────────────────────────────┘
         │ CNI Plugin 调用
         ▼
┌────────────────────────────────────────┐
│       CNI Plugin (二进制)               │
│  /opt/cni/bin/                         │
│  ├─ bridge                             │
│  ├─ flannel                            │
│  ├─ calico                             │
│  ├─ weave                              │
│  └─ ...                                │
└────────┬───────────────────────────────┘
         │ 网络配置
         ▼
┌────────────────────────────────────────┐
│       容器网络                          │
│  - 分配 IP 地址                        │
│  - 创建网络接口                        │
│  - 配置路由规则                        │
│  - 设置网络策略                        │
└────────────────────────────────────────┘

CNI 规范

CNI 配置文件

// /etc/cni/net.d/10-flannel.conflist
{
  "name": "cbr0",
  "cniVersion": "0.3.1",
  "plugins": [
    {
      "type": "flannel",
      "delegate": {
        "hairpinMode": true,
        "isDefaultGateway": true
      }
    },
    {
      "type": "portmap",
      "capabilities": {
        "portMappings": true
      }
    }
  ]
}

CNI 插件接口

// github.com/containernetworking/cni/pkg/skel/skel.go

type CmdArgs struct {
    ContainerID string    // 容器 ID
    Netns       string    // 网络命名空间路径
    IfName      string    // 接口名称(如 eth0)
    Args        string    // 额外参数
    Path        string    // CNI 插件搜索路径
    StdinData   []byte    // 配置数据(JSON)
}

// ADD 操作:添加网络接口
func cmdAdd(args *skel.CmdArgs) error {
    // 1. 解析配置
    conf, err := parseConfig(args.StdinData)
    if err != nil {
        return err
    }
    
    // 2. 分配 IP 地址
    ipAddr, err := allocateIP(conf)
    if err != nil {
        return err
    }
    
    // 3. 创建网络接口
    err = setupInterface(args.Netns, args.IfName, ipAddr, conf)
    if err != nil {
        return err
    }
    
    // 4. 配置路由
    err = setupRoutes(args.Netns, conf)
    if err != nil {
        return err
    }
    
    // 5. 返回结果
    result := &types.Result{
        CNIVersion: conf.CNIVersion,
        IPs: []*types.IPConfig{
            {
                Address: ipAddr,
                Gateway: conf.Gateway,
            },
        },
        Routes: conf.Routes,
        DNS:    conf.DNS,
    }
    
    return result.Print()
}

// DEL 操作:删除网络接口
func cmdDel(args *skel.CmdArgs) error {
    // 1. 解析配置
    conf, err := parseConfig(args.StdinData)
    if err != nil {
        return err
    }
    
    // 2. 删除网络接口
    err = deleteInterface(args.Netns, args.IfName)
    if err != nil {
        return err
    }
    
    // 3. 释放 IP 地址
    err = releaseIP(conf, args.ContainerID)
    if err != nil {
        return err
    }
    
    return nil
}

Flannel 网络实现

Flannel 架构

┌─────────────────────────────────────────────┐
│              etcd / Kubernetes API          │
│         (存储网络配置和子网分配)              │
└───────────────┬─────────────────────────────┘
                │
    ┌───────────┼───────────┐
    │           │           │
    ▼           ▼           ▼
┌────────┐  ┌────────┐  ┌────────┐
│ Node 1 │  │ Node 2 │  │ Node 3 │
│10.244.0│  │10.244.1│  │10.244.2│
│   .0/24│  │   .0/24│  │   .0/24│
└────┬───┘  └────┬───┘  └────┬───┘
     │           │           │
  Overlay Network (VXLAN/Host-gw)
     │           │           │
  Pod网络        Pod网络     Pod网络

VXLAN 模式

# 查看 VXLAN 设备
ip -d link show flannel.1

# VXLAN 设备配置
# 3: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN
#     vxlan id 1 local 192.168.1.101 dev eth0 srcport 0 0 dstport 8472 nolearning ageing 300

# 查看转发表
bridge fdb show dev flannel.1

# 路由规则
ip route
# 10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink
# 10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink

VXLAN 数据包封装

原始 IP 包:
┌──────────────────────────────────────────┐
│ 源 IP: 10.244.0.2 (Pod1)                 │
│ 目标 IP: 10.244.1.3 (Pod2)               │
│ 数据载荷                                  │
└──────────────────────────────────────────┘
         │ VXLAN 封装
         ▼
┌──────────────────────────────────────────┐
│ 外层 IP 头                                │
│ 源 IP: 192.168.1.101 (Node1)             │
│ 目标 IP: 192.168.1.102 (Node2)           │
├──────────────────────────────────────────┤
│ UDP 头 (端口 8472)                        │
├──────────────────────────────────────────┤
│ VXLAN 头 (VNI=1)                         │
├──────────────────────────────────────────┤
│ 内层以太网帧                              │
│ 源 MAC: Pod1 MAC                         │
│ 目标 MAC: Pod2 MAC                       │
├──────────────────────────────────────────┤
│ 原始 IP 包                                │
│ 源 IP: 10.244.0.2                        │
│ 目标 IP: 10.244.1.3                      │
│ 数据载荷                                  │
└──────────────────────────────────────────┘

Host-gw 模式

# Host-gw 不使用 Overlay,直接三层路由

# Node 1 路由表
ip route
# 10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1
# 10.244.1.0/24 via 192.168.1.102 dev eth0  # 直接路由到 Node2
# 10.244.2.0/24 via 192.168.1.103 dev eth0  # 直接路由到 Node3

# 性能更好,但要求节点在同一子网

Calico 网络实现

Calico 架构

┌──────────────────────────────────────────┐
│            Felix (Agent)                 │
│  - 配置 iptables 规则                     │
│  - 配置路由表                             │
│  - 编程 ACL                               │
└────────────┬─────────────────────────────┘
             │
             ▼
┌──────────────────────────────────────────┐
│        BIRD (BGP Daemon)                 │
│  - 宣告路由                               │
│  - 学习路由                               │
│  - 与其他节点交换路由                     │
└────────────┬─────────────────────────────┘
             │ BGP Peering
             ▼
    ┌────────────────────┐
    │  其他节点的 BIRD   │
    └────────────────────┘

BGP 路由模式

# Calico 使用 BGP 协议分发路由

# Node 1 宣告其 Pod 子网
# via BIRD: 10.244.0.0/24 via 192.168.1.101

# Node 2 学习到路由
ip route
# 10.244.0.0/24 via 192.168.1.101 dev eth0 proto bird

# 查看 BGP 邻居
calicoctl node status
# Calico process is running.
# IPv4 BGP status
# +--------------+-------------------+-------+----------+-------------+
# | PEER ADDRESS |     PEER TYPE     | STATE |  SINCE   |    INFO     |
# +--------------+-------------------+-------+----------+-------------+
# | 192.168.1.102| node-to-node mesh | up    | 10:00:00 | Established |
# | 192.168.1.103| node-to-node mesh | up    | 10:00:00 | Established |
# +--------------+-------------------+-------+----------+-------------+

IPIP 模式

# Calico IPIP:跨子网使用隧道

# 创建 tunl0 设备
ip link add tunl0 type ipip

# 查看 tunl0
ip -d link show tunl0
# 4: tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN
#     ipip any remote any local any ttl inherit nopmtudisc

# 路由规则
ip route
# 10.244.1.0/24 via 192.168.1.102 dev tunl0 onlink  # 同子网直接路由
# 10.244.2.0/24 via 192.168.2.103 dev tunl0 onlink  # 跨子网使用 IPIP

Cilium 网络实现

Cilium 架构(基于 eBPF)

┌──────────────────────────────────────────┐
│          Cilium Agent                    │
│  - eBPF 程序编译和加载                    │
│  - 网络策略编程                           │
│  - 服务负载均衡                           │
└────────────┬─────────────────────────────┘
             │ 加载 eBPF 程序
             ▼
┌──────────────────────────────────────────┐
│         Linux Kernel                     │
│  ┌────────────────────────────────────┐  │
│  │      eBPF Maps (BPF Hash)          │  │
│  │  - 网络策略                         │  │
│  │  - 连接跟踪                         │  │
│  │  - 负载均衡表                       │  │
│  └────────────────────────────────────┘  │
│  ┌────────────────────────────────────┐  │
│  │    eBPF Programs (TC/XDP)          │  │
│  │  - 数据包处理                       │  │
│  │  - 策略执行                         │  │
│  │  - NAT                              │  │
│  └────────────────────────────────────┘  │
└──────────────────────────────────────────┘

eBPF 数据路径

// Cilium eBPF 程序示例
SEC("tc")
int handle_ingress(struct __sk_buff *skb) {
    void *data_end = (void *)(long)skb->data_end;
    void *data = (void *)(long)skb->data;
    
    // 解析以太网头
    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end)
        return TC_ACT_OK;
    
    // 解析 IP 头
    if (eth->h_proto != bpf_htons(ETH_P_IP))
        return TC_ACT_OK;
    
    struct iphdr *ip = (void *)(eth + 1);
    if ((void *)(ip + 1) > data_end)
        return TC_ACT_OK;
    
    // 查找策略
    struct policy_key key = {
        .src_ip = ip->saddr,
        .dst_ip = ip->daddr,
        .protocol = ip->protocol,
    };
    
    struct policy_entry *policy = bpf_map_lookup_elem(&policy_map, &key);
    if (!policy)
        return TC_ACT_SHOT;  // 拒绝
    
    if (policy->action == POLICY_ALLOW)
        return TC_ACT_OK;    // 允许
    
    return TC_ACT_SHOT;      // 拒绝
}

CNI 插件对比

特性 Flannel Calico Cilium Weave
实现方式 VXLAN/Host-gw BGP/IPIP eBPF VXLAN
性能 中等 很高 中等
网络策略 需要额外插件 原生支持 原生支持 支持
多集群 不支持 支持 支持 支持
服务网格 不支持 Istio 原生支持 不支持
复杂度 中等
适用场景 简单集群 大规模集群 高性能需求 中小集群

自定义 CNI 插件开发

简单 Bridge CNI 插件

package main

import (
    "encoding/json"
    "fmt"
    "net"
    
    "github.com/containernetworking/cni/pkg/skel"
    "github.com/containernetworking/cni/pkg/types"
    "github.com/containernetworking/plugins/pkg/ns"
    "github.com/vishvananda/netlink"
)

type NetConf struct {
    types.NetConf
    BridgeName string `json:"bridge"`
    Subnet     string `json:"subnet"`
}

func cmdAdd(args *skel.CmdArgs) error {
    // 1. 解析配置
    conf := &NetConf{}
    if err := json.Unmarshal(args.StdinData, conf); err != nil {
        return err
    }
    
    // 2. 创建或获取 Bridge
    br, err := ensureBridge(conf.BridgeName, conf.Subnet)
    if err != nil {
        return err
    }
    
    // 3. 在容器网络命名空间中创建 veth 对
    hostVeth, containerVeth, err := setupVeth(args.ContainerID, args.IfName, args.Netns, br)
    if err != nil {
        return err
    }
    
    // 4. 分配 IP 地址
    ipAddr, err := allocateIP(conf.Subnet, args.ContainerID)
    if err != nil {
        return err
    }
    
    // 5. 配置容器接口
    err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
        link, err := netlink.LinkByName(args.IfName)
        if err != nil {
            return err
        }
        
        // 设置 IP 地址
        addr := &netlink.Addr{
            IPNet: &net.IPNet{
                IP:   ipAddr,
                Mask: net.CIDRMask(24, 32),
            },
        }
        if err := netlink.AddrAdd(link, addr); err != nil {
            return err
        }
        
        // 启动接口
        if err := netlink.LinkSetUp(link); err != nil {
            return err
        }
        
        // 添加默认路由
        gw := net.ParseIP(conf.Gateway)
        route := &netlink.Route{
            LinkIndex: link.Attrs().Index,
            Gw:        gw,
            Dst:       nil,  // 默认路由
        }
        if err := netlink.RouteAdd(route); err != nil {
            return err
        }
        
        return nil
    })
    
    if err != nil {
        return err
    }
    
    // 6. 返回结果
    result := &types.Result{
        IP4: &types.IPConfig{
            IP:      *ipAddr,
            Gateway: net.ParseIP(conf.Gateway),
        },
    }
    
    return result.Print()
}

func ensureBridge(name string, subnet string) (*netlink.Bridge, error) {
    // 检查 bridge 是否存在
    br, err := netlink.LinkByName(name)
    if err == nil {
        return br.(*netlink.Bridge), nil
    }
    
    // 创建 bridge
    la := netlink.NewLinkAttrs()
    la.Name = name
    br = &netlink.Bridge{LinkAttrs: la}
    
    if err := netlink.LinkAdd(br); err != nil {
        return nil, err
    }
    
    // 启动 bridge
    if err := netlink.LinkSetUp(br); err != nil {
        return nil, err
    }
    
    // 设置 IP 地址
    addr, err := netlink.ParseAddr(subnet)
    if err != nil {
        return nil, err
    }
    
    if err := netlink.AddrAdd(br, addr); err != nil {
        return nil, err
    }
    
    return br.(*netlink.Bridge), nil
}

CNI 故障排查

常见问题

1. Pod 无法分配 IP

# 检查 CNI 配置
ls -l /etc/cni/net.d/
cat /etc/cni/net.d/10-flannel.conflist

# 检查 CNI 插件二进制
ls -l /opt/cni/bin/

# 查看 kubelet 日志
journalctl -u kubelet -f | grep CNI

2. Pod 网络不通

# 检查路由
ip route

# 检查网络接口
ip link show

# 检查 iptables 规则
iptables-save | grep <pod-ip>

# 测试连通性
kubectl exec -it <pod> -- ping <target-ip>

3. CNI 插件崩溃

# 检查 CNI 插件日志(Flannel)
kubectl logs -n kube-system -l app=flannel

# 检查 Calico
kubectl logs -n kube-system -l k8s-app=calico-node

总结

CNI 是 Kubernetes 网络的核心,理解其原理对于:

  • 网络设计:选择合适的网络方案
  • 性能优化:识别网络瓶颈
  • 故障排查:快速定位网络问题
  • 自定义开发:开发定制网络插件

核心要点:

  1. CNI 规范定义了容器网络接口
  2. 不同实现方式:Overlay (VXLAN)、路由 (BGP)、eBPF
  3. 性能对比:eBPF > BGP > VXLAN
  4. 选择依据:集群规模、性能需求、复杂度