查看原文
其他

Cilium Masquerading 故障排查记录

lx1036 云原生实验室 2022-11-11


本文转自 lx1036 的博客,原文:https://juejin.cn/post/7112768193126465567/,版权归原作者所有。欢迎投稿,投稿请添加微信好友:cloud-native-yang

背景

在版本升级 cilium v1.8.1 到 v1.11.1 时,导致业务 pod 报错连接 mysql 授权错误,经过排查发现连接 mysql server 的 clientIP 是 业务 pod 所在的 nodeIP,而不是默认的 podIP,因为 mysql server 只授权了当前 K8s 集群的 pod cidr,所以报错授权问题。

矛盾点在于使用 cilium v1.8.1 时,出机器时的 IP 还是 podIP,但是 v1.11.1 却是 nodeIP,进一步发现 v1.8.2 版本也是 nodeIP。K8s 网络这块采用的是 Cilium + BGP 模式,podIP 在公司内网可达,所以希望的也是业务 pod 从当前节点出去 IP 应该是 podIP 才对,cilium 估计是做了 SNAT,把 podIP SNAT 成 nodeIP。

原因

原因在于 cilium 默认会做 podIP masq,可以参考官网文档 v1.8 :masquerading[1]

我们部署的 cilium 配置里也配置了 masquerade: true,实际上 cilium 会默认配置值为 true :

masquerade: 'true'
enable-bpf-masquerade: 'true'
native-routing-cidr: 10.20.30.0/24

升级 cilium v1.11.1 时我们还是用的以上配置, cilium 新版本这个老配置 masquerade: true 已经废弃,改用 enable-ipv4-masquerade: true, cilium 默认开启 podIP masquerade,见代码:daemon_main.go#L679-L680[2]

所以升级 cilium v1.11.1 时需要改下配置就解决问题了:

enable-ipv4-masquerade: 'false'
enable-bpf-masquerade: 'false'
ipv4-native-routing-cidr: 10.20.30.0/24 # 新版本废弃 native-routing-cidr 配置,使用该配置,默认也是使用集群 pod cidr,和配置值 cluster-pool-ipv4-cidr 相同

为何 cilium v1.8.1 没有报这个问题?尽管 cilium v1.8.1 我们使用的配置是 masquerade: true,但是这个版本有个 bug,导致配置了也不起作用,podIP Masq 也不会走对应的 ebpf SNAT 规则, 在版本 v1.8.2 里修复了这个 bug,所以 cilium v1.8.2 之后默认都是开启 podIP Masq,尽管这个不是我们想要的。bug 修复代码见:pull/12456[3]

如果 pod 访问的目标 ip 在 ipv4-native-routing-cidr 网段内,也不会走 podIP Masq,ebpf c 代码里会判断如果在该网段内就跳过不走 masq 逻辑。这样 pod 相互访问不会走 podIP Masq,只有访问集群外网络时才会这样。ebpf c 代码跳过 ipv4-native-routing-cidr 网段逻辑见:

  • config.go#L505-L518[4]
  • nodeport.h#L1160-L1170[5]

然后,包从容器出来,会经过 node 上 eth0 网卡,该网卡上下发了 ebpf SNAT 逻辑,会把 podIP SNAT 成 nodeIP,SNAT 逻辑代码函数见:

  • nat.h#L504-L570[6]
  • nat.h#L322-L378[7]

这里难点主要是如何使用 ebpf 代码去做 SNAT, cilium 这块代码值得学习,这里也是 cilium 核心逻辑之一。

最后,cilium 会把该 ebpf c 程序下发到 eth0 网卡 egress 出口侧(默认是 eth0 网卡,可以在 cilium daemon 里配置出口网卡), 可以在然后一台 K8s node 上执行以下命令看到 to-netdev ebpf 程序,这里 to-netdev 可以理解为这块 ebpf c 程序的名字:

$ tc filter show dev eth0 egress
filter protocol all pref 1 bpf chain 0
filter protocol all pref 1 bpf chain 0 handle 0x1 bpf_netdev_eth0.o:[to-netdev] direct-action not_in_hw tag aed7375159f1f3a4

to-netdev ebpf c 程序可见,这是包从 eth0 网卡 egress 侧出去时走的逻辑:bpf_host.c#L1003-L1103[8]

同理,from-netdev ebpf c 程序是包进入 eth0 网卡 ingress 侧走的逻辑:bpf_host.c#L962-L987[9] , 这里主要是防火墙或者 BPF NodePort 才有用,比如我们这里的 podIP Masq 时 BPF NodePort 是开启的。

iptables snat masquerading

cilium 除了使用 ebpf 来实现 snat masq,也可以使用下发 iptables 规则来实现,可以见代码: iptables.go#L1097-L1137[10]

可以修改 cilium 配置,然后使用命令 iptables -t nat -S CILIUM_POST_nat 查看:

enable-ipv4-masquerade: 'true'
enable-bpf-masquerade: 'false'
ipv4-native-routing-cidr: 10.20.30.0/24 # 新版本废弃 native-routing-cidr 配置,使用该配置,默认也是使用集群 pod cidr,和配置值 cluster-pool-ipv4-cidr 相同

下发的 iptables 规则类似如下:

$ iptables -t nat -S POSTROUTING
-P POSTROUTING ACCEPT
-A POSTROUTING -m comment --comment "cilium-feeder: CILIUM_POST_nat" -j CILIUM_POST_nat
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE


$ iptables -t nat -S CILIUM_POST_nat
-N CILIUM_POST_nat
-A CILIUM_POST_nat -s 20.30.137.0/25 -m set --match-set cilium_node_set_v4 dst -m comment --comment "exclude traffic to cluster nodes from masquerade" -j ACCEPT
-A CILIUM_POST_nat -s 20.30.137.0/25 ! -d 10.216.136.0/21 ! -o cilium_+ -m comment --comment "cilium masquerade non-cluster" -j MASQUERADE
-A CILIUM_POST_nat -m mark --mark 0xa00/0xe00 -m comment --comment "exclude proxy return traffic from masquerade" -j ACCEPT
-A CILIUM_POST_nat -s 127.0.0.1/32 -o cilium_host -m comment --comment "cilium host->cluster from 127.0.0.1 masquerade" -j SNAT --to-source 20.30.137.116
-A CILIUM_POST_nat -o cilium_host -m mark --mark 0xf00/0xf00 -m conntrack --ctstate DNAT -m comment --comment "hairpin traffic that originated from a local pod" -j SNAT --to-source 20.30.137.116

包走 netfilter POSTROUTING chain 时会首先跳到 CILIUM_POST_nat chain 走完该 chain 的所有 rules,再跳转到 KUBE-POSTROUTING chain 的所有 rules。CILIUM_POST_nat chain 包含的 rules 如上,podIP Masq 的 rule 主要是这条,通过 iptables 很简单就能实现 podIP SNAT 成 nodeIP:

-A CILIUM_POST_nat -s 20.30.137.0/25 ! -d 10.216.136.0/21 ! -o cilium_+ -m comment --comment "cilium masquerade non-cluster" -j MASQUERADE

当然,eBPF 因为会跳过 netfilter,包不必再去拷贝到内核里走 netfilter,性能相比 iptables 更高,所以还是使用 eBPF 来实现 podIP Masq,如果需要的话。不过,eBPF 虽然性能高,但实现复杂。

与 calico 对比

calico 也有 podIP masq 成 nodeIP 的功能,见 Configure outgoing NAT[11] , 可以通过参数 natOutgoing 配置:

apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
  name: default-ipv4-ippool
spec:
  cidr: 192.168.0.0/16
  natOutgoing: true

我们生产 K8s 有少量的集群,容器网络插件用的是 calico,配置都是关闭的 natOutgoing: false。calico 默认应该是下发 iptables 规则实现的 SNAT。

总结

cilium 默认使用 podIP Masq,这样当 pod 不是访问其他 pod 时,会把 podIP SNAT 为 nodeIP,尤其在 podIP 是私网不可达且访问集群外部资源时有用。但是,由于我们采用 cilium + BGP 模式,podIP 在公司内网可达,不需要这个功能,所以需要配置关闭。

另外,一个坑是我们配置一直没有关闭这个功能,所以配置一直都是错的,只是因为 cilium v1.8.1 自己的 bug,导致 podIP Masq 没有开启而已。

待调研

cilium podIP Masq ebpf 逻辑共用的 NodePort service 实现,可以调研下 cilium 如何实现 NodePort service?

参考文献

  • Masquerading[12]
  • pull/12456[13]
  • datapath: Enable BPF MASQ for veth mode in IPv4[14]

引用链接

[1]

masquerading: https://docs.cilium.io/en/v1.8/concepts/networking/masquerading/

[2]

daemon_main.go#L679-L680: https://github.com/cilium/cilium/blob/v1.11.1/daemon/cmd/daemon_main.go#L679-L680

[3]

pull/12456: https://github.com/cilium/cilium/pull/12456

[4]

config.go#L505-L518: https://github.com/cilium/cilium/blob/v1.11.1/pkg/datapath/linux/config/config.go#L505-L518

[5]

nodeport.h#L1160-L1170: https://github.com/cilium/cilium/blob/v1.11.1/bpf/lib/nodeport.h#L1160-L1170

[6]

nat.h#L504-L570: https://github.com/cilium/cilium/blob/v1.11.1/bpf/lib/nat.h#L504-L570

[7]

nat.h#L322-L378: https://github.com/cilium/cilium/blob/v1.11.1/bpf/lib/nat.h#L322-L378

[8]

bpf_host.c#L1003-L1103: https://github.com/cilium/cilium/blob/v1.11.1/bpf/bpf_host.c#L1003-L1103

[9]

bpf_host.c#L962-L987: https://github.com/cilium/cilium/blob/v1.11.1/bpf/bpf_host.c#L962-L987

[10]

iptables.go#L1097-L1137: https://github.com/cilium/cilium/blob/v1.11.1/pkg/datapath/iptables/iptables.go#L1097-L1137

[11]

Configure outgoing NAT: https://projectcalico.docs.tigera.io/networking/workloads-outside-cluster

[12]

Masquerading: https://docs.cilium.io/en/v1.8/concepts/networking/masquerading/

[13]

pull/12456: https://github.com/cilium/cilium/pull/12456

[14]

datapath: Enable BPF MASQ for veth mode in IPv4: https://github.com/cilium/cilium/commit/0962c029849168da34d88f57dd7d0b73876d823b




你可能还喜欢

点击下方图片即可阅读

Docker 火了:主机外可直接访问映射到 127.0.0.1 的服务

2022-06-25

解决 K8s 调度不均衡问题

2022-06-24

为什么 eBPF 如此受欢迎?

2022-06-23


云原生是一种信仰 🤘

关注公众号

后台回复◉k8s◉获取史上最方便快捷的 Kubernetes 高可用部署工具,只需一条命令,连 ssh 都不需要!



点击 "阅读原文" 获取更好的阅读体验!


发现朋友圈变“安静”了吗?

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存