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

【Tetragon / eBPF】Runtime Hooks 与容器边界:OCI hook、NRI 与 API server 窗口

文章导航

分类入口
kubernetessecurity
标签入口
#tetragon#runtime-hooks#oci#nri#cri-o#containerd#policyfilter#v1.7.0

目录

身份感知 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 Policiesk8s-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-hooktetragon.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.goRuntimeHook 把请求交给 pkg/rthooks.Runner.RunHooksRunner 对已注册的 callback 全部执行(一个出错不短路其余);任一失败则 gRPC 向客户端返回错误。pkg/rthooks 包注释写明:callback 在 init() 里用 RegisterCallbacksAtInit 挂到 globalRunner,再交给 gRPC server。

真正把容器登记进过滤状态的是 pkg/policyfilter/rthooks/rthooks.gocreateContainerHook:解析 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-hooksrthooks.ociHooks.hooksPath 默认 /usr/share/containers/oci/hooks.d。安装文档用 rthooks.enabled=true 加上该 interface。

containerd:有 NRI

文档写:containerd 1.7 引入 NRI2.0 起默认启用。tetragon-nri-hook 是一个 NRI 插件:在 CreateContainer 里向容器 spec 注入 OCI hook,指向同一份 tetragon-oci-hook(默认路径 /opt/tetragon/tetragon-oci-hook),并分别挂 createRuntimecreateContainer 两个 hook 名(contrib/tetragon-rthooks/cmd/nri-hook/main.go)。Helm rthooks.interface=nri-hookrthooks.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 / failTestProgcontrib/tetragon-rthooks/cmd/oci-hook/main.gocel.go)。createRuntimehookRequest 出错,则跑 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 failedconnecting 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)

论文 / 谱系

站内对照

实验台账


上一篇:对照替代路径 · 下一篇:选型收束与开放问题 · 系列目录

读完这篇,下一步读什么

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


By .