前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Deepflow Agent代码阅读杂记

Deepflow Agent代码阅读杂记

原创
作者头像
DifficultWork
发布2024-06-28 10:00:10
1220
发布2024-06-28 10:00:10
举报
文章被收录于专栏:阶梯计划阶梯计划

本文章是前端时间读代码时的随手记录,没有做系统整理,估计也不会填坑了,大家随便看看就好。

1 主体结构

1.1 构建思路:

用户态代码:rust+c,rust使用FFI(Foreign Function Interface)调用c,过程中使用 libc crate,它包含了 C 标准库中的类型别名和函数定义,编译时会静态连接libc。

1.2 构建结构:

build.rs

代码语言:make
复制
main()
|-- set_build_info()         这里还会检查git仓的信息
    set_build_libtrace()     这里会执行src/ebpf下的make
    set_linkage()            打印出链接信息

src/ebpf

代码语言:make
复制
|-- kernel    内核态ebpf程序
|-- user      用户态程序c实现
|-- mod.rs    rust FFI对user封装的mod

src/ebpf/Makefile

代码语言:txt
复制
定义了函数compile_socket_trace_elf:构建socket_trace.elf,使用bintobuffer把字节码转成buffer放到一个.c文件(bintobuffer是这个项目自带的一个工具)
定义了函数compile_perf_profiler_elf:构建perf_profiler.elf,使用bintobuffer把字节码转成buffer放到一个.c文件
build:先编译内核态生成.elf,然后再编译用户态生成.a(.a会被rust静态链接)

src/ebpf/kernel/Makefile

代码语言:txt
复制
将socket_trace和perf_profiler编译成.elf(elf文件收敛到两个,其他.c被include到这两个文件里)
将编译后的文件反编译成.objdump
剥离掉对象文件中的调试信息

1.3 初始化流程

src/main.rs

代码语言:rust
复制
main()
|-- src/trident.rs
     Trident::start()
     -> Self::run()
     -> Components::new()
     -> AgentComponents::new()
          |-- src/ebpf_dispatcher/ebpf_dispatcher.rs
               EbpfCollector::new()
               -> Self::ebpf_init()
                    |-- src/ebpf/mod.rs
                         bpf_tracer_init()

2 grpc接口

2.1 接口目录

  • proto的目录在和agent同级的message目录下
  • 生成的接口文件在agent/crates/public/src/proto下(其中telemetry是submodule,代码仓要git clone)
  • 使用tonic进行build,build文件为agent/crates/public/build.rs

2.2 trident.proto

rpc

  • Sync:向Server同步所在主机信息,Server会返回本机的容器列表
  • Push:向Server同步所在主机信息,Server会持续应答(Stream,猜测用于实时变化)
  • AnalyzerSync:和Sync的消息一样
  • Upgrade:应答是Stream,返回一堆二进制报文
  • Query:时钟同步
  • GenesisSync:待研究
  • KubernetesAPISync:k8 API信息,盲猜agent没用
  • PrometheusAPISync:同上

3 用户态

3.1 src/ebpf/kernel/include/socket_trace_common.h

  • pid这里是线程号,tgid是进程号,二者一致表示是单线程;
  • coroutine_id是go的协程号

3.2 src/ebpf/kernel/socket_trace.c

  • 这里定义了一个PERF_EVENT的map:socket_data,给用户态传数据
  • 两个尾调跳转PROG_ARRAY的map:progs_jmp_kp_map(kprobe/uprobe),progs_jmp_tp_map(for tracepoint)
  • Tracepoint 则更像是静态的,已经存在于内核中的 hook 点,不够灵活,但是相对固定,在不同版本的操作系统中变化不大,开销也更小;
  • k retprobe 是动态追踪,我们可以在内核函数的开头和结尾进行追踪,相对更灵活;但是其开销也更大。
  • infer_tcp_seq_offset:这个方法获取tcp_sock的copied_seq_offset,这里实现和操作系统有耦合,tcp_seq_offset对于不支持的btf的系统是通过推断得到的,对于支持btf的内核直接从btf文件读取得到的bpf_skc_to_tcp_sock() 这个辅助函数从5.9内核引入。
代码语言:c
复制
PROGTP(io_event)(void *ctx)
= SEC("prog/tp/io_event") int bpf_prog_tp__io_event(void *ctx)
{
     获取tgid,pid
    查询active_read_args_map
    如果被标记跟踪:
    {
        trace_io_event_common(ctx, data_args, T_INGRESS, id)
        {
            从trace_conf_map获取一下配置
            从trace_map中获取trace_info
            从io_event_buffer(map)中获取内存块读取buffer内容
            从data_buf(map)中获取存放socket_buffer的内存块v_buff(这个v_buff可能存多个__socket_data)
            将socket_buffer放到v_buff后面
            bpf_get_current_comm(用当前进程名填充socket_buffer的comm)
            设置尾调上下文socket_buffer->data
            触发尾调progs_jmp_tp_map(具体尾调函数看用户态代码)
        }
        删除跟踪
        return // 读写跟踪只能开启一个
    }
    查询active_write_args_map
    如果被标记跟踪:
    {
        trace_io_event_common(ctx, data_args, T_EGRESS, id)
        删除跟踪
        return // 读写跟踪只能开启一个
    }
}

4 probe挂载

4.1 src/ebpf_dispatcher/ebpf_dispatcher.rs

代码语言:rust
复制
ebpf_init
|-- src/ebpf/mod.rs
     running_socket_tracer
     |-- src/ebpf/user/socket.c
          running_socket_tracer
          -> process_events_handle_main
          -> process_probes_act
          |-- src/ebpf/user/trace.c
               tracer_hooks_attach/tracer_hooks_dettach
               -> tracer_hooks_process
               -> probe_attach
               -> exec_attach_kprobe/exec_attach_uprobe
               |-- src/ebpf/user/probe.c
                    program__attach_kprobe/program__attach_uprobe
                    -> program__attach_probe
                    -> bpf_attach_kprobe/bpf_attach_uprobe(bcc)

4.2 src/ebpf/user/socket.c

代码语言:c
复制
running_socket_tracer
bpf_bin_buffer指向ebpf字节码
buffer_sz字节码长度
|-- src/ebpf/user/trace.c
     tracer_bpf_load
     |-- src/ebpf/user/load.c
          ebpf_open_buffer
          |-- src/ebpf/user/elf.c
               elf_info_collect
          --> set_obj__version
          --> set_obj__license
          --> ebpf_obj__maps_collect
          --> ebpf_btf_collect
          --> ebpf_btf_ext_collect
          ebpf_obj_load
          --> bcc_create_map(bcc)
          |-- src/ebpf/user/btf_vmlinux.c
               ebpf_obj__load_vmlinux_btf
          --> load_obj__progs

4.3 用户编写的 eBPF 程序最终会被编译成 eBPF 字节码

4.3.1 eBPF 字节码使用 bpf_insn 结构来表示,如下:

代码语言:c
复制
struct bpf_insn {
    __u8    code;       // 操作码
    __u8    dst_reg:4;  // 目标寄存器
    __u8    src_reg:4;  // 源寄存器
    __s16   off;        // 偏移量
    __s32   imm;        // 立即操作数
};

4.3.2 bpf_insn 结构各个字段的作用:

  1. code:指令操作码,如 mov、add 等。
  2. dst_reg:目标寄存器,用于指定要操作哪个寄存器。
  3. src_reg:源寄存器,用于指定数据来源于哪个寄存器。
  4. off:偏移量,用于指定某个结构体的成员。
  5. imm:立即操作数,当数据是一个常数时,直接在这里指定。 eBPF 程序会被 LLVM/Clang 编译成 bpf_insn 结构数组,当内核要执行 eBPF 字节码时,会调用 __bpf_prog_run() 函数来执行。 如果开启了 JIT(即时编译技术),内核会将 eBPF 字节码编译成本地机器码(Native Code)。这样就可以直接执行,而不需要虚拟机来执行。

4.4 agent是怎样拿到本机的socket句柄信息的?

socket 句柄是通过 hook 系统调用获取 (得到socket fd),socket相关信息是通过 get_socket_from_fd() 得到 socket address,相关 socket 信息(比如 元组信息,tcpseq number 等)是通过 socket 内核结构读取的.

5 tracepoint

syscalls::sys_enter_write

代码语言:c
复制
TPPROG(sys_enter_write)
先把系统调用写到map里
代码语言:c
复制
TPPROG(sys_exit_write)
从map里读出来处理
--> process_syscall_data
--> process_data
      |-- src/ebpf/kernel/include/task_struct_utils.h
           get_socket_from_fd 在这里判断是不是socket

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 主体结构
    • 1.1 构建思路:
      • 1.2 构建结构:
        • 1.3 初始化流程
        • 2 grpc接口
          • 2.1 接口目录
            • 2.2 trident.proto
            • 3 用户态
              • 3.1 src/ebpf/kernel/include/socket_trace_common.h
                • 3.2 src/ebpf/kernel/socket_trace.c
                • 4 probe挂载
                  • 4.1 src/ebpf_dispatcher/ebpf_dispatcher.rs
                    • 4.2 src/ebpf/user/socket.c
                      • 4.3 用户编写的 eBPF 程序最终会被编译成 eBPF 字节码
                        • 4.3.1 eBPF 字节码使用 bpf_insn 结构来表示,如下:
                        • 4.3.2 bpf_insn 结构各个字段的作用:
                      • 4.4 agent是怎样拿到本机的socket句柄信息的?
                      • 5 tracepoint
                      相关产品与服务
                      容器服务
                      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档