本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。

在 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
  • 用于分类或操作的网络调度器或 qdiscs (tc)
  • XDP (eXpress Data Path) 这些以及其他更新的功能,如内核辅助函数和可用于与用户空间通信的共享数据结构(映射),扩展了 BPF 的功能。

eBPF 在 Kubernetes 中的现有用例

一些开源的 Kubernetes 工具已经使用了 eBPF,许多用例值得更仔细地研究,特别是在网络、监控和安全工具等领域。

使用 Cilium 进行动态网络控制和可见性

Cilium 是一个网络项目,它大量使用 eBPF 超能力来路由和过滤基于容器的系统的网络流量。通过使用 eBPF,Cilium 可以动态生成和应用规则——甚至可以在设备级别使用 XDP——而无需更改 Linux 内核本身。

Cilium Agent 在每个主机上运行。它不管理 IP 表,而是将网络策略定义转换为 BPF 程序,这些程序加载到内核中并附加到容器的虚拟以太网设备。这些程序在每个发送或接收的数据包上执行——规则被应用。

此图显示了 Cilium 项目的工作原理

根据所应用的网络规则,BPF 程序可以使用 tcXDP 附加。通过使用 XDP,Cilium 可以在尽可能最低的点附加 BPF 程序,这也是网络软件堆栈中性能最佳的点。

如果您想了解 Cilium 如何使用 eBPF,请查看该项目的 BPF 和 XDP 参考指南

在 Weave Scope 中跟踪 TCP 连接

Weave Scope 是一个用于监控、可视化和与基于容器的系统交互的工具。就本文而言,我们将重点关注 Weave Scope 如何获取 TCP 连接。

Weave Scope 采用一个在集群的每个节点上运行的代理。该代理监控系统,生成报告并将其发送到应用程序服务器。应用程序服务器编译接收到的报告,并在 Weave Scope UI 中呈现结果。

为了准确地绘制容器之间的连接,代理将一个 BPF 程序附加到跟踪套接字事件(连接的打开和关闭)的 kprobes。BPF 程序 tcptracer-bpf 被编译成 ELF 对象文件并使用 gobpf 加载。

(顺便提一下,Weave Scope 还有一个利用 eBPF 的插件:HTTP 统计信息。)

要了解更多关于其工作原理以及为何如此做的信息,请阅读 Kinvolk 团队为 Weaveworks 博客撰写的 这篇详尽的文章。您还可以观看关于该主题的 最新演讲

使用 seccomp-bpf 限制系统调用

Linux 提供了超过 300 个可用的系统调用(read、write、open、close 等)——或滥用。大多数应用程序只需要一小部分系统调用即可正常运行。seccomp 是一种 Linux 安全机制,用于限制应用程序可以使用的系统调用集,从而限制潜在的滥用。

seccomp 的原始实现限制性很强。一旦应用,如果应用程序试图做任何超出读写已打开文件的操作,seccomp 就会发送一个 SIGKILL 信号。

seccomp-bpf 支持更复杂的过滤器和更广泛的操作。seccomp-bpf,也称为 seccomp 模式 2,允许以 BPF 程序的ATOR形式应用自定义过滤器。当 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 程序附加到 cgroup。一旦附加,程序将对进入或退出 cgroup 中任何进程的所有数据包执行。

cgroup 是一个进程的分层分组。cgroup 在 Kubernetes 中,这种分组存在于容器级别。使用 cgroup-bpf 的一个想法是安装 BPF 程序,以收集每个 Pod 和/或每个容器的详细网络统计信息。

通常,这些统计信息是通过定期检查 Linux /sys 目录中的相关文件或使用 Netlink 来收集的。通过使用附加到 cgroup 的 BPF 程序,我们可以获得更详细的统计信息:例如,TCP 端口 443 上的数据包/字节数,或者来自 IP 10.2.3.4 的数据包/字节数。一般来说,由于 BPF 程序具有内核上下文,它们可以安全有效地向用户空间提供更详细的信息。

为了探索这个想法,Kinvolk 团队实现了一个概念验证项目:https://github.com/kinvolk/cgnet。该项目将一个 BPF 程序附加到每个 cgroup,并将信息导出到 Prometheus

当然,还有其他有趣的可能,比如进行实际的数据包过滤。但目前阻碍这一点的是 Docker 和 Kubernetes 中对 cgroup v2 的支持——cgroup-bpf 所必需的——在 Docker 和 Kubernetes 中。

应用程序应用的 LSM

Linux 安全模块 (LSM) 为 Linux 内核中的安全策略实现了一个通用框架。SELinuxAppArmor 是其中的例子。这两者都在系统全局范围内实现规则,将配置安全策略的责任放在管理员身上。

Landlock 是另一个正在开发中的 LSM,它将与 SELinux 和 AppArmor 共存。一个初始的补丁集已经提交给 Linux 内核,目前处于早期开发阶段。与其他 LSM 的主要区别在于,Landlock 旨在允许非特权应用程序构建自己的沙箱,从而有效地限制自身而不是使用全局配置。通过 Landlock,应用程序可以加载一个 BPF 程序,并在进程执行特定操作时执行它。例如,当应用程序使用 open() 系统调用打开文件时,内核将执行 BPF 程序,并且根据 BPF 程序的返回值,该操作将被接受或拒绝。

在某些方面,它与 seccomp-bpf 类似:seccomp-bpf 使用 BPF 程序允许非特权进程限制它们可以执行的系统调用。Landlock 将更强大并提供更大的灵活性。考虑以下系统调用:

C  
fd = open(“myfile.txt”, O\_RDWR);

第一个参数是“char *”,一个指向内存地址的指针,例如 0xab004718

使用 seccomp,BPF 程序只能访问系统调用的参数,但不能解引用指针,因此无法根据文件做出安全决策。seccomp 也使用经典的 BPF,这意味着它不能使用 eBPF 映射(用于与用户空间交互的机制)。此限制意味着安全策略不能根据 eBPF 映射中的配置在 seccomp-bpf 中更改。

Landlock 中的 BPF 程序不接收系统调用的参数,而是接收对内核对象的引用。在上面的示例中,这意味着它将拥有对文件的引用,因此无需解引用指针、考虑相对路径或执行 chroot。

用例:Kubernetes-based 无服务器框架中的 Landlock

在 Kubernetes 中,部署的单位是 Pod。Pod 和容器是隔离的主要单位。然而,在无服务器框架中,部署的主要单位是函数。理想情况下,部署的单位等于隔离的单位。这使得像 KubelessOpenFaaS 这样的无服务器框架陷入困境:是优化隔离单位还是部署单位?

为了实现最佳隔离,每个函数调用都必须发生在自己的容器中——但有利于隔离的东西并不总是对性能有利。反之,如果我们在同一个容器中运行函数调用,我们就会增加冲突的可能性。

通过使用 Landlock,我们可以将同一容器内的函数调用彼此隔离,例如,使得一个函数调用创建的临时文件对下一个函数调用不可访问。Landlock 与 Kubernetes-based 无服务器框架等技术的集成将是进一步探索的肥沃领域。

使用 eBPF 审计 kubectl-exec

在 Kubernetes 1.7 中,审计提案开始被采纳。目前处于预稳定状态,计划在 1.10 版本中达到稳定。顾名思义,它允许管理员记录和审计 Kubernetes 集群中发生的事件。

虽然这些事件记录 Kubernetes 事件,但它们目前不提供某些人可能需要的可见性级别。例如,虽然我们可以看到有人使用 kubectl exec 进入容器,但我们无法看到在该会话中执行了哪些命令。使用 eBPF,可以附加一个 BPF 程序,该程序将记录在 kubectl exec 会话中执行的任何命令,并将这些命令传递给一个记录这些事件的用户空间程序。然后,我们可以重放该会话,并知道发生的精确事件序列。

了解更多 eBPF

如果您有兴趣了解更多关于 eBPF 的信息,这里有一些资源:

结论

我们才刚刚开始看到 eBPF 的 Linux 超能力被应用于 Kubernetes 工具和技术中。毫无疑问,eBPF 的使用将不断增加。我们在此强调的只是您未来可能遇到的一小部分。真正令人兴奋的是,我们将看到这些技术如何以我们尚未想到的方式被使用。敬请期待!

Kinvolk 团队将在奥斯汀 KubeCon 的 Kinvolk 展位恭候。欢迎前来与我们讨论所有关于 Kubernetes、Linux、容器运行时以及 eBPF 的事情。