项目仓库:https://github.com/yunwei37/Eunomia
本项目由两部分组成:
eBPF
技术的教程实践和对应的命令行工具集,主要使用 C/C++
语言开发, 同时作为原型验证;eBPF
技术实现的用于监控容器的工具(Eunomia), 包含 profile
、容器集群网络可视化分析、容器安全感知告警、一键部署、持久化存储监控等功能, 主要使用 C/C++ 语言开发, 力求为工业界提供覆盖容器全生命周期的轻量级开源监控解决方案;Eunomia
是一个使用 C/C++ 开发的基于eBPF的云原生监控工具,旨在帮助用户了解容器的各项行为、监控可疑的容器安全事件,力求为工业界提供覆盖容器全生命周期的轻量级开源监控解决方案。它使用 Linux
eBPF
技术在运行时跟踪您的系统和应用程序,并分析收集的事件以检测可疑的行为模式。目前,它包含 profile
、容器集群网络可视化分析*、容器安全感知告警、一键部署、持久化存储监控等功能。
docker
镜像方式分发,一次编译,到处运行,一行代码即可启动,包含多种 ebpf 工具和多种监测点,支持多种输出格式(json, csv, etc) 并保存到文件;ebpf
自动收集容器相关元信息,并和多种指标相结合;prometheus
和 Grafana
,作为监控可视化和预警平台;graphql
在远程发起 http 请求并执行监控工具,将产生的数据进行聚合后返回,用户可自定义运行时扩展插件进行在线数据分析;可外接时序数据库,如 InfluxDB
等,作为可选的信息持久化存储和数据分析方案;除了收集容器中的一般系统运行时内核指标,例如系统调用、网络连接、文件访问、进程执行等,我们在探索实现过程中还发现目前对于 lua
和 nginx
相关用户态 profile
工具和指标可观测性开源工具存在一定的空白,但又有相当大的潜在需求;因此我们还计划添加一系列基于 uprobe 的用户态 nginx/lua
追踪器,作为可选的扩展方案;(这部分需求来自中科院开源之夏, APISIX 社区的选题)
和过去常用的 BCC
不同,Eunomia
基于 Libbpf
+ BPF CO-RE(一次编译,到处运行)开发。Libbpf 作为 BPF 程序加载器,接管了重定向、加载、验证等功能,BPF 程序开发者只需要关注 BPF 程序的正确性和性能即可。这种方式将开销降到了最低,且去除了庞大的依赖关系,使得整体开发流程更加顺畅。目前,我们已经发布了 pre-release
的版本,其中部分功能已经可以试用,只需下载二进制文件即可运行,
Eunomia
的 ebpf
追踪器部分是从 libbpf-tools
中得到了部分灵感,但是目前关于 ebpf 的资料还相对零散且过时,这也导致了我们在前期的开发过程中走了不少的弯路。因此, 我们也提供了一系列教程,以及丰富的参考资料,旨在降低新手学习eBPF技术的门槛,试图通过大量的例程解释、丰富对 eBPF、libbpf、bcc
等内核技术和容器相关原理的认知,让后来者能更深入地参与到 ebpf 的技术开发中来。另外,Eunomia
也可以被单独编译为 C++ 二进制库进行分发,可以很方便地添加自定义 libbpf检查器,或者直接利用已有的功能来对 syscall 等指标进行监测,教程中也会提供一部分 EUNOMIA
扩展开发接口教程。
教程目前还在完善中。
容器是一种应用层抽象,用于将代码和依赖资源打包在一起。多个容器可以在同一台机器上运行,共享操作系统内核。这使得容器的隔离性相对较弱,带来安全上的风险,最严重时会导致容器逃逸,严重影响底层基础设施的保密性、完整性和可用性。
eBPF 是一个通用执行引擎,能够高效地安全地执行基于系统事件的特定代码,可基于此开发性能分析工具**、网络数据包过滤、系统调用过滤,**系统观测和分析等诸多场景。eBPF可以由hook机制在系统调用被使用时触发,也可以通过kprobe或uprobe将eBPF程序附在内核/用户程序的任何地方。
这些机制让eBPF的跟踪技术可以有效地感知容器的各项行为,包括但不限于:
请基于eBPF技术开发一个监控工具,该工具可以监控容器的行为,并生成报表(如json文件)将各个容器的行为分别记录下来以供分析。
本赛题分为三个部分,第一部分为编写ebpf程序,感知容器行为,这一部分的关键点在于找到合适的ebpf程序挂载点。挂载点确定后我们可以使用现有的各种ebpf开发框架编写开发代码,完成此部分任务。第二部分信息存储则需要我们将内核态捕捉到的信息传递到用户态,然后在用户态进行输出。这一部分具有较大的可操作空间。我们可以使用最简单的打印方式,将所有数据打印出来,也可以将所有数据存储到日志文件中,还可以通过可视化的手段,将数据进行可视化展示。第三部分则涉及到了一个新的模块Seccomp,seccomp是linux内核中的一个安全模块,可以限制某一进程可以调用的syscall的数量。该技术可以进一步增强本工具的监督保护能力,因此本次也应当将其融合进入本项目。
eBPF是一项革命性的技术,可以在Linux内核中运行沙盒程序,而无需更改内核源代码或加载内核模块。通过使Linux内核可编程,基础架构软件可以利用现有的层,从而使它们更加智能和功能丰富,而无需继续为系统增加额外的复杂性层。
原始的eBPF程序编写是非常繁琐和困难的。为了改变这一现状,llvm于2015年推出了可以将由高级语言编写的代码编译为eBPF字节码的功能,同时,其将 bpf()
等原始的系统调用进行了初步地封装,给出了 libbpf
库。这些库会包含将字节码加载到内核中的函数以及一些其他的关键函数。在Linux的源码包的 samples/bpf/
目录下,有大量Linux提供的基于 libbpf
的eBPF样例代码。一个典型的基于 libbpf
的eBPF程序具有 *_kern.c
和 *_user.c
两个文件,
*_kern.c
中书写在内核中的挂载点以及处理函数, *_user.c
中书写用户态代码,完成内核态代码注入以及与用户交互的各种任务。
更为详细的教程可以参考该视频。
然而由于该方法仍然较难理解且入门存在一定的难度,因此现阶段的eBPF程序开发大多基于一些工具,比如:
BCC
BPFtrace
libbpf
go-libbpf
目前使用较多的是 BCC
工具,但本项目放弃了 BCC
,选择了 libbpf
作为我们的开发工具。
BCC
全称为 BPF Compiler Collection
,是一个python库,包含了完整的编写、编译、和加载 BPF
程序的工具链,以及用于调试和诊断性能问题的工具。自2015年发布以来,BCC
经过上百位贡献者地不断完善后,目前已经包含了大量随时可用的跟踪工具。并且 其官方项目库 提供了一个方便上手的教程,用户可以快速地根据教程完成 BCC
入门工作。用户可以在 BCC
上使用Python、Lua等高级语言进行编程。
相较于使用C语言直接编程,这些高级语言具有极大的便捷性,用户只需要使用C来设计内核中的 BPF
程序,其余包括编译、解析、加载等工作在内,均可由 BCC
完成。
然而使用 BCC
存在一个缺点便是在于其兼容性并不好。基于 BCC
的 eBPF
程序每次执行时候都需要进行编译,编译则需要用户配置相关的头文件和对应实现。在实际应用中,相信大家也会有体会,编译依赖问题是一个很棘手的问题。也正是因此,在本项目的开发中我们放弃了BCC,选择了可以做到一次编译-多次运行的 libbpf
工具。
libbpf-bootstrap
是一个基于 libbpf
库的BPF开发脚手架,从其 github 上可以得到其源码。 libbpf-bootstrap
综合了BPF社区过去多年的实践,为开发者提了一个现代化的、便捷的工作流,实现了一次编译,重复使用的目的。
基于 libbpf
的BPF程序在编译时会先将 *.bpf.c
文件编译为对应的.o
文件,然后根据此文件生成 skeleton
文件,即 *.skel.h
,这个文件会包含内核态中定义的一些数据结构,以及用于装载内核态代码的关键函数。在用户态代码 include
此文件之后调用对应的装载函数即可将字节码装载到内核中。
我们选择现代 C++ 语言(cpp20)开发 Eunomia 的时候也主要是看中和 libbpf 库以及 bpf 代码的良好兼容性,libbpf 库目前还在迅速更新迭代过程中,我可以直接基于 libbpf 库进行开发,不需要被其他语言(go/rust)的运行时 bpf 库所限制。现代 C++ 的开发速度和安全性应该并不会比其他语言差太多(要是编译提示能像 rust 那样好点就更好了,用了 concept 还是不够好)
Docker
类容器本身提供了较多命令用于观测容器,比如:
docker ps
命令可以显示出目前正在运行的所有容器的ID,名称,运行时间等等数据,
docker top
命令可以显示容器中所有正在运行的进程,并且显示其在宿主机上的进程号,通过这种方式我们可以在宿主机中找到和容器有关的进程号并进行重点追踪。
docker inspect
命令则可以根据需要具体查看容器的各种信息。
通过这些命令,我们可以较为快速地得到容器内的一些情况。
容器中的进程会映射到宿主机中,他们和宿主机上的其他进程最直接的区别就在于namespace。为了隔离资源,容器中的进程和宿主机上的进程具有不同的namespace。因此,监测容器行为可以转变为监测特定进程,通过复用现有process模块的基础上添加container追踪模块。
容器追踪模块的内核态ebpf代码和process模块一样,都是利用了 sched_process_exec
和 sched_process_exit
两个挂载点,区别在于用户态代码中对于内核态返回的数据的处理方式。在容器追踪模块中,每次有内核态数据写入时我们会调用 judge_container()
函数,该函数会检查此进程的namespace和其父进程是否相同。如果相同那么我们就会认为他和父进程是归为一类的,通过检查存有所有容器进程信息的哈希map即可确定此新进程的归属。如果不同,那么我们便会认为可能有新的容器产生。对于 Docker
类容器,我们会直接调用 Docker
给出的命令的进行观测。首先调用 docker ps -q
命令获得现有在运行的所有容器id,之后调用 docker top id
命令获取容器中的进程在宿主机上的进程信息,如果这些信息没有被记录到哈希map中,那么就将他们添加到其中并输出。在有进程退出时,我们只需要检查其是否存在于哈希map,如果存在删去即可。
Prometheus
是一套开源的监控、报警、时间序列数据库的组合,受启发于Google的Brogmon监控系统,2012年开始由前Google工程师在Soundcloud以开源软件的形式进行研发,并且于2015年早期对外发布早期版本。2016年,Prometheus
加入了云计算基金会,成为 kubernetes
之后的第二个托管项目。其架构如下所示:
Prometheus架构
Prometheus
具有以下特点:
PromQL
Grafana
是一款用Go语言开发的开源数据可视化工具,具有数据监控、数据统计和告警功能,是目前较为流行的一种时序数据展示工具,并且支持目前绝大部分常用的时序数据库。
在本项目中,我们计划将程序捕获到的数据使用 Prometheus
进行存储,之后对于存储的数据我们使用 Grafana
进行可视化
确保容器运行时安全的关键点:
ebpf
跟踪技术自动生成容器访问控制权限。包括:容器对文件的可疑访问,容器对系统的可疑调用,容器之间的可疑互访,检测容器的异常进程,对可疑行为进行取证。例如:项目仓库:https://github.com/yunwei37/Eunomia