cilium原理之ebpf尾调用与trace

背景

在深入剖析cilium原理之前,有两个关于epbf的基础内容需要先详细介绍一下:

1. ebpf尾调用

尾调用类似于程序之间的相互跳转,但它的功能更加强大。

2. trace

虽然之前使用trace_printk输出日志,但这个函数不能多用,会有性能问题。而且它的输出可读性差,不利于程序进行分析。本文将着重讲讲如何进行分析,方便程序后续跟踪。

1.ebpf尾调用介绍

尾调用能够从一个程序调到另一个程序,提供了在运行时(runtime)原子地改变程序行为的灵活性。为了选择要跳转到哪个程序,尾调用使用了程序数组map( BPF_MAP_TYPE_PROG_ARRAY),将map及其索引(index)传递给将要跳转到的程序。跳转动作一旦完成,就没有办法返回到原来的程序;但如果给定的map索引中没有程序(无法跳转),执行会继续在原来的程序中执行。

特殊在于,由于函数的调用是由map来保存的,可以使用TC更新map对应的函数为新的函数,实现ebpf程序的局部更新。当然,这就对功能编码也会有更高的要求,需要明确更新可能的影响范围。因为ebpf的单个程序大小是有限制的,而尾调用可以绕开这种限制。

以下是摘自cilium官方文档中的样例:(官方样例无法编译,下面摘取的内容经过作者调整)

#include <bits/types.h>
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/pkt_cls.h>
#include <linux/tcp.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

typedef __uint32_t uint32_t;

#define PIN_GLOBAL_NS           2

struct bpf_elf_map {
        __u32 type;
        __u32 size_key;
        __u32 size_value;
        __u32 max_elem;
        __u32 flags;
        __u32 id;
        __u32 pinning;
};

#ifndef __stringify
# define __stringify(X)   #X
#endif

#ifndef __section
# define __section(NAME)                  \
   __attribute__((section(NAME), used))
#endif

#ifndef __section_tail
# define __section_tail(ID, KEY)          \
   __section(__stringify(ID) "/" __stringify(KEY))
#endif

#ifndef BPF_FUNC
# define BPF_FUNC(NAME, ...)              \
   (*NAME)(__VA_ARGS__) = (void *)BPF_FUNC_##NAME
#endif

#define BPF_JMP_MAP_ID   1

static int (*bpf_trace_printk)(const char *fmt, int fmt_size,
                               ...) = (void *)BPF_FUNC_trace_printk;

#define printk(fmt, ...)                                                 \
  do {                                                                         \
    char _fmt[] = fmt;                                                         \
    bpf_trace_printk(_fmt, sizeof(_fmt), ##__VA_ARGS__);                       \
  } while (0)

static void BPF_FUNC(tail_call, struct __sk_buff *skb, void *map,
                     uint32_t index);

struct bpf_elf_map jmp_map __section("maps") = {
    .type           = BPF_MAP_TYPE_PROG_ARRAY,
    .id             = BPF_JMP_MAP_ID,
    .size_key       = sizeof(uint32_t),
    .size_value     = sizeof(uint32_t),
    .pinning        = PIN_GLOBAL_NS,
    .max_elem       = 1,
};

__section_tail(BPF_JMP_MAP_ID, 0)
int looper(struct __sk_buff *skb)
{
    printk("skb cb: %u\n", skb->cb[0]++);
    tail_call(skb, &jmp_map, 0);
    printk("skb end looper: cb: %u\n", skb->cb[0]);
    return TC_ACT_OK;
}

__section("prog")
int entry(struct __sk_buff *skb)
{
    skb->cb[0] = 0;
    tail_call(skb, &jmp_map, 0);
    printk("skb end: cb: %u\n", skb->cb[0]);
    return TC_ACT_OK;
}


char __license[] __section("license") = "GPL";

官方代码很清晰,需要注意的是:定义函数时,__section_tail(BPF_JMP_MAP_ID, 0)使用的是BPF_JMP_MAP_ID;而在bpf代码中,调用的时候使用的是tail_call(skb, &jmp_map, 0);中的jmp_map。而这两者之间的关系,则是在定义jmp_map时指定。

功能测试演示


# 将上面文件保存为tail.c
# build
clang -g -c -O2 -target bpf -c tail.c -o tail.o

# load
tc qdisc add dev enp1s0 ingress
tc filter replace dev enp1s0 ingress prio 1 handle 1 bpf da obj tail.o sec prog verbose

# cat log
cat /sys/kernel/debug/tracing/trace_pipe

虽然上面的程序是死循环,但测试后发现,程序不是真的死循环,会在looper中执行34次后结束。验证函数热更新功能。


          <idle>-0     [007] ..s. 443032.404122: 0: skb cb: 29
          <idle>-0     [007] ..s. 443032.404122: 0: skb cb: 30
          <idle>-0     [007] ..s. 443032.404123: 0: skb cb: 31
          <idle>-0     [007] ..s. 443032.404123: 0: skb cb: 32
          <idle>-0     [007] ..s. 443032.404123: 0: skb cb: 33
          <idle>-0     [007] ..s. 443032.404124: 0: skb end looper: cb: 34
          <idle>-0     [007] ..s. 443032.404146: 0: skb cb: 0
          <idle>-0     [007] ..s. 443032.404148: 0: skb cb: 1
          <idle>-0     [007] ..s. 443032.404149: 0: skb cb: 2
          <idle>-0     [007] ..s. 443032.404149: 0: skb cb: 3
          <idle>-0     [007] ..s. 443032.404150: 0: skb cb: 4

在之前的源代码中,添加如下函数:


__section("newloop")
int newlooper(struct __sk_buff *skb)
{
    printk("skb end in new looper: cb: %u\n", skb->cb[0]);
    return TC_ACT_OK;
}

# build
clang -g -c -O2 -target bpf -c tail.c -o tail.o

# update func
tc exec bpf graft m:globals/jmp_map key 0 obj tail.o sec newloop
# 特别注意:由于centos8自带的tc版本太低,无法更新,需要使用高版本的tc.可使用cilium自带的tc
# docker run -it --name=mytest --network=host --privileged -v $PWD:/hosts/ -v /sys/fs/bpf:/sys/fs/bpf -v /run/cilium/cgroupv2/:/run/cilium/cgroupv2 cilium:v1.12.7 bash 进入容器后,执行上面的命令

# 结果检查
cat /sys/kernel/debug/tracing/trace_pipe

2. send_trace_notify 跟踪

send_trace_notify –> ctx_event_output –> skb_event_outputevent_output


// 调用样例
send_trace_notify(ctx, TRACE_FROM_LXC, SECLABEL, 0, 0, 0,
        TRACE_REASON_UNKNOWN, TRACE_PAYLOAD_LEN);

// 函数定义
send_trace_notify(struct __ctx_buff *ctx, enum trace_point obs_point,
      __u32 src, __u32 dst, __u16 dst_id, __u32 ifindex,
      enum trace_reason reason, __u32 monitor)

msg = (typeof(msg)) {
    __notify_common_hdr(CILIUM_NOTIFY_TRACE, obs_point),  // 事件大类:CILIUM_NOTIFY_TRACE,子类:obs_point
    __notify_pktcap_hdr(ctx_len, (__u16)cap_len),
    .src_label  = src,   // 源对象id  cilium identity list, identity 全局唯一
    .dst_label  = dst,   // 目的对象id
    .dst_id    = dst_id, // 不同的子类,id取值不同
    .reason    = reason,  // 原因标识
    .ifindex  = ifindex, // 不同的子类,取值类型不同
  };

ctx_event_output(ctx, &EVENTS_MAP,
       (cap_len << 32) | BPF_F_CURRENT_CPU,
       &msg, sizeof(msg));

上报的信息,就是harbor展示的数据来源。

信息说明    

  1. 发送的信息,会放在ring buffer中。如果未读取,会进行覆盖写入。

  2. 这个buffer是cpu级别的,每个cpu中都会有一个独立的。

  3. 读取时,需要指定读取哪个cpu,-1表示读取所有cpu中的信息。

  4. 读取event的具体细节,可参考:

    https://man7.org/linux/manpages/man2/perf_event_open.2.html

下面为cilium使用go实现了perf读取库的使用说明:"github.com/cilium/ebpf/perf"

cilium目前的信息结构说明:

cilium monitor可以读取perf信息,并且能基于identity进行过滤,具体使用可参考该命令说明。


var err error

path := oldBPF.MapPath(signalmap.MapName)
signalMap, err := ebpf.LoadPinnedMap(path, nil)
if err != nil {
  log.WithError(err).Warningf("Failed to open signals map")
  return
}
events, err = perf.NewReader(signalMap, os.Getpagesize())
if err != nil {
  log.WithError(err).Warningf("Cannot open %s map! Ignoring signals!",
    signalmap.MapName)
  return
}

go func() {
  log.Info("Datapath signal listener running")
  for {
    record, err := events.Read()
    switch {
    case err != nil:
      signalCollectMetrics(nil, "error")
      log.WithError(err).WithFields(logrus.Fields{
        logfields.BPFMapName: signalmap.MapName,
      }).Errorf("failed to read event")
    case record.LostSamples > 0:
      signalCollectMetrics(nil, "lost")
    default:
      signalReceive(&record)
    }
  }
}()

消息结构如下:

总体分两大类:

  1. lost count,用于记数:数据包丢弃数。

  2. 详细报文说明。

各种sample数据格式说明。

cilium monitor使用样例。


# 使用cilium monitor查看网络策略执行情况:
# remoteID 表示针对对象
# ingress/egress表示入与出流量
# action表示策略结果
cilium monitor -t policy-verdict 
Listening for events on 8 CPUs with 64x4096 of shared memory
Press Ctrl-C to quit
level=info msg="Initializing dissection cache..." subsys=monitor
Policy verdict log: flow 0x95e0951 local EP ID 2467, remote ID kube-apiserver, proto 6, ingress, action allow, auth: disabled, match L3-L4, 172.18.0.6:46866 -> 172.18.0.8:2380 tcp SYN
Policy verdict log: flow 0x580738e1 local EP ID 2467, remote ID 16777218, proto 6, ingress, action allow, auth: disabled, match L3-L4, 172.18.0.2:40122 -> 172.18.0.8:6443 tcp SYN
Policy verdict log: flow 0x4dfe98a0 local EP ID 2467, remote ID kube-apiserver, proto 6, ingress, action allow, auth: disabled, match L3-L4, 172.18.0.4:52314 -> 172.18.0.8:2380 tcp SYN
Policy verdict log: flow 0xb3af0a31 local EP ID 2467, remote ID kube-apiserver, proto 6, egress, action allow, auth: disabled, match L3-L4, 172.18.0.8:42736 -> 172.18.0.6:2380 tcp SYN
Policy verdict log: flow 0xfdb19557 local EP ID 2467, remote ID kube-apiserver, proto 6, egress, action allow, auth: disabled, match L3-L4, 172.18.0.8:46246 -> 172.18.0.4:2380 tcp SYN
Policy verdict log: flow 0x5438a30a local EP ID 2467, remote ID 16777218, proto 6, ingress, action allow, auth: disabled, match L3-L4, 172.18.0.2:40164 -> 172.18.0.8:6443 tcp SYN

endpoint id,可通过这个方式查到。

展望

跟踪只允许基于endpointID进行跟踪,无法基于ip、端口等信息,当网络流量在多个节点中流转时,无法有效的进行跟踪。

这一块可基于数据分析组件来实现,它可以导出json结构数据,采集统一汇总分析统计。

1.访问入口与目标pod在同节点上,ip nat情况:

2.访问入口与目标pod在同节点上,流量跟踪情况:

可以看到,源端口在网络流转中是不变的,可以基于此作为跟踪线索。

可基于es+kibana,将cilium trace相关数据统一在es中存储,并基于kibana展示,如下图所示:

红色框分别为源ip、目标ip、源端口、目标端口。而特殊的flb_host_ip是坐着在采集时添加的节点主机的ip,用来分析不同主机间的流量。

作者:沃趣科技产品研发部

更多技术干货请关注公号【云原生数据库

squids.cn,云数据库RDS,迁移工具DBMotion,云备份DBTwin等数据库生态工具。

irds.cn,多数据库管理平台(私有云)。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据仓库-数据治理小厂实践

一、简介 数据治理贯穿数仓中数据的整个生命周期&#xff0c;从数据的产生、加载、清洗、计算&#xff0c;再到数据展示、应用&#xff0c;每个阶段都需要对数据进行治理&#xff0c;像有些比较大的企业都是有自己的数据治理平台或者会开发一些便捷的平台&#xff0c;对于没有平…

从DevOps状态报告看技术团队的文化建设

本文源自一次内部分享&#xff0c;借由此机会又把历年的DevOps状态报告翻看了一遍&#xff0c;其实大多数时候我们对于DevOps的理解都在于流程&#xff0c;工具&#xff0c;实践这些看得见摸得着的东西&#xff0c;但就像文末的几点思考所说的那样&#xff0c;我们一直相信技术…

轴承故障诊断分类模型全家桶-最全教程

Python轴承故障诊断 (一)短时傅里叶变换STFT-CSDN博客 Python轴承故障诊断 (二)连续小波变换CWT-CSDN博客 Python轴承故障诊断 (三)经验模态分解EMD-CSDN博客 Pytorch-LSTM轴承故障一维信号分类(一)-CSDN博客 Pytorch-CNN轴承故障一维信号分类(二)-CSDN博客 Pytorch-Trans…

VM Group

在复杂方案中模块过多可能造成查看或修改方案时存在视觉混乱&#xff0c;不够直观。此时可利用Group模块进行模块整合&#xff0c;同时Group模式也兼容循环的功能&#xff0c;如下图所示。 双击Group模块可进入Group内部&#xff0c;如下图所示。 在Group模块单击 可设置输入、…

Spring Cloud+SpringBoot b2b2c:Java商城实现一件代发设置及多商家直播带货商城 免 费 搭 建

【saas云平台】打造全行业全渠道全场景的saas产品&#xff0c;为经营场景提供一体化解决方案&#xff1b;门店经营区域化、网店经营一体化&#xff0c;本地化、全方位、一站式服务&#xff0c;为多门店提供统一运营解决方案&#xff1b;提供丰富多样的营销玩法覆盖所有经营场景…

北斗卫星时钟同步服务器对电力系统有多重要?

随着计算机和网络通信技术的飞速发展&#xff0c;火电厂热工自动化系统数字化、网络化的时代已经到来。一方面它为控制和信息系统之间的数据交换、分析和应用提供了更好的平台&#xff0c;另一方面对各种实时和历史数据时间标签的准确性提出了更高的要求。 通过卫星时钟来统一全…

Tomcat报404问题解决方案大全(包括tomcat可以正常运行但是报404)

文章目录 Tomcat报404问题解决方案大全(包括tomcat可以正常运行但是报404)1、正确的运行页面2、报错404问题分类解决2.1、Tomcat未配置环境变量2.2、IIs访问权限问题2.3、端口占用问题2.4、文件缺少问题解决办法&#xff1a; Tomcat报404问题解决方案大全(包括tomcat可以正常运…

uniapp如何原生app-云打包

首先第一步&#xff0c;需要大家在HBuilder X中找到一个项目&#xff0c;然后呢在找到上面的发行选项 发行->原生App-云打包 选择完该选中的直接大包就ok。 大包完毕后呢&#xff0c;会出现一个apk包&#xff0c;这是后将这个包拖动发给随便一个人就行了。 然后接收到的那…

【Java 并发】CyclicBarrier 介绍

1 简介 在多线程编程中, 协调和同步线程的执行是至关重要的。Java 提供了许多并发工具来帮助开发人员有效地管理多线程应用程序。 其中之一是 CyclicBarrier, 它是一个强大的同步辅助类, 可用于在多个线程之间创建同步点, 以便它们可以在同一时间点协调执行某个任务。 Cyclic…

Flink系列之:Checkpoints 与 Savepoints

Flink系列之&#xff1a;Checkpoints 与 Savepoints 一、概述二、功能和限制 一、概述 从概念上讲&#xff0c;Flink 的 savepoints 与 checkpoints 的不同之处类似于传统数据库系统中的备份与恢复日志之间的差异。 Checkpoints 的主要目的是为意外失败的作业提供恢复机制。 …

12、Qt:用QProcess类启动外部程序:简单使用

一、说明 简单使用&#xff1a;在一个函数中&#xff0c;使用QProcess类的临时对象调用可执行文件exe&#xff0c;只有这个exe执行完了&#xff0c;这个函数才往下执行&#xff0c;一次性打印出exe所有输出信息&#xff1b;复杂使用&#xff1a;创建QProcess类的全局对象&…

蛮力法之背包问题

问题: 有 n 个重量分别是 w1,w2....,wn 的物品&#xff08;物品编号为 1-n&#xff09;它们的价值分别为 v1,v2,...,vn 给定一个容量为 W 的背包。设计从这些物品中选取一部分放入该背包的方案。 每个物品要么选中要么不选中【其实每个物品只有 1 件】&#xff0c;要求选中…

CSS:盒子模型

CSS&#xff1a;盒子模型 盒子模型盒子模型的组成盒子内容边框 border内边距 padding盒子实际大小计算CSS3的盒子类型content-boxborder-box 外边距 margin外边距合并相邻块元素垂直外边距合并嵌套块元素垂直外边距塌陷 行内元素的内外边距 盒子相关属性圆角边框盒子阴影 盒子模…

python之导入.py文件

目录 1、文件结构 2、导入.py文件 2.1同一层内文件夹内的导入 2.2不同层内文件夹内的导入 1、文件结构 Paint_master是一个工程的根目录&#xff0c;忽略一些文件及文件夹后&#xff0c;其文件结构如下&#xff1a; src util ImageUtil.py view BaseAdjustDialog.py MainW…

字符串函数的模拟实现(部分字符串函数)

strlen函数模拟 size_t my_strlen(const char* arr) {int count 0;while(*arr){arr;count;}return count;} int main() { printf( " %zd", my_strlen("adsshadsa"));}//模拟实现strlen函数 strcpy函数模拟 char* my_strcpy(char* arr1, const char* ar…

Python算法例21 交错正负数

1. 问题描述 给出一个含有正整数和负整数的数组&#xff0c;将其重新排列成一个正负数交错的数组。 2. 问题示例 给出数组[-1&#xff0c;-2&#xff0c;-3&#xff0c;4&#xff0c;5&#xff0c;6]&#xff0c;重新排序之后&#xff0c;变成[-1&#xff0c;5&#xff0c;-…

Web前端-JavaScript(对象)

文章目录 1.对象1.1 概念1.2 创建对象三种方式**对象字面量创建对象**&#xff1a;new Object创建对象构造函数创建对象 1.3 遍历对象 2.作用域1.1 概述1.2 全局作用域1.3 局部作用域1.4 JS没有块级作用域1.5 变量的作用域1.6 作用域链1.7 预解析 1.对象 1.1 概念 什么是对象 …

Ubuntu 磁盘管理DF命令用法

Linux磁盘空间管理是系统运维中的核心环节&#xff0c;它直接影响到系统的稳定运行、数据的安全性和业务的连续性。 通过实施有效的磁盘空间管理策略&#xff0c;系统管理员可以确保系统的高效运作&#xff0c;满足不断变化的业务需求&#xff0c;并为用户提供可靠的服务。 因此…

【YOLOv8新玩法】姿态评估解锁找圆心位置

前言 Hello大家好&#xff0c;今天给大家分享一下如何基于深度学习模型训练实现圆检测与圆心位置预测&#xff0c;主要是通过对YOLOv8姿态评估模型在自定义的数据集上训练&#xff0c;生成一个自定义的圆检测与圆心定位预测模型 制作数据集 本人从网络上随便找到了个工业工件…

自动标注软件AnyLabeling安装

AnyLabeling自动标注软件介绍 该工具作为一个具有Segment Anything和YOLO模型的智能标签工具&#xff0c;可以快速、准确地对图像进行标注。 AnyLabeling LabelImg Labelme Improved UI Auto-labeling 在Python终端运行 pip install anylabeling启动AnyLabeling anylabe…
最新文章