k8smysql 启动即 OOM 排查复盘

一句话-1/-2 节点的 containerd.service 设了 LimitNOFILE=infinity,容器内 nofile 被拉到 ~2³⁰,mysqld 启动按 nofile 预分配 fd 结构 ≈ 16 GiB,撑爆 4Gi limit → 启动 1 秒即 OOMKilled。与镜像、MySQL 配置、PVC 全无关。

现象

  • k8smysql/mysql-* pod CrashLoopBackOffOOMKilledstartedAt → finishedAt1 秒
  • 死在 entrypoint 的 mysqld --verbose --help 自检步
  • 同镜像、同配置(4Gi limit)在 irr-yfl-mysqlshuiku-work(均跑在 -10)上 1/1 Running

根因

节点 containerd LimitNOFILE 容器内 nofile mysqld --help
-1 / -2 infinity 1,073,741,816 (≈2³⁰) 吃 16GiB,被杀
-10 524288 1024 28MB,正常
  • /usr/lib/systemd/system/containerd.serviceLimitNOFILE=infinity
  • 内核按 fs.nr_open 上限给容器 ~2³⁰ 的 nofile
  • mysqld 多线程,启动按 nofile 预分配 fd 相关结构:1,073,741,816 × 16B ≈ 16 GiB
  • 该 16GiB 为匿名映射,随运行逐页 touch,RSS 一路涨到撑爆 limit

决定性证据

# 容器内
ulimit -n  →  1073741816              # 罪魁
# /proc/<pid>/maps 里的"幽灵映射"
16777216 kB    0                      # 精确 16 GiB 匿名映射 (= 1073741816 × 16B)
# mysqld 默认下
mysqld --verbose --help → exit=137, peak RSS ~14GB, 被OOM
# 设 ulimit -n 1048576 后(=docker-compose 那段 ulimits)
mysqld --verbose --help → exit=0, peak 28MB   ← 修复验证
# 节点
systemctl show containerd | grep LimitNOFILE → -1: infinity  /  -10: 524288

线索最初来自旧 docker-compose:

1
2
3
4
ulimits:
  nofile:        # Fix memory leak issue on some systems when LimitCORE=infinity (containerd)
    soft: 1048576
    hard: 1048576

走过的弯路(教训)

按时间顺序的错误假设,每一个都被证据推翻:

  1. 「limit 被改小 / LimitRange / 配额」 —— kubectl get pod -o jsonpath resources = 4Gi 实打实,LimitRange/ResourceQuota 全空
  2. 「节点内存不足」 —— -1 16GB,requests 才用 10%
  3. 「镜像损坏」 —— mysqld md5 在 -1/-10 完全一致6eccb50f3cf4f6afb8cdfcbcbb36298e);且 -10mysqld --help 仅 2MB
  4. 「MySQL 参数(innodb_buffer_pool_size)设大」 —— /etc/mysql/* 配置全空;强制 --innodb-buffer-pool-size=128M 后 16G 映射照常出现
  5. 「PV 钉坏节点」 —— PV nodeAffinity 确实把 pod 锁在 -1,但那是间接因素(锁在问题节点),非 OOM 直接原因

核心教训:容器「启动即 OOM」且多节点表现不同时,第一步该看 容器内 ulimit -n 和节点 containerd 的 LimitNOFILE,而不是怀疑应用/镜像/MySQL 参数。ulimit -n 达 2³⁰ 级就是这类泄漏的指纹。

修复

治本(节点侧,推荐)

1
2
3
sed -i 's/^LimitNOFILE=infinity/LimitNOFILE=1048576/' /usr/lib/systemd/system/containerd.service
sed -i 's/^LimitCORE=infinity/LimitCORE=0/'           /usr/lib/systemd/system/containerd.service
systemctl daemon-reload && systemctl restart containerd

影响该节点所有 pod。-10 已正常(524288),无需改;只改 -1/-2

临时(单 pod,不动节点)

k8s securityContext 不能直接设 nofile 数值,需在 entrypoint 包一层:

1
2
3
4
5
6
command: ["/bin/sh", "-c"]
args:
  - >-
    ulimit -n 1048576 &&
    exec docker-entrypoint.sh mysqld
    --character-set-server=utf8mb4 ...    

实测加上后容器已正常运行。

验证

  • ✅ deployment 加 ulimit -n 1048576 后,k8smysql pod 正常 Running
  • ✅ 对照实验:-1ulimit -n 1048576 后 mysqld --help exit=0、peak=28MB

影响范围 & 待办

  • -1/-2 上所有容器都受这个 LimitNOFILE=infinity 影响;钉在 -1k8sminio/k8sredis/k8stdengine 若启动期也按 nofile 预分配,理论上同样有风险 → 建议治理本(改 containerd)
  • 节点 -1/-2 改 containerd LimitNOFILE(治本)
  • 通知集群管理员,统一各节点 containerd 配置

排查工具箱(下次复用)

1
2
3
4
5
6
# 容器内 ulimit 指纹
kubectl exec <pod> -- ulimit -n                 # 达 2^30 = 中招
# mysqld 大映射定位
awk '/^[0-9a-f]/{split($1,a,"-");s=int((strtonum("0x"a[2])-strtonum("0x"a[1]))/1024);print s"kB",$NF}' /proc/<pid>/maps | sort -rn | head
# 节点 containerd 限制
systemctl show containerd | grep -E "LimitNOFILE|LimitCORE"

相关

  • 最佳实践 deployment 模板(含 ulimit 修复):/home/nysiem/mysql-deployment.yaml
  • 排查记忆:~/.claude/projects/-home-nysiem/memory/k8szhslzhyy-bad-node-mysql-oom.md