Linux下统计网络带宽的工具很多,但大部分是按网卡、进程、IP维度进行统计。统计容器维度的网络流量,虽然可在容器的namespace查看,或者由cgroup提供的net_cls做标记再配合iptable统计,但使用起来并不方便,这里介绍一种基于ebpf来统计容器维度网络流量的办法,以及使用时遇到的坑。
原理与实现
ebpf的原理不再介绍了,用来做流量统计,最大的优势是十分灵活,能够根据需求对统计项目进行定制。基本原理就是记录内核中各网络相关函数的参数,再按不同维度,如 cgroupID, 进程,IP地址等进行分类,统计,计算,输出。
统计流量
ebpf可通过跟踪内核函数,统计不同层次的网络流量。各层的流量差异主要在于包头,重传,控制报文等等。
L4 TCP 纯数据流量:
上行:kprobe统计 tcp_sendmsg(struct sock *sk,struct msghdr *msg, size_t size) size
下行:kprobe统计 tcp_cleanup_rbuf(struct sock *sk, int copied) copied
L4 UDP 纯数据流量:
上行:kprobe统计 udp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len) len
下行:kprobe统计 skb_consume_udp(struct sock *sk, struct sk_buff *skb, int len) len
L3 IP 流量
上行: kprobe统计 ip_output(struct net *net, struct sock *sk, struct sk_buff *skb) skb->len
下行: kprobe统计 ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,struct net_device *orig_dev) skb->len
L2 全部网络包流量:
上行:tracepoint统计 net/net_dev_queue args->len
下行:tracepoint统计 net/netif_receive_skb args->len
获取容器ID
容器基于cgroup创建,容器内的进程生成时其 task_struct->cgroups 会记录其所属cgroup相关信息。ebpf程序运行在内核态,可直接访问进程的task_struct结构,进而获取其cgroupID, 在统计流量时即可方便的区分当前网络流量属于哪个cgroup。
static void fill_container_id(char *container_id) {
struct task_struct *curr_task;
struct css_set *css;
struct cgroup_subsys_sta