实时捕捉与追溯:得物基于 eBPF 打造云上网络连接异常摄像头

news2024/11/15 13:37:13

近期我们容器 SRE 团队基于 eBPF 技术建设网络连接异常感知能力,灰度上线过程中发现了生产环境 10+ 以上的应用配置错误、程序 Bug 等问题。在和应用负责同学同步风险过程中,大家都挺好奇我们如何实现在对应用无侵入的情况下发现服务连接异常的。本篇文档将尝试从技术角度和大家聊聊我们是如何通过内核超能力 eBPF 为以摄像头式的实现应用连接异常感知和检测。

背景

在云上生产环境中,相信大家一定遇到过应用偶发访问中间件或下游服务异常的场景,这些异常究竟是云厂商虚拟网络环境的抖动还是应用自身连接设置不当,亦或网络策略变更所导致,我们总是不能快速便捷地做出初步判断,此类问题的排查多半需要具有丰富网络经验的 SRE 同学在主机上使用 tcpdump 抓包分析;但与此同时,网络异常问题多半具有偶发性,我们不仅需要在捉摸不定的场景下捕获到异常信息,同样也需要能够基于应用信息进行历史异常数据追溯,偶发性和可追溯性叠加,这无疑进一步增加了此类问题排查难度。

业内有句万金油的大法:一切问题都皆可归结于网络偶发抖动导致,至于到底是否是网络抖动导致的,云商一般也会是惜字如金“指标正常,没有异常”,这类问题往往最后被时间所淹没。

那么,是否可参考城市道路上架设摄像头的模式,即可实现网络异常实时查看又可进行历史异常追溯呢?经过一番调研,我们注意结合 Linux 内核观察和业内流行的 eBPF 技术可实现网络质量摄像头的能力,本文尝试以网络连接异常场景建设网络摄像头能力进行展开,关键技术给与介绍和实践验证。

方案调研

经过业内相关技术调研,我们发现 Linux 内核的跟踪机制配合 eBPF 技术可实现灵活的网络事件采集和过滤能力,并且可实现多个网络事件的联合分析和汇聚,这个章节我将分别给与介绍。为简单起见,Linux 内核跟踪机制我仅以跟踪点机制进行介绍(实际场景中内核中还有动态跟踪机制 kprobe 更加灵活,但跨内核移植性略差)。

可观测基石: Linux 内核网络跟踪点

内核跟踪点(Tracepoint) 是内核开发人员提前在代码中预定义的探测点,开发者在这些跟踪点上注入自己定义的处理函数,以便在事件发生时进行数据采集。内核跟踪点的优势在于是内核开发者预先定义好的,位置和语义明确,使用时不需要修改内核代码。通过Linux 内核中的 tracefs 文件机制,使用者可以方便地启用或禁用特定的跟踪点,按需采集自己感兴趣的数据。Linux 内核跟踪点可在目录 /sys/kernel/debug/tracing/events/中按照类别查看

网络跟踪点介绍

网络相关的跟踪点主要位于在 /sys/kernel/debug/tracing/events/目录下的 net /sock/ tcp 等子目录,其中 tcp 目录下跟踪主要和 TCP 网络协议相关,sock 目录下为网络资源对象 socket 相关的跟踪点。如果本地安装了性能分析工具 perf ,则可以使用 perf list 'tcp:*' 'sock:inet*' 命令查看当前内核相关的 跟踪点(此处为 5.10 内核版本,不同内核版本跟踪点数量略有不同):

  sock:inet_sock_set_state                           [Tracepoint event]
  tcp:tcp_retransmit_skb                             [Tracepoint event]
  tcp:tcp_retransmit_synack                          [Tracepoint event]
  tcp:tcp_rcv_space_adjust                           [Tracepoint event]
  tcp:tcp_probe                                      [Tracepoint event]
  tcp:tcp_send_reset                                 [Tracepoint event]
  tcp:tcp_receive_reset                              [Tracepoint event]
  tcp:tcp_destroy_sock                               [Tracepoint event]

这里我们对常用的网络相关跟踪点功能给与简单介绍,结合这些跟踪点可实现不同场景下的网络质量监控和诊断:

网络跟踪点实践

Linux 内核中可基于 tracefs 机制直接控制跟踪点的启用和停止,方便我们可以快速进行测试和数据搜集,这里以 sock/inet_sock_set_state 跟踪点观察 Socket 对象状态变化为例演示:

echo "1" > /sys/kernel/debug/tracing/events/sock/inet_sock_set_state/enable

cat /sys/kernel/debug/tracing
etcd-3825    [006] ..s2.  2198.949362: inet_sock_set_state: family=AF_INET protocol=IPPROTO_TCP sport=46142 dport=32766 saddr=127.0.0.1 daddr=127.0.0.1 saddrv6=::ffff:127.0.0.1 daddrv6=::ffff:127.0.0.1 oldstate=TCP_ESTABLISHED newstate=TCP_CLOSE_WAIT
....

echo "0" > /sys/kernel/debug/tracing/events/sock/inet_sock_set_state/enable

上述样例输出中,我们可以看到进程 etcd 进程中的某个连接状态从 TCP_ESTABLISHED TCP_CLOSE_WAIT 的变化该跟踪点打印数据的格式,可在文件/sys/kernel/debug/tracing/events/sock/inet_sock_set_state/format 中查看。

内核可编程:Linux 内核超能力 eBPF

尽管我们在上述场景进行了验证,可以获取到文本数据,但是仍然存在较多难以解决的问题,比如我们需要基于特定条件过滤和多个跟踪点场景进行关联分析,基于 tracefs 的方式可能会面临采集信息低效,而且无法实现多更重点场景数据关联合并聚合分析场景。此时,我们就需要赋予内核编程能力的超能力 eBPF 技术来进行高效和灵活的采集和聚合分析能力。

eBPF 超能力

eBPF 是一项革命性的技术,起源于 Linux 内核,它可以在特权上下文中(如操作系统内核)运行沙盒程序,其可用于安全有效地扩展内核的功能,而无需通过更改内核源代码或加载内核模块的方式来实现。

eBPF 自 2014 年引入到内核以后,经历了快速的发展和完善,当前已经成为顶级的内核模块。应用场景也从最初的网络数据包过滤扩展到内核可观测、安全、网络和调度等多个领域。eBPF 可实现动态地编程内核以实现高效的网络、可观测性、追踪和安全性。eBPF 相对于内核,犹如 JavaScript 与 HTML 本身,eBPF 技术赋予了内核的可编程能力,带来了众多的创新可能。

就 eBPF 技术而言,其具有以下特性:

  • 高性能:eBPF 通过 JIT 编译以及运行在内核空间中,可以大幅提高内核中数据处理能力;

  • 安全:eBPF 程序经过严格的验证,不会导致内核崩溃,并且只能由特权用户进行修改;

  • 灵活:功能和用例的修改、增加即时加载到内核并生效,而不需要重启或者打补丁。

eBPF 技术原理

eBPF 技术原理简单而言就是在内核中基于事件运行用户自定义的程序,并且能通过提供的 map/perf 等机制提供用户空间与内核空间的双向数据交换。工作原理的核心流程如下图所示:

  1. 编译: 用户可基于场景编写特定用途的 eBPF 程序,借助于 LLVM(clang)套件将其编译为通用的 eBPF 字节码(有点类似 Java 字节码);

  2. 加载: 通过系统调用将编译后的字节码通过系统调用 bpf() 加载到内核对应的事件 Hook 点;

  3. 验证:内核会针对加载的 eBPF 字节码进行各种安全检查,确保程序运行时的安全性,避免对内核造成灾难;验证通过后,将 eBPF 字节码通过 JIT 优化为二进制指令(默认启用),并与特定的事件相关联;

  4. 触发运行:在内核对应的事件触发时,就会运行对应的 eBPF 程序,基于上下文数据进行处理或将处理后的数据发送至用户空间的程序进行进一步处理分析。

如果你想进一步了解 eBPF 技术,推荐进一步阅读BPF 技术概览文章

使用 eBPF 注意事项

用于 eBPF 是在内核事件发生时触发的运行的程序,其需要保障安全和高效:

  • 不能访问内核任务函数,不同的场景下只能访问特定场景下的上下文信息,比如网络类型的 eBPF 程序只能访问到网络相关上下文信息;

  • eBPF 程序运行的栈只有 512 个字节,能够运行的指令数量也最大只能为 100 万条指令。

上述限制都是为了保障在内核事件触发后不会影响内核安全和内核运行效率。在介绍完内核网络跟踪点和 eBPF 基础技术后,我们进入到整体方案设计阶段。

架构设计和实现

方案架构设计

在整体架构上,我们可将程序部署在 K8s 容器集群或单机 ECS 上。当 K8s 调度到单机或部署到单机上,需要运行用户空间程序 net-collector 和内核事件处理程序 bpf_collector,核心流程如下:

  1. 用户空间程序 net-collector 用于加载 BPF 程序至内核中;

  2. 当内核中有网络连接异常信息时,内核中的 bpf_collector 程序就会被触发;

  3. bpf_collector 程序将采集到数据发送到用户空间程序 net-collector

  4. 用户空间程序 net-collector 在进行数据过滤和聚合后将数据写入到本地日志记录文件中;

  5. 单机部署的日志采集 agent 将数据采集到日志平台;

  6. 最后,我们可通过可视化界面完成日志平台保存数据的消费处理和架构化展示,以应用维度提供异常展示。

 

部署在单机 eBPF 程序,就像核心路口的摄像头一样,可时时刻刻按照约定规则采集信息,我们既可以实时查看采集的信息,还可以通过日志平台进行回溯查看。

方案实现

以网络连接超时为例,我们尝试采集环境中所有新建连接的网络重传事件,这里有两个关键点:

  • 连接初始阶段,这时 TCP 状态位于 SYN_SENT 状态;

  • 在连接状态出现了数据包的重传,基于上述网络跟踪点章节介绍,我们适合选取跟踪点 tcp:tcp_retransmit_skb 附着编写的 eBPF 程序即可。

 

单机采集端的实现,我们需要编写两部分程序:

  1. 用户空间程序 net-colloctor:用于将 eBPF 程序加载到内核,并接受在内核事件触发 eBPF 程序传递的数据,进行采集、聚合和分析,并写入到日志文件记录中;

  2. 内核中运行的 eBPF 程序 bpf-collector:内核程序附着与 tcp:tcp_retransmit_skb 跟踪点,在网络连接重传事件发生时候,采集网络上下文信息,并将其发送至用户空间程序 net-colloctor 进行分析。

内核空间 eBPF 程序

内核中的 eBPF 程序给与简单说明,核心包括 3 个步骤:

SEC("tracepoint/tcp/tcp_retransmit_skb") // 与跟踪点对应的声明
int handle_tcp_retransmit_skb(struct trace_event_raw_tcp_event_sk_skb *ctx)
{
    struct event event = {};
    const struct sock *sk = ctx->skaddr;

    // 步骤 ①
    char state = 0;
    state = BPF_CORE_READ(sk, __sk_common.skc_state);  
    if (state != TCP_SYN_SENT)
    {
        return 0;
    }

    // 步骤 ②
    event.family = BPF_CORE_READ(sk, __sk_common.skc_family); 
    event.ts_us = bpf_ktime_get_ns() / 1000;
    event.type = RESTRANSMIT;

    event.sport = BPF_CORE_READ(ctx, sport);
    event.dport = BPF_CORE_READ(ctx, dport);

    switch (event.family)
    {
    case AF_INET:
       bpf_probe_read_kernel(&event.saddr, sizeof(ctx->saddr), BPF_CORE_READ(ctx, saddr));
       bpf_probe_read_kernel(&event.daddr, sizeof(ctx->daddr), BPF_CORE_READ(ctx, daddr));
       break;
    case AF_INET6:
       bpf_probe_read_kernel(&event.saddr, sizeof(ctx->saddr_v6), BPF_CORE_READ(ctx, saddr_v6));
       bpf_probe_read_kernel(&event.daddr, sizeof(ctx->daddr_v6), BPF_CORE_READ(ctx, daddr_v6));
       break;
    default:
    }

    // 步骤 ③
    bpf_perf_event_output(ctx, &dw_net_col_events, BPF_F_CURRENT_CPU, &event, sizeof(event));

    return 0;
}

上述代码中的核心步骤介绍如下:

步骤 1:获取到触发重传函数上下文中的 TCP Sock 对象的状态信息,这里我们仅采集 TCP_SYN_SENT 状态的,否则就直接返回;

步骤 2:基于内核跟踪点的 Famliy 信息来区分是 IPv4 或 IPv6,获取到必要的信息并填充到 event 对象,event 对象是用户空间程序与内核 BPF 程序公用的数据结构;

步骤 3:将内核中封装好的数据发送到用户空间。

这里需要注意的是,不同内核版本的跟踪点结构体可能会发生变化。为了同时适配多个内核版本,我们使用可以跨多个内核版本移植方案 CO-RE。CO-RE 全称为 Compile Once – Run Everywhere,中文翻译为一次编译、到处运行,当前社区已经提供在低版本支持 CO-RE 的实现,我们在 4.19 内核中由于没有默认支持 CO-RE,这需要进行一些特定的处理步骤。关于 CO-RE 详细介绍可参考这篇文档。

用户空间程序

用户空间的程序主要用于加载 BPF 程序到内核,并读取 BPF 程序运行过程中上报的数据。程序使用 go 开发,基于库 cilium/ebpf。简化代码如下所示:

func main() {
    flag.Parse()
    var btfSpec *btf.Spec
    var err error

    // 步骤 ①
    // Load pre-compiled programs and maps into the kernel.
    var opts ebpf.CollectionOptions
    opts.Programs.KernelTypes = btfSpec

    objs := bpfObjects{}
    if err := loadBpfObjects(&objs, &opts); err != nil {
       log.Fatalf("loading objects: %v", err)
    }
    defer objs.Close()
    
    // 步骤 ②
    tpRestranSkb, err := link.Tracepoint("tcp", "tcp_retransmit_skb", objs.HandleTcpRetransmitSkb, nil)
    if err != nil {
       log.Fatalf("opening tracepoint tcp/tcp_retransmit_skb %s", err)
    }
    defer tpRestranSkb.Close()
    
    // 步骤 ③
    rd, err := perf.NewReader(objs.DwNetColEvents, os.Getpagesize()*10)
    if err != nil {
       log.Fatalf("creating perf event reader: %s", err)
    }
    defer rd.Close()
    
    
    // 步骤 ④
    var event bpfEvent
    for {
       record, err := rd.Read()

       if err := binary.Read(bytes.NewBuffer(record.RawSample), 
                            binary.LittleEndian, &event); err != nil {
          continue
       }
        
       
       eventHandler.Handle(&event)
    }
}

上述代码已经经过简化,上述核心步骤介绍如下:

步骤 1:加载内核运行的 eBPF 程序到内存对象中,完成内存初始化并加载到内核;

步骤 2:将加载到内核的 eBPF 程序与我们定义的跟踪点关联,此处关联后当事件触发时就可运行 eBPF 程序;

步骤 3:打开与 eBPF 程序约定的通信 perf 通道,后续可以持续读取数据;

步骤 4:循环读取 perf 通道中的数据,然后调用 eventHandler.Handle(&event) 函数进行后续的各种分析和处理,这里主要是数据过滤、聚合分析和缓存等各种我们特定的业务处理。

容器集群部署

在单机上运行 BPF 程序需要特权模式 privileged: true 和挂载宿主机相关目录 /lib/modules/sys/kernel/debug。同时考虑在版本升级和变更的灰度,我们还需要将部署策略 updateStrategy 设置为type: OnDelete。 部署文件主要设置内容如下所示:

  template:
    spec:
      containers:
        - name: main
          image: repoin.shizhuang-inc.net/component/net_conn_collector:0.1
          imagePullPolicy: IfNotPresent
          securityContext:
            privileged: true
            capabilities:
              add:
                - SYS_ADMIN
          volumeMounts:
            - name: modules-host
              mountPath: /lib/modules
              readOnly: false
            - name: debug-fs-host
              mountPath: /sys/kernel/debug
              readOnly: false
      volumes:
        - name: modules-host
          hostPath:
            path: /lib/modules
        - name: debug-fs-host
          hostPath:
            path: /sys/kernel/debug
  updateStrategy:
    type: OnDelete

效果展示

当在部分 K8s 集群灰度上线后,我们可直观针对主机上网络连接异常情况基于主机和上下游分析。基于原始数据,我们与 CMDB 数据整合打通,通过图形化的方式展示异常链路,可快速定位到应用上下游,大幅提升了问题定位效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2038924.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

贷齐乐hpp+php特性注入

文章目录 运行过程waf第一层waf拦截第二层waf拦截 数据库查询语句注入思路注入 运行过程 foreach ($_REQUEST as $key > $value) {$_REQUEST[$key] dowith_sql($value);}$request_uri explode("?", $_SERVER[REQUEST_URI]);if (isset($request_uri[1])) {$rewr…

77.游戏分析工具计算数据偏移

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 内容参考于:易道云信息技术研究院 上一个内容:76.游戏分析工具读取内存存到对象数据分析器一 以它的代码为基础进行修改 COBJContext…

免费领取 | S-SDLC差距分析2024发布会,服务名额先到先得

​2024全新版S-SDLC研发安全能力成熟度模型即将发布!可帮助企业进一步引入行业最佳实践,弥补安全开发能力短板,快速提升软件安全开发整体水平。 8月15日,我们将开启直播,正式发布2024全新版S-SDLC研发安全能力成熟度模…

代码随想录算法训练营第十五天

力扣题部分: 110.平衡二叉树 题目链接:. - 力扣(LeetCode) 题面: 给定一个二叉树,判断它是否是平衡二叉树 平衡二叉树 是指该树所有节点的左右子树的深度相差不超过 1 思路(递归): 还是递归三部曲(关于三部曲的具体内容和对递归看法建议可见昨…

Vulnhub JIS-CTF靶机详解

项目地址 https://www.vulnhub.com/entry/jis-ctf-vulnupload,228/https://www.vulnhub.com/entry/jis-ctf-vulnupload,228/ 修改靶机的网卡 开机时长按shift,进入此页面 选择root模式进入 将只读模式改为读写模式 mount -o remount,rw / 查看本机的网卡名称 …

【数据分享】《青海省统计年鉴》2000-2023

而今天要限时免费分享的数据就是2000-2023年间出版的《青海省统计年鉴》并以多格式提供免费下载。(无需分享朋友圈即可获取) 数据介绍 青海省,位于中国西北部,青藏高原的东北部,是一个资源丰富、民族众多的地区。…

利用WebSocket实现来单提醒和用户催单

文章目录 概要整体架构流程技术细节小结 概要 来单提醒: 业务场景:在电商平台、外卖平台等应用场景中,当有新的订单产生时,系统需要及时通知相关人员处理新订单。 目的:确保订单能够得到快速响应,提高客户…

XSS盲打与cookie劫持

目录 【学习目标、重难点知识】 【学习目标】 【重难点知识】 XSS盲打(加载远程攻击payload) XSS偷cookie cookie收集 在线XSS收集平台的使用 1. BeeF框架的使用 BeeF简介 安装和使用 XSS 一些实战应用 1. XSS PDF 2. 公网投毒 3. 网站挂马…

WUP-CH34X ch34x系列芯片USB转串口通信uniapp插件使用说明

插件地址:WUP-CH34X 系列芯片USB转串口通信安卓库 简介 本文档是针对 CH340/CH341/CH342/CH343/CH344/CH347/CH9101/CH9102/CH9103/CH9104/CH9143的 USB 转串口安卓库的开发说明文档。 主要介绍如何使用芯片的 USB 转异步串口功能(以下简称 CH34XUART…

python 使用正则表达式判断图片路径是否是超链接

在Python中,判断一个给定的字符串(假设为图片路径)是否是网页链接(URL),你可以通过检查该字符串是否符合URL的基本格式来实现。虽然这个方法不能保证链接一定指向图片,但它能判断该字符串是否是…

La-Z-Boy EDI项目测试流程详解

在此前的文章《家居EDI:La-Z-Boy EDI 项目案例》中,为大家介绍了La-Z-Boy的EDI 需求以及在知行之桥EDI系统中的具体实现,本文主要为大家介绍La-Z-Boy EDI项目测试流程。 梳理需求文档 La-Z-Boy 提供的EDI资料主要包括:EDI 850订…

在IntelliJ IDEA中利用Git拉取项目

1 访问gitee或github,找到项目对应的仓库,并复制仓库地址。 2 打开IDEA,依次选择菜单:File->New->Project from Version Control 3 在弹出框中输入仓库路径(从第一步中gitee或github复制的路径)并点击Clone 4 在弹出框中输入gite…

Mybatis框架——使用案例详细教程

文章目录 一、项目创建1.1 创建 Idea 项目1.2 导入必要依赖 二、数据库配置2.1 创建数据库2.2 配置数据库连接信息 三、MyBatis 配置3.1 创建配置文件 mybatis-config.xml3.2 创建 SqlSessionFactory 实例 四、项目测试和运行4.1 创建实体类4.2 创建 mapper.xml 文件4.3 创建 m…

CentOS7安装Docker教程(含最新镜像地址)

文章目录 1 安装前必读2 安装Docker的详细步骤3 配置镜像加速 1 安装前必读 在安装 Docker 之前,先说一下配置,我这里是Centos7 Linux 内核:官方建议 3.10 以上,3.8以上貌似也可。 注意:本文的命令使用的是 root 用户…

UE基础 —— 工具和编辑器

目录 Level Editor Static Mesh Editor Material Editor Blueprint Editor Physics Asset Editor Behavior Tree Editor Niagara Editor UMG UI Editor Font Editor Sequencer Editor Animation Editor Control Rig Editor Sound Cue Editor Media Editor nDisp…

JUC-变量的线程安全

成员变量和静态变量是否线程安全? 如果它们没有共享,则线程安全,即没有被外部访问。 如果它们被共享了,根据它们的状态是否能够改变,又分两种情况 如果只有读操作,则线程安全 如果有读写操作,…

【图形学】TA之路-矩阵应用平移-旋转-大小

矩阵应用:在 Unity 中,Transform 和矩阵之间的关系非常密切。Transform 组件主要用于描述和控制一个物体在三维空间中的位置、旋转和缩放,而这些操作背后实际上都是通过矩阵来实现的 1. Transform 组件与矩阵的关系 Transform 组件包含以下…

java简单实现双链表代码

package com.se.day03.aGenericity.eDataStructrue;/*** 自定义一个双链表的数据结构。**/public class MyDoubleList<E> {//新创建容器时&#xff0c;头部和尾部元素都是null,size0;private Node head; //头部元素private Node tail; //尾部元素private int size; // …

特殊数组Z(前缀和)

前言&#xff1a;想了好一会才想到是前缀和来写&#xff0c;并且我一开始的是从考虑这个数和这个数后面一个数&#xff0c;导致边界烦了我好久 看了一下&#xff0c;考虑这个数和这个数前一个数更好 class Solution { public:vector<bool> isArraySpecial(vector<int…

按键按下,LED 点亮,但是,理论和现象不符

通过 Debug &#xff0c;解决了一个 Bug&#xff0c;很开心&#x1f604;&#xff0c;记录下 想实现的效果&#xff1a;按下 PB12 上的按钮&#xff0c;PA7 上的 LED 点亮&#xff1b;松开&#xff0c;LED 熄灭 单片机型号&#xff1a;STM32F103C8T6 PB12 为上拉电阻&#xf…