【Tetragon / eBPF】进程模型:exec_id、血缘、in_init_tree 与 procfs 回填

💡 原文中文,约11600字,阅读约需28分钟。
📝

内容提要

本文介绍Tetragon v1.7.0的进程血缘模型。exec_id由节点名、ktime和PID组成,解决PID复用问题,但不消除内核execve_map未命中。内核map按TGID保存权威状态,用户态cache按exec_id服务导出。in_init_tree标记容器PID 1子树,区分kubectl exec注入。procFS是启动回填标志。父进程不在map时,fork子进程不会入图,这是血缘缺口的关键失败路径。

🔎

延伸解读

exec_id 不是 PID,也不是完整血缘的保证

exec_id 由节点名、ktime 和 PID 组成,用于解决 PID 复用问题,但它并不保证内核 execve_map 一定命中。如果父进程不在 map 中,fork 出的子进程不会进入 map,导致血缘缺失。因此,exec_id 只是稳定标识,不是血缘完整性的保证。

in_init_tree 的判定逻辑与局限

in_init_tree 通过检查父进程标志或自身 nspid 是否为 1 来标记容器 PID 1 子树,用于区分 kubectl exec 等注入进程。但若容器 PID 1 在 agent 启动前已存在且 procfs 回填未正确标记,则整棵子树可能无法继承该标志,导致误判。

procfs 回填的竞态与快照性质

procfs 回填是 agent 启动时的快照,用于补全已有进程的 map 条目。但扫描期间退出的短命进程不会出现在 map 中,且扫描与 live hook 之间存在 TOCTTOU 窗口,可能导致血缘信息不完整。因此,procfs 回填并非实时,存在固有竞态。

Q&A

Tetragon 中 exec_id 由哪些部分组成?它主要解决什么问题?

exec_id 由节点名、ktime 和 PID 组成,格式为 base64(node:ktime:pid)。它用于跨节点、跨时间唯一标识一次进程执行,解决 PID 复用问题,但不解决内核 execve_map 未命中的问题。

Tetragon 如何区分容器内正常进程和通过 kubectl exec 注入的进程?

Tetragon 使用 in_init_tree 标志来区分。该标志标记容器 PID 1 的子树成员,通过继承或当前 nspid==1 来设置。主机进程为 false,注入的进程(如 kubectl exec 的 shell)通常不在该子树中,因此 in_init_tree 为 false。

Tetragon 中 procfs 回填的作用是什么?它有什么局限性?

procfs 回填是 Tetragon 启动时扫描 /proc 目录,将已存在的进程信息写入内核 execve_map,并标记为 procFS 标志。它用于修复容器先于 Tetragon 启动导致的 in_init_tree 错误,但它是扫描时刻的快照,无法捕获扫描期间退出的短命进程,存在竞态窗口。

在 Tetragon 中,如果父进程不在 execve_map 中,fork 子进程会发生什么?

如果父进程不在 execve_map 中,fork 子进程时,内核的 bpf_fork.c 程序会因找不到父而直接返回,不会创建消息,也不会将子进程写入 execve_map。因此子进程不会进入进程树,这是血缘缺口的关键失败路径。

Tetragon 中 execve_map 和用户态 process cache 分别承担什么角色?

内核 execve_map 按 TGID 保存权威状态,服务 hook 和选择器;用户态 process cache 按 exec_id 缓存进程信息,服务 JSON 导出和祖先展开。两者独立,任何一层未命中都会导致血缘不完整。

Tetragon 的 exec_id 能否完全保证进程血缘的完整性?为什么?

不能。exec_id 只解决 PID 复用问题,不解决内核 execve_map 未命中。如果父进程从未进入 map(如 agent 启动窗口、map 满)或已从 map 删除,子进程可能无法关联到父,导致血缘缺口。

🏷️

标签

➡️

继续阅读