首先环境
root@k8s-work2:~# kgp | grep python
default calico-python-daemon-8p6zt 1/1 Running 0 4h54m 10.222.219.6 k8s-work1 <none> <none>
default calico-python-daemon-b5xgr 1/1 Running 0 4h54m 10.222.160.6 k8s-work2 <none> <none>
default calico-python1-daemon-74hcx 1/1 Running 0 4h54m 10.222.160.1 k8s-work2 <none> <none>
default calico-python1-daemon-9wbq6 1/1 Running 0 4h54m 10.222.219.1 k8s-work1 <none> <none>
default calico-python2-daemon-8v689 1/1 Running 0 4h54m 10.222.160.2 k8s-work2 <none> <none>
default calico-python2-daemon-mdn45 1/1 Running 0 4h54m 10.222.219.2 k8s-work1 <none> <none>
default calico-python3-daemon-2jlc5 1/1 Running 0 4h54m 10.222.160.3 k8s-work2 <none> <none>
default calico-python3-daemon-mlc68 1/1 Running 0 4h54m 10.222.219.3 k8s-work1 <none> <none>
default calico-python4-daemon-fl6kn 1/1 Running 0 4h54m 10.222.219.4 k8s-work1 <none> <none>
default calico-python4-daemon-w6nhd 1/1 Running 0 4h54m 10.222.160.4 k8s-work2 <none> <none>
default calico-python5-daemon-h79cp 1/1 Running 0 4h54m 10.222.219.5 k8s-work1 <none> <none>
default calico-python5-daemon-vqx6z 1/1 Running 0 4h54m 10.222.160.5 k8s-work2 <none> <none>
default cilium-python-daemon-4x7zw 1/1 Running 0 3h24m 10.222.2.82 k8s-work2 <none> <none>
default cilium-python-daemon-gglvj 1/1 Running 0 3h24m 10.222.1.239 k8s-work1 <none> <none>
default cilium-python-no-check-daemon-g78qf 1/1 Running 0 3h24m 10.222.1.245 k8s-work1 <none> <none>
default cilium-python-no-check-daemon-hz6rq 1/1 Running 0 3h24m 10.222.2.181 k8s-work2 <none> <none>
default cilium-python1-daemon-k2zkl 1/1 Running 0 3h24m 10.222.1.48 k8s-work1 <none> <none>
default cilium-python1-daemon-mbrqv 1/1 Running 0 3h24m 10.222.2.159 k8s-work2 <none> <none>
root@k8s-work2:~# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.16.189.2 0.0.0.0 UG 0 0 0 ens160
10.222.0.0 10.222.2.80 255.255.255.0 UG 0 0 0 cilium_host
10.222.0.18 172.16.189.100 255.255.255.255 UGH 0 0 0 tunl0
10.222.1.0 10.222.2.80 255.255.255.0 UG 0 0 0 cilium_host
10.222.1.160 172.16.189.101 255.255.255.255 UGH 0 0 0 tunl0
10.222.2.0 10.222.2.80 255.255.255.0 UG 0 0 0 cilium_host
10.222.2.80 0.0.0.0 255.255.255.255 UH 0 0 0 cilium_host
10.222.12.0 172.16.189.100 255.255.255.0 UG 0 0 0 tunl0
10.222.160.0 0.0.0.0 255.255.255.0 U 0 0 0 *
10.222.160.1 0.0.0.0 255.255.255.255 UH 0 0 0 cali87be4f2366d
10.222.160.2 0.0.0.0 255.255.255.255 UH 0 0 0 cali7354d967ee8
10.222.160.3 0.0.0.0 255.255.255.255 UH 0 0 0 cali7cd4eff55c7
10.222.160.4 0.0.0.0 255.255.255.255 UH 0 0 0 cali1055b94bacf
10.222.160.5 0.0.0.0 255.255.255.255 UH 0 0 0 califdfa0cb4427
10.222.160.6 0.0.0.0 255.255.255.255 UH 0 0 0 cali5a868a043b0
10.222.219.0 172.16.189.101 255.255.255.0 UG 0 0 0 tunl0
172.16.189.0 0.0.0.0 255.255.255.0 U 0 0 0 ens160
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
root@k8s-work2:~#
可以看到我有一批部署 calico 之后就部署的 pod
也有一批 部署 cilium 之后创建的 pod
1. 问题
先说正常的结果:
- 同节点的(calico 和 cilium) 新旧两批 pod 互相 ping 都是通的
- 跨节点只能实现 calico 和 calico 的 pod 是通的, cilium 和 cilium 的 pod 是通的
也即是说
- 跨节点 cilium ping calico 的 pod 是不通的
2. 问题分析
先说结论: calico 的 network policy iptables 策略导致的丢包
2.1 路由
root@k8s-work2:~# route -n | grep tunl0
10.222.0.18 172.16.189.100 255.255.255.255 UGH 0 0 0 tunl0
10.222.1.160 172.16.189.101 255.255.255.255 UGH 0 0 0 tunl0
10.222.12.0 172.16.189.100 255.255.255.0 UG 0 0 0 tunl0 #
10.222.219.0 172.16.189.101 255.255.255.0 UG 0 0 0 tunl0
root@k8s-work2:~# route -n | grep cilium
10.222.0.0 10.222.2.80 255.255.255.0 UG 0 0 0 cilium_host
10.222.1.0 10.222.2.80 255.255.255.0 UG 0 0 0 cilium_host
10.222.2.0 10.222.2.80 255.255.255.0 UG 0 0 0 cilium_host
10.222.2.80 0.0.0.0 255.255.255.255 UH 0 0 0 cilium_host
root@k8s-work2:~#
cilium 和 calico 的路由思路是一致的,基于目的路由判断,cilium 发出的包就会经过 tunl0
2.2 iptables
拿一个接口的 iptables 规则来看
root@k8s-work2:~# route -n | grep cali7cd4eff55c7
10.222.160.3 0.0.0.0 255.255.255.255 UH 0 0 0 cali7cd4eff55c7
root@k8s-work2:~# iptables-save | grep cali7cd4eff55c7
:cali-fw-cali7cd4eff55c7 - [0:0]
:cali-sm-cali7cd4eff55c7 - [0:0]
:cali-tw-cali7cd4eff55c7 - [0:0]
-A cali-from-endpoint-mark -m comment --comment "cali:-XK1s_VTErdsCJtI" -m mark --mark 0xd9b00000/0xfff00000 -g cali-fw-cali7cd4eff55c7
-A cali-from-wl-dispatch-7 -i cali7cd4eff55c7 -m comment --comment "cali:st9dOv2F3h7sA4M5" -g cali-fw-cali7cd4eff55c7
-A cali-fw-cali7cd4eff55c7 -m comment --comment "cali:VQN-OFZyerQtZBEn" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A cali-fw-cali7cd4eff55c7 -m comment --comment "cali:u292TzXDexF2Ict6" -m conntrack --ctstate INVALID -j DROP
-A cali-fw-cali7cd4eff55c7 -m comment --comment "cali:YwYmsk0jfYuWiajI" -j MARK --set-xmark 0x0/0x10000
-A cali-fw-cali7cd4eff55c7 -p udp -m comment --comment "cali:HWlfQ2zqu9wCTQ-Z" -m comment --comment "Drop VXLAN encapped packets originating in workloads" -m multiport --dports 4789 -j DROP
-A cali-fw-cali7cd4eff55c7 -p ipencap -m comment --comment "cali:9NvRbBeNCLKHDxin" -m comment --comment "Drop IPinIP encapped packets originating in workloads" -j DROP
-A cali-fw-cali7cd4eff55c7 -m comment --comment "cali:ebjVwiHeMZvKiKFY" -j cali-pro-kns.default
-A cali-fw-cali7cd4eff55c7 -m comment --comment "cali:GAIptoj1ZMCwh18V" -m comment --comment "Return if profile accepted" -m mark --mark 0x10000/0x10000 -j RETURN
-A cali-fw-cali7cd4eff55c7 -m comment --comment "cali:3YT1psFK5wku9bce" -j cali-pro-ksa.default.default
-A cali-fw-cali7cd4eff55c7 -m comment --comment "cali:zcLRYSQ3ImT0wOP0" -m comment --comment "Return if profile accepted" -m mark --mark 0x10000/0x10000 -j RETURN
-A cali-fw-cali7cd4eff55c7 -m comment --comment "cali:opk4g-ppHGHwfQcq" -m comment --comment "Drop if no profiles matched" -j DROP
-A cali-set-endpoint-mark-7 -i cali7cd4eff55c7 -m comment --comment "cali:Odss9VHU85V30TJC" -g cali-sm-cali7cd4eff55c7
-A cali-sm-cali7cd4eff55c7 -m comment --comment "cali:Q8C_0b-CTy2I_fHR" -j MARK --set-xmark 0xd9b00000/0xfff00000
-A cali-to-wl-dispatch-7 -o cali7cd4eff55c7 -m comment --comment "cali:QI5N3ve2MqWvQZog" -g cali-tw-cali7cd4eff55c7
-A cali-tw-cali7cd4eff55c7 -m comment --comment "cali:GUg67y3reVUdAvdo" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A cali-tw-cali7cd4eff55c7 -m comment --comment "cali:kZieBSTYFnhhzV85" -m conntrack --ctstate INVALID -j DROP
-A cali-tw-cali7cd4eff55c7 -m comment --comment "cali:O9nqOgxsBvDWpwMN" -j MARK --set-xmark 0x0/0x10000
-A cali-tw-cali7cd4eff55c7 -m comment --comment "cali:sSPf_ATXC8dLcV7c" -j cali-pri-kns.default
-A cali-tw-cali7cd4eff55c7 -m comment --comment "cali:QK9pHStGx3z7st31" -m comment --comment "Return if profile accepted" -m mark --mark 0x10000/0x10000 -j RETURN
-A cali-tw-cali7cd4eff55c7 -m comment --comment "cali:7TKz5idGlNYQQgn5" -j cali-pri-ksa.default.default
-A cali-tw-cali7cd4eff55c7 -m comment --comment "cali:Bd-ZvIsH71_qbiYC" -m comment --comment "Return if profile accepted" -m mark --mark 0x10000/0x10000 -j RETURN
-A cali-tw-cali7cd4eff55c7 -m comment --comment "cali:NY1yjTePbQ1ldQ-J" -m comment --comment "Drop if no profiles matched" -j DROP
root@k8s-work2:~# ip a | grep cali7cd4eff55c7
237: cali7cd4eff55c7@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP group default qlen 1000
root@k8s-work2:~#
root@k8s-work2:~# iptables-save | grep IPIP
-A cali-INPUT -p ipencap -m comment --comment "cali:PajejrV4aFdkZojI" -m comment --comment "Allow IPIP packets from Calico hosts" -m set --match-set cali40all-hosts-net src -m addrtype --dst-type LOCAL -j ACCEPT
-A cali-INPUT -p ipencap -m comment --comment "cali:_wjq-Yrma8Ly1Svo" -m comment --comment "Drop IPIP packets from non-Calico hosts" -j DROP
-A cali-OUTPUT -p ipencap -m comment --comment "cali:AJBkLho_0Qd8LNr3" -m comment --comment "Allow IPIP packets to other Calico hosts" -m set --match-set cali40all-hosts-net dst -m addrtype --src-type LOCAL -j ACCEPT
突然 感觉 iptables 的数量真的很多,比 kube-ovn 要多的多,主要包括:
- cali-from-endpoint-mark 只有 calico 出来,才会 mark 0xd9b00000/0xfff00000
- cali-fw-cali7cd4eff55c7 ,然后是防火墙规则,比如 drop vxlan, 但我们 cilium 使用的 geneve,
- 经过 fw 之后会统一打上 0x10000/0x10000 标签
也不是这里丢的包
跨节点的 pod 通信,最终肯定会经过 IPIP,封包后会经过这三条规则。
不是这里丢的包
root@k8s-work2:~# iptables-save | grep IPIP
-A cali-INPUT -p ipencap -m comment --comment "cali:PajejrV4aFdkZojI" -m comment --comment "Allow IPIP packets from Calico hosts" -m set --match-set cali40all-hosts-net src -m addrtype --dst-type LOCAL -j ACCEPT
-A cali-INPUT -p ipencap -m comment --comment "cali:_wjq-Yrma8Ly1Svo" -m comment --comment "Drop IPIP packets from non-Calico hosts" -j DROP
-A cali-OUTPUT -p ipencap -m comment --comment "cali:AJBkLho_0Qd8LNr3" -m comment --comment "Allow IPIP packets to other Calico hosts" -m set --match-set cali40all-hosts-net dst -m addrtype --src-type LOCAL -j ACCEPT
丢包的位置,我认为是这里
root@k8s-work2:~# iptables-save | grep profile | grep cali7cd4eff55c7
-A cali-fw-cali7cd4eff55c7 -m comment --comment "cali:GAIptoj1ZMCwh18V" -m comment --comment "Return if profile accepted" -m mark --mark 0x10000/0x10000 -j RETURN
-A cali-fw-cali7cd4eff55c7 -m comment --comment "cali:zcLRYSQ3ImT0wOP0" -m comment --comment "Return if profile accepted" -m mark --mark 0x10000/0x10000 -j RETURN
-A cali-fw-cali7cd4eff55c7 -m comment --comment "cali:opk4g-ppHGHwfQcq" -m comment --comment "Drop if no profiles matched" -j DROP
-A cali-tw-cali7cd4eff55c7 -m comment --comment "cali:QK9pHStGx3z7st31" -m comment --comment "Return if profile accepted" -m mark --mark 0x10000/0x10000 -j RETURN
-A cali-tw-cali7cd4eff55c7 -m comment --comment "cali:Bd-ZvIsH71_qbiYC" -m comment --comment "Return if profile accepted" -m mark --mark 0x10000/0x10000 -j RETURN
-A cali-tw-cali7cd4eff55c7 -m comment --comment "cali:NY1yjTePbQ1ldQ-J" -m comment --comment "Drop if no profiles matched" -j DROP
root@k8s-work2:~#
这里的 profile 到底是什么东西?
3. calico 的 profile
你注意到 cali-fw-* 和 cali-tw-* 链中出现的 "Return if profile accepted"、"Drop if no profiles matched" 这些注释,确实是理解 Calico 网络策略(policy)和 iptables 实现逻辑的核心。
下面我会详细讲清楚 profile 的来源、生成机制、在 iptables 中的作用、与 NetworkPolicy 的关系、以及为什么有 “Return if profile accepted / Drop if no profiles matched” 这些规则。
🧩 一、什么是 Calico Profile?
在 Calico 中,Profile 是一种策略对象(Policy Object),定义了允许/拒绝哪些流量通过。
它存在的意义类似于:
- 为 命名空间 (Namespace) 或 WorkloadEndpoint (Pod) 自动生成的默认策略;
- 或者是用户显式定义的 NetworkPolicy 最终编译结果的一部分。
换句话说:
Calico profile = NetworkPolicy 编译后的底层实现单元。
二、Profile 的来源(谁创建的)
Calico 的组件 Felix 会自动创建和维护 profile:
| 来源 | 对应的 Profile 名称 | 创建者 | 说明 |
|---|---|---|---|
| Kubernetes Namespace | kns.<namespace> | Felix 自动创建 | 每个命名空间都会有一个 profile |
| 每个 WorkloadEndpoint(Pod) | ksa.<serviceaccount>、k8sns.<namespace> 等 | Felix 自动生成 | 用于默认 ingress/egress 控制 |
| 用户定义的 Calico Policy | profile 内嵌匹配规则 | 由 API server → Felix 编译生成 | 用户 NetworkPolicy 被编译成 profile 链(和 policy 链) |
查看命令示例:
calicoctl get profile
输出大概会有:
NAME
kns.default
kns.kube-system
kns.monitoring
🧱 三、Profile 作用机制(在 iptables 中体现)
每个 Profile 都会对应两类链:
| 方向 | 链名模式 | 功能 |
|---|---|---|
| ingress(进入工作负载) | cali-pri-<profile> | 检查入站包是否被 profile 允许 |
| egress(从工作负载发出) | cali-pro-<profile> | 检查出站包是否被 profile 允许 |
然后,在工作负载 veth 设备对应的主机端链中(如 cali-fw-caliXXXX、cali-tw-caliXXXX),会依次调用这些 profile 链。
🧩 四、你看到的这些规则的结构
以你的例子:
-A cali-fw-cali7cd4eff55c7 -m comment --comment "Return if profile accepted" -m mark --mark 0x10000/0x10000 -j RETURN
-A cali-fw-cali7cd4eff55c7 -m comment --comment "Drop if no profiles matched" -j DROP
结构逻辑是:
-
cali-fw-cali7cd4eff55c7:
这是 workload veth 设备对应的 forward 链,名字对应于 host 端 veth(如cali7cd4eff55c7)。 -
Felix 在生成 iptables 时,会这样展开:
cali-fw-cali7cd4eff55c7 ↓ 调用与该 workload 关联的 profile 链(cali-pri-XXX, cali-pro-XXX) ↓ 如果任何一个 profile 规则允许包通过,会设置 mark 0x10000 ↓ “Return if profile accepted” 规则检查 mark == 0x10000,则 RETURN ↓ 如果没有任何 profile 匹配(即 mark 没被置位),执行 “Drop if no profiles matched” -
cali-tw-cali7cd4eff55c7同理,只是针对 转发/反向方向。
⚙️ 五、Profile 是如何与 Pod 绑定的
Felix 在启动时会监听 Kubernetes API,对每个 Pod 生成一个 WorkloadEndpoint 对象,内容大致包括:
apiVersion: projectcalico.org/v3
kind: WorkloadEndpoint
metadata:
name: node1-k8s-podname-eth0
namespace: default
spec:
orchestrator: k8s
node: node1
workload: podname
interfaceName: cali7cd4eff55c7
profiles:
- kns.default
- ksa.default
- your-custom-profile
上面的 profiles: 字段指明了这个 Pod 使用哪些 profile。
Felix 根据这些 profile 自动生成并链接到 cali-fw-cali7cd4eff55c7 链。
📊 六、一个完整包流过程(结合 profile)
以 PodA -> PodB(同节点)为例:
-
包从 PodA 出发 → 进入主机端 veth → 命中
cali-tw-caliXXXX链; -
调用该 workload 对应的 egress profile 链 (
cali-pro-kns.default);- 若允许,则 mark 0x10000;
- 若不允许,则继续;
-
“Return if profile accepted” 检查该 mark,如果已设置 → RETURN;
-
如果所有 profile 都没置位 mark → “Drop if no profiles matched” → 丢弃。
同理,进入 PodB 时的入站包走 cali-fw-caliYYYY → ingress profiles (cali-pri-kns.default)。
🧩 七、为什么叫 “profile accepted”
Calico 的实现逻辑:
-
Profile 中的“allow”规则会执行:
-j MARK --set-xmark 0x10000/0x10000 -j RETURN -
所以被 profile 允许的包就会带上标志 0x10000;
-
上层链看到该标志后,就直接
RETURN; -
如果没有被任何 profile 允许,mark 仍为 0,最终被“Drop if no profiles matched”丢弃。
🧠 八、与 NetworkPolicy 的关系
| Calico 对象 | 对应 k8s 概念 | 生效范围 | 关系 |
|---|---|---|---|
Profile (kns.<ns>) | Namespace 的默认策略 | 命名空间 | 基础安全边界 |
Profile (ksa.<sa>) | ServiceAccount | Pod 所属身份 | 精细化 |
| NetworkPolicy | Kubernetes NetworkPolicy | 可自定义匹配 | 编译后成为若干 profile/policy 链并链接到工作负载链中 |
所以在实际运行中:
Profile 是默认存在的策略层(namespace + SA),
NetworkPolicy 则是用户定义的附加规则层,二者共同决定允许/拒绝。
🧩 九、实际可验证命令
你可以直接看某个 Pod 对应的 profiles:
# 查 workloadendpoint
calicoctl get workloadendpoint -o yaml | grep -A5 profiles
输出示例:
profiles:
- kns.default
- ksa.default
查看 profile 内容:
calicoctl get profile kns.default -o yaml
会看到类似:
spec:
ingress:
- action: Allow
source:
selector: all()
egress:
- action: Allow
destination:
selector: all()
这些内容最终编译成 iptables 链:
cali-pri-kns.default
cali-pro-kns.default
并嵌入到 workload 的 fw/tw 链中。
✅ 十、总结一句话
Profile 是 Calico 自动生成或由策略编译得到的安全策略单元,用于定义 Pod(或命名空间)的默认 ingress/egress 行为。
每个 Pod 的 veth 对应链(如cali-fw-cali7cd4eff55c7)会调用这些 profile 链来检查是否允许流量。
“Return if profile accepted” 意味着某个 profile 已经允许该包(标志 0x10000 已置位),
“Drop if no profiles matched” 则是防止没有任何 profile 允许的包被放行。