原载于:http://arthurchiao.art/blog/cilium-life-of-a-packet-pod-to-service-zh/
引言
面临的问题
传统的基于二层转发(Linux bridge、Netfilter/iptables、OVS 等)和/或 三层路由的网络虚拟化方案中,数据包的转发路径通常非常清晰,通过一些常见工 具或命令就能判断包的下一跳(可能是一个端口或网络设备),最终能画出一张类似下 图的网络拓扑图 [1]:
Fig 1. Network topology inside an OpenStack compute node
当网络出现问题时,例如,一个容器访问另一个容器网络不通,只要沿着这条路径上的设 备依次抓包,再配合路由表、ARP 表分析,一般很快就能定位到问题出在那个环节。
不幸的是,在 Cilium/eBPF 方案中,网络拓扑已经没有这么直观了,例如,下面是默认 安装时 Cilium 节点的网络拓扑:
Fig 2. Network topology inside an Cilium-powered k8s node
可以看到,各个设备之间看上去都是“孤立的”,很多地方并没有桥接设备或常规的路由/转 发规则将它们连起来。如果用 tcpdump
抓包,会看到包一会从一个地方消失了,一会 又从另一个地方冒出来了,中间怎么过来的,一头雾水,只知道是 eBPF 干的。
这可能是上手 Cilium 网络排障(trouble shooting)时最苦恼的事情之一。
本文目的
本文试图用常规 Linux 工具来探索 Cilium 的整个转发路径,并分析在每个转发 节点分别做了什么事情,最终得到一张与图 1 一样直观的转发路径图,结果如图 3 所示:
Fig 3. Traffic path of Pod-to-ServiceIP
具体来说,我们从一个 Pod 访问 ServiceIP 开始,并假设这个 ServiceIP 对应 的后端 Pod 位于另一台 node 上,然后探索包所经过的路径和处理过程。
[2,3,5] 都有较大的篇幅介绍 Cilium/eBPF 的转发路径,但并没有具体到 BPF 代码层面。
如前面所说,转发路径的许多地方是靠 BPF 代码“粘合”到一起的,因此完成本文的任务 离不开对相应 BPF 代码的分析,这是本文最大的不同。
但要理解整个转发路径,还是强烈建议理论先行,在阅读本文之前先理解 [2,3]。
环境及配置信息
Cilium 的 eBPF 转发路径随跨主机网络方案和内核版本而有差异,本文假设:
1.跨主机网络方案:直接路由(BGP [4])2.Linux kernel 4.19
:Cilium/eBPF 的较完整功能依赖这个版本及以上的内核3.Cilium 1.8.2
,配置:
•kube-proxy-replacement=probe
(默认)•enable-ipv4=true
(默认)•datapath-mode=veth
(默认)
4.没有对通信双方应用任何 network policy(默认)5.两个物理网卡做 bond,宿主机 IP 配置在 bond 上
满足以上 2 & 3 条件下,所有对包的拦截和修改操作都由 BPF 代码完成,不依赖 Netfilter/iptables,即所谓的 kube-proxy free 模式。
其他说明
为方便起见,本文会在宿主机上用 nsenter-ctn
进入到容器执行命令,它 其实是对 nsenter
命令的一个简单封装:
{% raw %}
(NODE1) $ cat ~/.bashrc...function nsenter-ctn () {
CTN=$1 # container ID or name PID=$(sudo docker inspect --format "{
{.State.Pid}}" $CTN) shift 1 # remove the first argument, shift others to the left nsenter -t $PID $@}
{% endraw %}
这和登录到容器里执行相应的命令效果是一样的,更多信息参考 man nseneter
。
Step 1: POD1 eth0
发送
下面几个网络设备在我们的场景中用不到,因此后面的拓扑图中不再画它们:
1.
cilium_net/cilium_host
这对 veth pair 在 Kernel4.19
+ Cilium1.8
部署中已经没什么作用了(事实上社区在考虑去掉它们)。2.cilium_vxlan
这个设备是 VxLAN 封装/接封装用的,在直接路由模式中不会出现。
拓扑简化为下图:
Fig. Step 1.
1.1 访问 ServiceIP
从 POD1 访问 ServiceIP 开始,例如:
# * -n: execute command in pod's network namespace# * 10.224.1.1: ServiceIP(NODE1) $ nsenter-ctn POD1 -n curl 10.224.1.1:80
包会从容器的 eth0
虚拟网卡发出去,此时能确定的 IP 和 MAC 地址信息有,
1.src_ip=POD1_IP
2.src_mac=POD1_MAC
3.dst_ip=Se