潜江58同城,江门关键词优化排名,注册新公司网上核名网站,佛山网站seo推广推荐1.背景
在集群网络使用cilium之后#xff0c;最明显的情况就是#xff1a;服务暴露vipport#xff0c;在集群内怎么测试都正常#xff0c;但集群外访问可能是有问题的。而这就在于cilium所使用的ebpf科技。 2.引子#xff1a;curl请求的路程
相对底层一点的语言#xf…1.背景
在集群网络使用cilium之后最明显的情况就是服务暴露vipport在集群内怎么测试都正常但集群外访问可能是有问题的。而这就在于cilium所使用的ebpf科技。 2.引子curl请求的路程
相对底层一点的语言比如c语言在创建一个tcp连接时主要分两步(其它语言可能会更简单) int socket_desc;struct sockaddr_in server;//Create socketsocket_desc socket(AF_INET , SOCK_STREAM , 0);server.sin_addr.s_addr inet_addr(1.1.1.1);server.sin_family AF_INET;server.sin_port htons( 80 );//Connect to remote serverif (connect(socket_desc , (struct sockaddr *)server , sizeof(server)) 0) 一个连接的创建主要分两个步骤 创建socket对象 发起connect连接
而实际上在内核层它经历的步骤会非常多。可以通过perf工具来查看
perf trace -e net:* -e sock:* -e syscalls:* curl 1.1.1.1 -s /dev/stdout 上面的输出很多而syscalls:sys_enter_socket前面的很长一段是curl程序打开本身加载动态链接库需要的系统调用。 而本次需要关心的是以下这部分截取的部分内容 108.294 curl/15819 syscalls:sys_enter_socket(family: INET, type: STREAM)108.351 curl/15819 syscalls:sys_exit_socket(__syscall_nr: 41, ret: AX25)108.939 curl/15819 syscalls:sys_enter_connect(fd: 3, uservaddr: { .family: UNSPEC }, addrlen: 16)108.991 curl/15819 sock:inet_sock_set_state(skaddr: 0xffff902527424c80, oldstate: 7, newstate: 2, dport: 80, family: 2, protocol: 6, saddr: 0x7f176658943b, daddr: 0x7f176658943f, saddr_v6: 0x7f1766589443, daddr_v6: 0x7f1766589453)109.090 curl/15819 net:net_dev_queue(skbaddr: 0xffff9024f0a2d4e8, len: 74, name: enp1s0)109.140 curl/15819 net:net_dev_start_xmit(name: enp1s0, skbaddr: 0xffff9024f0a2d4e8, protocol: 2048, ip_summed: 3, len: 74, network_offset: 14, transport_offset_vali
d: 1, transport_offset: 34, gso_segs: 1, gso_type: 1) 从上面可以看出在定义socket后接着就是connect连接而在sock:inet_sock_set_state这一步有输出地址相关信息但输出的是内存地址无法直接查看。能通过bcc工具集中的tcplife来查看。 # 一个终端中运行
tcplife -D 12345
# 另一个终端中运行
curl 1.1.1.1:12345 虽然访问的是不存在的地址但内核也会基于默认路由走默认网关将报文发送到enp1s0网卡上。而在sock:inet_sock_set_state可以抓取到源地址与目的地址信息。 既然我们能在sock:inet_sock_set_state点挂载程序抓取报文信息那我们是否可以在挂载点修改socket的目的地址与目的端口信息 答案是肯定的。但cilium是在cgroup/connect4进行修改的(和上面从perf查出来的不同但可以通过bcc的工具来验证。cgroup是高版本内核才有的特殊具体可参考链接里面有标识内核版本的特性。 那么这是如何查到的呢 [rootc7-1 ~]# bpftool prog |grep sock
1653: type 18 name sock6_connect tag d526fd1cb49a372e gpl
1657: cgroup_sock name sock6_post_bind tag e46a7916c9c72e67 gpl
1661: type 18 name sock6_sendmsg tag 19094f9c26d4dddf gpl
1665: type 18 name sock6_recvmsg tag 282bf4c10eff7f73 gpl
1669: type 18 name sock4_connect tag 57eae2cf019378cc gpl
1673: cgroup_sock name sock4_post_bind tag ddd7183184f2e6e9 gpl
1677: type 18 name sock4_sendmsg tag 570ef9d580ce0589 gpl
1681: type 18 name sock4_recvmsg tag 0bdebe7409ceb49f gpl
[rootc7-1 ~]# bpftool prog |grep connect
1653: type 18 name sock6_connect tag d526fd1cb49a372e gpl
1669: type 18 name sock4_connect tag 57eae2cf019378cc gpl 在有运行cilium的机器上使用bpftool工具查询挂载的程序发现与socket相关的就是这些。 再到cilium的源代码中查看对应的代码段定义
github.com/cilium/cilium/bpf$ grep -i __section( *.c
bpf_host.c:__section(from-netdev)
bpf_host.c:__section(from-host)
bpf_host.c:__section(to-netdev)
bpf_host.c:__section(to-host)
bpf_lxc.c:__section(from-container)
bpf_lxc.c:__section(mydebug1)
bpf_lxc.c:__section(mydebug2)
bpf_lxc.c:__section(to-container)
bpf_network.c:__section(from-network)
bpf_overlay.c:__section(from-overlay)
bpf_overlay.c:__section(to-overlay)
bpf_sock.c:__section(cgroup/connect4)
bpf_sock.c:__section(cgroup/post_bind4)
bpf_sock.c:__section(cgroup/bind4)
bpf_sock.c:__section(cgroup/sendmsg4)
bpf_sock.c:__section(cgroup/recvmsg4)
bpf_sock.c:__section(cgroup/getpeername4)
bpf_sock.c:__section(cgroup/post_bind6)
bpf_sock.c:__section(cgroup/bind6)
bpf_sock.c:__section(cgroup/connect6)
bpf_sock.c:__section(cgroup/sendmsg6)
bpf_sock.c:__section(cgroup/recvmsg6)
bpf_sock.c:__section(cgroup/getpeername6)
bpf_xdp.c:__section(from-netdev) 由此cilium使用的科技就很明显了。 3. 手写ebpf
1. ebpf程序实现
在看cilium源码实现之前先手写一个最简单的修改目的地址与端口的程序。因为cilium本身框架很复杂代码也有相关所以先以最简单的写死的程序入手。代码可以参考cilium源码。 #include bpf/ctx/unspec.h
#include bpf/api.h#define SKIP_POLICY_MAP 1
#define SKIP_CALLS_MAP 1#define SYS_REJECT 0
#define SYS_PROCEED 1# define printk(fmt, ...) \({ \const char ____fmt[] fmt; \trace_printk(____fmt, sizeof(____fmt), \##__VA_ARGS__); \})__section(cgroup/connect4)
int sock4_connect(struct bpf_sock_addr *ctx )
{if (ctx-user_ip4 ! 0x04030201) { // des ip is 1.2.3.4return SYS_PROCEED;}printk(aa %x , ctx-user_ip4);ctx-user_ip40x19280a0a; // set to 10.10.40.25printk(set ok %x,%x, ctx-user_ip4, ctx-user_port);return SYS_PROCEED;
}BPF_LICENSE(Dual BSD/GPL); 程序说明 判断目标ip是1.2.3.4才处理对应16进制顺序相反是因为系统为小端模式。 输出目的ip方便debug。 修改目的ip为指定的ip。 输出设置的结果。 入参bpf_sock_addr可从cilium的源码中找到相关定义。
mysock.c
/* User bpf_sock_addr struct to access socket fields and sockaddr struct passed* by user and intended to be used by socket (e.g. to bind to, depends on* attach type).*/
struct bpf_sock_addr {__u32 user_family; /* Allows 4-byte read, but no write. */__u32 user_ip4; /* Allows 1,2,4-byte read and 4-byte write.* Stored in network byte order.*/__u32 user_ip6[4]; /* Allows 1,2,4,8-byte read and 4,8-byte write.* Stored in network byte order.*/__u32 user_port; /* Allows 1,2,4-byte read and 4-byte write.* Stored in network byte order*/__u32 family; /* Allows 4-byte read, but no write */__u32 type; /* Allows 4-byte read, but no write */__u32 protocol; /* Allows 4-byte read, but no write */__u32 msg_src_ip4; /* Allows 1,2,4-byte read and 4-byte write.* Stored in network byte order.*/__u32 msg_src_ip6[4]; /* Allows 1,2,4,8-byte read and 4,8-byte write.* Stored in network byte order.*/__bpf_md_ptr(struct bpf_sock *, sk);
}; 2. 程序加载
基于k8s部署cilium后cilium会在容器中初始化好环境我们可以直接使用省去编译环境、cgroupv2配置的麻烦。
将上面的文件复制到cilium的容器中本样例中使用的cilium版本为1.12.7)。
file./mysock.cclang -O2 -target bpf -stdgnu89 -nostdinc -emit-llvm -g -Wall -Wextra -Werror -Wshadow -Wno-address-of-packed-member -Wno-unknown-warning-option -Wno-gnu-variable-sized-type-not-at-end -Wdeclaration-after-statement -Wimplicit-int-conversion -Wenum-conversion -I. -I/run/cilium/state/globals -I/var/lib/cilium/bpf -I/var/lib/cilium/bpf/include -D__NR_CPUS__8 -DENABLE_ARP_RESPONDER1 -DCALLS_MAPcilium_calls_lb -c $file -o - | llc -marchbpf -mcpuv2 -mattrdwarfris -filetypeobj -o mysock.obpftool cgroup detach /run/cilium/cgroupv2 connect4 pinned /sys/fs/bpf/tc/globals/mytest
rm -f /sys/fs/bpf/tc/globals/mytesttc exec bpf pin /sys/fs/bpf/tc/globals/mytest obj mysock.o type sockaddr attach_type connect4 sec cgroup/connect4
bpftool cgroup attach /run/cilium/cgroupv2 connect4 pinned /sys/fs/bpf/tc/globals/mytest3.测试
开启四个终端分别执行如下命令直接在主机上执行
# command 1
cat /sys/kernel/debug/tracing/trace_pipe
# command 2
tcpconnect -P 80
# command 3
tcplife -D 80
# command 4
curl 1.2.3.4:80 因为我们会变更目的ip,所以就基于端口来抓包。 用tcplife抓包抓的是上面perf的sock:inet_sock_set_state时的状态。 用tcpconnect抓的是connect() syscall时的状态。 4. 自己搭建ebpf环境
1. 挂载cgroup2 mkdir -p /run/cilium/cgroupv2
mount -t cgroup2 none /run/cilium/cgroupv2/ 2. 加载ebp程序
因为centos8自带的tc与bpftool版本有点低所以使用cilium中已经适配好的版本。
docker run -it --namemytest --networkhost --privileged -v $PWD:/hosts/ -v /sys/fs/bpf:/sys/fs/bpf -v /run/cilium/cgroupv2/:/run/cilium/cgroupv2 cilium:v1.12.7 bashcd /hosts/
# 可以直接用之前编译好的文件
tc exec bpf pin /sys/fs/bpf/tc/globals/mytest obj mysock.o type sockaddr attach_type connect4 sec cgroup/connect4
bpftool cgroup attach /run/cilium/cgroupv2 connect4 pinned /sys/fs/bpf/tc/globals/mytest 很香你会发现功能已经实现了。 5. cilium逻辑讲解 框架已定型通过在ebpf中获取目的ip与目的端口然后基于映射规则将目的ip与端口进行修改从而实现vip到目的地址的转换。 由于它是在connect阶段做的转换类似在调用connect函数时注册一个回调函数和dnat是不同的所以不需要在回包时转换还原。
cilium service list 这个命令可以查看cilium基于service配置的映射规则ebpf程序再从这个规则中找到合适的bacend并修改目的地址然后完成转换。 6.展望
1. 这个功能可以做什么
服务暴露关心的主要是两点1. vip的高可用。2.负载均衡。而这两点通过本文所介绍的方式都是可以实现的。
1.vip的高可用 vip的高可用其本质就是在服务异常时可以切换到服务b这里暂不考虑有状态服务分主备的情况。 当我们在客户端运行ebpf程序时就不需要这个vip了。在应用时可以配置一个虚拟的地址比如1.2.3.4由ebpf程序来决定转换到哪个实际的后端服务。而且当服务a异常后可以变更映射规则切换到服务b。这一切对应用都是透明的。
2.负载均衡 既然可以将目的地址映射到服务a那么基于服务a,b,c之间做负载均衡也是可行的。包括设置权重、熔断等。 如istio需要在客户端注入sidecar运行envoy程序其实也是相类似的逻辑只不过它是通过代理实现。除了解析目的地址外它还支持解析数据包比如解析http协议在异常时自动重试实现服务切换应用无感知。相对于应用来说只是卡顿了一下。 ebpf程序能够满足大部分场景而且很高效。 作者
沃趣科技产品研发部