5.2 资源限制如何防止拖垮内核


5.2 资源限制如何防止拖垮内核

本节摘要:验证器挡住越界,挡不住你申请两千万条目的哈希表,也挡不住你在每个系统调用上走完接近上限的指令。memlock、Map 容量、指令数、栈大小、运行时复杂度,是第二套刹车。eBPF 安全包含“不会写坏内存”和“不会把节点合法地饿死”两层。

核心问题

阅读完本节,你应当能够:

  1. 列出加载期与运行期主要资源上限
  2. 解释为什么 Map 内存会先于 CPU 把你卡住
  3. 设计计数与事件的预算,而不是“先开最大”
  4. 把资源失败和验证失败从同一句 invalid argument 里拆开

通过验证的程序仍可能是一台合法的吸尘器:每个包查三张大表,每个系统调用送 4KB 事件。内核必须在证明安全之后继续记账。

一、两层刹车

加载期。 指令条数、验证状态数、程序大小、Map 的键值尺寸和条目上限、锁定内存额度。超了,加载失败。这是好失败:还没挂上。

运行期。 单次执行的复杂度要可预测;Map 插入在满员时失败;环形缓冲区满了丢事件;某些 Helper 有速率限制。运行期失败往往静默,程序还在,数据没了。

医院监护仪不会因为“电极贴法安全”就允许它以每秒百万次写盘。病房的电源和护士注意力也是资源。eBPF 的资源限制就是病房配电。

限制 主要发生在 超限表现 处理
指令数与复杂度 加载 验证拒绝 拆程序、减循环
栈大小 加载 验证拒绝 少放局部大结构
memlock / cgroup 内存 加载或扩表 创建 Map 失败 降容量、提额度、算预算
Map 条目 运行 插入失败 告警、限基数、LRU 明确语义
环形缓冲 运行 丢事件 丢计数、降采样、加大缓冲
跟踪打印 运行 被限速或禁用 不要当生产日志

二、内存预算要先于功能清单

连接跟踪表最容易把节点吃满。假设每条 128 字节,一百万连接是一百多兆,再乘 per-CPU 或副本,再加上内核自己的连接表。eBPF Map 不是“再开一块无限缓存”。

预算算法很土,但必须写在设计里:

条目上限 × 键值对齐后大小 × 副本或 CPU 数 + 程序与 JIT 映像 + 环形缓冲 = 每节点 eBPF 内存 对照 memlock 与节点余量

开发机 ulimit 很大,生产容器默认很小。于是出现“我家能加载,流水线不能”。这不是验证器问题,是额度问题。第 8 章部署要把额度做成模板,而不是每个操作员手调。

⚠️ 常见坑:把 max_entries 写成一个“足够大的整数”以求永不插入失败。你把 OOM 从应用层搬到内核记账层,排障更难。
💡 关键直觉:宁可插入失败并告警,也不要默默占掉节点上给业务留的内存。失败可见,膨胀不可见。

三、时间预算:热钩子上的指令就是延迟

XDP 和系统调用入口上,多一次哈希、一次字符串扫描,都会变成 P99。验证器保证你停得下来,不保证你停得快。复杂度上限按“最坏路径指令数”想,不要按“常见路径很快”想。攻击者会打你的最坏路径。

实践:

  • 热钩子:一次 lookup + 整数运算,尽量不送事件
  • 次热:有界循环扫描固定大小头
  • 冷路径:超阈值才拷栈、才提交 ringbuf

多程序叠加时,内核会在同一事件上跑多条。你的程序“很轻”乘以十个团队各挂一条,节点照样变慢。平台要有挂钩清单和预算,不能只审单个程序。

问题:提高 memlock 是不是就可以把表开很大?

额度只是许可。物理内存、缓存污染、插入延迟不会一起消失。把表开到接近节点内存,是在用 eBPF 做数据库,这不是它的工作。

问题:资源限制会不会让验证器变严?

两条线。验证器管证明;记账管配额。有时看起来像一次失败,实际是创建 Map 时额度不够。日志和 errno 要一起看。

四、给一套探针做资源预算表示例

假设某节点 32 核、64 吉字节内存,计划常驻三套程序:系统调用延迟直方图、XDP 丢包、cgroup 连接限制。预算可以写成:延迟程序 per-CPU 数组可忽略;事件环形缓冲 8 兆,丢失告警阈值每秒 1000;XDP 无大表;连接 HASH 20 万条目,每条约 64 字节,约 12 兆,再加余量到 20 兆。三套合计远小于节点内存的百分之一,但要写进目录,防止第四套“临时”表再来 200 万条目。

CPU 预算按最热钩子算。系统调用跟踪若过滤后每核每秒 2 万次、每次 200 纳秒,大约占用不到百分之一核,可接受。若有人关掉过滤,次数跳到 50 万,占用变成不可接受。所以过滤不是功能,是预算的一部分,开关默认必须开着。XDP 程序每次必须短到能在线速下忽略,否则宁可不挂。

memlock 不要设成无限。无限等于取消第二道闸。设成预算的两倍,让意外的新表在加载失败,而不是在页回收里表现为全节点抖动。失败可见,抖动不可见。容器的记账若和主机 memlock 不一致,以更严的那档为准,并在文档里写明,避免开发者在主机测通、在容器挂不上。

资源告警要有所有者。Map 内存、插入失败、丢失计数,三条告警路由到程序所有者,而不是泛泛的内核组。内核组无法知道你的连接表是不是应该有 20 万。所有权与第 8 章清单是同一件事的资源面。

压测时把预算当断言:超过 CPU 或内存阈值即失败,即使功能正确。功能正确但超预算,对生产来说就是不正确。这能挡住“先上线再优化”的探针,那些探针很少真的会再优化。

现场笔记:临时表变成常驻内存

应急时开了 200 万条目的 HASH,“先扛过今晚”。今晚过了,表还在,节点内存水位永久抬了一截,回收更勤,业务尾延迟变差。临时没有超时,就是永久。后来临时表必须带 TTL 和所有者,到期由巡检卸掉,卸不掉升级为事故。

memlock 在容器里比主机严,开发机测通、流水线失败。有人把流水线额度调成无限,问题消失,生产容器又失败。正确是让开发容器和流水线、生产使用同一档额度。三套额度是三套现实,程序会在夹缝里假装健康。

CPU 预算没当断言,功能测试全绿,上线后最坏路径被打满。后来压测把最坏路径当必测:构造深嵌套或大表满员,超预算即失败。功能正确但超预算,对生产就是不正确。这句话写进门禁比写进周报有用。

延伸讨论:预算当断言,临时当有期合同

功能测试绿而超预算,对生产就是红。压测必打最坏路径。临时表必须到期和所有者,到期卸不掉当事故。三套额度对齐:开发容器、流水线、生产,取最严。无限 memlock 是拆掉第二道闸。告警有所有者,内核组无法知道你的表是否该有二十万条。CPU 预算按过滤打开时的频率算,过滤是预算的零件,默认必须开。关掉过滤等于超预算运行。超预算运行碰巧没炸,不能写进月报当优化完成。月报要写预算数字和实测数字的差。差在变大,就要减功能,不是加额度。加额度是把房间的保险丝换粗,火灾还在。保险丝粗了,烧的是整层。整层是节点上的业务。业务比探针贵,所以探针让路。让路写在门禁里,不写在口号里。

对照清单

  1. 安全证明不等于资源安全,合法程序仍能吸干内存和 CPU。
  2. Map 预算等于条目乘大小乘副本,要写进设计并对照 memlock。
  3. 宁可插入失败并告警,也不要默默占掉业务内存。
  4. 热钩子按最坏路径计延迟,攻击者会走那条路。
  5. 十条很轻的探针等于一条很重,平台要有总预算不只审单个程序。
  6. 临时表必须到期和所有者,今晚扛过去会变成永久水位。
  7. 开发容器流水线生产三套额度对齐取最严,无限额度是拆闸。
  8. 过滤是 CPU 预算的零件,默认必须开,关掉过滤等于超预算运行。
  9. 功能正确但超预算对生产就是不正确,压测把预算当断言。
  10. 丢失计数、插入失败、Map 内存三条告警路由到所有者不是泛内核组。
  11. 提高 memlock 不会取消缓存污染,把表开到接近节点内存是在用 eBPF 做数据库。
  12. 资源失败和验证失败要从同一句错误里拆开,日志和 errno 一起看。

资源限制看起来像在找茬,其实在替你挡合法灾难。合法灾难比非法越界更难复盘,因为它不 oops,只是慢慢把业务挤瘦。挤瘦会先被当成业务增长。增长会带来更多连接,更多连接把表撑得更满。满了还不告警,检测器就瞎了。瞎了还以为健康。健康的假象是资源闸门存在的理由。把闸门拆掉换无限额度,等于允许假象生长。生长到页回收打颤时,再找 eBPF 的人,已经晚了一个容量规划周期。周期以周计。故障以分钟计。对不上的时候,先怪额度,再怪自己没把预算当断言。

\n\n## 课堂补充\n\n合法灾难不oops只挤瘦业务。满员不告警检测器就瞎。临时无到期就是永久。三套额度取最严。过滤是预算零件。超预算的正确仍是错误。告警路由到所有者。无限memlock是拆闸。最坏路径必测。多程序要总账。eBPF不是数据库。失败可见膨胀不可见。\n\n\n\n## 生产验收条\n\n1. 围绕「资源限制如何防止拖垮内核」,生产验收只认能关掉、能计数、能对账,不认口头保证。\n2. 围绕「资源限制如何防止拖垮内核」,把所有者、版本、卸载方式写成清单三件套,缺一视为幽灵。\n3. 围绕「资源限制如何防止拖垮内核」,对照实验必须能回答开关前后业务指标动了没有。\n4. 围绕「资源限制如何防止拖垮内核」,失败要分类到验证、额度、挂载、未触发、丢失,禁止只丢一句笼统错误。\n5. 围绕「资源限制如何防止拖垮内核」,热路径默认克制,阈值之后才出栈,出栈之前先证明钩子活着。\n6. 围绕「资源限制如何防止拖垮内核」,和平台已有挂钩点冲突时先登记再加载,禁止手工抢挂。\n7. 围绕「资源限制如何防止拖垮内核」,内核版本矩阵没跑绿就不能把功能写成必成功路径。\n8. 围绕「资源限制如何防止拖垮内核」,回滚必须碰到内核对象,心跳停止才算撤回成功。\n\n## 一节小结

  • 安全证明不等于资源安全:合法程序仍能吸干内存和 CPU。
  • 加载期失败优于运行期静默:额度问题要在挂载前暴露。
  • Map 预算必须写进设计:条目 × 大小 × 副本。
  • 满员要可见:插入失败告警,避免假健康。
  • 热钩子按最坏路径计延迟:攻击者会找到那条路。
  • 多程序叠加要平台级预算:单个很轻,十个就不轻。

下一章离开内核契约,看人怎么把这些程序写出来、选哪套工具,才交得出去。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U