如何高效进行 Pod 内存压力测试
本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

Chaos Mesh 内置的 StressChaos 工具可向 Pod 注入 CPU 和内存压力。当您测试或基准评估 CPU/内存敏感型程序并需要了解其在高压下的表现时,该工具尤为实用。
但在实际测试使用过程中,我们发现 StressChaos 存在若干可用性与性能问题。例如:为何实际内存消耗远低于配置值?为解决这些问题,我们开发了新的测试方案。本文将详细说明故障排查与修复过程,助您充分发挥 StressChaos 的效能。
开始前请确保已在集群中安装 Chaos Mesh,具体步骤请参阅官网安装指南。
向目标注入压力
下面以 hello-kubernetes 为例演示 StressChaos 注入流程(该项目通过 helm charts 管理)。第一步克隆 hello-kubernetes 仓库并修改 Helm Chart 以设置资源限制。
git clone https://github.com/paulbouwer/hello-kubernetes.git
code deploy/helm/hello-kubernetes/values.yaml # or whichever editor you prefer
定位 resources 配置行并修改为:
resources:
requests:
memory: '200Mi'
limits:
memory: '500Mi'
注入前需先观测目标内存消耗。进入 Pod 启动 shell 后执行(请替换示例中的 Pod 名称):
kubectl exec -it -n hello-kubernetes hello-kubernetes-hello-world-b55bfcf68-8mln6 -- /bin/sh
查看内存使用摘要:
/usr/src/app $ free -m
/usr/src/app $ top
如下方输出所示,当前 Pod 内存消耗为 4,269 MB。
/usr/src/app $ free -m
used
Mem: 4269
Swap: 0
/usr/src/app $ top
Mem: 12742432K used
PID PPID USER STAT VSZ %VSZ CPU %CPU COMMAND
1 0 node S 285m 2% 0 0% npm start
18 1 node S 284m 2% 3 0% node server.js
29 0 node S 1636 0% 2 0% /bin/sh
36 29 node R 1568 0% 3 0% top
这显然异常:我们设置了 500 MiB 内存限制,但 Pod 实际消耗数 GB 内存。即便累加进程内存使用量也未达 500 MiB,不过 top 和 free 命令的统计结果基本一致。
接下来对 Pod 执行 StressChaos 并观察变化。所用 yaml 配置如下:
apiVersion: chaos-mesh.org/v1alpha1
kind: StressChaos
metadata:
name: mem-stress
namespace: chaos-mesh
spec:
mode: all
selector:
namespaces:
- hello-kubernetes
stressors:
memory:
workers: 4
size: 50MiB
options: ['']
duration: '1h'
保存 yaml 文件(此处命名为 memory.yaml),应用混沌实验:
~ kubectl apply -f memory.yaml
stresschaos.chaos-mesh.org/mem-stress created
再次检查内存使用情况:
used
Mem: 4332
Swap: 0
Mem: 12805568K used
PID PPID USER STAT VSZ %VSZ CPU %CPU COMMAND
54 50 root R 53252 0% 1 24% {stress-ng-vm} stress-ng --vm 4 --vm-keep --vm-bytes 50000000
57 52 root R 53252 0% 0 22% {stress-ng-vm} stress-ng --vm 4 --vm-keep --vm-bytes 50000000
55 53 root R 53252 0% 2 21% {stress-ng-vm} stress-ng --vm 4 --vm-keep --vm-bytes 50000000
56 51 root R 53252 0% 3 21% {stress-ng-vm} stress-ng --vm 4 --vm-keep --vm-bytes 50000000
18 1 node S 289m 2% 2 0% node server.js
1 0 node S 285m 2% 0 0% npm start
51 49 root S 41048 0% 0 0% {stress-ng-vm} stress-ng --vm 4 --vm-keep --vm-bytes 50000000
50 49 root S 41048 0% 2 0% {stress-ng-vm} stress-ng --vm 4 --vm-keep --vm-bytes 50000000
52 49 root S 41048 0% 0 0% {stress-ng-vm} stress-ng --vm 4 --vm-keep --vm-bytes 50000000
53 49 root S 41048 0% 3 0% {stress-ng-vm} stress-ng --vm 4 --vm-keep --vm-bytes 50000000
49 0 root S 41044 0% 0 0% stress-ng --vm 4 --vm-keep --vm-bytes 50000000
29 0 node S 1636 0% 3 0% /bin/sh
48 29 node R 1568 0% 1 0% top
可见 stress-ng 实例已注入 Pod,但内存仅增加 60 MiB,与预期不符。文档显示应增加 200 MiB(4 个进程 × 50 MiB)。
将内存压力从 50 MiB 提升至 3,000 MiB 以突破 Pod 内存限制。先删除实验,修改参数后重新应用:
此时 shell 突然退出(返回码 137)!稍后重连容器发现内存使用恢复正常,且未找到任何 stress-ng 进程。发生了什么?
StressChaos 为何消失?
Kubernetes 通过 cgroup 机制限制容器内存。查看 Pod 的 500 MiB 限制:
/usr/src/app $ cat /sys/fs/cgroup/memory/memory.limit_in_bytes
524288000
输出值以字节为单位,对应 500 * 1024 * 1024。
Requests 仅用于调度 Pod 的部署位置。Pod 本身没有内存限制或请求,但可以视为其所有容器资源的总和。
我们从一开始就犯了错误。free 和 top 命令并不感知 cgroup。它们依赖 /proc/meminfo(procfs)获取数据。遗憾的是,/proc/meminfo 的历史非常悠久,早于 cgroup 的出现。它会提供宿主机的内存信息而非容器内存。让我们重新开始,看看这次能得到怎样的内存使用数据。
要获取 cgroup 感知的内存用量,请输入:
/usr/src/app $ cat /sys/fs/cgroup/memory/memory.usage_in_bytes
39821312
应用 50 MiB 的 StressChaos 后,结果如下:
/usr/src/app $ cat /sys/fs/cgroup/memory/memory.usage_in_bytes
93577216
这比未施加 StressChaos 时增加了约 51 MiB 内存占用。
接下来,为什么我们的 shell 会退出?退出码 137 表示"容器收到 SIGKILL 信号而失败"。这引导我们检查 Pod 状态,请特别关注 Pod 状态和事件记录。
~ kubectl describe pods -n hello-kubernetes
......
Last State: Terminated
Reason: Error
Exit Code: 1
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
......
Warning Unhealthy 10m (x4 over 16m) kubelet Readiness probe failed: Get "http://10.244.1.19:8080/": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
Normal Killing 10m (x2 over 16m) kubelet Container hello-kubernetes failed liveness probe, will be restarted
......
事件日志揭示了 shell 崩溃的原因。hello-kubernetes 配置了存活探针,当容器内存接近限制时,应用开始失效,Kubernetes 决定终止并重启它。Pod 重启后,StressChaos 自动停止。这种情况下,可以说混沌实验是成功的——它暴露了 Pod 的脆弱性。您现在可以修复问题并重新施加混沌。一切似乎都很完美,除了一件事:为什么四个 50 MiB 的 vm worker 总共只消耗 51 MiB?答案需要深入 stress-ng 的源代码:
vm_bytes /= args->num_instances;
哎呀!原来文档有误。多个 vm worker 会共享指定的总内存量,而不是每个 worker 独立占用 mmap 映射的内存。至此我们终于得到了完整的解释。后续章节将讨论内存压测的其他场景。
没有存活探针会怎样?
让我们删除探针配置重试。找到 deploy/helm/hello-kubernetes/templates/deployment.yaml 中的以下行并删除:
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
删除后,升级部署:
这个场景有趣之处在于内存使用量持续上升后突然骤降,如此反复波动。现在发生了什么?让我们检查内核日志,特别注意最后两行:
/usr/src/app $ dmesg
......
[189937.362908] [ pid ] uid tgid total_vm rss nr_ptes swapents oom_score_adj name
[189937.363092] [441060] 1000 441060 63955 3791 80 3030 988 node
[189937.363110] [441688] 0 441688 193367 2136 372 181097 1000 stress-ng-vm
......
[189937.363148] Memory cgroup out of memory: Kill process 443160 (stress-ng-vm) score 1272 or sacrifice child
[189937.363186] Killed process 443160 (stress-ng-vm), UID 0, total-vm:773468kB, anon-rss:152704kB, file-rss:164kB, shmem-rss:0kB
从输出可以清晰看出,stress-ng-vm 进程因内存溢出(OOM)错误被终止。
当进程无法获取所需内存时,情况会变得复杂。它们很可能失败。与其等待进程崩溃,不如主动终止部分进程来释放内存。OOM killer 会按特定顺序终止进程,力求在造成最小影响的同时回收最多内存。详细了解此机制可参阅 OOM killer 的介绍文章。
观察上述输出,可以看到 node 进程(我们的主应用进程,本不该被终止)的 oom_score_adj 值高达 988。这相当危险,因为它是最高优先级被终止的进程。但有个简单方法可以防止 OOM killer 终止特定进程:创建 Pod 时,它会被分配服务质量(QoS)等级。详细信息请参阅为 Pod 配置服务质量。
通常,如果您创建了精确指定资源请求的 Pod,它会被归类为 Guaranteed Pod。当存在其他可终止实体时,OOM 杀手不会终止 Guaranteed Pod 中的容器。这些实体包括非 Guaranteed Pod 和 stress-ng 工作进程。没有资源请求的 Pod 会被标记为 BestEffort,OOM 杀手会优先终止这类 Pod。
以上就是本次技术探索的全部内容。我们建议不要使用 free 和 top 命令评估容器内存状态。在为 Pod 分配资源限制时需谨慎,并选择正确的 QoS 等级。未来我们将编写更详细的 StressChaos 技术文档。
深入理解 Kubernetes 内存管理机制
Kubernetes 会驱逐内存使用量过高(但未超过限制值)的 Pod。Kubernetes 从 /sys/fs/cgroup/memory/memory.usage_in_bytes 获取 Pod 内存用量,并减去 memory.stat 中的 total_inactive_file 项。
请注意 Kubernetes 不支持 swap 交换分区。即使节点启用了 swap,Kubernetes 也会以 swappiness=0 参数创建容器,这意味着交换功能实际被禁用。这主要是出于性能考量。
memory.usage_in_bytes 等于 resident set(常驻内存)加上 cache(缓存),而 total_inactive_file 表示操作系统在内存不足时可回收的缓存内存。memory.usage_in_bytes - total_inactive_file 被称为 working_set(工作集)。您可通过 kubectl top pod <your pod> --containers 获取该 working_set 值。Kubernetes 使用此值决定是否驱逐您的 Pod。
Kubernetes 会定期检查内存使用情况。如果容器内存使用量增长过快或无法被驱逐,则会触发 OOM 杀手。Kubernetes 有保护自身进程的机制,因此总是选择终止容器。容器被终止后是否重启取决于您的重启策略。若容器因内存溢出被终止,执行 kubectl describe pod <your pod> 时会看到重启原因为 OOMKilled。
另一个值得注意的点是内核内存。自 v1.9 版本起,Kubernetes 默认启用内核内存支持功能,这也是 cgroup 内存子系统的特性。您可以限制容器内核内存用量,但不幸的是,这会导致内核 v4.2 及以下版本出现 cgroup 泄漏。解决方案是升级内核至 v4.3 或禁用该功能。
StressChaos 的实现原理
StressChaos 是测试容器在内存不足场景下行为的有效工具。它利用强大的 stress-ng 工具分配内存并持续写入数据。由于容器内存限制通过 cgroup 实现,我们需要将 stress-ng 进程注入特定 cgroup。幸运的是,通过足够权限向 /sys/fs/cgroup/ 中的文件写入数据,即可将任意进程分配到指定 cgroup。
如果您对 Chaos Mesh 感兴趣并希望参与改进,欢迎加入我们的 Slack 频道 (#project-chaos-mesh)!或向 GitHub 代码库提交 PR 和 issue。