系统、网络与交付事故¶
cgroup CPU 限流引发周期性长尾¶
相关技术说明
容器配置 2 CPU quota/100ms period1 时,可在周期早段因多线程突发消耗完额度,随后被 throttle 到下一周期,即使宿主机和分钟级平均 CPU 都不满。cpu.stat 的 nr_throttled、throttled_usec 应与请求延迟、运行队列和应用 profile2 对齐。CPU request 与 limit3 分别影响调度保障和硬周期上限;提高 limit、减少并行和水平扩容的资源副作用不同。
根因是新版将压缩和 JSON 序列化并行化,瞬时需要 6~8 核,但 limit 未随并发模型校准。促成因素包括压测未使用生产 cgroup4、监控只看平均 CPU、发布门禁没有 throttling。检测缺口是缺少每容器限流时间比率和 P995 的关联告警。
实践经验
- 影响: 订单 API P50 基本不变,P99 从 180ms 升至 1.8s,约 7%请求触发上游重试;持续 34 分钟,无数据丢失。
- 时间线/证据链: 10:02 发布 10%;10:06 P99报警;10:09 节点 CPU 仅 52%排除整机饱和;10:13
cpu.stat限流时间与尖峰同步;10:17 profile 证实压缩并行;10:21 在同限额压测复现。 - 决策与止血: 立即停止扩量,关闭并行压缩 flag,并把健康旧版本扩到 150%。未全局取消 limit,避免同节点其他服务受扰。副作用是带宽增加、节点和下游连接压力升高,均设临时告警。
- 永久修复: 按每请求 CPU 峰值限制 worker;重新设置 request/limit与副本容量;CI性能测试固定生产配额;金丝雀检查 P99、每请求 CPU 和 throttled ratio。
- 验证与复盘: 回放峰值 2 倍流量一小时,限流时间低于基线且订单正确率不降;在下一次演练中主动把 limit 降半,确认自动暂停。行动项有负责人、截止日与“不得只调大配额”的设计审查。
inode 耗尽与已删除日志句柄叠加导致节点不可写¶
相关技术说明
字节空间、inode6、配额和已删除但仍打开的文件是独立维度。df -h 不能覆盖 inode,df -i 才能发现小文件耗尽;lsof +L1 揭示 unlink 后仍占块的句柄。大目录全量扫描/删除本身会制造 I/O,日志轮转的 copytruncate 还存在丢失窗口。处理必须先停止写入源,再分批清理或让进程 reopen。
根因是调试 flag 为每个请求创建空追踪文件,同时 Java 日志进程未响应轮转后的 reopen,形成 inode 100%且部分块不释放。促成因素是日志与根盘共用、轮转只按字节、调试开关无 TTL。检测只覆盖磁盘字节 80%阈值,没有 inode、创建速率和 deleted-open bytes。
实践经验
- 影响: 三台节点无法创建临时文件,systemd 服务与容器拉起失败;已有请求部分可读,约 11%写请求失败,持续 47 分钟。
- 时间线/证据链: 14:20 出现
ENOSPC;14:23df -h仅 61%使最初判断受阻;14:27df -i100%;14:31 按目录抽样定位追踪小文件;14:36lsof +L1又找到 18GiB 已删日志。 - 决策与止血: 关闭调试 flag、摘除故障节点;将旧追踪目录同盘改名后限速批删,滚动重启日志持有进程释放句柄。没有直接递归删除活跃运行时目录。批删期间 I/O 仍升高,因此一次只处理一个节点。
- 永久修复: 追踪改成有界批量流并远端存储;日志独立分区、rename+reopen;开关带到期时间;建立字节、inode、文件创建率、deleted-open空间四类告警。
- 验证与复盘: 在预演节点生成百万小文件并模拟转发中断,确认水位降级优先丢 debug、业务仍可写;恢复后核对审计日志完整性。复盘新增“不要只看
df -h”值班卡和自动诊断脚本。
内存回收、OOM 与客户端重试形成重启风暴¶
相关技术说明
cgroup memory.max 是硬边界,memory.high 可在到达硬限前触发回收/节流反馈;memory.events 区分 high、oom 和 oom_kill。退出码 137 只有结合这些事件或内核日志才能认定 OOM7。内存 PSI8 some/full 能比剩余字节更早反映任务因回收停顿。在 Kubernetes 中,只有在 cgroup v2、兼容内核并显式评估启用 MemoryQoS 时,才应由 kubelet 管理 memory.high;不能由应用或运维脚本直接改 Pod cgroup。客户端在响应未知时重试会提高并发,再推高每实例工作集,形成正反馈。
根因是图片解码器升级后单任务峰值从 80MiB 增至 230MiB,而 worker 数与容器上限未变。促成因素有无限重试、readiness 在冷启动缓存未完成时即通过、所有副本同批更新。检测只看 RSS 平均,没有 memory.events、PSI 和队列年龄。
实践经验
- 影响: 图片处理成功率降至 62%,队列积压 18 万条;重复任务增加对象存储读取费用,部分前端图片延迟约两小时,无原始数据丢失。
- 时间线/证据链: 09:00 全量更新;09:04 首批 OOMKilled;09:07 重启副本接受积压流量再次 OOM;09:12
memory.events与输入尺寸相关;09:17 堆分析排除长期泄漏,峰值由单任务解码缓冲导致。 - 决策与止血: 停止发布,限制入口与 worker 并发,超大图片进入延迟队列,回退解码器。代价是队列 SLA下降,但避免反复重启;客户端重试上限降为一并加抖动。
- 永久修复: 流式/分块解码,按像素成本做加权 semaphore;以生产 request/limit 做压力测试;默认以队列背压、VPA建议和 OOM 事件门禁治理。若平台满足 cgroup v2/内核条件并决定试用默认关闭的 MemoryQoS,则由 kubelet 灰度设置
memory.high,同时监控节流和潜在活锁,禁止手工写 Pod cgroup;新版本逐批发布并预热后就绪。 - 验证与复盘: 用历史最大尺寸集合在两倍峰值回放,验证无 OOM且PSI在阈值内;故障注入杀掉实例确认任务幂等。复盘将“内存泄漏”与“并发峰值”作为两个假设分别取证。
集群 DNS 出现固定约 5 秒延迟¶
相关技术说明
数秒级固定延迟通常对应 stub resolver 对首选 nameserver 超时后重试/切换,但具体值依实现与配置。需要比较 getent 的真实 NSS 路径、dig @每个DNS、resolv.conf 的 search/ndots,并抓取 UDP/TCP 53。UDP响应截断后若 TCP/53 被阻断、网络策略漏放某个 DNS9 IP 或 conntrack 丢包,都可产生类似症状。
根因是一次策略生成器升级只允许第二个集群 DNS 地址,resolv.conf 中首选地址无响应,等待后才切换。促成因素是健康检查只探测 Service VIP 总体、节点缓存没有暴露逐上游指标、应用使用短名触发多次搜索域查询。检测缺口是没有每节点到每个 DNS 端点的合成探测。
实践经验
- 影响: 约 40%新建连接首次请求增加 5 秒,支付 P99 超 SLO;缓存命中请求正常,事故持续 28 分钟。
- 时间线/证据链: 16:11 P99报警;16:14 HTTP trace 显示 DNS 阶段;16:18 FQDN 仍慢排除 search 为主因;16:21 抓包见首选 DNS 无响应、次选立即成功;16:24 网络策略差异对应故障节点。
- 决策与止血: 回滚策略并临时将健康 DNS 排首位;没有把 resolver timeout 全局降到极低,避免网络短抖时误失败。对非关键请求启用已有缓存,接受短时记录陈旧。
- 永久修复: 策略模板覆盖所有 DNS IP 的 UDP/TCP 53;节点缓存记录逐上游延迟/失败;应用关键依赖使用 FQDN并审查 ndots;变更前做每节点合成解析。
- 验证与复盘: 主动阻断任一 DNS 端点,验证探测先告警且解析在预算内切换;测试大响应 TCP fallback。复盘补齐网络策略契约测试和明确的缓存陈旧度边界。
conntrack 与 NAT 端口耗尽造成跨协议随机失败¶
相关技术说明
conntrack10 为 NAT11/有状态防火墙维护会话,新流在表满时可能被丢弃;NAT 还受特定源/目标组合的可用端口限制。两者症状都可能是新 TCP、UDP/DNS 首包失败,但证据不同:前者看 count/max、insert failed、内核日志和状态分布,后者看 NAT port allocation error、源 IP/目标热点与 TIME_WAIT。fd 仍充足并不能排除这两类问题。
根因是新版 SDK 禁用连接池,并对连接超时立即重试三次,节点 conntrack 留下大量 SYN-SENT,单 NAT IP 到热点 SaaS 端点的端口也接近上限。促成因素是批任务与在线服务共享节点/出口,sysctl 容量沿用小规模默认,未定义重试预算。检测缺口是只监控 NAT 带宽,不监控端口和 conntrack 插入失败。
实践经验
- 影响: 外部支付查询、DNS 和少量内部新连接同时失败;已有长连接大多正常,约 8%请求受影响,持续 39 分钟。
- 时间线/证据链: 11:30 新 SDK 扩至50%;11:34 新连接错误升高;11:38 fd与带宽正常;11:41 内核提示 conntrack full,SYN-SENT占比异常;11:45 NAT指标显示到单目标端口分配失败,完成双瓶颈确认。
- 决策与止血: 回退 SDK、暂停批任务、熔断外部端点并增加一个受控出口 IP;仅适度扩 conntrack 上限且确认内存余量。未激进缩短 TCP超时,以免破坏有效慢连接。
- 永久修复: 统一复用 HTTP client,按正常流量比例设重试预算;在线/批任务分池和出口;基于连接率×寿命建容量模型;conntrack 状态与NAT端口失败作为扩容指标。
- 验证与复盘: 在隔离环境制造短连接和失效目标,确认限流先于表满、已有长流不受影响;容量测试覆盖最坏目标集中度。复盘将“连接容量”独立于带宽纳入架构审查。
Ingress 504 与多层重试压垮数据库¶
相关技术说明
504 表示某网关等待上游超过其预算,必须先确认生成层和阶段;延长 read timeout 只增加等待容量。若客户端、Ingress、sidecar 和应用都重试,额外流量会乘法放大。deadline 应端到端传播,一层承担主要重试;长同步报表更适合异步 operation。连接断开或超时后服务端可能仍在执行 SQL,必须支持取消或幂等查询。
根因是新报表 SQL 扫描量超预估,超过 Ingress 60 秒超时;客户端自动重试,sidecar又重试一次,数据库继续运行已断开请求,活跃查询倍增。促成因素是预发数据量过小、缺少 statement timeout和并发隔舱。检测只报5xx,没有 upstream time、仍运行的断开查询和重试放大系数。
实践经验
- 影响: 报表全失败并拖慢交易库,核心写入 P99 达 3 秒;未发生账务错误,但对账延迟,事故持续 52 分钟。
- 时间线/证据链: 13:00 功能开放;13:06 Ingress 504;13:10 应用日志显示同一幂等键重复;13:14 数据库活动查询数为入口请求近四倍;13:18 trace 证实多层重试且客户端断开后 SQL 未取消。
- 决策与止血: 关闭报表 flag、取消相关查询并暂停所有代理重试;交易连接池隔舱保留。未简单将网关超时提高到五分钟,防止连接和数据库资源更久被占用。
- 永久修复: 报表改异步提交、operation查询和幂等键;只读副本承载,SQL有statement timeout与成本限制;传播deadline/取消;入口建立重试预算和并发准入。
- 验证与复盘: 用生产规模脱敏数据回放,模拟客户端断开确认查询被取消;副本失效时交易不受影响。复盘新增“超时不是取消”的检查项,所有层列出默认重试并消除重复。
公共 PR 读取共享 CI runner 遗留云凭证¶
相关技术说明
CI runner 执行仓库代码,应按不可信计算处理。持久自托管 runner 若跨任务复用工作区、环境或容器层,后续任务可读取残留;公开 fork PR 更不能与生产发布共享身份、网络和缓存。日志遮蔽只保护已知文本,不能阻止文件读取或编码外传。OIDC短期身份能缩短窗口,但信任条件仍需绑定受保护仓库、分支和环境。
根因是 fork PR 与发布 job 共用持久 runner,发布脚本把临时凭证写入未清理文件。促成因素包括 runner 拥有广泛出站网络、PR允许写共享缓存、凭证有效期六小时。检测是出口代理发现异常域名请求,但没有第一时间关联到具体 workflow。
实践经验
- 影响: 凭证内容被读取,未证实成功外传或资源修改;按照潜在泄露处置,冻结发布 76 分钟并轮换相关角色。
- 时间线/证据链: 08:12 出站告警;08:16 按 runner/run ID 定位公共PR;08:20磁盘取证发现上个 job 的凭证文件;08:25 云审计未见成功API调用;08:28吊销会话、隔离runner和缓存。
- 决策与止血: 停止 runner 池、撤销信任和会话、保存取证快照后重建;所有相同池的制品标记待重新构建。副作用是发布暂停,但未在未知完整性下继续使用产物。
- 永久修复: PR使用无secret一次性沙箱,发布池独立账户/子网;OIDC会话绑定受保护环境且15分钟有效;任务后销毁磁盘;缓存按信任域隔离,出站默认拒绝。
- 验证与复盘: 红队PR尝试读前任务、metadata、socket和缓存均失败;云侧验证错误subject无法换角色。复盘改进run ID到云会话/网络日志的关联,并制定制品污染响应手册。
浮动镜像标签与破坏性数据库迁移导致回滚失效¶
相关技术说明
可移动 tag 不能唯一标识发布内容;部署应固定 digest并记录源码、配置、证明。代码回滚也不自动回滚 schema和数据。破坏性迁移需expand/contract,让 N/N-1版本共存;发布控制器若只看到 workload ready,而不验证业务和迁移阶段,会在错误版本上继续扩量。
根因是生产清单引用 app:release 浮动 tag,第二次构建覆盖该 tag;部署同时删除旧列。事故后“回滚同一 tag”仍拉到新摘要,旧代码即使恢复也无法读取已删除列。促成因素是每环境重建、registry允许改 tag、迁移与应用在一个不可暂停 job。检测缺口是没有运行实例 digest差异和schema兼容门禁。
实践经验
- 影响: 用户资料更新失败约24分钟,读取因缓存部分可用;从审计事件重放恢复少量未落库更新。
- 时间线/证据链: 19:00发布;19:05写错误升高;19:08执行回滚但错误未降;19:11对比实例 image ID发现“旧tag”仍是新digest;19:15数据库日志确认旧列已删除;19:19启用兼容视图并部署已知旧digest。
- 决策与止血: 冻结所有发布,创建兼容视图恢复旧读写契约,按注册表历史摘要回到最后良好制品。未尝试从备份整库覆盖,避免丢失事故后有效事务。
- 永久修复: 构建一次并按digest晋级;生产tag不可变;迁移拆成expand、双写/回填、切读、contract,删除需确认旧版本全下线;部署记录schema版本并执行兼容检查。
- 验证与复盘: 在影子库用生产规模验证锁和双版本,演练任一阶段回退;审计所有集群不存在tag引用。复盘规定“回滚完成”需同时验证制品摘要、配置、schema和业务数据。
-
CPU quota/period 是 cgroup 的周期性 CPU 配额机制:任务在一个周期耗尽额度后会被暂停到下一周期。 ↩
-
profile(性能剖析数据)按调用栈归集 CPU、内存或阻塞时间,用于定位代码热点。 ↩
-
request 表示调度和资源保障基线,limit 表示容器可使用资源的硬上限;两者用途不同。 ↩
-
cgroup 是 Linux control group(控制组),用于限制并统计一组进程的 CPU、内存与 I/O 等资源。 ↩
-
P99 是第 99 百分位数,表示 99% 的观测值不超过该值,常用于衡量尾延迟。 ↩
-
inode 是 Unix/Linux 文件系统保存文件元数据的数据结构;inode 耗尽时即使仍有字节空间也无法创建新文件。 ↩
-
OOM 是 Out Of Memory(内存不足),表示系统或 cgroup 无法满足内存分配并可能触发进程终止。 ↩
-
PSI 是 Pressure Stall Information(压力停顿信息),衡量任务因 CPU、内存或 I/O 资源不足而停顿的时间。 ↩
-
DNS 是 Domain Name System(域名系统),负责把域名解析为 IP 地址等记录。 ↩
-
conntrack 是 Linux 内核连接跟踪机制,为有状态防火墙和 NAT 记录会话状态。 ↩
-
NAT 是 Network Address Translation(网络地址转换),通过改写地址或端口连接不同网络。 ↩