身份感知 TracingPolicy 要在 BPF 里按 namespace、Pod 标签、容器字段过滤,agent 必须在过滤生效前知道「这个 cgroup 属于哪只 Pod」。信息可以从 Kubernetes API server 来,但那条路是异步的:容器可能已经开始跑,policyfilter 才更新。对观测,这是漏事件;对 Override/SIGKILL,这是窗口里的误放行。
Runtime Hooks
的工作是:在容器运行时创建容器的路径上同步通知
Tetragon agent,让 policyfilter
在进程真正执行用户镜像之前就绪。本文钉 v1.7.0 tag 文档
docs/content/en/docs/concepts/runtime-hooks.md
与源码
pkg/rthooks/、pkg/policyfilter/rthooks/、contrib/tetragon-rthooks/。无
hook 时的 best-effort 语义与第
8 篇 对齐,本篇不重写 selector 组合表。
本篇在系列中的位置
篇目 核心内容 第 14 篇 · 对照替代路径 Falco / auditd / CNP / seccomp 第 15 篇 · Runtime Hooks OCI hook、NRI、fail-open/closed 第 16 篇 · 选型收束 排除树与开放问题 系列目录 全部篇目
版本锚定:Tetragon v1.7.0。Helm
rthooks.enabled默认 false;开钩子是显式选择,不是装上 DaemonSet 就有。gRPC 默认地址与 Upgrade notes 一致:unix:///var/run/tetragon/tetragon.sock。
一、API server 延迟窗口
tag 文档 Kubernetes Identity Aware
Policies(k8s-filtering.md)写明:namespaced
策略要「总是正确应用」,需要在容器开始执行之前做动作;Tetragon
通过 OCI
runtime hooks 支持这一点。若未加入此类
hook,Tetragon 使用 API server 信息,以 best-effort
方式应用策略。
best-effort 不是「大概正确」的修辞。控制面看到 Pod 变成
Running,与 agent 把
(pod UID, container ID, cgroup id) 写入
policyfilter map,中间隔着 API 延迟、watch 队列与 agent
处理。对 enforcement 策略,窗口里的进程已经能发
syscall。Garfinkel NDSS 2003
把「策略判定与操作完成之间的间隔」列为拦截工具的核心陷阱;这里的间隔不在单次
open() 的用户缓冲,而在 Pod 生命周期与
BPF 过滤状态的同步。
第 8 篇把无 hook 时的行为标成轴上的已知缺口。本篇给出补齐该缺口的机制,以及补齐之后仍然存在的失败模式(agent 挂了、hook 二进制没装上、NRI 未启用)。
没有 Runtime Hooks 时,不要把「第一个请求被拦、创建瞬间的
exec 没拦」写成 selector 随机失效。先问:policyfilter 的
AddPodContainer 是 hook 同步做的,还是等 API
的?
二、tetragon-oci-hook
到 tetragon.sock
概念文档把数据路径画成:OCI hook 二进制联系 agent 的 gRPC Unix socket,agent 再更新内核过滤状态。按 tag 原文改成:
flowchart LR
hook["tetragon-oci-hook"] --> sock["tetragon.sock gRPC"]
sock --> agent["tetragon agent"]
agent --> pf["policyfilter maps"]
v1.7.0
contrib/tetragon-rthooks/cmd/oci-hook/main.go
默认 agent 地址是
unix:///var/run/tetragon/tetragon.sock(与
Upgrade notes 的默认 gRPC 一致)。hook 在
createRuntime 路径读取
config.json(兼容 containerd createRuntime /
createContainer 与 CRI-O userdata 布局),抽出 cgroup
路径、rootDir、Pod/容器注解,构造
RuntimeHookRequest 里的
CreateContainer,再 gRPC 打到 agent。
agent 侧 pkg/server/server.go 的
RuntimeHook 把请求交给
pkg/rthooks.Runner.RunHooks。Runner
对已注册的 callback
全部执行(一个出错不短路其余);任一失败则
gRPC 向客户端返回错误。pkg/rthooks
包注释写明:callback 在 init() 里用
RegisterCallbacksAtInit 挂到
globalRunner,再交给 gRPC server。
真正把容器登记进过滤状态的是
pkg/policyfilter/rthooks/rthooks.go 的
createContainerHook:解析 cgroup id、Pod
UID、container id,从 watcher 取
Pod(注释写明:容器尚未出现在 API status,故读 Pod
而不是等容器 status),调用
pfState.AddPodContainer(...)。失败会打
tetragon_policyfilter_operations_total(label
subsys=rthooks)。这是身份感知策略与纯内核 hook
的接缝:内核 selector
仍按进程/参数匹配;谁被这套策略选中,由 policyfilter map
在容器启动前填好。
安装位置:Helm rthooks.installDir 默认
/opt/tetragon,由 tetragon-rthooks
DaemonSet 把 tetragon-oci-hook
放到宿主机。/opt 只读时文档要求改
installDir(例如
/run/tetragon),否则是
FailedMount,不是「策略写错」。
三、CRI-O OCI hooks、containerd NRI、无 NRI
同一份
tetragon-oci-hook,按运行时用不同方式被调用。
CRI-O:OCI hooks 目录
CRI-O 实现 containers/common 的 OCI hooks
目录约定。启用方式是往该目录放一份 hook JSON,让运行时在
create 阶段 exec tetragon-oci-hook。Helm
rthooks.interface=oci-hooks,rthooks.ociHooks.hooksPath
默认
/usr/share/containers/oci/hooks.d。安装文档用
rthooks.enabled=true 加上该 interface。
containerd:有 NRI
文档写:containerd 1.7 引入 NRI,2.0
起默认启用。tetragon-nri-hook 是一个 NRI
插件:在 CreateContainer 里向容器 spec
注入 OCI hook,指向同一份
tetragon-oci-hook(默认路径
/opt/tetragon/tetragon-oci-hook),并分别挂
createRuntime 与 createContainer
两个 hook
名(contrib/tetragon-rthooks/cmd/nri-hook/main.go)。Helm
rthooks.interface=nri-hook,rthooks.nriHook.nriSocket
默认 /var/run/nri/nri.sock。containerd 1.7
需要在 config.toml 里显式打开 NRI
插件段;安装文档给出
[plugins."io.containerd.nri.v1.nri"] 示例与
minikube/kind 补丁脚本。本篇不逐步截图发行版菜单。
containerd:无 NRI
文档单列一条:可以把自定义 container spec 配成包含
tetragon-oci-hook。没有 NRI、也没有把 hook 写进
spec 时,路径退回第一节的 API server
best-effort。排障时「containerd 集群却从未开
rthooks」与「开了 nri-hook 但 1.7 未启用
NRI」是两种失败:前者从未同步;后者 DaemonSet
在跑,运行时不调用插件。
Helm 把 rthooks 做成独立
DaemonSet(安装文档示例里除 tetragon-*
外还有 tetragon-rthooks-*)。只看 tetragon
两个容器 Ready,不能证明 hook 已进入 CRI。
四、agent 不可达:fail-open 与 fail-closed
概念文档原句:tetragon-oci-hook
可以配置成在连不上 agent 时失败或成功——否则
Tetragon 自己的 Pod、以及其他关键容器会被钩子卡死。
实现落在 oci-hook 的 checkFail /
failTestProg(contrib/tetragon-rthooks/cmd/oci-hook/main.go、cel.go)。createRuntime
若 hookRequest 出错,则跑
CEL:默认表达式按注解里的 namespace 是否落在 allow
列表 决定要不要让容器失败。默认 allow 字符串是
kube-system。Helm 文档补充:Tetragon 所在
namespace 总是作为例外加入,且
rthooks.failAllowNamespaces 可再加名单(「The
namespace Tetragon is deployed in is always added as an
exception」)。
语义可以写成:
| 容器所在 namespace | agent 不可达时 | 文档用语 |
|---|---|---|
Tetragon 自己的 namespace,以及
failAllowNamespaces |
hook
不让容器失败(日志:failCheck determined that we should not fail this container) |
对名单内 fail-open |
| 其他 namespace | checkFail 判定应失败则
os.Exit(1),创建中止 |
对名单外 fail-closed |
这是生产上必须写进 ADR 的一对:fail-closed 才能堵住「agent 挂了、enforce 策略全部失效却仍在调度工作负载」;fail-open 才能让 tetragon、kube-system 组件在 agent 尚未就绪时启动,避免死锁。把整个节点配成全局 fail-closed 且 allow 列表为空,可能让包括 hook 自己依赖的关键 Pod 无法创建。把整个节点配成全局 fail-open,则 Runtime Hooks 在 agent 故障时退回与无 hook 相同的窗口,却多了一层「以为已经同步」的错觉。
安装文档里的 JSON
日志字段(hook request to the agent failed、connecting to agent (context deadline exceeded))是官方示例,用来说明
failCheck 分支;本文不当作本机采集。现场应读宿主机
installDir 下的
tetragon-oci-hook.log(默认
/opt/tetragon/tetragon-oci-hook.log)。
--disable-grpc 时 hook 主动不联系
agent(源码日志
gRPC was disabled, so will not be contacting the agent),过滤状态不会经这条路径更新。
五、与纯内核 hook 的分工
Runtime Hooks 不是又一种
kprobe。kprobe/LSM/fentry 决定「匹配什么
syscall/函数」;Runtime Hooks 决定「policyfilter 认为当前
cgroup 属于哪套 K8s 身份」。没有后者,第
8 篇 的 namespaced 策略在时间轴上是
best-effort;有后者,仍然要满足:agent 进程在听
tetragon.sock、hook 二进制在宿主机可执行、CRI
真正调用了 hook。
flowchart TD
cri["CRI-O or containerd"] --> inj{"How is oci-hook invoked?"}
inj -->|"CRI-O hooks.d"| oci["OCI hooks JSON"]
inj -->|"containerd NRI"| nri["tetragon-nri-hook"]
inj -->|"no NRI no spec"| api["API server best-effort"]
nri --> bin["tetragon-oci-hook"]
oci --> bin
bin --> sock["unix tetragon.sock"]
sock --> cb["policyfilter rthooks callback"]
cb --> map["cgroup to policy id"]
api --> map
map --> kern["In-kernel identity filter"]
hook2["kprobe LSM fentry"] --> sel["Selectors Override Signal"]
kern --> sel
纯内核、不按 Pod 过滤的
TracingPolicy(选择器全空的集群级策略)可以不依赖本节。一旦策略的正确性建立在「只打中某
namespace / 某标签」,Runtime Hooks 从可选项变成与 BTF
同级的部署前提。第 13 篇排障若已排除 BTF
与撞名,下一问就是:rthooks DaemonSet 在不在、CRI
接的是哪一种 interface、事故窗口里
tetragon-oci-hook.log 是 succeeded 还是
deadline。
开放问题留给终章:标签风暴下 policyfilter 更新延迟如何纳入 SLO;以及与 Cilium identity 窗口如何写进同一份值班口令。本篇只关闭「没有 hook 时保证什么」:保证的是尽力而为,不保证策略在容器启动之前已经生效。
参考资料
规范 / 官方文档 / 源码(A)
- Tetragon
v1.7.0:
docs/content/en/docs/concepts/runtime-hooks.md;docs/content/en/docs/installation/runtime-hooks.md;docs/content/en/docs/concepts/tracing-policy/k8s-filtering.md(无 hook 时 API server best-effort) - 源码 tag
v1.7.0:
pkg/rthooks/rthooks.go、runner.go;pkg/policyfilter/rthooks/rthooks.go(createContainerHook→AddPodContainer);pkg/server/server.go(RuntimeHook);contrib/tetragon-rthooks/cmd/oci-hook/main.go、cel.go;contrib/tetragon-rthooks/cmd/nri-hook/main.go - Helm @
tag:
install/kubernetes/tetragon/values.yaml(rthooks.enabled、interface、failAllowNamespaces、installDir) - OCI Runtime Spec:POSIX platform hooks;containerd NRI 文档(1.7 引入,2.0 默认启用)
论文 / 谱系
- Garfinkel, T. (2003). Traps and Pitfalls. NDSS 2003. 策略判定与操作完成之间的窗口
- Wright, C. et al. (2002). Linux Security Modules. USENIX Security 2002. 对象级 hook 谱系(内核过滤侧,不是 OCI hook 本身)
站内对照
实验台账
- 本篇无集群实测;不粘贴 oci-hook JSON 日志当本机输出。安装命令以 tag 文档为准,可在声明的 CRI-O/containerd 环境执行。
→ 上一篇:对照替代路径 · 下一篇:选型收束与开放问题 · 系列目录
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【Tetragon / eBPF】K8s 身份感知策略:Namespaced、选择器与 hostSelector
钉清 TracingPolicyNamespaced、podSelector、containerSelector 与 v1.7.0 hostSelector(null 与 {});改编官方组合表;说明无 OCI hook 时 API server best-effort,以及 policyfilter 关闭时的加载失败语义。
【Tetragon / eBPF】排障坐标系:无事件、策略不生效、导出缺口、误阻断与血缘空洞
按五轴映射 Tetragon v1.7.0 故障:无事件落在 Hook/BTF/加载,策略不生效落在 collectionKey 撞名与 policyfilter,JSON allow/deny 不等于 gRPC,Override 依赖 CONFIG_BPF_KPROBE_OVERRIDE,血缘空洞来自 execve_map 未命中与 procFS 回填。
【Tetragon / eBPF】运行时安全全景:缺口、五轴坐标系与 16 篇路线
相对 Cilium 数据面、可观测对照与 Falco 口号,钉清 Tetragon v1.7.0 运行时安全内核缺口;定义进程血缘、Sensor/Hook、策略加载、过滤分层、强制执行五条坐标系,给出 16 篇阅读路线。
【Tetragon / eBPF】进程模型:exec_id、血缘、in_init_tree 与 procfs 回填
钉清 Tetragon v1.7.0 的进程身份:process_exec/exit 字段、exec_id 如何由 node:ktime:pid 生成、execve_map 与用户态 cache 的分工、in_init_tree 如何区分 kubectl exec 注入,以及 fork 时父不在 map 则子不入图的失败路径。