TT Lab
开始
学习 学习路径 课程

由我来搞坏 — 先写假设的混沌实验室

CPU 让它变慢,内存让它死掉

在 TT Lab 中继续学习

一句话总结

CPU 限制和内存限制只是名字相似,它们的“咬人”方式完全不同。一个让程序变慢,一个直接杀死程序。

为什么需要它

填写 limits 通常只是一种习惯。要么复制前任写的值,要么在评审时听到“请指定限制”,就随便填一个数字。此后几个月,会遇到两类事件。一类是“什么都没改,突然变慢了”,另一类是“重启一直在循环,日志里却没有任何线索”。这两类事件原因不同,却都出自 limits 这一行。

工作原理

Pod 和容器的资源管理文档明确区分了两种限制的执行方式。

cpu 限制通过限流(throttling)来执行。内核只允许容器使用规定份额的 CPU,份额用完后,这个容器会一直被暂停到下一个周期到来。这是由内核强制执行的硬限制,容器不能超出限制多用。但它不会被杀死。文档特意写明“运行时不会因为 Pod 或容器大量使用 CPU 而终止它们”,足见这个点常被误解。症状只有变慢,进程和探针都还活着,所以日志里什么也不会留下。

memory 限制通过 OOM kill 来执行。容器超过限制后,内核可以将其终止。不过文档说明这是响应式(reactive)的——它发生在内核检测到内存压力时,所以超出限制的容器也可能不会立刻被杀死。被杀死时,会留下退出码 137 和 OOMKilled 原因。

             집행 방식        증상                  남는 흔적
  cpu        스로틀링        지연만 늘어난다        없음(로그가 조용하다)
  memory     OOM kill        재시작·CrashLoop       exit 137 · OOMKilled

还有一个鲜为人知的陷阱:以内存作为介质的 emptyDir 卷,会被计入容器的内存用量。同一份文档写道:“kubelet 不把 tmpfs emptyDir 卷作为本地临时存储,而是作为容器内存使用来跟踪”。为了快速写临时文件而挂了内存卷,本以为是在往磁盘写文件的代码,会把内存限制顶上去,导致容器被杀死。而症状并不是“写文件时挂了”,而只是 OOMKilled。

还要分清 requests 和 limits 的作用。requests 是调度器决定放在哪里时使用的值,limits 是 kubelet 和内核决定允许用多少时使用的值。所以只写 requests 时,节点空闲的话可以用得比它更多;只写 limits 时,Kubernetes 会把同样的值复制为 requests。

再补充一点。降低限制时,必须同时考虑与请求量的关系。限制不能小于请求量,所以即使只想把内存限制降到 40Mi,如果请求量写的是 64Mi,API 服务器会直接拒绝这次修改。准备实验时经常会遇到“本以为会出故障,命令却被拒绝了”的情况,这时通常是触碰到了这类一致性规则。本实验的应用把请求量设得比较低,也是为了在实验中不碰到这堵墙。

在现场相遇的样子

最常见的事故,是为了“安全”把 cpu 限制压低到 100m 的配置。平时什么事也没有,流量稍微一涨,响应时间就成倍飙升。成功率不变,所以告警不会触发;CPU 使用率曲线紧贴着限制呈一条平线,反而“看起来还有余量”。这类事件在亲自测量限流之前,找不到原因。

还有反方向的事故。把内存限制给得很宽裕,OOM 确实消失了,但如果没有同时提高请求量,节点吃紧时这个 Pod 会最先被驱逐。文档指出,如果 Pod 中有容器使用的内存超过了请求量,节点内存不足时它被驱逐的可能性很大。也就是说,限制决定单个容器的上限,请求量决定资源不足时的顺序。用实验来确认时,也要分别调整这两者,才能说清楚原因是什么。

所以处理资源类事件时,先按症状分出方向。如果是日志安静的延迟,先看 cpu;如果是重启和退出码 137,先看 memory。只要把这个方向分对,排查时间就会大大缩短。反过来,“变慢了,那就加内存试试”这样的应对,什么也改变不了,只会徒增成本。修改限制会触发滚动更新、重新创建 Pod,所以刚改完时还会出现暂时变好的错觉。要不被这种错觉蒙蔽,就必须有修改前后用同一种方式测得的数字。

下一项测验要确认什么

确认 cpu 限制与 memory 限制的执行方式有何不同,OOM 执行是响应式意味着什么,以及以内存为介质的 emptyDir 是按什么来计算的。