写于 2022-03,背景:K8s 1.23 GA,1.24 即将引入对 dockershim 移除。本文聚焦"Pod 资源调优"——关闭 swap、CPU/内存单位、requests/limits 区别、3 类探针最佳实践。
一、为什么 K8s 强制关 swap
K8s 安装文档里第一条永远是"关闭 swap",很多人不知道为什么。根本原因是 Kubelet 设计哲学:
在计算集群中,我们希望 OOM 时直接杀进程,报错让运维处理,故障转移到其他节点重启。而不是用 swap 续命,导致节点 hang 住,集群性能大幅下降,运维还收不到报警。
机械盘 swap 更糟:swap 写磁盘,性能掉到个位数;机器卡死,连 SSH 都登不进去,结局就是硬重启。
| 类型 | 是否需要 swap | 原因 |
|---|---|---|
| 计算集群(批处理、Spark、Flink) | 不需要 | OOM 立即杀,故障转移 |
| 服务型集群(MySQL、K8s、Redis) | 不需要 | 性能稳定,OOM 即报警 |
| 个人开发机 | 保留 | 内存偶尔不够,swap 救命 |
swapoff -a + /etc/fstab 注释 swap 行,双保险:
| |
iptables 不用关(K8s 自己用 iables 做 Service 转发),但 firewalld / ufw 建议关,避免端口冲突。
二、CPU / 内存单位
2.1 CPU 单位
| |
CPU 后缀:
m:毫核(millicores),1 Core = 1000m- 数字(不带 m):直接写核数(
cpu: 2= 2 Core = 2000m)
CPU 是可压缩资源:
- 超过
limits→ 容器被节流(throttled),不是杀掉 requests= 调度保证(这台节点上至少有这么多 CPU 可用)
2.2 内存单位
| 单位 | 进制 | 例子 |
|---|---|---|
| K / M / G / T / P / E | 1000 | 500M = 500 × 1000 字节 |
| Ki / Mi / Gi / Ti / Pi / Ei | 1024 | 512Mi = 512 × 1024 × 1024 字节 |
内存是不可压缩资源:
- 超过
limits→ 容器被 OOMKilled(Exit Code 137)
三、requests vs limits
| 字段 | 含义 | 作用 |
|---|---|---|
requests | 调度的"最低保证" | 调度器看节点剩余资源够不够 requests |
limits | 容器能用的"上限" | CPU 节流 / 内存 OOM |
Java 应用特殊坑:
- JVM 默认堆内存 = 物理内存的 1/4
- Pod
memory.limits: 1Gi时,JVM 堆可能只分到 256MB - 必须显式设
-Xmx/-Xms
| |
四、QoS 等级
K8s 根据 requests/limits 设置给 Pod 打 3 个 QoS 等级:
| QoS 等级 | 特征 | 驱逐顺序 |
|---|---|---|
| Guaranteed | requests == limits(CPU 和 memory 都设了) | 最后被驱逐 |
| Burstable | requests < limits | 中间 |
| BestEffort | requests 和 limits 都没设 | 最先被驱逐(节点压力时) |
生产建议:
- 核心服务用
Guaranteed(不设 requests = BestEffort,节点紧张第一个杀) - 普通服务用
Burstable(设 requests,不设 limits 或 limits 更大) - 批处理可用
BestEffort(节点紧张先死批处理是合理的)
五、3 类探针
K8s 有 3 类探针,每类都有 3 种检测方式:
| 探针 | 检测目标 | 失败后果 |
|---|---|---|
livenessProbe | 容器是否存活 | kubelet 重启容器 |
readinessProbe | 容器是否就绪 | 从 Service Endpoints 移除 |
startupProbe | 容器是否启动完成 | 阻断 liveness / readiness |
3 种检测方式:
httpGet:HTTP GET 请求(最常用)tcpSocket:TCP 端口exec:执行命令
5.1 livenessProbe:活人检测
作用:容器是不是死了?死了 kubelet 重启它。
关键参数:
initialDelaySeconds:第一次探测前等多久(启动慢的应用要加大)periodSeconds:探测间隔timeoutSeconds:单次探测超时failureThreshold:连续失败几次标记失败successThreshold:连续成功几次标记成功(liveness 必须是 1)
| |
为什么 initialDelaySeconds 要大? JVM 启动慢,启动 5~10 分钟常见。如果 initialDelaySeconds = 60,JVM 还没启动完就被杀了,反复重启。
5.2 readinessProbe:就绪检测
作用:容器是不是能接流量了?没就绪就从 Service Endpoints 摘掉。
| |
liveness 与 readiness 的区别:
- liveness 是"是不是活着"(死 → 重启)
- readiness 是"是不是能干活"(不能 → 摘流量,不重启)
典型场景:
- 启动期间:readiness 失败 → 流量不来,但容器继续启动
- 运行时下游 DB 挂:readiness 失败 → 摘流量,但容器不重启
- 应用死锁:liveness 失败 → 重启容器
5.3 startupProbe:启动检测(K8s 1.16+)
作用:慢启动应用(如 Java)需要更长探测时间,用 startupProbe 替代 liveness 的 initialDelaySeconds。
| |
好处:不用猜 initialDelaySeconds 该设多久。
5.4 Spring Boot 完整示例
| |
六、Node 资源预留
K8s 节点本身要跑 kubelet、kube-proxy、containerd 等系统进程,需要预留资源:
| |
作用:保留资源给系统,避免 Pod 把节点搞崩。
驱逐条件(eviction-hard):
memory.available < 500Mi:驱逐 BestEffort → Burstablenodefs.available < 10%:磁盘满了开始杀 Podimagefs.available < 15%:镜像存储满了
七、LimitRange 与 ResourceQuota
7.1 LimitRange:namespace 默认值
| |
不设 requests/limits 的 Pod 会用 defaultRequest 和 default 兜底。
7.2 ResourceQuota:namespace 总配额
| |
超过配额就拒绝创建新资源。
八、Pod 调度与资源
8.1 资源不够 Pod 怎么调度失败
| |
解决:
- 加节点
- 减小 requests
- 用 HPA 自动扩
8.2 Pod 资源使用率监控
| |
九、常见坑
- Java 没设 -Xmx:JVM 堆自动算 = 物理内存 / 4,1Gi limit 时只分到 256MB,应用反复 OOM
- liveness initialDelaySeconds 太短:慢启动应用被反复杀
- liveness 检测太严格:临时 GC pause 30 秒就触发重启
- readiness 失败后没流量:Spring Boot
/actuator/health/readiness没启用或路径写错 - requests/limits 单位错:
500不是 500m,是 500 核(不可能) - BestEffort 服务被先杀:核心服务忘设 requests
十、生产清单
| 检查项 | 推荐值 |
|---|---|
| requests.requests.memory | Java 1Gi 起,Go 256Mi 起 |
| limits.requests.memory | requests 的 1.5~2 倍 |
| limits.requests.cpu | requests 的 1.5~2 倍(突发流量) |
| liveness failureThreshold | 3 |
| readiness failureThreshold | 5 |
| startupProbe periodSeconds × failureThreshold | > JVM 启动时间 |
| JVM 启动慢应用 | 必加 startupProbe |
| 节点 kube-reserved | cpu=500m, memory=1Gi |
| 节点 eviction-hard | memory.available<500Mi |
十一、前置知识 / 下一步
前置:
- Pod 基础概念(参考 2017-06-15《Kubernetes 入门》)
- Deployment 配置
下一步:
- Kubeadm 一键部署(2022-06-15)—— 生产部署实战
- K8s 集群插件(2021-09-15)—— CNI/CoreDNS/Metrics/Dashboard
