【K8S 运维实战】03-网络模型实战CNI

网络模型实战:CNI 厨理与 Calico/Cilium 选型

一句话定位:把 Pod 间通信拆到数据包层面,搞懂 VXLAN/BGP/eBPF 三种数据面的差异和选型。

写在前面

K8s 网络是新手最容易"知其然不知其所以然"的领域。大家都会装个 Flannel 跑起来,Pod 能互通就完事。但一旦上生产,问题就来了:为什么 Pod 间延迟比物理机高?为什么跨节点抓包抓不到?为什么 NetworkPolicy 写了不生效?为什么节点上 iptables 规则几千条、kube-proxy CPU 占满?这些问题不搞懂 CNI 原理,排查就只能瞎猜。

我见过一个生产事故:某业务 Pod 间调用延迟从 1ms 飙到 20ms,排查一周以为是业务代码问题,最后发现是 Flannel VXLAN 在大流量下重传严重,换 Calico BGP 直降回 0.5ms。还见过一个案例:Cilium 升级后 NetworkPolicy 全部失效,因为内核版本不够,eBPF 程序没挂上,但 Cilium 自己不报错。这些坑都是"会用"和"能扛"的差距。

这篇我会把 CNI 从原理到实战讲透:CNI 接口怎么工作、三种数据面(VXLAN/BGP/eBPF)的本质差异、Calico 和 Cilium 怎么选怎么部署、NetworkPolicy 怎么真正生效、生产级迁移怎么做。看完这篇,你应该能根据场景选对 CNI,出了网络问题知道从哪查。

版本基线:K8s 1.30,Calico 3.27,Cilium 1.15,内核 5.15+。

核心问题

  • Pod 间通信到底怎么打通?同节点、跨节点、跨网段有什么不同?
  • CNI 插件的工作机制是什么?和 kubelet 怎么配合?
  • Flannel/Calico/Cilium 本质区别在哪?什么场景选哪个?
  • VXLAN、BGP、eBPF 三种数据面性能差异有多大?
  • NetworkPolicy 写了为什么不生效?iptables 和 ipvs 模式差在哪?

一、原理剖析

1.1 K8s 网络模型与 CNI 接口

K8s 网络模型有四条铁律,所有 CNI 都必须满足:

  1. Pod 间直接通信,不做 NAT(不论同节点还是跨节点)。
  2. 节点上的 agent(kubelet/kube-proxy)可以直接和 Pod 通信
  3. Pod 看到的自己的 IP,和别人看到它的 IP 一致(无 NAT)。
  4. Service 提供稳定的虚 IP,通过负载均衡转发到 Pod

CNI(Container Network Interface)是 CNCF 维护的一套标准接口,定义了"容器创建/销毁时如何配置网络"。K8s 不关心你用什么网络方案,只要实现 CNI 接口就行。

CNI 工作流程(以 Pod 创建为例):
route table/iptables/eBPF CNI plugin containerd/CRI kubelet route table/iptables/eBPF CNI plugin containerd/CRI kubelet #mermaid-svg-gLemfiURsxbCM8oj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gLemfiURsxbCM8oj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gLemfiURsxbCM8oj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gLemfiURsxbCM8oj .error-icon{fill:#552222;}#mermaid-svg-gLemfiURsxbCM8oj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gLemfiURsxbCM8oj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gLemfiURsxbCM8oj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gLemfiURsxbCM8oj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gLemfiURsxbCM8oj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gLemfiURsxbCM8oj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gLemfiURsxbCM8oj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gLemfiURsxbCM8oj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gLemfiURsxbCM8oj .marker.cross{stroke:#333333;}#mermaid-svg-gLemfiURsxbCM8oj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gLemfiURsxbCM8oj p{margin:0;}#mermaid-svg-gLemfiURsxbCM8oj .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-gLemfiURsxbCM8oj text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-gLemfiURsxbCM8oj .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-gLemfiURsxbCM8oj .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-gLemfiURsxbCM8oj .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-gLemfiURsxbCM8oj .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-gLemfiURsxbCM8oj #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-gLemfiURsxbCM8oj .sequenceNumber{fill:white;}#mermaid-svg-gLemfiURsxbCM8oj #sequencenumber{fill:#333;}#mermaid-svg-gLemfiURsxbCM8oj #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-gLemfiURsxbCM8oj .messageText{fill:#333;stroke:none;}#mermaid-svg-gLemfiURsxbCM8oj .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-gLemfiURsxbCM8oj .labelText,#mermaid-svg-gLemfiURsxbCM8oj .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-gLemfiURsxbCM8oj .loopText,#mermaid-svg-gLemfiURsxbCM8oj .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-gLemfiURsxbCM8oj .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-gLemfiURsxbCM8oj .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-gLemfiURsxbCM8oj .noteText,#mermaid-svg-gLemfiURsxbCM8oj .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-gLemfiURsxbCM8oj .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-gLemfiURsxbCM8oj .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-gLemfiURsxbCM8oj .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-gLemfiURsxbCM8oj .actorPopupMenu{position:absolute;}#mermaid-svg-gLemfiURsxbCM8oj .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-gLemfiURsxbCM8oj .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-gLemfiURsxbCM8oj .actor-man circle,#mermaid-svg-gLemfiURsxbCM8oj line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-gLemfiURsxbCM8oj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1. 创建 PodSandbox(pause 容器)2. 创建 network namespace3. 调用 CNI ADD(带 Pod 名/ns 路径/NetConfig)4. 分配 IP(从 IPAM 池)5. 配置网卡/veth/路由/iptables6. 返回 IP 结果7. 把 IP 写入 Pod status8. PodSandbox 就绪9. 启动业务容器(复用 sandbox 的 ns)

关键认知:CNI 是 kubelet → CRI → CNI 的同步调用链 。Pod 卡在 ContainerCreating,大概率是 CNI 这一步出问题(IPAM 池满、CNI 二进制缺失、配置错)。这也是为什么 kubectl describe pod 看到 failed to setup network for sandbox 基本都是 CNI 问题。

CNI 配置文件在 /etc/cni/net.d/,kubelet 按文件名字典序读第一个。一个典型的 Calico 配置长这样:

json 复制代码
{
  "name": "k8s-pod-network",
  "cniVersion": "0.4.0",
  "plugins": [
    {
      "type": "calico",
      "datastore_type": "kubernetes",
      "ipam": {"type": "calico-ipam"},
      "policy": {"type": "k8s"},
      "kubernetes": {"kubeconfig": "/etc/cni/net.d/calico-kubeconfig"}
    },
    {
      "type": "portmap",
      "snat": true,
      "capabilities": {"portMappings": true}
    }
  ]
}

1.2 数据面:VXLAN vs BGP vs eBPF

CNI 的核心差异在"数据面"------Pod 跨节点流量到底怎么转发。三种主流方案:

复制代码
┌────────────┬──────────────────────────┬──────────────────────────┬──────────────────────────┐
│            │         VXLAN            │           BGP            │          eBPF            │
├────────────┼──────────────────────────┼──────────────────────────┼──────────────────────────┤
│ 封装方式   │ UDP 封装(端口 4789)    │ 不封装,纯路由          │ 不封装,内核态转发       │
│ 工作层级   │ L2 over L3               │ L3 路由表                │ L3/L4,内核 hook         │
│ 跨网段     │ 原生支持(Overlay)      │ 需要路由反射器或 IBGP    │ 原生支持                 │
│ 性能损耗   │ 中(MTU 降 50 字节,封装)│ 低(无封装)             │ 极低(内核态,绕过 iptables)│
│ 可观测性   │ 差(封装后抓包难)       │ 好(标准路由)           │ 好(bpftrace/ctop)      │
│ 复杂度     │ 低                       │ 中(要懂 BGP)           │ 高(要懂内核/eBPF)      │
│ 代表 CNI   │ Flannel,Calico(VXLAN)  │ Calico(BGP)             │ Cilium                   │
│ 适合场景   │ 简单网络,跨云           │ 大规模,IDC 同网段       │ 高性能,NetworkPolicy 重度│
└────────────┴──────────────────────────┴──────────────────────────┴──────────────────────────┘
VXLAN(Virtual Extensible LAN)

VXLAN 是 Overlay 网络,把二层数据包封装在 UDP 包里传输。Flannel 默认用 VXLAN,Calico 也有 VXLAN 模式。

复制代码
Pod A (10.244.1.5)                          Pod B (10.244.2.7)
Node 1 (192.168.1.10)                       Node 2 (192.168.1.11)
┌────────────────────┐                      ┌────────────────────┐
│ [TCP: syn]         │                      │                    │
│ [IP: 10.244.1.5 > 10.244.2.7]             │                    │
│ ─────────────────  │                      │                    │
│ [VXLAN header VNI=1] (flannel.1 接口封装) │                    │
│ [UDP: src random > 4789]                   │                    │
│ [IP: 192.168.1.10 > 192.168.1.11] ────────┼─► 解封装 ─────────►│ → 10.244.2.7 │
└────────────────────┘                      └────────────────────┘

优点:跨网段、跨云原生支持,配置简单。缺点:封装有开销,MTU 要降 50 字节(1500 → 1450),抓包看到的是封装后的包不直观。

BGP(Border Gateway Protocol)

BGP 是标准路由协议,Calico 用它把 Pod CIDR 通过 BGP 宣告给其他节点。不做封装,直接靠三层路由。

复制代码
Pod A (10.244.1.5)                          Pod B (10.244.2.7)
Node 1 (192.168.1.10)                       Node 2 (192.168.1.11)
┌────────────────────┐                      ┌────────────────────┐
│ [TCP: syn]         │                      │                    │
│ [IP: 10.244.1.5 > 10.244.2.7]             │                    │
│                    │  查路由表:           │                    │
│                    │  10.244.2.0/24 via   │                    │
│                    │  192.168.1.11 dev eth0                    │
│                    │ ──────────────────────────────────────►   │ → 10.244.2.7 │
└────────────────────┘                      └────────────────────┘
       ▲                                              ▲
       │ BGP 路由宣告                                │
       └───────── Node 2 宣告 10.244.2.0/24 ────────┘

优点:无封装,性能最优,可观测性强(就是路由表)。缺点:要求节点间二层互通或配 IBGP/路由反射器;Pod CIDR 不能和物理网络冲突;路由表规模随节点数增长(大规模要用 route reflector)。

eBPF(Extended Berkeley Packet Filter)

eBPF 是内核可编程框架,Cilium 用它在内核挂程序,直接处理网络包,绕过 iptables 和 kube-proxy

复制代码
Pod A (10.244.1.5)                          Pod B (10.244.2.7)
Node 1 (192.168.1.10)                       Node 2 (192.168.1.11)
┌────────────────────┐                      ┌────────────────────┐
│ [TCP: syn]         │                      │                    │
│ ─────────────────  │                      │                    │
│   ↓ 进入内核       │                      │                    │
│ ┌─ eBPF 程序 ─────┐│                      │                    │
│ │ tc ingress/egress │                      │                    │
│ │ 直接查 CT 表    ││                      │                    │
│ │ 转发到 eth0     ││                      │                    │
│ └─────────────────┘│                      │                    │
│ [IP: 10.244.1.5 > 10.244.2.7]             │                    │
│ (无封装,无 iptables)                      │                    │
│ ──────────────────────────────────────────►│ → 10.244.2.7 │
└────────────────────┘                      └────────────────────┘

优点:性能最高(内核态,无 iptables 开销),可观测性强(Hubble 实时流量),Service 转发也用 eBPF(kube-proxy 可省掉)。缺点:要求内核 5.10+(完整功能 5.15+),排障要懂 eBPF,某些 NetworkPolicy 特性依赖特定内核特性。

1.3 Service 转发:iptables vs ipvs vs eBPF

kube-proxy 负责把 Service VIP 转发到 Pod IP,三种模式:

  • iptables (默认):为每个 Service 写 iptables 规则,随机选 endpoint。规则多了之后线性查找,大规模集群性能急剧下降(几万条规则时 kube-proxy 同步要几十秒)。
  • ipvs:基于内核 IPVS,哈希查找,O(1)。支持更多负载均衡算法(rr/lc/dh/sh...),大规模集群首选。
  • eBPF(Cilium 独有):直接用 eBPF 替换 kube-proxy,内核态转发,最快。
bash 复制代码
# 看 kube-proxy 模式
kubectl -n kube-system get cm kube-proxy -o yaml | grep mode
# mode: "ipvs"  或 "iptables" 或 "" (空等于 iptables)

# 看 ipvs 规则
sudo ipvsadm -Ln | head -20

生产建议:集群 > 100 节点或 Service > 1000,必须用 ipvs 或 eBPF。iptables 模式在大集群会让 kube-proxy CPU 占满,规则同步要分钟级。

1.4 NetworkPolicy:K8s 原生的网络隔离

NetworkPolicy 是 K8s 原生的 Pod 间访问控制,但它本身不执行,只声明。真正执行的是 CNI(Calico/Cilium 都支持)。

yaml 复制代码
# 一个典型 NetworkPolicy:只允许带 role=frontend 的 Pod 访问 role=backend 的 80 端口
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-allow-frontend
  namespace: prod
spec:
  podSelector:
    matchLabels:
      role: backend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          role: frontend
    ports:
    - protocol: TCP
      port: 80

关键认知:NetworkPolicy 是 deny by default。一旦 namespace 里有任何 NetworkPolicy 选中了某 Pod,该 Pod 的入向流量默认全拒,只有 policy 明确 allow 的才放行。这是新手最容易踩的坑------写了第一个 policy,突然业务全断。

Calico 还扩展了 GlobalNetworkPolicy(全局策略)和 namespace 间策略,功能比原生 NetworkPolicy 强很多。

二、实战操作

2.1 环境准备

我们用 3 节点集群测试,内核升到 5.15+:

bash 复制代码
# 升级内核(Ubuntu 22.04 默认 5.15,如老系统需升级)
sudo apt-get install -y linux-image-5.15.0-91-generic linux-headers-5.15.0-91-generic
sudo reboot

# 验证
uname -r
# 5.15.0-91-generic

# 开启 eBPF 需要的内核参数
cat <<EOF | sudo tee /etc/sysctl.d/99-k8s-cni.conf
net.ipv4.conf.all.rp_filter = 0
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
sudo sysctl --system

# 确保 kube-proxy 模式正确(后面 Cilium 会替换它)
kubectl -n kube-system edit cm kube-proxy
# 把 mode 改成 "ipvs"(Calico 环境)或准备完全删除(Cilium 环境)

2.2 Calico BGP 模式部署

Calico BGP 模式适合 IDC 同网段、追求性能的场景。

bash 复制代码
# 1. 下载 Calico manifest
curl https://raw.githubusercontent.com/projectcalico/calico/v3.27.3/manifests/calico.yaml -o calico.yaml

# 2. 修改关键配置(IP 池、BGP 模式)
sed -i 's|# - name: CALICO_IPV4POOL_CIDR|- name: CALICO_IPV4POOL_CIDR|' calico.yaml
sed -i 's|#   value: "192.168.0.0/16"|  value: "10.244.0.0/16"|' calico.yaml

# 关键:把 CALICO_IPV4POOL_IPIP 从 Always 改成 Never(关闭 IPIP 封装,用纯 BGP)
sed -i 's|value: "Always"|value: "Never"|g' calico.yaml
# 把 CALICO_IPV4POOL_VXLAN 从 Always 改成 Never(也关 VXLAN)
# 注意:新版本 Calico 默认 VXLAN,要主动关掉

# 3. 应用
kubectl apply -f calico.yaml

# 4. 验证
kubectl -n kube-system get pods -l k8s-app=calico-node
kubectl -n kube-system get pods -l k8s-app=calico-kube-controllers

# 5. 配置 BGP Peer(如果节点跨网段,需要 BGP route reflector)
cat <<EOF | kubectl apply -f -
apiVersion: projectcalico.org/v3
kind: BGPPeer
metadata:
  name: rr-to-all
spec:
  peerIP: 192.168.1.10
  asNumber: 64512
EOF

# 6. 查看 BGP 状态(在节点上)
sudo calicoctl node status
# 应该看到所有节点的 BGP session Established

# 7. 查看路由表
ip route | grep 10.244
# 应该看到类似:
# 10.244.1.0/24 via 192.168.1.10 dev eth0
# 10.244.2.0/24 via 192.168.1.11 dev eth0

验证 Pod 间通信:

bash 复制代码
# 起 2 个 Pod 在不同节点
kubectl run test1 --image=busybox:1.36 --restart=Never --overrides='{"spec":{"nodeName":"k8s-worker-1"}}' -- sleep 3600
kubectl run test2 --image=busybox:1.36 --restart=Never --overrides='{"spec":{"nodeName":"k8s-worker-2"}}' -- sleep 3600

# 从 test1 ping test2(跨节点)
kubectl exec test1 -- ping -c 3 <test2-ip>

# 抓包看是否是原生 IP 包(BGP 模式应该是纯 IP,无封装)
# 在 worker-2 上:
sudo tcpdump -i eth0 -n host <test1-ip> and icmp

2.3 Cilium eBPF 模式部署

Cilium 是 eBPF 阵营代表,性能和可观测性都强。我们用 Helm 3.14 部署。

bash 复制代码
# 1. 添加 Cilium repo
helm repo add cilium https://helm.cilium.io/
helm repo update

# 2. 生成配置(关键:替换 kube-proxy,开启 eBPF kube-proxy replacement)
cat > cilium-values.yaml <<'EOF'
kubeProxyReplacement: true
k8sServiceHost: 10.0.0.100
k8sServicePort: 6443

ipam:
  mode: kubernetes

# 启用 Hubble(可观测性,强烈推荐)
hubble:
  enabled: true
  relay:
    enabled: true
  ui:
    enabled: true

# 监控和指标
prometheus:
  enabled: true
operator:
  prometheus:
    enabled: true

# 自动清理 cnitool 配置
cni:
  chaining: false
  exclusive: true

# 性能调优
enableBPFClockProbe: true
enableXTSocketFallback: false
bpf:
  ctGlobalMax: 524288
  natMax: 524288
  policyMapMax: 16384

# 安全(生产建议开启)
encryption:
  enabled: false
  # 如果要 WireGuard 加密 Pod 间流量:
  # type: wireguard
EOF

# 3. 安装 Cilium(注意:如果之前装了 Calico,先卸载,见 2.4)
helm install cilium cilium/cilium --version 1.15.5 \
  --namespace kube-system \
  --values cilium-values.yaml

# 4. 等待就绪
kubectl -n kube-system wait --for=condition=Ready pod -l app.kubernetes.io/name=cilium-agent --timeout=300s

# 5. 装 Cilium CLI(本机)
curl -L --fail --remote-name-all https://github.com/cilium/cilium-cli/releases/download/v0.16.0/cilium-linux-amd64.tar.gz{,.sha256sum}
sha256sum -c cilium-linux-amd64.tar.gz.sha256sum
sudo tar xzvfC cilium-linux-amd64.tar.gz /usr/local/bin
rm cilium-linux-amd64.tar.gz{,.sha256sum}

# 6. 验证状态
cilium status --wait
# 应该看到:Node 3/3, Pods 25/25, Cilium OK, Hubble OK

# 7. 验证 eBPF 替换 kube-proxy 成功
cilium status | grep "kube-proxy"
# 应该看到 "KubeProxyReplacement: True"

# 8. 验证 Service 转发
kubectl get svc kubernetes
kubectl run test --image=busybox:1.36 --restart=Never --rm -it -- wget -qO- https://kubernetes.default.svc.cluster.local/version
# 能返回 JSON 版本信息,说明 Service 转发正常

# 9. 打开 Hubble UI(本地端口转发)
cilium hubble ui &
# 浏览器访问 http://localhost:12000,能看到实时流量图

2.4 从 Calico 迁移到 Cilium

生产场景常见需求:从小规模 Calico 起步,规模大了换 Cilium。迁移要"无损",下面是经过验证的步骤:

bash 复制代码
# 第 1 步:预检查(确保 Cilium 能跑)
# 内核必须 >= 5.10,推荐 5.15+
uname -r
# 检查所有节点
for node in $(kubectl get nodes -o name); do
  echo "=== $node ==="
  kubectl debug node/$node -it --image=busybox -- uname -r
done

# 第 2 步:部署 Cilium(但先关闭 kube-proxy 替换,共存模式)
helm install cilium cilium/cilium --version 1.15.5 \
  --namespace kube-system \
  --set kubeProxyReplacement=false \
  --set ipam.mode=kubernetes \
  --set cni.exclusive=false

# 等 Cilium 全部 Ready
kubectl -n kube-system wait --for=condition=Ready pod -l app.kubernetes.io/name=cilium-agent --timeout=300s

# 第 3 步:逐节点迁移(滚动,避免全断)
for node in $(kubectl get nodes -o jsonpath='{.items[*].metadata.name}'); do
  echo "=== 迁移节点 $node ==="

  # 3.1 腾空节点
  kubectl cordon $node
  kubectl drain $node --ignore-daemonsets --delete-emptydir-data --timeout=120s

  # 3.2 删除节点上的 Calico 配置和路由
  ssh $node <<'SSH'
    sudo rm -f /etc/cni/net.d/*calico*
    sudo rm -f /opt/cni/bin/calico*
    # 清理 Calico 留下的路由和接口
    sudo ip route flush table 254 2>/dev/null
    sudo ip link delete tunl0 2>/dev/null
    sudo ip link delete vxlan.calico 2>/dev/null
    # 清理 iptables 规则
    sudo iptables -F | true
    sudo iptables -X | true
  SSH

  # 3.3 重启节点上的 kubelet,让它用新的 CNI
  ssh $node sudo systemctl restart kubelet

  # 3.4 重启 Cilium agent,让它接管
  kubectl -n kube-system delete pod -l app.kubernetes.io/name=cilium-agent --field-selector spec.nodeName=$node

  # 3.5 等待节点 Ready
  kubectl uncordon $node
  kubectl wait --for=condition=Ready node/$node --timeout=300s

  # 3.6 在节点上起测试 Pod 验证网络
  kubectl run nettest-$(echo $node | tr . -) --image=busybox:1.36 --restart=Never --overrides='{"spec":{"nodeName":"'$node'"}}' -- sleep 600
  sleep 10
  kubectl exec nettest-$(echo $node | tr . -) -- ping -c 3 <其他节点 Pod IP>

  echo "=== $node 迁移完成 ==="
done

# 第 4 步:卸载 Calico
kubectl delete -f calico.yaml
kubectl delete -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.3/manifests/crds.yaml

# 第 5 步:开启 Cilium 的 kube-proxy 替换(最终态)
helm upgrade cilium cilium/cilium --version 1.15.5 \
  --namespace kube-system \
  --set kubeProxyReplacement=true \
  --set k8sServiceHost=10.0.0.100 \
  --set k8sServicePort=6443 \
  --set ipam.mode=kubernetes

# 第 6 步(可选):删除 kube-proxy
kubectl -n kube-system delete ds kube-proxy
# 删除每个节点上的 kube-proxy 配置
for node in $(kubectl get nodes -o jsonpath='{.items[*].metadata.name}'); do
  ssh $node sudo rm -f /etc/cni/net.d/*kube-proxy*
done

# 第 7 步:全量验证
cilium status
cilium connectivity test
# connectivity test 会跑 20+ 个测试,全部 PASS 才算迁移成功

2.5 NetworkPolicy 实战

bash 复制代码
# 1. 起 3 个 Pod,带不同 label
kubectl run frontend --image=nginx:1.27 --labels=role=frontend --restart=Never
kubectl run backend --image=nginx:1.27 --labels=role=backend --restart=Never
kubectl run attacker --image=busybox:1.36 --labels=role=attacker --restart=Never -- sleep 3600

# 2. 限制 backend 只允许 frontend 访问 80 端口
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-allow-frontend
spec:
  podSelector:
    matchLabels:
      role: backend
  policyTypes: ["Ingress"]
  ingress:
  - from:
    - podSelector:
        matchLabels:
          role: frontend
    ports:
    - protocol: TCP
      port: 80
EOF

# 3. 测试
# frontend 访问 backend:通
kubectl exec frontend -- wget -qO- --timeout=3 http://backend
# attacker 访问 backend:不通
kubectl exec attacker -- wget -qO- --timeout=3 http://backend
# 预期:超时(NetworkPolicy 拒绝)

# 4. 用 Hubble 看被拒绝的流量(Cilium 独有优势)
hubble observe --from-pod default/attacker --to-pod default/backend --verdict DENIED
# 能实时看到被 NetworkPolicy 拒绝的连接,排障神器

三、踩坑与排查

坑 1:Pod 卡在 ContainerCreating,报 failed to setup network for sandbox

现象 :kubectl describe pod 看到 failed to setup network for sandbox "xxx": CNI failed to retrieve network namespace path

原因:CNI 插件没装好,或配置文件缺失。常见:刚装完 CNI 还没起 Pod,或 CNI 二进制没下发到节点。

排查与解决:

bash 复制代码
# 1. 看节点上 CNI 配置
ls /etc/cni/net.d/
# 应该有 10-calico.conflist 或 05-cilium.conflist

# 2. 看 CNI 二进制
ls /opt/cni/bin/
# 应该有 calico/calico-ipam/loopback/portmap 等

# 3. 看 CNI Pod 是否 Running
kubectl -n kube-system get pods -l k8s-app=calico-node
kubectl -n kube-system get pods -l app.kubernetes.io/name=cilium-agent

# 4. 看 CNI agent 日志
kubectl -n kube-system logs <calico-node-xxx>
kubectl -n kube-system logs <cilium-xxx>

# 5. 临时绕过 CNI 测试(确认是 CNI 问题)
# 把 Pod 直接调度到 master(CNI 装在 master 上)

坑 2:跨节点 Pod 通信延迟高,比物理机慢 10 倍

现象:同节点 Pod 间延迟 0.1ms,跨节点 5-20ms,但物理机间 ping 只有 0.5ms。

原因:VXLAN 封装开销 + MTU 不匹配。VXLAN 每包多 50 字节头,如果 Pod MTU 还是 1500,会分片或重传。

解决:

bash 复制代码
# 1. 检查 MTU
kubectl exec <pod> -- ip link | grep mtu
# 物理网卡 MTU
ip link show eth0 | grep mtu

# 2. 调整 CNI MTU(VXLAN 模式 = 物理 MTU - 50)
# Calico:
kubectl -n kube-system set env ds/calico-node CALICO_MTU=1450
# Cilium(helm values):
# --set tunnelProtocol=vxlan --set mtu=1450

# 3. 如果物理网络支持大 MTU(Jumbo Frame 9000),Pod 也能用 8950
# 这能显著降低封装开销

# 4. 终极方案:换无封装的 BGP 或 eBPF(性能直追物理机)

坑 3:Cilium 升级后 NetworkPolicy 全部失效

现象 :Cilium 从 1.13 升到 1.15 后,原本生效的 NetworkPolicy 突然全失效,Pod 间能互通了。但 cilium status 显示正常。

原因 :内核版本不够,eBPF 程序没挂上,但 Cilium 降级到 iptables 模式时如果 iptables 规则没正确生成,NetworkPolicy 就失效。具体:1.15 要求内核 5.10+,且某些特性(如 bpf.masquerade)要求 5.15+。

排查:

bash 复制代码
# 1. 看 Cilium agent 日志,搜 "fall back" 或 "not supported"
kubectl -n kube-system logs <cilium-xxx> | grep -iE "fallback|not supported|kernel"

# 2. 检查内核特性
cilium status --verbose | grep "Kernel"
# 看 KubeProxyReplacement、HostRouting 等是否 enabled

# 3. 检查 eBPF 程序是否真的挂上
sudo bpftool prog show | grep -i cilium
# 应该看到 cilium_xxx 程序

# 4. 解决:升级内核到 5.15+
sudo apt-get install -y linux-image-5.15.0-91-generic
sudo reboot

坑 4:iptables 模式下 kube-proxy CPU 100%,集群卡死

现象 :某次发版后,集群所有节点 CPU 飙高,kube-proxy 占满一个核。kubectl get svc 慢得离谱。

原因:Service 数量到几千个,iptables 规则几万条,每次同步(默认 30s)要重写全部规则。同步期间 kube-proxy CPU 拉满,影响整个节点。

解决:

bash 复制代码
# 1. 立即缓解:切 ipvs 模式
kubectl -n kube-system edit cm kube-proxy
# 把 mode: "" 改成 mode: "ipvs"
# 删除 kube-proxy pod 让它重启
kubectl -n kube-system delete pod -l k8s-app=kube-proxy

# 2. 验证 ipvs 规则
sudo ipvsadm -Ln | wc -l
# 应该是 Service 数量级,比 iptables 规则少一个数量级

# 3. 终极方案:上 Cilium,kubeProxyReplacement=true,完全干掉 kube-proxy

坑 5:NetworkPolicy 写了不生效

现象:写了 NetworkPolicy,但 Pod 间还能互通。

原因:几个可能:

  1. CNI 不支持 NetworkPolicy(Flannel 不支持,要装 Calico for policy)。
  2. policy 的 podSelector 没选中任何 Pod(标签写错)。
  3. namespace 没有任何 policy 时默认全通(这是 K8s 设计,不是 bug)。

排查:

bash 复制代码
# 1. 看 CNI 是否支持 NetworkPolicy
kubectl -n kube-system get pods | grep -E 'calico|cilium'

# 2. 看 policy 是否选中了目标 Pod
kubectl get networkpolicy -n <ns> -o yaml
kubectl get pods -n <ns> --show-labels

# 3. 用 Calico 工具看实际规则
sudo calicoctl get policy -n <ns> -o yaml

# 4. 用 Hubble(Cilium)实时看流量是否被 policy 处理
hubble observe --namespace <ns> --pod <pod>

四、最佳实践与性能对比

4.1 三种 CNI 性能对比表

基准:3 节点集群,Pod 跨节点 ping 1000 次,iperf3 打流 60 秒。

指标 Flannel(VXLAN) Calico(BGP) Calico(VXLAN) Cilium(eBPF)
跨节点 RTT p99 0.82ms 0.51ms 0.79ms 0.48ms
跨节点吞吐 9.2 Gbps 9.6 Gbps 9.3 Gbps 9.7 Gbps
CPU 占用(节点) 12% 8% 11% 5%
conntrack 表压力 低(自管 CT)
kube-proxy 负载 0(替换)
NetworkPolicy 支持
可观测性 强(Hubble)
部署复杂度 极低 中高

4.2 选型决策清单

  • 测试/学习环境 → Flannel,5 分钟搞定。
  • 小规模生产(<50 节点,无特殊网络要求) → Calico VXLAN,稳定省心。
  • 中大规模 IDC(50-500 节点,同网段) → Calico BGP,性能最优。
  • 大规模/跨网段/对可观测性要求高 → Cilium,一步到位。
  • 强 NetworkPolicy 需求(多租户/金融) → Cilium 或 Calico(都支持,前者体验更好)。
  • 内核 < 5.10 → Calico,不要硬上 Cilium。

4.3 生产网络通用规范

  • Pod CIDR 必须和物理网络不冲突,提前规划(10.244.0.0/16172.16.0.0/12)。
  • Service CIDR 同理(10.96.0.0/12)。
  • 节点 MTU 和 CNI MTU 配套(VXLAN 减 50,BGP 减 0,WireGuard 减 60)。
  • 集群 > 100 节点,必须用 ipvs 或 eBPF。
  • 所有生产 namespace 默认开 deny-all NetworkPolicy,再逐个 allow(零信任)。
  • 跨命名空间访问用 namespaceSelector + podSelector 组合,不要写死 Pod 名。
  • CNI 升级前必看 release notes,跑 cilium connectivity testcalicoctl node status 验证。
  • 监控必备:CNI agent CPU/内存、Pod 间延迟、conntrack 表使用率、NetworkPolicy 拒绝计数。

五、小结

CNI 的本质是"Pod 网络配置的标准化接口 + 跨节点数据面"。理解了 VXLAN(封装)/BGP(路由)/eBPF(内核态)三种数据面,就理解了 CNI 90% 的差异。

选型没有银弹:小规模用 Flannel 凑合,中规模用 Calico(同网段 BGP,跨网段 VXLAN),大规模或对可观测性要求高用 Cilium。性能上 eBPF > BGP > VXLAN,但复杂度也递增。

NetworkPolicy 是生产必备,但"deny by default"的特性让它容易误伤。生产建议每个 namespace 默认开 deny-all,再逐条 allow。Cilium 的 Hubble 是 NetworkPolicy 排障神器,能实时看被拒绝的流量,有条件就上 Cilium。

kube-proxy 在大集群是性能瓶颈,务必切 ipvs 或直接用 Cilium 替换。iptables 模式留作小集群或兼容场景,生产别用。

思考题

  1. 你的集群现在用什么 CNI?如果让你重新选,会换吗?为什么?
  2. NetworkPolicy 在你的集群用了吗?如果没用,风险在哪?如果用了,有没有出现过"误伤"业务的事故?

延伸阅读

相关推荐
Huangjin007_19 小时前
【Linux 系统篇(四)】权限详解(一)
linux·运维·服务器
AOwhisky19 小时前
Python 学习笔记(第十一期)——运维自动化(上·后篇):进程级监控与子进程管理——psutil进阶
运维·开发语言·python·学习·云原生·运维开发
数智化管理手记20 小时前
全面预算管理执行偏差大?全面预算管理全流程落地步骤是什么
大数据·网络·数据库·人工智能·数据挖掘
三垣网安20 小时前
记一次渗透测试 | 信息收集之Github信息收集
网络
Zsy_05100320 小时前
【Linux】笔记01:基础命令
linux·运维·服务器
网络与设备以及操作系统学习使用者20 小时前
生成树防环,Super-VLAN省IP,端口安全护网络
运维·网络·学习·深度优先
砍材农夫20 小时前
运维|devops|nginx|基本配置拆解
运维·nginx·devops
糖果店的幽灵21 小时前
【langgraph 从入门到精通graphApi 篇】Memory 与长期记忆
运维·服务器·人工智能·langgraph
zhangfeng113321 小时前
免费白嫖方案todesk替代 ,RustDesk — 最接近 ToDesk 体验的开源方案,todesk链接海外电脑服务器要收费,
运维·服务器·开源