更多精彩内容,欢迎观看:
《云原生网络数据面可观测性最佳实践》—— 一、容器网络内核原理——3.tc子系统(上):https://developer.aliyun.com/article/1221715?spm=a2c6h.13148508.setting.17.15f94f0eJz5i4D
2) Linux Traffic Control 在云原生中的应用
基于Cgroup的网络数据包Qos
Cgroup子系统是容器技术的基础,通常我们对cgroup的理解都在于cgroup通过cgroupfs文件接口,让内核在为应用程序提供cpu时间片分配和内存分配的过程中遵循一定的配额限制,实际上cgroup在较早的版本中已经支持对某个cgroup中的网络流量进行优先级的调整,以实现单个节点内不同cgroup之间的Qos动作。
Cgroup子系统中提供网络流量优先级的功能为net_cls和net_prio,需要配合TC子系统一起生效,例如,我们给某一个容器所在的cgroup添加一个net_cls设置:
mkdir /sys/fs/cgroup/net_cls/0 echo 0x100001 > /sys/fs/cgroup/net_cls/0/net_cls.classid
在这里,我们选取了设定的class为100001,然后,我们将eth0网卡的root根队列的class设置为10,类型修改为htb,用于进行限速:
tc qdisc add dev eth0 root handle 10: htb
我们在10:这个根队列下,针对我们配置的10:1这个class配置带宽限流配置:
tc class add dev eth0 parent 10: classid 10:1 htb rate 40mbit
最后配置一个filter,将cgroup流量引入到10:1的class中,完成对这个cgroup net_cls的配置:
tc filter add dev eth0 parent 10: protocol ip prio 10 handle 1: cgroup
而net_prio的原理则相对更加直观一点,通过在对应的cgroup路径中的ifpriomap种配置网卡和对应的优先级数值,使对应的cgroup中管理的进程创建出来的socket都具有priority属性,priority属性会成为sk_buff结构体的属性从而携带到进入qdisc,如果qdisc支持优先级调度,则会根据priority来完成流量的Qos,操作如下:
echo "eth0 5" > /cgroup/net_prio/iscsi/net_prio.ifpriomap
基于TC eBPF的高性能可编程数据面实现
从上文的介绍中,我们了解到,在eBPF的多个内核提供的可执行的触发点中,TC子系统是其中原生支持的一种,实际上,许多开源的解决方案也都选择TC子系统作为eBPF程序执行的触发点,包括cilium和terway。
我们通过一个简单的eBPF程序来对TC子系统支持eBPF的能力进行验证:
首先,我们需要按照规范,在TC子系统提供的上下文环境中开发一个简单的eBPF程序:
#include <linux/bpf.h> #include <linux/pkt_cls.h> #include <stdint.h> #include <iproute2/bpf_elf.h> #ifndef __section # define __section(NAME) \ __attribute__((section(NAME), used)) #endif #ifndef __inline # define __inline \ inline __attribute__((always_inline)) #endif #ifndef lock_xadd # define lock_xadd(ptr, val) \ ((void)__sync_fetch_and_add(ptr, val)) #endif #ifndef BPF_FUNC # define BPF_FUNC(NAME, ...) \ (*NAME)(__VA_ARGS__) = (void *)BPF_FUNC_##NAME #endif static void *BPF_FUNC(map_lookup_elem, void *map, const void *key); struct bpf_elf_map acc_map __section("maps") = { .type = BPF_MAP_TYPE_ARRAY, .size_key = sizeof(uint32_t), .size_value = sizeof(uint32_t), .pinning = PIN_GLOBAL_NS, .max_elem = 2, }; static __inline int account_data(struct __sk_buff *skb, uint32_t dir) { uint32_t *bytes; bytes = map_lookup_elem(&acc_map, &dir); if (bytes) lock_xadd(bytes, skb->len); return TC_ACT_OK; } __section("ingress") int tc_ingress(struct __sk_buff *skb) { return account_data(skb, 0); } __section("egress") int tc_egress(struct __sk_buff *skb) { return account_data(skb, 1); } char __license[] __section("license") = "GPL";
随后我们创建一个clsact类型的qdisc,并且将流量全部定位到这个qdisc中:
clang -g -O2 -Wall -target bpf -I ~/iproute2/include/ -c tc-example.c -o tc-example.o # 创建一个clsact类型的qdisc作为root根qdisc,然后加载ebpf程序到发送方向 tc qdisc add dev enp3s0 clsact tc filter add dev enp3s0 egress bpf da obj tc-example.o sec egress # 通过filter show可以查看到网卡在egress上家在的ebpf程序 tc filter show dev enp3s0 egress