服务网格eBPF应用探索之(一)eBPF基础知识

简介: 1)技术背景在eBPF诞生之前,对内核的调试和开发有着相当高的门槛,不仅要十分熟悉庞大的内核代码及开发流程,同时重新编译内核后若希望生效还需要重启OS,开发效率也相当低下。而eBPF提供了相当友好的内核开发/观测机制,即:由用户编写符合一定规范的代码,编译后加载至内核,内核会在指定的时机执行这段代码,内核同时还会将Hook点相关的上下文传递给这段代码供使用,代码可以修改上下文,或是通过返回值来改变

1)技术背景

在eBPF诞生之前,对内核的调试和开发有着相当高的门槛,不仅要十分熟悉庞大的内核代码及开发流程,同时重新编译内核后若希望生效还需要重启OS,开发效率也相当低下。而eBPF提供了相当友好的内核开发/观测机制,即:由用户编写符合一定规范的代码,编译后加载至内核,内核会在指定的时机执行这段代码,内核同时还会将Hook点相关的上下文传递给这段代码供使用,代码可以修改上下文,或是通过返回值来改变内核接下来的行为。

毫无疑问这个机制是突破性的,丰富的挂载点使得我们几乎可以在内核的任意位置执行eBPF程序,这些程序可以输出内核状态或当前事件的上下文,甚至可以影响内核对该事件的处理,例如我们可以编写一个eBPF程序挂载至网络包传输的事件上,通过这段程序的逻辑来决定是否将该包丢弃,从而实现一个类似防火墙的功能。事实上,内核提供了大量的挂载点,算上kprobe挂载点,你几乎可以在内核代码的任意函数挂载一段eBPF程序。linux kernel从3.15开始支持BPF,到现在经历了大量迭代,这里有所有BPF相关特性与其开始支持的内核版本的完整列表。

2)eBPF程序的形态

一个完整eBPF程序通常由两部分组成,我称它们为eBPF程序用户态程序,eBPF程序是被内核在Hook点调用并执行的程序,这个比较容易理解,而用户态程序则通常扮演以下角色:

  • 加载eBPF程序到内核
  • 通过共享数据向内核eBPF程序传输数据或从内核eBPF程序读取数据( 除了通过用户态程序,你还可以通过 bpftool 来加载eBPF程序,参考 文档

你可以用多种语言(C/C++/Golang/Rust)开发用户态程序,但是,由于eBPF程序需要在内核中执行,所以类似Golang这种带有GC机制的语言则完全不可能被允许用于开发eBPF程序,目前eBPF程序只能够使用C和Rust开发。

3)eBPF程序类型

我们之前提到,eBPF程序可以在内核各处触发执行,然而不同的内核事件必然存在着完全不同的上下文,为此,eBPF提供了多种eBPF程序,你可以通过内核代码文件(这个是目前最全的)来看到内核提供的所有eBPF程序类型。例如BPF_PROG_TYPE_SOCKOPS是在Socket相关代码中触发的eBPF程序类型,BPF_PROG_TYPE_SK_MSG则是在sendmsg系统调用时触发的eBPF程序类型。

4)eBPF Helper functions

eBPF程序运行在内核中,无法也完全不需要像其他用户态应用程序一样调用系统调用来与OS交互,为了使得eBPF程序能够与操作系统或上下文进行交互,kernel提供了eBPF程序的“专属API集合”,它们就是BPF Helper functions。以下是文档介绍的节选。

These helpers are used by eBPF programs to interact with the system, or with the context in which they work. For instance, they can be used to print debugging messages, to get the time since the system was booted, to interact with eBPF maps, or to manipulate network packets. 

我们在上文提到过,eBPF程序有多种类型,每一种类型的上下文一定是不同的,那么为了与不同的上下文交互,每一种eBPF程序都只能调用其类型对应的一组eBPF Helper functions,以下是文档中对这部分的描述。

Since there are several eBPF program types, and that they do not run in the same context, each program type can only call a subset of those helpers.

5)eBPF Map

很多时候,我们希望在eBPF程序之间,或是eBPF程序用户态程序之间传递一些信息,例如eBPF程序收集内核事件相关数据,传递到用户态程序打印日志或进一步处理;或是eBPF程序之间共同协作时共享数据。在用户态编程中我们用到的通信手段在eBPF的场景下全部无法使用,于是,内核提供了eBPF Map作为原生的数据共享机制,文档中是这样描述的:

eBPF maps are a generic data structure for storage of different data types. Data types are generally treated as binary blobs, so a user just specifies the size of the key and the size of the value at map-creation time. In other words, a key/value for a given map can have an arbitrary structure.

eBPF Map为了满足不同的场景衍生出了多种多样的类型,如下图所示:

你可以在eBPF程序中定义一个Map,代码看起来大致是这样:

struct bpf_map SEC("maps") map = {
    .type = BPF_MAP_TYPE_HASH,
    .key_size = sizeof(int),
    .value_size = sizeof(int),
    .max_entries = 1,
}

除了指定key和value的大小,你还需要指定这个map最大可以存储多少条数据 -- 运行在内核的程序必须每个角落都明明白白,示例代码使用的TYPE是BPF_MAP_TYPE_HASH,这种类型的Map与传统意义上的Map一致,为Key/Value映射数据结构,虽然打着MAP的名头,实际上还有BPF_MAP_TYPE_ARRYA类型,若使用这种类型的话,数据结构则是一个数组。

如果希望对eBPF Map进行操作,则需要使用eBPF Map相关的一组eBPF Helper function,主要有以下三个,分别用于对Map查找、新增/修改、删除操作。

void *bpf_map_lookup_elem(struct bpf_map *map, const void *key);
long bpf_map_update_elem(struct bpf_map *map, const void *key, const void *value, u64 flags);
long bpf_map_delete_elem(struct bpf_map *map, const void *key);

并发问题

你可能注意到,用户态程序和eBPF程序或许是并发的,多个eBPF程序之间也是并发的,eBPF Map是线程安全的吗?在这一篇LWN文章中对该问题进行了讨论,限于篇幅,本文决定不对该问题展开讨论。

6)eBPF 验证器

David Miller - The only thing sitting between our eBPF programs and a deep dark chasm of destruction is the eBPF verifier.

eBPF程序的目的之一是降低定制内核的门槛,然而这使得大量并不具备内核开发技能的开发者拥有了编写运行与内核的代码的机会,为了保障内核不被挂起或是崩溃或是其他异常,eBPF的程序有着严格的要求,eBPF验证器负责在加载eBPF程序时对eBPF程序进行一系列检查,通过检查的eBPF程序才会被放行至JIT Compiler进行编译。

  • 程序不能有可能无法结束的循环( 循环在5.3内核中被允许,但要求必须能够结束)
  • 程序不能超过最大指令数量(参考 代码中的BPFMAXINSNS),在linux内核中这个值是4096
  • 代码中不允许存在unreachable code

事实上还有很多检查的细节,本文无法一一列举,但这个视频是一个了解eBPF verifier不错的资料。

7)总结

本文介绍了eBPF的基础知识,下一篇文章将开始介绍一个来自intel的开源项目istio-tcpip-bypass,这个项目使用eBPF对服务网格中本机通信的场景(Pod与Pod在同一Host上,或Pod与Sidecar通信)进行了优化,使得这部分通信可以绕过协议栈,以此提升了10-20%的性能。

目录
相关文章
|
2月前
|
Kubernetes Dubbo Cloud Native
如何将Dubbo应用接入服务网格
介绍使用传统Dubbo微服务体系的客户要如何将自己的服务接入到服务网格这一新一代云原生基础设施。
|
2月前
|
Cloud Native 测试技术 开发者
阿里云服务网格ASM多集群实践(二):高效按需的应用多环境部署与全链路灰度发布
介绍服务网格ASM提出的一种多集群部署下的多环境部署与全链路灰度发布解决方案。
|
4月前
|
负载均衡 监控 Go
Golang深入浅出之-Go语言中的服务网格(Service Mesh)原理与应用
【5月更文挑战第5天】服务网格是处理服务间通信的基础设施层,常由数据平面(代理,如Envoy)和控制平面(管理配置)组成。本文讨论了服务发现、负载均衡和追踪等常见问题及其解决方案,并展示了使用Go语言实现Envoy sidecar配置的例子,强调Go语言在构建服务网格中的优势。服务网格能提升微服务的管理和可观测性,正确应对问题能构建更健壮的分布式系统。
412 1
|
9月前
|
人工智能 Kubernetes TensorFlow
轻松搭建基于服务网格的 AI 应用,然后开始玩
轻松搭建基于服务网格的 AI 应用,然后开始玩
65257 26
|
11月前
|
监控 安全 大数据
阿里服务的ASM、MSE和ARMS都有其各自的应用场景
阿里服务的ASM、MSE和ARMS都有其各自的应用场景
320 39
|
11月前
|
监控 安全 测试技术
服务网格和CI/CD集成:讨论服务网格在持续集成和持续交付中的应用。
服务网格和CI/CD集成:讨论服务网格在持续集成和持续交付中的应用。
116 0
|
存储 Serverless 异构计算
使用ASM管理Knative服务(2):使用Knative on ASM部署Serverless应用
如何在阿里云服务网格ASM中开启Knative on ASM功能, 并结合ACK或者ASK部署管理Serverless应用服务。
631 0
使用ASM管理Knative服务(2):使用Knative on ASM部署Serverless应用
|
存储 Java Spring
从零开始造Spring04---补充之ASM的原理以及在Spring中的应用
ASM 是一个可以操作Java 字节码的框架。可以读取/修改class中的字节码。ASM可以直接产生二进制class文件,也可以在类被加载Java虚拟机之前动态改变类行为,Java class被存储在严格格式定义的.class文件里,这些文件拥有足够的元数据来解析勒种的所有元素:类名称, 方法,属性以及Java字节码(指令)。ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
508 0
从零开始造Spring04---补充之ASM的原理以及在Spring中的应用
|
弹性计算 人工智能 Kubernetes
基于英特尔®架构的阿里云服务网格ASM技术加速应用服务加密通信
微服务架构的发展带来了诸多优势,然而这些分布众多的微服务也会增加安全性方面的挑战,每个微服务都是一个可被攻击的目标。为了提升网络通信的安全防护能力,有效对抗网络威胁,采用微服务架构的服务网格普遍采用了基于安全传输层协议(TLS)的安全数据传输。但同时,TLS协议中的非对称加解密会消耗大量的CPU资源,影响了服务网格的性能表现,并带来了较高的总体拥有成本(TCO)。
556 0
基于英特尔®架构的阿里云服务网格ASM技术加速应用服务加密通信
|
弹性计算 Kubernetes Cloud Native
非容器应用与 K8s 工作负载服务网格化实践|学习笔记(二)
快速学习非容器应用与 K8s 工作负载服务网格化实践
249 0
非容器应用与 K8s 工作负载服务网格化实践|学习笔记(二)