美文网首页动态跟踪
用EBPF来观测数据包在k8s中的流转

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

作者: smartxff | 来源:发表于2020-10-14 18:54 被阅读0次

    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 <module>
        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: <LOOPBACK,UP,LOWER_UP> 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: <BROADCAST,MULTICAST,UP,LOWER_UP> 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   <none>           <none>
    service-c-6fc688b9cf-fs5hc   1/1     Running   0          18s   10.58.0.11   172.17.2.66   <none>           <none>
    
    kubectl get svc
    NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
    service-a    ClusterIP   10.68.147.120   <none>        80/TCP    73s
    service-c    ClusterIP   10.68.104.62    <none>        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中的流转

        本文链接:https://www.haomeiwen.com/subject/utgwpktx.html