文章历史样本中,子进程软值为 32,编号 0 到 31 的表项已占用,得到 EMFILE;耗尽前建立的管道向仍能打开文件的控制进程报告。

在实际失败的进程中诊断描述符耗尽

进程使用文件描述符访问的不只是普通文件。网络套接字、管道、事件接口和复制出来的引用,都要占用描述符表中的入口。RLIMIT_NOFILE 约束新分配可以使用的编号,其软值比允许的最大编号大一。因此,即使机器仍有充足磁盘空间和内存,服务也可能因为自己的表项额度用尽而无法继续接受工作。排查时必须先确认耗尽的是哪一种资源。 检查真正失败的进程。EMFILE 属于进程边界,ENFILE 属于系统边界;预先打开的控制通道可在新分配失败时继续工作。 坏掉的样子 EMFILE 表示调用进程触及适用的描述符限制,ENFILE 则表示系统打开文件资源耗尽。这两种错误需要沿不同方向调查。提高机器级文件设置,不会自动改变失败进程的软值;扩大某个应用的限制,也不能修复系统级资源短缺。把日志里的错误名和实际限制对应起来,比看到“文件太多”就随意调整参数更有用。 耗尽还会破坏诊断。打开日志文件、枚举目录本身,也可能需要新的描述符。如果应用直到额度用光后才尝试读取 /proc/self/fd,检查动作就可能一起失败。已经存在的描述符通常仍可使用,因此预先建立的控制管道有价值:它让进程无需再分配资源,也能够报告当前错误,而不会把诊断失败误认为路径消失。 描述符数量持续增长,需要结合上下文解释。它可能反映正常并发增加、连接池逐渐填满,也可能是某些所有者忘记关闭引用。曲线本身不能证明泄漏。应同时比较描述符类型、工作负载,以及工作排空后的数量,再沿超出预期寿命的引用追踪所有权。只有把数量变化与资源生命周期关联起来,才能选择正确修复。 快速验证 把下面脚本保存为 exhaustion.py,在一次性 Linux 环境中运行 python3 exhaustion.py。此前记录的实测使用 Linux 6.12.76 和 Python 3.11.2。这里的描述符版本改用管道代替套接字,并为等待就绪设置截止时间。控制进程启动独立子进程,后者只把自身软上限降到三十二;继承的软硬值原本都必须允许这个设置: ...

十月 27, 2026 · 1 分钟 · XB Field Notes
受限 cgroup 包围组内候选进程,无关宿主进程留在边界之外;宿主空闲内存不能单独判断控制组 OOM。

排查 OOM 应先确认分配范围

当一次分配在相关限制范围内无法满足,适当的回收尝试也不能解决问题时,Linux 可能调用内存不足终止机制。它是最后阶段的恢复手段,不是理解业务重要性的调度器。有些分配会直接失败而不杀进程,配置也可能使系统进入恐慌处理。因此,排查应先识别分配范围与实际事件,不能一开始就认定整台机器的每一字节内存都已经用完。 先看范围,再看评分 全局内存短缺与内存控制组短缺是两回事。某个控制组可以在主机仍有可用内存时达到自身上限;它的 OOM 处理不能任意挑选组外进程来解决组内问题。允许使用的内存节点等其他限制也可能影响分配。因此,主机剩余内存图表不足以证明或否定某次失败,更不能在没有检查事件范围时,把所有容器异常都归类为全局容量不足。 在适用的选择规则内,内存占用与调整值会影响牺牲者选择。内核并不知道哪个进程商业价值高、哪个空闲服务可以丢弃,也不知道哪一个业务必须持续可用。一个体积很大的数据库可能被选中,即使压力也来自许多小工作者。procfs 中的 oom_score 是可观察数值,不是承诺把所有可见进程排序以后,就能准确预测任何内存域下一次会杀谁。 理解调整值,不许诺必然存活 oom_score_adj 的范围为负一千到正一千。正值增加被选中的倾向,负值降低它;负一千会把该任务排除在这项内核 OOM 选择之外。一般负值并不保证存活,这个设置也不能阻止显式信号、服务管理器或独立用户态内存压力策略终止任务。调整分值不会增加容量,也不会修复泄漏。把它解释成“重要进程已经免疫所有内存故障”,会造成危险的运维误判。 保护所有任务不是可行的容量方案。如果没有合适的牺牲者,不能假定内核会越过控制组边界,去杀一个无关的监管进程。整组终止与恐慌设置需要分别分析。调整值可以被子进程继承,而持久服务设置应归入监管器配置;修改之前需要明确授权并验证适用范围。下面实验不会改动这些值,也不制造内存压力,避免把接口学习变成真实故障。 坏掉的样子 反复重启可能掩盖持续泄漏。如果同一个工作负载不断增长直至失败,增加内存也许只是延长两次失败之间的间隔。应关联分配速度、常驻和记账内存、负载大小、回收及交换活动。单个进程的常驻大小不能完整表示控制组全部费用或共享内存关系。降低上限可以约束增长范围,却也可能让应用更早失败;不能只观察邻居未被终止,就认为业务恢复问题已经解决。 退出状态一百三十七与 SIGKILL 相符,但并不是 OOM 专属证据。应查看运行时的终止原因、控制组内存事件计数和操作员可见的内核记录。分配失败、进程被杀、内核 OOM 和用户态压力管理器终止,是需要分开的情况。看不到内核日志,也可能只是当前观察范围受限,不能直接证明没有事件发生。诊断必须说明证据来自哪里、有哪些缺口。 评分会随内存和范围变化。读取 procfs 的过程中进程可能退出,容器内的进程视图也可能隐藏主机的大部分任务。一次成功读取并不是原子的整机快照。分析真实失败时,应保留事件时间与受影响的控制组,然后比较相同范围内的观察,而不是拿事后另一个进程的分数去解释之前的牺牲者选择。 OOM 范围及事件上下文先于评分排名;此处 PID 排序是虚构展示夹具。 快速验证 保存为 oom_observe.py,使用 Python 3.9 或更新版本执行 python3 oom_observe.py。程序只读取自身进程的两个评分文件,不创建压力负载,不修改任何限制或策略。两条确定性断言仅对明确虚构的记录测试展示顺序,实时读取和模型检查会分别报告结果,避免把一种成功误当成另一种验证。 ...

十月 26, 2026 · 1 分钟 · XB Field Notes
报文经过输入链优先级 0 的接受后,仍到达优先级 10 的终止丢弃。

防火墙策略必须匹配报文路径、地址族和链顺序

防火墙在网络栈的特定位置执行报文策略。一条有用规则因此不只包含端口,还要明确地址族、挂钩、方向、连接状态和相对其他规则的位置。nftables 基础链按指定优先级接入 Netfilter 挂钩。报文实际经过的路径,决定它会遇到哪些裁决,而不是运维文档中描述的先后顺序。审阅策略必须保留这些执行上下文。 坏掉的样子 规则位于错误挂钩时,预期流量可能完全不受影响。INPUT 处理交付给当前命名空间本地的流量,FORWARD 处理经过它转发的流量,OUTPUT 面向本地产生的流量。宿主的容器转发路径,不一定就是本地输入路径。假定宿主级规则保护所有附近服务之前,应先查清实际流量经过哪个命名空间和挂钩。 地址族和方向错误也容易被掩盖。允许服务 TCP 目的端口,不同于匹配通常临时分配的客户端来源端口。只覆盖 IPv4 的策略不会自动覆盖 IPv6;IPv6 通信还依赖邻居发现等 ICMPv6 功能,丢弃所有非 TCP 报文,可能让 TCP 端口明明被允许的连接仍然失败。端口规则不能脱离链路和协议所需控制流量解释。 nftables 的 accept 不一定是所有基础链中的最终裁决。同一挂钩上更晚优先级的另一条链,仍能丢弃报文;drop 则会结束其遍历。只审阅一个表或一条链,可能看到允许计数器增长,却无法解释客户端为何失败。应查看全部适用规则集及其挂钩优先级,不能把第一条可见 accept 当成系统已经最终放行的证明。 后续输入链可丢弃先前已接受的报文。端口结果是实验预期;当前集成仍待验证。 快速验证 将脚本保存为 check-firewall.py,只在获得授权、装有 Docker 的一次性虚拟机中执行 python3 check-firewall.py。历史文章报告使用 nftables 1.0.6 和 Python 3.11.16;保留验证文件中未找到原始集成日志,本轮没有重跑。需启用 Python 断言;清理仅尽力执行。两个命名空间通过一次性 veth 使用 IPv4 与 IPv6 通信。NET_ADMIN 和 SYS_ADMIN 是重要权限;规则限定在命名空间内,并不让容器变成可靠安全边界。设置失败时不要削弱宿主保护。镜像摘要也不会冻结 apt 仓库,指定版本的软件包以后可能无法取得: ...

七月 1, 2026 · 3 分钟 · XB Field Notes