用EBPF来观测数据包在k8s中的流转

1.背景

  从一开始接触k8s,就被其复杂的网络架构虐的体无完肤。不同的网络组建使用了不同的虚拟网络设备,要理解整个k8s网络,会设计许多层面的知识。即使通过学习,实验,测试。从理论层面明白了k8s网络的数据流路径。但是没有真切的看到,还是会心里打一个大大的问号?k8s的数据包真的是经过了那么多层的虚拟设备,那么多层的iptables规则链吗?从我接触到的资料而言,大多都是从理论层面讲解k8s的网络。没有一个直观的体验。今天记住了。明天处理问题又忘了。确实让人难受。
  在此之前,这种问题没找到什么好的方法。或许是我太浅薄的缘故。不过,总算是遇到了一个技术让我想看到的东西都可以清晰的呈现在眼前。这个技术就是EBPF.

2.EBPF

  EBPF有许多的使用场景,作为一个运维,目前最关注的就是其在动态跟踪方面的实践。我想知道一些之前无法直观看到的东西。动态跟踪技术可以帮助我实现。如果未曾了解过什么事动态跟踪。可以看章亦春大佬的这篇文章动态跟踪技术漫谈.写的相当之详细,所以此处就不再说明。文章中其实没有太多篇幅提及EBPF。提到更多的是SystemTap。也许比EBPF更强大吧。待以后再验证吧。
  继续说EBPF,至于什么是BPF,然后又如何来的EBPF,此处依然不赘述,贴出一篇文章来:eBPF简史,理论性的东西实在不太擅长。也就只能记录下自己的思考,以及实践了。直接操作eBPF实在是不太容易,所以目前比较好的方式是使用框架来进行。我目前主要使用iovisor出的BCC,内核层的代码用c语言,用户层的代码用python。简单又高效。

3.BCC工具安装

   此处使用的工具为tracepkt,此工具基于ping命令实现。使用方式就是把ping 127.0.0.1 改为python tracepkt.py 127.0.0.1. ping命令的问题在于,其只是一个网络的连通性和延迟测试。没有跟多的信息展示出来。而tracepkt工具,可以展示出icmp数据经过了哪些iptables链和虚拟/真实网卡设备。对于容器网络而言,其实只是在不能的namespace而已。服务器在一个namespace中,容器网络在另一个nemesapce中。此工具就可以输出数据包所处于哪个namespace,简直是恐怖如斯。仿佛是梦中的工具一般。
   tracepkt主要依赖BCC。BCC对操作系统内核是有要求的。centos7需要升级内核。ubuntu18.04直接就可以使用。内核版本需要大于4.1.对内核的配置参数也有要求。不过ubuntu18.04默认就开启了。
  此处仅展示ubuntu18.04中的安装过程,其他系统安装请参考官方文档: BCC 安装

sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4052245BD4284CDD
echo "deb https://repo.iovisor.org/apt/$(lsb_release -cs) $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/iovisor.list
sudo apt-get update
sudo apt-get install bcc-tools libbcc-examples linux-headers-$(uname -r)

   安装完成后就可以运行基于BCC的工具了。其实bcc-tools官方本身就包含了许多强大的工具。这些工具我们后面在表。在目录/usr/share/bcc/tools下。大家也可以自行测试着玩。
   安装完BCC环境之后就可以下载tracepkt工具了

git clone https://github.com/smartxff/tracepkt.git

   这是我自己维护的版本,目前之添加一个小小的功能。可以展示出当前数据包在哪个内核函数/TRACEPOINT。
   在某些服务器上由于没开启ipv6可能会报错。

python3 tracepkt.py
...
...
Traceback (most recent call last):
  File "tracepkt.py", line 127, in 
    b = BPF(src_file='tracepkt.c')
  File "/usr/lib/python3.8/site-packages/bcc/__init__.py", line 364, in __init__
    self._trace_autoload()
  File "/usr/lib/python3.8/site-packages/bcc/__init__.py", line 1183, in _trace_autoload
    fn = self.load_func(func_name, BPF.KPROBE)
  File "/usr/lib/python3.8/site-packages/bcc/__init__.py", line 403, in load_func
    raise Exception("Failed to load BPF program %s: %s" %
Exception: Failed to load BPF program b'kretprobe__ipt_do_table': Invalid argument

   可以注释掉ipv6相关的代码在tracepkt.c文件中的最后几行注释掉就可以了

/*
int kprobe__ip6t_do_table(struct pt_regs *ctx, struct sk_buff *skb, const struct nf_hook_state *state, struct xt_table *table)
{
    return __ipt_do_table_in(ctx, skb, state, table);
};

int kretprobe__ip6t_do_table(struct pt_regs *ctx)
{
    return __ipt_do_table_out(ctx);
}
*/

4.跟踪开始---服务器

   我们先直接运行命令看会有什么输出

python tracepkt.py
TRACEPOINT                         NETWORK NS        INTERFACE    TYPE ADDRESSES                          IPTABLES
ipt_do_table                   [           0]                  request 127.0.0.1 -> 127.0.0.1                 nat.OUTPUT      :ACCEPT
ipt_do_table                   [           0]                  request 127.0.0.1 -> 127.0.0.1              filter.OUTPUT      :ACCEPT
ipt_do_table                   [  4026531993]               lo request 127.0.0.1 -> 127.0.0.1                 nat.POSTROUTING :ACCEPT
net:net_dev_queue              [  4026531993]               lo request 127.0.0.1 -> 127.0.0.1
net:netif_rx                   [  4026531993]               lo request 127.0.0.1 -> 127.0.0.1
ipt_do_table                   [  4026531993]               lo request 127.0.0.1 -> 127.0.0.1              filter.INPUT       :ACCEPT
ipt_do_table                   [           0]                    reply 127.0.0.1 -> 127.0.0.1              filter.OUTPUT      :ACCEPT
net:net_dev_queue              [  4026531993]               lo   reply 127.0.0.1 -> 127.0.0.1
net:netif_rx                   [  4026531993]               lo   reply 127.0.0.1 -> 127.0.0.1
ipt_do_table                   [  4026531993]               lo   reply 127.0.0.1 -> 127.0.0.1              filter.INPUT       :ACCEPT
输出内容主要有6列,我们做一下解释:
* TRACEPOINT: 此时数据包在哪个内核函数中抓取到了。这是我自己加的列。原版没有这一列。
* NETWORK NS: 此时数据包所处于的哪个名字空间中。可以把不同的名字空间想象成不同的网络。
* INTERFACE: 表示此数据包在在哪个接口上。命令默认是ping 127.0.0.1 .所以所有数据包在回环口上。
* TYPE:     表示icmp的request和reply两种类型的包
* ADDRESS:  数据包的流向。源地址 -> 目标地址
* IPTABLES: 数据包经过的iptables链,及执行的动作。此处全是ACCEPT。

额外的一些信息
* ipt_do_table:是一个内核函数。主要是在经过netfilter HOOK的时候进行调用。然后就可以从此函数中抓取,iptables的信息。
* net: * : 都是内核中的一些tracepoint。就是开放出来用来观测的。属于eBPF最建议使用的一种方式。

   这样的展示是不是特别清晰明了?从本地发出的数据包最新经过OUTPUT链,而且nat比filter优先级更高。接着经过nat的POSTROUTING链。4026531993 是我们当前所处于的名字空间。数据包经过net_dev_queue发送出去,netif_rx收到request数据包开始进行处理,数据包进过filter.INPUT进入。收到request数据包后构造reply数据包。经过filter的OUTPUT链,和net_dev_queue发出。netif_rx收到数据包进过filter.INPUT进入。
   因为只发送了一个数据包。所以就只有一个ICMP的请求。其实回环口的数据包不够清晰。如果不了解内核函数,也不太能分清哪里是发送,哪里是接收。
   我们在ping一个外部的ip地址吧。

python tracepkt.py 172.17.0.6
TRACEPOINT                         NETWORK NS        INTERFACE    TYPE ADDRESSES                          IPTABLES
ipt_do_table                   [           0]                  request 172.17.2.65 -> 172.17.0.6              nat.OUTPUT      :ACCEPT
ipt_do_table                   [           0]                  request 172.17.2.65 -> 172.17.0.6           filter.OUTPUT      :ACCEPT
ipt_do_table                   [  4026531993]             eth0 request 172.17.2.65 -> 172.17.0.6              nat.POSTROUTING :ACCEPT
net:net_dev_queue              [  4026531993]             eth0 request 172.17.2.65 -> 172.17.0.6
net:napi_gro_receive_entry     [  4026531993]             eth0   reply 172.17.0.6 -> 172.17.2.65
ipt_do_table                   [  4026531993]             eth0   reply 172.17.0.6 -> 172.17.2.65           filter.INPUT       :ACCEPT

   napi和gro都是内核层面的东西。数据包到达网卡后会触发一个硬中断。这个硬中断做一个很简单的操作。就是触发一个软中断,就是napi这个东东持续接收数据包。
   我们看一个有趣的例子.172.17.2.65是我自己服务器的ip

python tracepkt.py 172.17.2.65
TRACEPOINT                         NETWORK NS        INTERFACE    TYPE ADDRESSES                          IPTABLES
ipt_do_table                   [           0]                  request 172.17.2.65 -> 172.17.2.65             nat.OUTPUT      :ACCEPT
ipt_do_table                   [           0]                  request 172.17.2.65 -> 172.17.2.65          filter.OUTPUT      :ACCEPT
ipt_do_table                   [  4026531993]               lo request 172.17.2.65 -> 172.17.2.65             nat.POSTROUTING :ACCEPT
net:net_dev_queue              [  4026531993]               lo request 172.17.2.65 -> 172.17.2.65
net:netif_rx                   [  4026531993]               lo request 172.17.2.65 -> 172.17.2.65
ipt_do_table                   [  4026531993]               lo request 172.17.2.65 -> 172.17.2.65          filter.INPUT       :DROP

   之前遇到过一个特别诡异的问题。就是这台机器访问其他机器是可以痛的。其他机器访问这台机器也是可以通的。就是自己访问不了自己。抓破脑袋都想不明白。本来以为是网卡问题。然后就吧ping换成tracepkt命令。得!清晰明了。数据包在filter的INPUT链上被DROP了。因为之前很少使用iptables,所以没往这方面想。所以,还是工具靠谱!然后就可以解决问题了。

iptables -D  INPUT -s 172.17.2.65/32 -j DROP

   删除这条规则即可。

4 跟踪开始--容器和k8s

   172.18.0.2是我当前机器上运行的一个容器的ip地址。我们看看本机访问这个容器经过了哪些路径

TRACEPOINT                         NETWORK NS        INTERFACE    TYPE ADDRESSES                          IPTABLES
ipt_do_table                   [           0]                  request 172.18.0.1 -> 172.18.0.2               nat.OUTPUT      :ACCEPT
ipt_do_table                   [           0]                  request 172.18.0.1 -> 172.18.0.2            filter.OUTPUT      :ACCEPT
ipt_do_table                   [  4026531993]          docker0 request 172.18.0.1 -> 172.18.0.2               nat.POSTROUTING :ACCEPT
net:net_dev_queue              [  4026531993]          docker0 request 172.18.0.1 -> 172.18.0.2
net:net_dev_queue              [  4026531993]      veth29f2c28 request 172.18.0.1 -> 172.18.0.2
net:netif_rx                   [  4026532328]             eth0 request 172.18.0.1 -> 172.18.0.2
net:net_dev_queue              [  4026532328]             eth0   reply 172.18.0.2 -> 172.18.0.1
net:netif_rx                   [  4026531993]      veth29f2c28   reply 172.18.0.2 -> 172.18.0.1
net:netif_receive_skb_entry    [  4026531993]          docker0   reply 172.18.0.2 -> 172.18.0.1
ipt_do_table                   [  4026531993]          docker0   reply 172.18.0.2 -> 172.18.0.1            filter.INPUT       :ACCEPT

   此处主要看名字空间的变化。4026531993是机器的名字空间。4026532328是容器的名字空间。所以eth0是容器内部的接口名。docker0是进入docker网络的网管。veth29f2c28是在此服务器上的虚拟设备接口。类似网线的一端,另一端就是容器内的eth0.两个docker0不是说经过docker0两次。而只是在不同的内核函数中抓到的包的信息。注意前面的TRACEPOINT。
   上面是从机器访问容器的路径。如果的容器之间的请求呢?如果重新构造一个容器内部的BCC环境实在是复杂,对于这种我们。我们有其他思路。容器的网络其实只是在某个namespace罢了。那我们是否可以进入某个容器的namespace内呢?还真有。

docker ps -qa
4cee6b4d5215
78cd2c04cff2

# 78cd2c04cff2是其中一个容器,ip是172.18.0.3
# 4cee6b4d5215的ip是172.18.0.2. 

# 我们进入78cd2c04cff2
nsenter -n --target `docker inspect -f {{.State.Pid}} 4cee6b4d5215`

# 此时看网卡名已经变了
ip a

1: lo:  mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
14: eth0@if15:  mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:12:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.18.0.3/16 brd 172.18.255.255 scope global eth0
       valid_lft forever preferred_lft forever

   进入容器的名字空间后。我们就可以在此名字空间下,执行服务上命令了。是不是很方便!

python tracepkt.py 172.18.0.2
TRACEPOINT                         NETWORK NS        INTERFACE    TYPE ADDRESSES                          IPTABLES
net:net_dev_queue              [  4026532398]             eth0 request 172.18.0.3 -> 172.18.0.2
net:netif_rx                   [  4026531993]      vethc3163ed request 172.18.0.3 -> 172.18.0.2
ipt_do_table                   [  4026531993]          docker0 request 172.18.0.3 -> 172.18.0.2               nat.PREROUTING  :ACCEPT
ipt_do_table                   [  4026531993]      veth29f2c28 request 172.18.0.3 -> 172.18.0.2            filter.FORWARD     :ACCEPT
ipt_do_table                   [  4026531993]      veth29f2c28 request 172.18.0.3 -> 172.18.0.2               nat.POSTROUTING :ACCEPT
net:net_dev_queue              [  4026531993]      veth29f2c28 request 172.18.0.3 -> 172.18.0.2
net:netif_rx                   [  4026532328]             eth0 request 172.18.0.3 -> 172.18.0.2
net:net_dev_queue              [  4026532328]             eth0   reply 172.18.0.2 -> 172.18.0.3
net:netif_rx                   [  4026531993]      veth29f2c28   reply 172.18.0.2 -> 172.18.0.3
ipt_do_table                   [  4026531993]      vethc3163ed   reply 172.18.0.2 -> 172.18.0.3            filter.FORWARD     :ACCEPT
net:net_dev_queue              [  4026531993]      vethc3163ed   reply 172.18.0.2 -> 172.18.0.3
net:netif_rx                   [  4026532398]             eth0   reply 172.18.0.2 -> 172.18.0.3

   原来容器之间一次简单的请求这么复杂。经过了三个名字空间,五个网卡。注意两个不同名字空间下的eth0,是不同名字空间下的网卡。此处也可以对比从服务器访问和容器间访问。经过的netfilter HOOK是不同的。

   接下来就是最终的k8s环境的跟踪了。此命令的局限性在于只能跟踪当前服务器内核的各种事件。所以我们先搭建一个allinone的k8s。就是所有组建运行在一台服务器上。k8s的环境如下。

kubectl get pod -o wide
NAME                         READY   STATUS    RESTARTS   AGE   IP           NODE          NOMINATED NODE   READINESS GATES
service-a-6bd6fd88b5-h6z9z   1/1     Running   0          6s    10.58.0.12   172.17.2.66              
service-c-6fc688b9cf-fs5hc   1/1     Running   0          18s   10.58.0.11   172.17.2.66              

kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service-a    ClusterIP   10.68.147.120           80/TCP    73s
service-c    ClusterIP   10.68.104.62            80/TCP    101s

   这连个容器都在同一个服务器上。我们现在需要进入service-a容器,然后ping service-c的ip,
   我们先进入service-a容器的名字空间。

function e() {
   set -eu
   ns=${2-"default"}
   pod=`kubectl -n $ns describe pod $1 | grep -Eo 'docker://.*$' | head -n 1 | sed 's/docker:\/\/\(.*\)$/\1/'`
   pid=`docker inspect -f {{.State.Pid}} $pod`
   echo "enter pod netns successfully for $ns/$1"
  nsenter -n --target $pid
}

# default是namespace
e service-a-6bd6fd88b5-h6z9z default

python tracepkt.py 10.58.0.11
TRACEPOINT                         NETWORK NS        INTERFACE    TYPE ADDRESSES                          IPTABLES
Possibly lost 79 samples
net:net_dev_queue              [  4026532930]             eth0 request 10.58.0.12 -> 10.58.0.11
net:netif_rx                   [  4026531993]     veth3998aa4b request 10.58.0.12 -> 10.58.0.11
ipt_do_table                   [  4026531993]             cni0 request 10.58.0.12 -> 10.58.0.11               raw.PREROUTING  :ACCEPT
ipt_do_table                   [  4026531993]             cni0 request 10.58.0.12 -> 10.58.0.11            mangle.PREROUTING  :ACCEPT
ipt_do_table                   [  4026531993]             cni0 request 10.58.0.12 -> 10.58.0.11               nat.PREROUTING  :ACCEPT
ipt_do_table                   [  4026531993]     veth8309a74e request 10.58.0.12 -> 10.58.0.11            mangle.FORWARD     :ACCEPT
ipt_do_table                   [  4026531993]     veth8309a74e request 10.58.0.12 -> 10.58.0.11            filter.FORWARD     :ACCEPT
ipt_do_table                   [  4026531993]     veth8309a74e request 10.58.0.12 -> 10.58.0.11            mangle.POSTROUTING :ACCEPT
ipt_do_table                   [  4026531993]     veth8309a74e request 10.58.0.12 -> 10.58.0.11               nat.POSTROUTING :ACCEPT
net:net_dev_queue              [  4026531993]     veth8309a74e request 10.58.0.12 -> 10.58.0.11
net:netif_rx                   [  4026532857]             eth0 request 10.58.0.12 -> 10.58.0.11
net:net_dev_queue              [  4026532857]             eth0   reply 10.58.0.11 -> 10.58.0.12
net:netif_rx                   [  4026531993]     veth8309a74e   reply 10.58.0.11 -> 10.58.0.12
ipt_do_table                   [  4026531993]             cni0   reply 10.58.0.11 -> 10.58.0.12               raw.PREROUTING  :ACCEPT
ipt_do_table                   [  4026531993]             cni0   reply 10.58.0.11 -> 10.58.0.12            mangle.PREROUTING  :ACCEPT
ipt_do_table                   [  4026531993]     veth3998aa4b   reply 10.58.0.11 -> 10.58.0.12            mangle.FORWARD     :ACCEPT
ipt_do_table                   [  4026531993]     veth3998aa4b   reply 10.58.0.11 -> 10.58.0.12            filter.FORWARD     :ACCEPT
ipt_do_table                   [  4026531993]     veth3998aa4b   reply 10.58.0.11 -> 10.58.0.12            mangle.POSTROUTING :ACCEPT
net:net_dev_queue              [  4026531993]     veth3998aa4b   reply 10.58.0.11 -> 10.58.0.12
net:netif_rx                   [  4026532930]             eth0   reply 10.58.0.11 -> 10.58.0.12
出来的结果和docker的没差太多。主要原因在于一些跟细节的信息没有获取到。

其实tracepkt还有一个另一个版本。主要来自tracepkt作者的博客。有国内大佬已经翻译成中文了 [使用 Linux tracepoint、perf 和 eBPF 跟踪数据包](http://arthurchiao.art/blog/trace-packet-with-tracepoint-perf-ebpf-zh/).这篇文章中实现的tracepkt没有iptables部分,也不支持ipv6.但是可以抓取到所有icmp包。因为是持续抓取,所以得占用一个终端。然后在另一个终端执行ping命令。
# 这是抓取终端

python tracepkt.py
version [namespace id]  dev                     icmptype    pid icmpseq src           dst
4   [4026531993]    cni0                    Echo request    12027   1   10.58.0.1   10.58.0.12  net:net_dev_queue
4   [4026531993]    veth3998aa4b            Echo request    12027   1   10.58.0.1   10.58.0.12  net:net_dev_queue
4   [4026532930]    eth0                    Echo request    12027   1   10.58.0.1   10.58.0.12  net:netif_rx
4   [4026532930]    eth0                    Echo Reply      12027   1   10.58.0.12  10.58.0.1   net:net_dev_queue
4   [4026531993]    veth3998aa4b            Echo Reply      12027   1   10.58.0.12  10.58.0.1   net:netif_rx
4   [4026531993]    cni0                    Echo Reply      12027   1   10.58.0.12  10.58.0.1   net:netif_receive_skb_entry




4   [4026532930]    eth0                    Echo request    13192   1   10.58.0.12  10.58.0.11  net:net_dev_queue
4   [4026531993]    veth3998aa4b            Echo request    13192   1   10.58.0.12  10.58.0.11  net:netif_rx
4   [4026531993]    veth3998aa4b            Echo request    13192   1   10.58.0.12  10.58.0.11  net:net_dev_queue
4   [4026532930]    eth0                    Echo request    13192   1   10.58.0.12  10.58.0.11  net:netif_rx
4   [4026531993]    veth8309a74e            Echo request    13192   1   10.58.0.12  10.58.0.11  net:net_dev_queue
4   [4026532857]    eth0                    Echo request    13192   1   10.58.0.12  10.58.0.11  net:netif_rx
4   [4026531993]    veth409f6ead            Echo request    13192   1   10.58.0.12  10.58.0.11  net:net_dev_queue
4   [4026532782]    eth0                    Echo request    13192   1   10.58.0.12  10.58.0.11  net:netif_rx
4   [4026531993]    veth7765b5f5            Echo request    13192   1   10.58.0.12  10.58.0.11  net:net_dev_queue
4   [4026532717]    eth0                    Echo request    13192   1   10.58.0.12  10.58.0.11  net:netif_rx
4   [4026531993]    veth22af838f            Echo request    13192   1   10.58.0.12  10.58.0.11  net:net_dev_queue
4   [4026532644]    eth0                    Echo request    13192   1   10.58.0.12  10.58.0.11  net:netif_rx
4   [4026531993]    vethf7954c62            Echo request    13192   1   10.58.0.12  10.58.0.11  net:net_dev_queue
4   [4026532577]    eth0                    Echo request    13192   1   10.58.0.12  10.58.0.11  net:netif_rx
4   [4026531993]    vethe980d1cc            Echo request    13192   1   10.58.0.12  10.58.0.11  net:net_dev_queue
4   [4026532507]    eth0                    Echo request    13192   1   10.58.0.12  10.58.0.11  net:netif_rx
4   [4026531993]    veth7740529c            Echo request    13192   1   10.58.0.12  10.58.0.11  net:net_dev_queue
4   [4026532432]    eth0                    Echo request    13192   1   10.58.0.12  10.58.0.11  net:netif_rx
4   [4026531993]    veth2697c574            Echo request    13192   1   10.58.0.12  10.58.0.11  net:net_dev_queue
4   [4026532366]    eth0                    Echo request    13192   1   10.58.0.12  10.58.0.11  net:netif_rx
4   [4026532857]    eth0                    Echo Reply      13192   1   10.58.0.11  10.58.0.12  net:net_dev_queue
4   [4026531993]    veth8309a74e            Echo Reply      13192   1   10.58.0.11  10.58.0.12  net:netif_rx
4   [4026531993]    veth3998aa4b            Echo Reply      13192   1   10.58.0.11  10.58.0.12  net:net_dev_queue
4   [4026532930]    eth0                    Echo Reply      13192   1   10.58.0.11  10.58.0.12  net:netif_rx





4   [4026532930]    eth0                    Echo request    14251   1   10.58.0.12  10.58.0.11  net:net_dev_queue
4   [4026531993]    veth3998aa4b            Echo request    14251   1   10.58.0.12  10.58.0.11  net:netif_rx
4   [4026531993]    veth8309a74e            Echo request    14251   1   10.58.0.12  10.58.0.11  net:net_dev_queue
4   [4026532857]    eth0                    Echo request    14251   1   10.58.0.12  10.58.0.11  net:netif_rx
4   [4026532857]    eth0                    Echo Reply      14251   1   10.58.0.11  10.58.0.12  net:net_dev_queue
4   [4026531993]    veth8309a74e            Echo Reply      14251   1   10.58.0.11  10.58.0.12  net:netif_rx
4   [4026531993]    veth3998aa4b            Echo Reply      14251   1   10.58.0.11  10.58.0.12  net:net_dev_queue
4   [4026532930]    eth0                    Echo Reply      14251   1   10.58.0.11  10.58.0.12  net:netif_rx
# 这是命令执行终端

# 直接在机器上ping 容器ip
ping 10.58.0.12 -c1


# 进入容器a ping 容器c
function e() {    set -eu;    ns=${2-"default"};    pod=`kubectl -n $ns describe pod $1 | grep -Eo 'docker://.*$' | head -n 1 | sed 's/docker:\/\/\(.*\)$/\1/'`;    pid=`docker inspect -f {{.State.Pid}} $pod`;    echo "enter pod netns successfully for $ns/$1";   nsenter -n --target $pid; }
e service-a-6bd6fd88b5-h6z9z defaults
ping 10.58.0.11 -c1

#再ping一次容器
ping 10.58.0.11 -c1

   有没有发现在容器内的第一次ping居然抓取了这么多包。为什么呢?当发送第一个ping包的时候,并不知道目标ip的mac地址是多少。所以得发送一个广播包。死知识有清晰的展现出来了。是不是很有趣?

5.结束语

   其实对于跟踪k8s而言,这个工具还有很多不完善的地址。并没有展现成更细节的东西。比如哪里做的dnat。哪里snat。像基于iptables的kube-proxy不支持ping怎么办?
   问题有许多。但就目前情况来看,都是可以解决的。希望有一天。对于k8s的跟踪能更全面。

   eBPF相关的文章倒是有几篇。也有不少公司有相关实践。奈何没看见什么普罗大众交流的途径。所以小弟在此建了一个qq群。希望有这方面经验,或者有兴趣一起探讨的。有一个交流的地址:

后续更新

默认的tracepkt工具只能执行当前ping命令的数据包。如果我想在对端抓取icmp包就不行了。我们只用把当前代码进行简单改造就可以达成目标。在内核代码,只有确定是icmp数据包时再发送perf event,用户层代码就一直死循环读取事件并输出。其实还可以继续改进向tcpdump看齐。但是目前已经够用了。就先这样把。

   qq群:747399875

你可能感兴趣的:(用EBPF来观测数据包在k8s中的流转)