本文已超过一年。较旧的文章可能包含过时内容。请检查页面信息自发布以来是否已不再正确。
在 Kubernetes 中使用 eBPF
引言
Kubernetes 提供了一个高级 API 和一组组件,几乎隐藏了系统层面的所有复杂且——对某些人而言——有趣的细节。应用开发者无需了解机器的 IP 表、cgroups、namespaces、seccomp,甚至现在连应用运行其上的容器运行时都不需要了解。但在底层,Kubernetes 及其所依赖的技术(例如容器运行时)严重依赖于核心 Linux 功能。
本文重点介绍一种在网络、安全审计以及追踪和监控工具中越来越多使用的核心 Linux 功能。此功能称为扩展 Berkeley Packet Filter (eBPF)。
注: 在本文中,我们同时使用两个缩写:eBPF 和 BPF。前者用于表示扩展 BPF 功能,后者用于表示“经典”BPF 功能。
什么是 BPF?
BPF 是一个驻留在 Linux 内核中的迷你虚拟机,用于运行 BPF 程序。在运行之前,BPF 程序通过 bpf() 系统调用加载,并进行安全性验证:检查循环、代码大小等。BPF 程序附加到内核对象上,当这些对象上发生事件时执行——例如,当网络接口发出数据包时。
BPF 的超能力
BPF 程序本质上是事件驱动的,这是一个极其强大的概念,并在事件发生时在内核中执行代码。Netflix 的 Brendan Gregg 将 BPF 称为 Linux 超能力。
eBPF 中的“e”
传统上,BPF 只能附加到套接字上进行套接字过滤。BPF 的第一个用例是在 tcpdump
中。当你运行 tcpdump
时,过滤器被编译成一个 BPF 程序并附加到原始的 AF_PACKET
套接字,以便打印出过滤后的数据包。
但多年来,eBPF 增加了附加到其他内核对象的能力。除了套接字过滤之外,一些支持的附加点包括
- Kprobes (及其用户空间等效项 uprobes)
- Tracepoints
- 网络调度器或 qdisc 用于分类或动作 (tc)
- XDP (eXpress Data Path) 此功能以及其他更新的功能,如内核内辅助函数和可用于与用户空间通信的共享数据结构 (map),扩展了 BPF 的功能。
eBPF 与 Kubernetes 的现有用例
几个开源的 Kubernetes 工具已经在使用 eBPF,许多用例值得仔细研究,特别是在网络、监控和安全工具等领域。
使用 Cilium 实现动态网络控制和可见性
Cilium 是一个网络项目,它大量使用 eBPF 的超能力来路由和过滤基于容器系统的网络流量。通过使用 eBPF,Cilium 可以动态生成和应用规则——甚至在设备级别使用 XDP——而无需修改 Linux 内核本身。
Cilium Agent 在每个主机上运行。它不管理 IP 表,而是将网络策略定义转换为 BPF 程序,这些程序被加载到内核中并附加到容器的虚拟以太网设备上。这些程序在每个发送或接收的数据包上执行——应用规则。
此图显示了 Cilium 项目的工作原理
根据应用的网络规则,BPF 程序可以附加到 tc 或 XDP。通过使用 XDP,Cilium 可以将 BPF 程序附加到最低层,这也是网络软件栈中性能最高的点。
如果你想了解更多关于 Cilium 如何使用 eBPF 的信息,请查看项目的 BPF 和 XDP 参考指南。
在 Weave Scope 中跟踪 TCP 连接
Weave Scope 是一个用于监控、可视化和与基于容器系统交互的工具。在本文中,我们将重点介绍 Weave Scope 如何获取 TCP 连接。
Weave Scope 采用一个在集群每个节点上运行的 Agent。该 Agent 监控系统,生成报告并将其发送到应用服务器。应用服务器编译收到的报告,并在 Weave Scope UI 中呈现结果。
为了准确绘制容器之间的连接,Agent 将一个 BPF 程序附加到跟踪套接字事件(打开和关闭连接)的 kprobes 上。BPF 程序 tcptracer-bpf 被编译成一个 ELF 目标文件,并使用 gobpf 加载。
(顺带一提,Weave Scope 还有一个使用 eBPF 的插件:HTTP 统计。)
要了解更多关于其工作原理以及为何如此实现的信息,请阅读 Kinvolk 团队为 Weaveworks Blog 撰写的这篇详细文章。你也可以观看关于此主题的最新演讲。
使用 seccomp-bpf 限制系统调用
Linux 有超过 300 个可用的系统调用(如 read, write, open, close 等)——或可能被滥用。大多数应用程序只需一小部分系统调用即可正常工作。seccomp 是一个 Linux 安全设施,用于限制应用程序可以使用的系统调用集合,从而限制潜在的滥用。
seccomp 的最初实现限制性很强。一旦应用,如果应用程序试图执行任何超出读取和写入已打开文件范围的操作,seccomp 就会发送一个 SIGKILL
信号。
seccomp-bpf 提供了更复杂的过滤器和更广泛的操作。seccomp-bpf,也称为 seccomp 模式 2,允许以 BPF 程序的形式应用自定义过滤器。当 BPF 程序加载后,该过滤器会应用于每个系统调用,并采取适当的操作(允许、杀死、捕获等)。
seccomp-bpf 在 Kubernetes 工具中被广泛使用,并在 Kubernetes 本身中暴露。例如,seccomp-bpf 在 Docker 中用于应用自定义seccomp 安全配置文件,在 rkt 中用于应用seccomp 隔离器,并在 Kubernetes 本身中用于其安全上下文。
但在所有这些情况下,BPF 的使用都隐藏在 libseccomp 后面。在幕后,libseccomp 根据提供给它的规则生成 BPF 代码。生成后,BPF 程序被加载并应用规则。
eBPF 与 Kubernetes 的潜在用例
eBPF 是一项相对较新的 Linux 技术。因此,仍有许多用途尚未探索。eBPF 本身也在不断发展:eBPF 中正在添加新功能,这将开启目前尚不可能的新用例。在以下章节中,我们将探讨其中一些最近才成为可能的功能以及即将出现的功能。我们希望这些功能能被开源工具所利用。
Pod 和容器级别的网络统计
BPF 套接字过滤并不新鲜,但针对每个 cgroup 的 BPF 套接字过滤是新特性。在 Linux 4.10 中引入的 cgroup-bpf 允许将 eBPF 程序附加到 cgroups。一旦附加,该程序将针对 cgroup 中任何进程进入或退出的所有数据包执行。
一个 cgroup 除其他功能外,是一个进程的层次结构分组。在 Kubernetes 中,这种分组存在于容器级别。利用 cgroup-bpf 的一个想法是安装 BPF 程序,收集详细的每个 Pod 和/或每个容器的网络统计信息。
通常,这些统计信息是通过定期检查 Linux 的 /sys
目录中的相关文件或使用 Netlink 来收集的。通过为此目的将 BPF 程序附加到 cgroups,我们可以获得更详细的统计信息:例如,tcp 端口 443 上有多少数据包/字节,或来自 IP 10.2.3.4 的数据包/字节数。一般来说,由于 BPF 程序具有内核上下文,它们可以安全高效地向用户空间提供更详细的信息。
为了探索这个想法,Kinvolk 团队实现了一个概念验证项目:https://github.com/kinvolk/cgnet。该项目将一个 BPF 程序附加到每个 cgroup,并将信息导出到 Prometheus。
当然还有其他有趣的可能,例如进行实际的数据包过滤。但目前阻碍这一进展的障碍是 Docker 和 Kubernetes 对 cgroup v2 的支持——cgroup-bpf 需要此支持。
应用层应用的 LSM
Linux Security Modules (LSM) 在 Linux 内核中实现了一个通用的安全策略框架。SELinux 和 AppArmor 就是这类模块的例子。这两者都在系统全局范围内实施规则,由管理员负责配置安全策略。
Landlock 是另一个正在开发中的 LSM,它可以与 SELinux 和 AppArmor 共存。初步的补丁集已提交到 Linux 内核,目前处于早期开发阶段。与其他 LSM 的主要区别在于,Landlock 旨在允许非特权应用构建自己的沙箱,从而有效地限制自身,而不是使用全局配置。通过 Landlock,应用程序可以加载一个 BPF 程序,并在进程执行特定操作时让其执行。例如,当应用程序使用 open() 系统调用打开文件时,内核将执行 BPF 程序,并且根据 BPF 程序的返回值,该操作将被接受或拒绝。
考虑以下系统调用
C
fd = open(“myfile.txt”, O\_RDWR);
第一个参数是“char *”,一个指向内存地址的指针,例如 0xab004718
。
使用 seccomp 时,BPF 程序只能访问系统调用的参数,但不能解引用指针,这使得无法根据文件做出安全决策。seccomp 也使用经典 BPF,这意味着它不能利用 eBPF maps,即与用户空间交互的机制。这个限制意味着 seccomp-bpf 中的安全策略无法根据 eBPF map 中的配置进行更改。
Landlock 的 BPF 程序不接收系统调用的参数,而是接收对内核对象的引用。在上面的示例中,这意味着它将拥有对文件的引用,因此不需要解引用指针、考虑相对路径或执行 chroot。
用例:Landlock 在基于 Kubernetes 的无服务器框架中的应用
在 Kubernetes 中,部署单位是 Pod。Pod 和容器是主要的隔离单位。然而,在无服务器框架中,主要的部署单位是函数。理想情况下,部署单位等于隔离单位。这使得像 Kubeless 或 OpenFaaS 这样的无服务器框架陷入困境:是优化隔离单位还是部署单位?
为了实现最佳隔离,每个函数调用都必须在自己的容器中进行——但有利于隔离的并不总是有利于性能。相反,如果我们在同一容器内运行函数调用,就会增加发生冲突的可能性。
通过使用 Landlock,我们可以在同一容器内隔离函数调用,例如,使一个函数调用创建的临时文件无法被下一个函数调用访问。Landlock 与基于 Kubernetes 的无服务器框架等技术的集成将是一个值得进一步探索的领域。
使用 eBPF 审计 kubectl-exec
在 Kubernetes 1.7 中,审计提案开始被引入。它目前处于预稳定阶段,并计划在 1.10 版本中达到稳定。顾名思义,它允许管理员记录和审计 Kubernetes 集群中发生的事件。
虽然这些事件记录了 Kubernetes 事件,但它们目前无法提供某些人所需的可见性级别。例如,虽然我们可以看到有人使用 kubectl exec
进入了容器,但我们无法看到在该会话中执行了什么命令。使用 eBPF,可以附加一个 BPF 程序,该程序将记录在 kubectl exec
会话中执行的任何命令,并将这些命令传递给记录这些事件的用户空间程序。然后我们可以回放该会话,并知道发生的具体事件序列。
了解更多关于 eBPF 的信息
如果您有兴趣了解更多关于 eBPF 的信息,这里有一些资源
一个全面的eBPF 阅读清单,正为此目的而设
BCC (BPF 编译器集合) 提供了用于使用 eBPF 的工具,以及许多利用 BCC 的示例工具。
一些视频
- BPF:追踪及更多,作者 Brendan Gregg
- Cilium - 使用 BPF 和 XDP 实现容器安全和网络,作者 Thomas Graf
- 在 Kubernetes 中使用 BPF,作者 Alban Crequy
结论
我们刚刚开始看到 eBPF 的 Linux 超能力在 Kubernetes 工具和技术中得到应用。毫无疑问,eBPF 的使用将不断增加。我们在这里重点介绍的只是未来可能看到的一小部分。真正令人兴奋的是看到这些技术将以我们尚未想到的方式得到应用。敬请关注!
Kinvolk 团队将在奥斯汀 KubeCon 大会的 Kinvolk 展位恭候。欢迎前来与我们聊聊一切与 Kubernetes、Linux、容器运行时以及 eBPF 相关的事情。