内核级的真相:为什么 eBPF 正在取代基于用户空间的 Agent 成为安全可观测性的首选
一、问题的起点:监控和被监控对象在同一权限层
Kubernetes 集群里最常见的安全监控形态是 sidecar 容器或 DaemonSet。它们的本质都是用户空间进程,和它们要监控的工作负载共享同一个内核、同一套 capability、同一组可见的 PID,甚至同一个网络栈。这看起来只是部署拓扑的问题,实际上是结构性的安全弱点。
作者 Niranjan Sharma 复盘的一次生产事故就是典型样本:攻击者拿到容器的 root 权限之后,第一件事不是外连、不是提权,而是 kill -9 $(pgrep security-agent),然后 truncate 掉日志文件,再通过 memfd_create() 加载无文件载荷,整个攻击链在监控视角下完全不存在。memfd_create() 不落文件系统,文件完整性监控看不到;进程注入能伪装在 Agent 已白名单化的受信任二进制 PID 之下;日志规避利用的是"恶意行为发生"和"日志上报"之间的时间窗口,先毁证据再开始表演。
此外,这类 Agent 在性能账上也不便宜。为了检查网络流量,它要把每个连接代理到自身,数据包在用户空间/内核空间之间反复跨越,加上序列化、解析、传输,集群里专门跑安全的 CPU 预算能超过业务负载本身。
二、eBPF 是怎么把问题翻过来的
eBPF 不是一个新工具,而是一个"无需编写内核模块,在内核里跑受沙箱保护程序"的通用插桩框架。BPF 名字的来源(Berkeley Packet Filter)已经不重要,关键是它今天能做到的三件事:
- verifier 在加载前做静态分析:证明程序不会导致内核崩溃、不会越权访问内存、不会无限循环,验证不过的程序根本不会运行。
- 运行在内核上下文:没有用户/内核切换,没有额外的代理开销,可以直接读内核数据结构。
- 挂点极广:成千上万的内核函数、系统调用、网络事件、跟踪点都可以挂探针。
verifier 的"过度严格"经常被吐槽——一个完全合法的程序被它拒绝,你不得不为了证明"我不会死循环"而把代码重构成它能看懂的形态。但也正是这种保守性,让 Meta、Google、Netflix 敢在大规模生产内核里跑 eBPF。Meta、Google、Netflix 愿意用,本身就是对 verifier 边界条件经过反复工程验证的背书。
在安全场景里,探针最常挂的位置是系统调用接口。connect()、execve()、open() 这些函数是每个进程——包括攻击者进程——要执行特权操作时都必须跨越的边界。探针在那个边界上抓参数、抓进程/线程 ID、抓容器 ID、抓 K8s Pod 元数据、抓 capabilities、抓父进程链。攻击者要在容器里把 root 拿到才能 kill Agent,要在内核里 root 才能禁用 eBPF 探针,后者完全是另一个难度等级的问题。
三、收益:不只是安全,还有算账
把一整套用户空间安全 Agent 替换为单个 eBPF Agent 的组织,报告的 CPU 消耗下降在 60%–80%。背后有两笔账:
- CPU 账:用户空间 Agent 把每个数据包都从内核搬到用户空间解析一遍,eBPF 在内核里做预过滤,真正重要的事件才离开节点。
- 数据量账:SIEM 通常按 GB 计费,90% 的事件在摄取之后被直接丢弃。eBPF 让过滤前置到内核,SIEM 摄取成本随之下浮。
另一个常被忽略的工程性收益是结构化上下文。同样是"进程 X 调用 connect() 访问 169.254.169.254",裸 syscall 视角和 "prod 命名空间下 payment-api Pod 试图访问云元数据服务" 的 K8s 视角,在响应链路上的价值差距是 15 分钟交叉排查和立刻行动的差距。
四、对比表:用户空间 Agent vs eBPF 内核级探针
| 维度 | 用户空间 Agent (sidecar/DaemonSet) | eBPF 内核级探针 (Falco/Tetragon) |
|---|---|---|
| 权限层级 | 与被监控进程同层(用户空间) | 内核态,被监控进程不可达 |
| 容器逃逸后的存活 | 极易被 kill -9 / truncate | 需先拿到宿主机内核权限才能禁用 |
| 内核版本要求 | 无 | 4.15–5.7 之间的多个版本逐步就位,主流 K8s 发行版 5.4+ 已满足 |
| 上下文切换开销 | 高(每个包跨用户/内核) | 几乎为零(内核内执行) |
| CPU 开销 | 高,可超过业务负载 | 比前者低 60%–80% |
| 遥测数据量 | 大量冗余事件上行 | 内核预过滤后只发关键事件 |
| 主动阻断能力 | 弱(事件回传到控制面再决策) | 强(Tetragon 可微秒级 SIGKILL) |
| 部署侵入性 | 低,常规容器 | 早期需 privileged: true,后续收紧到 CAP_BPF 等最小集 |
| 生态成熟度 | 商业方案历史悠久 | Falco(CNCF 毕业)、Tetragon(Cilium 子项目)已生产可用 |
五、生产落地的三段式推进
不管厂商文档怎么宣传,直接上强制执行都会出事。作者给出的路径是根据信心程度推进,不是根据日历表推进:
阶段 1:观察和学习(被动模式)
以 DaemonSet 部署 Falco 或 Tetragon,只观察不阻断。部署需要 hostPID: true、hostNetwork: true、privileged: true,并挂载 /sys/fs/bpf 和 /sys/kernel/debug。Falco 的官方 Helm chart 已经把这些处理好了,第一次部署建议直接用它。
这个阶段的目标是建立基线:每个服务运行哪些二进制、建立哪些网络连接、触碰哪些文件、正常的进程树形态。事件流先写廉价归档存储,不要灌进实时分析平台。几轮部署周期后基线稳定,再进下一阶段。
阶段 2:基于基线写告警规则(行为检测,不是签名匹配)
Falco 的规则格式是声明式的,关键是把 K8s 上下文带进 condition 里。下面是原文中两条典型规则,一条针对"支付服务里出现未审批进程",一条针对"容器访问云元数据服务":
- rule: Unexpected Process in Payment Service desc: Detect execution of binaries not in the approved list condition: > spawned_process and container.name startswith "payment-" and not proc.name in (java, jcmd, jstat) output: > Unexpected process executed in payment container (user=%user.name container=%container.name process=%proc.name cmdline=%proc.cmdline parent=%proc.pname) priority: WARNING tags: [container, process, payment] - rule: Container Accessing Cloud Metadata Service desc: Detect attempts to access instance metadata condition: > outbound and fd.sip = "169.254.169.254" and container.id != host output: > Container attempted metadata service access (container=%container.name pod=%k8s.pod.name namespace=%k8s.ns.name dest=%fd.sip) priority: CRITICAL tags: [network, cloud, metadata]
这个阶段必须花真功夫调优,逐条审查告警、理解误报、消除已知安全模式。告警量可控、并且用已知攻击场景验证过规则之后,才能进下一阶段。
阶段 3:强制执行(微秒级响应)
Tetragon 可以借助 bpf_send_signal() 在违规系统调用完成之前就向进程发 SIGKILL。一个典型场景:容器 connect() 到 169.254.169.254 → 探针截获 → 策略评估 → SIGKILL 立即发出 → 系统调用根本没完成 → 告警发出。整个过程是微秒级,不是传统事件响应的分钟甚至小时级。
这一阶段高度依赖纪律性。一次误报杀掉合法进程,就是生产事故。阶段 1、2 存在的意义,就是给这一步攒够置信度,别让它反过来成为新风险。
六、工具选型:Falco、Tetragon 还是商业方案
- Falco:大多数团队的起点。CNCF 毕业项目,社区大,默认规则集参照 MITRE ATT&CK 覆盖反向 Shell、容器逃逸、敏感路径访问等。最大价值是给事件附加 K8s 上下文。
- Tetragon:Cilium 子项目,需要主动强制执行(在内核中同步阻断)时首选。代价是社区更小、与 Cilium 栈绑定更紧。
- Sysdig / Datadog / Wiz:商业厂商,均已基于 eBPF 重构 Agent。如果已经在用其中一家,先盘清现有方案的 eBPF 能力边界,不一定急着引入新工具。
七、eBPF 部署本身的安全:Verifier 只管字节码,运维安全靠你自己
eBPF 程序运行在内核、权限极高,部署侧的安全姿势不能省:
- 加载 eBPF 程序需要
CAP_BPF(5.8 之前要CAP_SYS_ADMIN)。 - 早期可以先用特权容器跑起来,但要尽快收紧到最小能力集,通常是
CAP_BPF+CAP_PERFMON+CAP_SYS_RESOURCE。 - 用 OPA Gatekeeper、Kyverno 这类 admission controller 把高权限工作负载限制在安全命名空间内。
- 锁定哪些 service account 能部署高权限容器,并监控该命名空间的未授权变更。
- Agent 镜像固定到已验证的 digest,不要用可变 tag。
八、结论与适用边界
应用层日志不会消失,业务调试、请求链路追踪依然要靠它。但在安全场景里,攻击者的第一步往往就是先关掉你能看到的层——eBPF 把可见性建在容器级攻陷不可轻易触及的内核系统调用边界上,这是它和传统 Agent 的根本差异。
适用场景:
- 多租户 K8s 集群,容器逃逸是真实威胁
- SIEM 摄取成本高企,日志噪声成灾
- 需要在毫秒/微秒级对已知违规模式做主动阻断
- 节点内核 ≥ 5.4,主流发行版已默认满足
不太适用 / 谨慎评估的场景:
- 单体应用 + 传统 VM 部署,系统调用面窄,投入产出不成正比
- 强合规场景需要"应用层审计",eBPF 替代不了业务日志
- 集群内核版本过旧(< 4.15 关键能力缺失),需要先做内核升级
上手建议:在预发布(staging)集群里以纯观察模式部署一次 Falco,30 分钟,你当前监控视野与 eBPF 在 syscall 层揭示内容之间的差距,会比任何论证都有说服力。
我的解读:对 PC 前端工程师的工程化启示
这篇文章表面是 K8s/内核/安全 SRE 的话题,但背后有一类工程方法论对前端工程化同样适用。
第一,可观测性必须建在攻击者/异常者够不到的层面。前端链路里,线上数据采集、错误监控、性能打点往往和业务代码共享同一个 JS Runtime、同一份 bundle,一旦页面被 XSS 注入或被浏览器扩展污染,你上报的错误/性能数据本身就是失真的。和 eBPF 把探针下沉到系统调用边界的思路一样,核心数据采集应该往"业务代码动不到"的层走——例如对关键转化漏斗用服务端日志做对账、对核心 Web Vitals 字段用 RUM + 服务端采样做交叉验证、对支付/鉴权这类关键路径的事件流走独立的、可被业务代码注入影响的脚本碰不到的上报通道。
第二,"先观察、再告警、最后强制执行"是一条普适的灰度纪律。前端的功能发布/灰度/回滚也适用:一个错误熔断/降级策略,直接 100% 开关推到生产,本质上是"强制执行"前的告警调优缺位。前端的做法可以是 feature flag 先 1% → 内部灰度 → 告警/指标稳定后逐步放量 → 最后才考虑硬熔断。和文中"信心程度推进,不是日历表推进"是同一种节奏感。
第三,工具链选型要算"数据噪声账"。前端监控栈里最有价值的优化,往往不是"多采一个字段",而是"在前置过滤层把噪声砍掉再上行"——这和 eBPF 在内核里做预过滤、让 SIEM 只看到关键事件,是完全同构的工程决策。把过滤责任下沉到采集端(SDK/Worker),让上报链路和告警系统只处理"值得看的事件",这是把"按 GB 计费"从成本问题变成质量问题的关键。