土法炼钢 · 系统与基础设施

【Tetragon / eBPF】排障坐标系:无事件、策略不生效、导出缺口、误阻断与血缘空洞

文章导航

分类入口
kubernetessecurity
标签入口
#tetragon#ebpf#troubleshooting#tracingpolicy#override#execve-map#policyfilter#v1.7.0

目录

「策略已经 apply,为什么 tetra getevents 仍是空的?」与「denylist 已经滤掉,为什么 CLI 还在刷事件?」很少落在同一层。前者可能是 hook 没挂上、BTF 找不到、或三条加载路径撞名;后者在 v1.7.0 文档里已经写成 Caution:JSON allow/deny 作用于 gRPC。若一上来重装 DaemonSet 或改随机 TracingPolicy YAML,因果链会被冲掉,还可能把可恢复的加载失败放大成双故障。

本文是系列第 13 篇:按五轴映射症状,并把每一步钉到 bugtool / metrics / 加载键 / 内核 config。口诀与第 01 篇相同:先点名轴,再下钻模块。观测字段与 metrics 更名见第 12 篇;强制执行语义见第 10 篇。无 Tetragon 节点则不粘贴伪造 tetra / JSON 输出。

本篇在系列中的位置

篇目 核心内容
第 12 篇 · 运维与导出工程 metrics、轮转、SIEM 与 gRPC 地址
第 13 篇 · 排障坐标系 按五轴归因
第 14 篇 · 对照替代路径 Falco / auditd / CNP·Hubble / seccomp
系列目录 全部篇目

版本锚定:Tetragon v1.7.0(源码 tag v1.7.0,提交 1de2ed8「Prepare for v1.7.0 release」)。加载键是 collectionKey{name, namespace}pkg/sensors/collection.go);不是 live 文档里的 k8s/grpc/static domain sharding。官方 Troubleshooting / System dump 为 bugtool 入口;机制结论以 tag 下 docs/content/en/docs/pkg/bpf/ 为准。


一、先点名轴,再动 YAML

轴一:进程血缘     — exec_id / execve_map / process cache / procFS 回填
轴二:Sensor/Hook  — 内置 process sensor;kprobe / tracepoint / uprobe / LSM / fentry
轴三:策略加载     — kubectl CR、tetra gRPC add、--tracing-policy 文件,同一 collection map
轴四:过滤分层     — 内核 selector vs JSON export allow/deny vs field/redaction
轴五:强制执行     — Override(需 CONFIG_BPF_KPROBE_OVERRIDE)vs Signal/SIGKILL

每轴固定三列:症状 → 先查什么 → 不要先做什么。与 Cilium 排障 同一纪律:一次否证一轴

flowchart TD
  symptom["Symptom"]
  symptom --> axis["Name the axis first"]
  axis --> a1["Lineage execve_map cache"]
  axis --> a2["Hook Sensor BTF"]
  axis --> a3["Load path collectionKey"]
  axis --> a4["Filter JSON vs gRPC"]
  axis --> a5["Enforcement Override Signal"]
触发选轴的症状 先查 不要先做
短命进程无父、flagsprocFS/miss、血缘像断裂 process cache 指标、execve_map、启动时 procfs 回填 写成「零缺口血缘」后停查
策略已 apply,任何事件都没有 BTF 路径、hook 名、tetra probe 能力、sensor 是否 attach 先重装 DaemonSet;先改 selector 值
改了 CR「不生效」、同名策略互相覆盖 三条加载路径是否共用 name+namespace 假定 kubectl 是唯一所有者
denylist 已配,tetra getevents 仍看见事件 该 sink 是 JSON file 还是 gRPC 把导出过滤当成内核过滤
以为拦住了,副作用已发生;或 Override 从未生效 CONFIG_BPF_KPROBE_OVERRIDE、monitor vs enforce 把 SIGKILL 写成「syscall 未发生」

默认入口顺序(可裁剪):agent 日志里的 BTF/加载错误 → tetra tracingpolicy list(看集合键与 state)→ 分清观察通道(JSON 文件 vs gRPC)→ 需要归档时 tetra bugtool。节流造成的「EXEC 突然消失」先对照第 11 篇,不要直接定轴二。

层坐标(全文复用)

典型对象 否证问题
Agent 日志 / BTF pkg/btf 发现路径、/sys/kernel/btf/vmlinux 程序有没有加载成功?
Policy 集合 collectionKey{name, namespace} 你改的那份策略是否就是正在跑的那份?
内核 selector TracingPolicy selectors(AND、每 hook 最多 5、first-match) 事件在 hook 内就被丢掉了吗?
Sink JSON file vs gRPC/tetra 过滤发生在哪一条出口?
bugtool / metrics pkg/bugtool/tetragon_process_cache_early_deletions_total 有没有可归档的状态,而不是口头复述?

tetra bugtoolcmd/tetra/bugtool,默认写出 tetragon-bugtool.tar.gz)由 pkg/bugtool.Bugtool 打包。v1.7.0 doBugtool 会尝试收入:init info、lib 下的 BPF 文件、BTF、agent 日志、metrics、dmesg、tc / bpftool / gops / pprof、policyfilter map、process cache、execve_map、gRPC 探测、pmap、cgroup 内存统计、BPF map 统计、tracefs。官方 System dump 文档把入口写成 kubectl exec … -- tetra bugtool。本文不展开任何归档内容——没有本机 dump,就不假装读过 tar。


二、无事件:Hook、加载与 BTF

症状

先定轴二,还是轴三?

轴二:BPF 程序没有 attach,或 hook 符号/类型写错,或内核没有足够的 BTF/特性。
轴三:程序在跑,但你以为生效的那份策略根本不在 collection map 里(下一节)。
两轴都空时,先否证轴二:没有程序,谈策略无意义。

BTF / CO-RE

v1.7.0 pkg/btf/btf.goobserverFindBTF 按顺序找:环境变量 TETRAGON_BTF、用户指定 --btf、默认内核 BTF defaults.DefaultBTFFile/sys/kernel/btf/vmlinux)、再回退到 lib 目录下的候选文件。找不到则返回明确错误,而不是静默用「大概能跑」的通用字节码。这是 CO-RE 的前提,不是可选项。站内 eBPF 系列 讲 verifier/BTF 全书;本轴只问:这台内核有没有 vmlinux BTF,agent 有没有找到它。

官方 FAQ 把 tetra probe configtetra probe 标成能力探测入口(后者需能加载探测用 BPF)。把输出留在事故节点;本文不复制文档里的示例表冒充实测。

Hook 名与类型

策略「已 apply」只说明控制面对象存在。kprobe 符号写错、把 LSM hook 写成 kprobe、在没有 fentry 支持的内核上用 spec.fentries,都会让 sensor 加载失败或永远打不中。对照第 3 篇 的 hook 类型表与第 5 篇 的 attach 顺序。LoadErrorState 的集合仍占着 collectionKey(下一节),表现为「CR 还在,事件没有」。

不要先做:为「让事件出来」把所有 selector 删光却不看加载错误;把空事件写成「eBPF 不适合这台机器」而不读 BTF 错误。


三、策略不生效:三条路径撞名、policyfilter、selector

v1.7.0 文档 tracing-policy/_index.md 写明三条加载路径:

  1. Kubernetes:kubectl apply/delete TracingPolicy / TracingPolicyNamespaced
  2. gRPC:tetra add/delete
  3. 静态文件:--tracing-policy--tracing-policy-dir(默认目录 defaults.DefaultTpDir = /etc/tetragon/tetragon.tp.d,见 cmd/tetragon/main.goloadTpFromDir

三条路径进入同一 pkg/sensors collection map。键的定义是:

// Tetragon v1.7.0 pkg/sensors/collection.go(注释已删)
type collectionKey struct {
    name, namespace string
}

Manager.AddTracingPolicycollectionKey{tp.TpName(), tp.TpNamespace()}。同名且同 namespace 的第二次 add,若已有集合不处于 LoadErrorStatepkg/sensors/handler.go 直接返回:

failed to add tracing policy %s, a sensor collection with the key already exists

这就是「改了 CR 不生效」的机制原因之一:文件或 gRPC 侧已经占用了同一个 name+namespace,CR 更新进不了 map;或反过来,你在看 gRPC list,改的却是另一条路径上的同名对象。v1.7.0 没有把 k8s / grpc / static 分成独立 domain——那是 tag 之后的 live 文档能力,见第 16 篇 开放问题。排障口语应说「集合键撞名」,不要说「domains 命令没列出」。

policyfilter

身份感知(namespace / podSelector / containerSelector / hostSelector)依赖 pkg/policyfilter。Helm chart v1.7.0 values.yaml 默认 tetragon.enablePolicyFilter: True;daemon flag --enable-policy-filter 的代码默认值是 falsepkg/option/flags.go)。主机直接跑二进制、又写了 TracingPolicyNamespaced 时,这条默认差会造成「CR 合法、过滤从未建立」。

pkg/sensors/k8s.goupdatePolicyFilter 写明:若过滤实际不需要(集群级策略且选择器全空或全 {} 匹配一切),则不调用 AddPolicy,policyfilter 关闭时仍能加载;一旦需要过滤(有 namespace,或 selector 真的在筛选),AddPolicy 在 disabled 状态下返回 "policyfilter is disabled",集合进入 LoadErrorState

无 OCI runtime hook 时,身份过滤是 API server 的 best-effort,窗口见第 8 篇第 15 篇。排障时不要把「刚启动的容器漏了一次 enforce」默认写成 selector 写错。

selector

内核选择器语义钉在 tag 文档 selectors.md:同一 selector 内过滤器 AND;每个 hook 最多 5 个 selector;多个 selector 是 first-match(短路 OR);无 action 则默认 Post。事件在 BPF 里被 NoPost 吃掉,用户态看起来像「策略没匹配」。先读第 7 篇,再改 YAML。

不要先做:同时从三条路径塞同名策略「看谁赢」;在 Helm 已开 policyfilter、主机安装却未开的环境之间复制 YAML 并认定语义相同。


四、导出缺口:JSON allow/deny 不是 gRPC

这是轴四,不是「CLI 坏了」。

v1.7.0 docs/content/en/docs/concepts/events.md Caution(原文大意,不把文档示例 JSON 当本机输出):denylist / allowlist 只作用于以文件 sink 导出的 JSON(例如配置了 filetetragon.log)。它们不影响 gRPC API。tetra getevents 走 gRPC 时仍会看到未被该导出过滤器丢掉的事件。

tag 文档把默认 JSON 路径写成 /var/run/cilium/tetragon/tetragon.log。把文件接到 SIEM 之后,值班若用 tetra getevents 验收 denylist,会得到「过滤没生效」的假阴性。完整分层见第 4 篇;SIEM 只能接到 file sink 见第 12 篇

另一种缺口是内核已经 Post、用户态 field filter / redaction 裁掉了字段,事件还在但「看起来不像那条规则」。先问 哪一个 sink、哪一层过滤器,再改策略。

学术对照:Garfinkel(NDSS 2003)指出,观测工具若在用户态解释「发生了什么」,必须交代自己看到的是哪一份副本。JSON 与 gRPC 是两份出口,不是同一完备性证明。导出完备性仍是开放问题,见第 16 篇

不要先做:为了对齐 CLI 与日志,关掉全部导出过滤当长期方案;把 tetra 管道 JSON(kubectl logs … | tetra getevents)与 gRPC tetra getevents 当成同一条路径——前者吃的是已经按文件过滤写出来的日志,后者不是。


五、误阻断与 Override 不可用

症状

Override 的内核门闩

tag 文档 selectors.mdOverride 使用内核 error injection,仅在编译了 CONFIG_BPF_KPROBE_OVERRIDE 的内核上可用。官方 FAQ 把该项列入 Enforcement 所需配置;tetra probe 输出里的 override_return 是运行时对应位。没有该 config,不要把 Override 演示写成成功阻断。

enforcement/_index.md 把两种强制执行钉死:

动作 保证 不保证
Override 函数不执行,向调用者返回 argError 内核未开 error injection 时不可用;不是任意函数都能改返回值
Signal / SIGKILL 进程被同步终止 不保证当前这次 syscall 没有副作用(官方 write() 例:数据仍可能写入)

要「这次操作本身不完成」,文档要求 Signal Override 组合。monitor vs enforce 模式见 mode.md第 10 篇:monitoring 会省略 enforcement 动作。YAML 仍写着 Override,运行时模式却是 monitor,表现为「策略在、阻断无」。

这不是产品口号差异。Bishop 与 Dilger(Computing Systems 9(2), 1996)把 TOCTTOU 写成:检查与使用之间存在可被并发改写的绑定;Garfinkel NDSS 2003 §4.3 把同一结构搬到系统调用拦截(参数竞态、路径竞态)。Tetragon 的 SIGKILL 停在「进程结束」,不停在「该次内核操作未发生」。把「装了 Tetragon 就能在 syscall 前拦住」写成绝对句,会在轴五事故里无法归因。

不要先做:在未确认 CONFIG_BPF_KPROBE_OVERRIDE 前把 Override 当生产门禁;用 SIGKILL 当 Override 的别名。


六、血缘空洞:execve_map 未命中与 procFS

站内 linux/ebpf-security 曾用「零缺口血缘」概括内核进程树。本系列不沿用该口号。v1.7.0 源码给出两条必须写进排障手册的失败路径。

fork 时父不在 map

bpf/process/bpf_fork.c(Tetragon v1.7.0,以下经删减)挂在 kprobe/wake_up_new_task。若找不到父进程的 execve_map 项,子进程不会被插入 map,也不会发 clone 消息:

/* Do not try to create any msg or calling execve_map_get
 * (that will add a new process in the execve_map) if we
 * cannot find it's parent in the execve_map.
 */
parent = __event_find_parent(task);
if (!parent)
    return 0;

父缺失会沿子树传播:后续子孙同样进不了图。排障时应把「进程不在 map」当作轴一的一等故障,而不是「eBPF 保证完整树」的反例去忽略。

启动快照:procFS 标志

agent 启动时 cmd/tetragon/main.go 调用 procevents.GetRunningProcs(),从 --procfs(默认 /proc)列出已存在进程,写入事件缓冲。api/v1/tetragon/tetragon.protopkg/api/flags.go 把这类事件标为 procFS:它们不是本次 execve 观测到的,而是 init 时从 proc 接口回填。proto 同时说明:miss 表示缓冲里找不到父信息,将尽量从内核结构恢复,args 不可用

Garfinkel NDSS 2003 §4.1 的教训是:用户态复制 OS 状态会与内核不一致。procfs 快照是启动窗口的折中,不是与 execve hook 同构的观测。flags 里出现 procFS 时,血缘深度与参数完备性都要降级解释。

process cache 的过早删除

用户态 cache(pkg/process/cache.go)在 GC 已把条目标为 deleted 之后若再次收到删除,会增加计数器。v1.7.0 指标名:

tetragon_process_cache_early_deletions_total

pkg/process/metrics.go 的 Help:GC 试图删除一个已经标记为 deleted 的进程的次数;可能表示 GC 删得过早。docs/content/en/docs/reference/metrics.md 使用同一句话。该计数上升是轴一的信号,不是 cache 工作正常的证明。未采集到该指标时不要编造曲线。

bugtool 会尝试 dump process cache 与 execve_mapaddProcessCache / addExecveMap)。用归档对照「map 里有没有这个 pid」,不要用 /proc 事后重建冒充事故瞬间的内核图。

不要先做:把短命进程缺失写成 Falco 才有的问题;把 procFS 事件的 args 空洞当成 hook 失效。


参考资料

规范 / 官方文档 / 源码(A)

论文 / 谱系

站内对照

实验台账


轴选错时,后面的 YAML 与重装都在解决另一个问题。记住一个动作:先说出是血缘、hook、加载键、sink 还是 Override 门闩,再改那一层的对象。

上一篇:运维与导出工程 · 下一篇:对照替代路径 · 系列目录

读完这篇,下一步读什么

优先读同系列或同问题的下一篇,把单篇消费变成主题集群。

2026-08-18 · kubernetes / security

【Tetragon / eBPF】强制执行:Override 与 SIGKILL 各自保证什么

按 Tetragon v1.7.0 官方 Enforcement 文档钉死:Override 使函数不执行并返回 argError;SIGKILL 终止进程但不保证当前 syscall 无副作用;monitor 与 enforce 如何把动作抹掉;以及 Bishop–Dilger–Garfinkel 的 TOCTOU 谱系落到 bpf_enforcer.c 的边界。


By .