Calico IPIP 使用指南

模式介绍

项目文档:https://docs.tigera.io/calico/latest/networking/configuring/vxlan-ipip

IPIP (IP-in-IP) 是一种隧道封装技术,将一个 IP 包封装在另一个 IP 包中传输。

使用场景

场景 是否推荐 说明
AWS 跨 VPC/子网 子网边界无法路由 Pod IP,用 ipipMode: CrossSubnet
AWS 多 AZ 部署 同 AZ 直通,跨 AZ 封装
公有云(无网络控制权) 无法配置底层路由,用 ipipMode: Always/CrossSubnet
快速起步/POC 不折腾底层网络,直接用 ipipMode: Always
混合网络环境 部分支持/不支持 Pod IP 路由,用 ipipMode: CrossSubnet
已有 BGP 基础设施 IPIP 天然集成 BGP
Azure 环境 Azure 不支持 IPIP 协议 → 用 VXLAN
需要 IPv6 IPIP 仅支持 IPv4 → 用 VXLAN
高性能/低延迟需求 封装有开销 → 纯路由模式
网络密集型 workload 高吞吐场景受影响 → 纯路由或 eBPF
底层网络支持 Pod IP 路由 封装是多余 → ipipMode: Never + BGP peering
裸金属/自建机房 有完整网络控制权 → 纯路由模式

部署流程

通过 Kind 快速生成集群并部署 Calico IPIP 模式

bash 复制代码
#!/bin/bash
set -v

# 1. Prepare NoCNI environment
cat <<EOF | HTTP_PROXY= HTTPS_PROXY= http_proxy= https_proxy= kind create cluster --name=calico-ipip --image=burlyluo/kindest:v1.27.3 -v=9 --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  disableDefaultCNI: true
nodes:
  - role: control-plane
  - role: worker
EOF

# 2. Remove taints
controller_node_ip=`kubectl get node -o wide --no-headers | grep -E "control-plane|bpf1" | awk -F " " '{print $6}'`
kubectl taint nodes $(kubectl get nodes -o name | grep control-plane) node-role.kubernetes.io/control-plane:NoSchedule-
kubectl get nodes -o wide

# 3. Collect startup message
controller_node_name=$(kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' | grep control-plane)
if [ -n "$controller_node_name" ]; then
  timeout 1 docker exec -t $controller_node_name bash -c 'cat << EOF > /root/monitor_startup.sh
#!/bin/bash
ip -ts monitor all > /root/startup_monitor.txt 2>&1
EOF
chmod +x /root/monitor_startup.sh && /root/monitor_startup.sh'
else
  echo "No such controller_node!"
fi

# 4. Install CNI[Calico v3.23.2]
kubectl apply -f calico.yaml

# 5. Wait all pods ready
kubectl wait --timeout=100s --for=condition=Ready=true pods --all -A
bash 复制代码
## calico.yaml
## https://gitee.com/rowan-wcni/wcni-kind/blob/master/LabasCode/calico/01-calico-ipip/calico.yaml

创建测试 Pod

实际就是 Nginx,仅用于后续互访时抓包

yaml 复制代码
apiVersion: apps/v1
kind: DaemonSet
metadata:
  labels:
    app: wluo
  name: wluo
spec:
  selector:
    matchLabels:
      app: wluo
  template:
    metadata:
      labels:
        app: wluo
    spec:
      containers:
      - image: burlyluo/nettool:latest
        name: nettoolbox
        env:
          - name: NETTOOL_NODE_NAME
            valueFrom:
              fieldRef:
                fieldPath: spec.nodeName
        securityContext:
          privileged: true

查看部署结果

bash 复制代码
root@network-demo:~# kubectl get pods -A -o wide
NAMESPACE            NAME                                                READY   STATUS    RESTARTS   AGE    IP              NODE
default              wluo-fd9cp                                          1/1     Running   0          27h    10.244.51.194   calico-ipip-control-plane
default              wluo-nckpl                                          1/1     Running   0          27h    10.244.79.6     calico-ipip-worker
kube-system          calico-kube-controllers-7bdccfc7d8-flvfz            1/1     Running   0          3d7h   10.244.79.2     calico-ipip-worker
kube-system          calico-node-8rqcx                                   1/1     Running   0          3d7h   172.18.0.3      calico-ipip-control-plane
kube-system          calico-node-rm4pj                                   1/1     Running   0          3d7h   172.18.0.2      calico-ipip-worker
kube-system          coredns-5d78c9869d-cqm77                            1/1     Running   0          3d7h   10.244.79.3     calico-ipip-worker
kube-system          coredns-5d78c9869d-th2np                            1/1     Running   0          3d7h   10.244.79.1     calico-ipip-worker
kube-system          etcd-calico-ipip-control-plane                      1/1     Running   0          3d7h   172.18.0.3      calico-ipip-control-plane
kube-system          kube-apiserver-calico-ipip-control-plane            1/1     Running   0          3d7h   172.18.0.3      calico-ipip-control-plane
kube-system          kube-controller-manager-calico-ipip-control-plane   1/1     Running   0          3d7h   172.18.0.3      calico-ipip-control-plane
kube-system          kube-proxy-4fcfz                                    1/1     Running   0          3d7h   172.18.0.3      calico-ipip-control-plane
kube-system          kube-proxy-6klbx                                    1/1     Running   0          3d7h   172.18.0.2      calico-ipip-worker
kube-system          kube-scheduler-calico-ipip-control-plane            1/1     Running   0          3d7h   172.18.0.3      calico-ipip-control-plane
local-path-storage   local-path-provisioner-6bc4bddd6b-2xgp8             1/1     Running   0          3d7h   10.244.79.4     calico-ipip-worker

验证效果

查询 Node 节点 BGP、路由表、网络设备信息

bash 复制代码
## 节点信息
root@network-demo:~# kubectl get node -o wide
NAME                        STATUS   ROLES           AGE    VERSION   INTERNAL-IP
calico-ipip-control-plane   Ready    control-plane   2d4h   v1.27.3   172.18.0.3
calico-ipip-worker          Ready    <none>          2d4h   v1.27.3   172.18.0.2

## Pod 信息
root@network-demo:~# kubectl get pods -o wide                                                      
NAME         READY   STATUS    RESTARTS   AGE     IP              NODE
wluo-fd9cp   1/1     Running   0          4h36m   10.244.51.194   calico-ipip-control-plane
wluo-nckpl   1/1     Running   0          4h36m   10.244.79.6     calico-ipip-worker

1.查询节点 BGP 信息

bash 复制代码
## https://docs.tigera.io/calico/latest/networking/configuring/bgp
## 简单来说,可以将 BGP 理解为导航、IPIP 理解为车:

## BGP 分发路由信息(往哪走):
## - 节点之间同步路由信息,告诉本节点目标 Pod 网段在哪个 Node IP

## IPIP 封装数据包(发过去):
## - 实际数据包原/目的都是 Pod IP,底层路由器不认识 Pod 网段
## - IPIP 把数据包封装后在 Node IP 间传输


## 可以看出,与对端节点形成了 BGP Mesh
root@network-demo:~# docker exec calico-ipip-control-plane calicoctl node status
Calico process is running.

IPv4 BGP status
+--------------+-------------------+-------+------------+-------------+
| PEER ADDRESS |     PEER TYPE     | STATE |   SINCE    |    INFO     |
+--------------+-------------------+-------+------------+-------------+
| 172.18.0.2   | node-to-node mesh | up    | 2026-03-20 | Established |
+--------------+-------------------+-------+------------+-------------+


## 179 端口的 bird 就是 BGP 的守护进程,两台节点间通过 TCP 连接形成交互的路由数据库
root@network-demo:~# docker exec calico-ipip-control-plane ss -anp | awk '/bird/ && /179/'
Netid   State    Recv-Q   Send-Q   Local Address:Port   Peer Address:Port   Process
tcp     LISTEN   0        8        0.0.0.0:179          0.0.0.0:*           users:(("bird",pid=1486,fd=7))
tcp     ESTAB    0        0        172.18.0.3:50005     172.18.0.2:179      users:(("bird",pid=1486,fd=8))

## 显示 bird 运行了哪些协议
root@network-demo:~# docker exec calico-ipip-control-plane birdc -s /run/calico/bird.ctl show protocol
BIRD v0.3.3+birdv1.6.8 ready.
name            proto    table    state  since       info
static1         Static   master   up     2026-03-20  # 静态路由协议
kernel1         Kernel   master   up     2026-03-20  # 内核路由同步
device1         Device   master   up     2026-03-20  # 设备监控
direct1         Direct   master   up     2026-03-20  # 直连路由
Mesh_172_18_0_2 BGP      master   up     2026-03-20  Established # BGP 邻居已建立

## 查看 bird 路由表(不是内核路由表)
## 注:这里是 bird 自己维护的路由表,bird 内部路由表
root@network-demo:~# docker exec calico-ipip-control-plane birdc -s /run/calico/bird.ctl show route
BIRD v0.3.3+birdv1.6.8 ready.
0.0.0.0/0          via 172.18.0.1 on eth0 [kernel1 2026-03-20] * (10)
# 可以看出,访问 79.6 时走了这条 BGP 路由
10.244.79.0/26     via 172.18.0.2 on eth0 [Mesh_172_18_0_2 2026-03-20] * (100/0) [i]
10.244.51.194/32   dev cali2dc361fe88d [kernel1 07:12:41] * (10)
172.18.0.0/16      dev eth0 [direct1 2026-03-20] * (240)
10.244.51.192/26   blackhole [static1 2026-03-20] * (200)
10.244.51.192/32   dev tunl0 [direct1 2026-03-20] * (240)

## 显示内核路由表由 bird 添加或管理的所有路由
## Bird 将内部路由表中最优路由下发到内核的结果
root@network-demo:~# docker exec calico-ipip-control-plane ip route show proto bird
blackhole 10.244.51.192/26
10.244.79.0/26 via 172.18.0.2 dev tunl0 onlink

2.查询 Node 路由

这一步没啥意义,上一步的 ip route show proto bird 已经看了,仅作为路由信息补充。

bash 复制代码
root@network-demo:~# docker exec calico-ipip-control-plane route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.18.0.1      0.0.0.0         UG    0      0        0 eth0
10.244.51.192   0.0.0.0         255.255.255.192 U     0      0        0 *
10.244.51.194   0.0.0.0         255.255.255.255 UH    0      0        0 cali2dc361fe88d
10.244.79.0     172.18.0.2      255.255.255.192 UG    0      0        0 tunl0
172.18.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0

root@network-demo:~# docker exec calico-ipip-control-plane ip route show
default via 172.18.0.1 dev eth0
blackhole 10.244.51.192/26 proto bird
10.244.51.194 dev cali2dc361fe88d scope link
## 去 10.244.79.0/26 的流量使用 Calico bird 下发,通过 IPIP 隧道 tunl0 发到 172.18.0.2
10.244.79.0/26 via 172.18.0.2 dev tunl0 proto bird onlink
## 去 172.18.0.0/16 的流量使用内核生成的直连路由,无需网关。走主机 eth0
172.18.0.0/16 dev eth0 proto kernel scope link src 172.18.0.3

3.查询 tunl0 设备

Pod 互访时 tunl0 设备进行 ipip 封装后,根据路由规则,使用 node eth0 网卡传输。

简单来讲 calico.tunl0 与 flannel.ipip 本质上是同一类东西,都是做 IPIP 封装/解封的。

bash 复制代码
2: tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
    inet 10.244.51.192/32 scope global tunl0
       valid_lft forever preferred_lft forever

root@network-demo:~# docker exec calico-ipip-control-plane ip -d link show tunl0
2: tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0 promiscuity 0 minmtu 0 maxmtu 0 
    ipip any remote any local any ttl inherit nopmtudisc addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535

查询 Pod 路由、ARP、网络设备信息

验证路由规则、calico 生成的网络设备开启 proxy_arp 后的效果

1.查询 Pod 网卡信息

bash 复制代码
## Pod 所处网段子网掩码为 32,即该网段只存在 Pod IP
## /32 意味着 Pod 是"孤岛",任何其他 IP 都不在本地网段
root@network-demo:~# kubectl exec wluo-fd9cp -- ip address show eth0
10.244.51.194/32
c2:58:3e:3d:f7:1c

2.查询 Pod 路由

容器默认网关的 IP 169.254.1.1 是什么其实无所谓。因为通过 scope link 配置后,这条路由被标记为本地链路路由,通信走的是二层转发,依赖的是 MAC 地址而非 IP 地址。

至于为什么还需要一个 IP 地址,详见下面衍生问题回答。

bash 复制代码
## 结合 Pod 网卡信息发现,Pod IP 与网关地址并不在同一网段中,所以任何目标都走默认网关
root@network-demo:~# kubectl exec wluo-fd9cp -- ip route show   
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link

root@network-demo:~# kubectl exec wluo-fd9cp -- route -n     
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         169.254.1.1     0.0.0.0         UG    0      0        0 eth0
169.254.1.1     0.0.0.0         255.255.255.255 UH    0      0        0 eth0

## 验证 cali2dc361fe88d 网卡是否开启 proxy_arp
root@network-demo:~# docker exec calico-ipip-control-plane cat /proc/sys/net/ipv4/conf/cali2dc361fe88d/proxy_arp
1

3.查询 Pod ARP 表

bash 复制代码
## 邻居/ARP 表(两条命令都是在查这个,推荐使用 ip 查询)
## 通过 ARP 获取 169.254.1.1 的 MAC:ee:ee:ee:ee:ee:ee
## 请求给网关时,其实是 calixxx 网口通过 proxy_arp 响应的(calixxx 网口假装自己是网关)
## 所以 169 这个 IP 是什么其实无所谓,
root@network-demo:~# kubectl exec wluo-fd9cp -- ip neighbor show
10.244.51.192 dev eth0 lladdr ee:ee:ee:ee:ee:ee STALE
169.254.1.1 dev eth0 lladdr ee:ee:ee:ee:ee:ee REACHABLE

root@network-demo:~# kubectl exec wluo-fd9cp -- arp -n
Address                  HWtype  HWaddress           Flags Mask            Iface
10.244.51.192            ether   ee:ee:ee:ee:ee:ee   C                     eth0
169.254.1.1              ether   ee:ee:ee:ee:ee:ee   C                     eth0

4.查询 Pod Veth Pair 设备

可以看到 Node calixxxx 网络设备并没有 IP 地址,所以 Pod 内网关 IP 169.254.1.1 是什么才无所谓。

bash 复制代码
## 查询 cali2dc361fe88d 网卡信息(验证 Mac 地址是否正确)
root@network-demo:~# docker exec calico-ipip-control-plane ip address show cali2dc361fe88d
7: cali2dc361fe88d@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP group default
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netns cni-a5b10eb8-6a8c-f994-5de8-9c130c368868
    inet6 fe80::ecee:eeff:feee:eeee/64 scope link
       valid_lft forever preferred_lft forever

root@network-demo:~# docker exec calico-ipip-control-plane ip -d link show cali2dc361fe88d
7: cali2dc361fe88d@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP mode DEFAULT group default
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netns cni-a5b10eb8-6a8c-f994-5de8-9c130c368868 promiscuity 0 minmtu 68 maxmtu 65535
    veth addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535

5.延伸问题

  • 每个 Pod 都通过 veth pair 对应一个 cali 网卡设备,他们的 mac 地址都是 ee:ee:ee 为什么不会冲突?

    • 实际上,在一个广播域中 mac 地址唯一即可。每个 Pod IP 都是 /32,所以 mac 地址不可能冲突。
  • 通过 veth pair 连接 Pod eth0 与 Node calixxx 网卡,为什么还要使用 mac 地址?

    • 非 点对点(P2P)设备必须符合 TCP/IP 约束。
    bash 复制代码
    ## 可以在 '查询 Pod Veth Pair 设备' 步骤中看到:cali veth 设备声明自己是 BROADCAST,MULTICAST 类型的以太网设备

Pod 网卡处抓包

bash 复制代码
## Pod 互访
root@network-demo:~# kubectl exec wluo-fd9cp -- curl -s 10.244.79.6
PodName: wluo-nckpl | PodIP: eth0 10.244.79.6/32

Node 网卡处抓包

验证 tunl0 ipip 封装效果