主题
第五部分 性能优化与调优
"先度量,再优化。没有基线的调优只是碰运气,没有验证的优化只是自我安慰。"
数千节点的 RKE2 集群,性能问题从来不是单点问题:etcd 的一次慢盘会放大成 apiserver 超时,apiserver 的超时会放大成 kubelet 全节点 relist,最终表现为"整个集群都在抖"。本部分按照"现象 → 根因 → 优化 → 验证"的固定结构,逐层拆解控制平面、节点、网络、DNS、镜像、调度与存储的优化手段。所有命令基于 RKE2 v1.28+,数据不确定处均采用保守表述。
第 1 章 性能优化方法论
1.1 为什么先讲方法论
大规模集群的优化工作,80% 的时间花在"定位"上,20% 的时间花在"修改"上。没有方法论的调优常见三种失败:
- 头痛医头:apiserver 慢就加 apiserver 副本,实际瓶颈在 etcd 磁盘;
- 优化无验证:改完参数没有对比数据,无法回答"好了多少";
- 收益无边界:局部最优损害全局(如一味调大 QPS 把 apiserver 打爆)。
正确姿势永远是:建立基线 → 压测找拐点 → 定位瓶颈 → 单变量优化 → 回归验证。
1.2 性能基线与压测
1.2.1 基线指标体系
在优化前,先采集以下基线(Prometheus 查询示例):
| 层级 | 关键指标 | PromQL 示例 | 健康参考 |
|---|---|---|---|
| apiserver | 请求延迟 P99 | histogram_quantile(0.99, sum(rate(apiserver_request_duration_seconds_bucket[5m])) by (le, verb, resource)) | LIST < 5s(大 LIST),其余 < 1s |
| apiserver | inflight 请求 | sum(apiserver_current_inflight_requests) by (request_kind) | 远低于上限 |
| etcd | WAL fsync P99 | histogram_quantile(0.99, rate(etcd_disk_wal_fsync_duration_seconds_bucket[5m])) | < 10ms |
| etcd | backend commit P99 | histogram_quantile(0.99, rate(etcd_disk_backend_commit_duration_seconds_bucket[5m])) | < 25ms(保守) |
| etcd | DB 大小 | etcd_mvcc_db_total_size_in_bytes | < quota 的 60% |
| scheduler | 调度延迟 P99 | histogram_quantile(0.99, rate(scheduler_scheduling_attempt_duration_seconds_bucket[5m])) | < 100ms 量级 |
| kubelet | PLEG 延迟 | histogram_quantile(0.99, rate(kubelet_pleg_relist_duration_seconds_bucket[5m])) | < 1s |
| kubelet | Pod 启动延迟 | histogram_quantile(0.99, rate(kubelet_pod_start_duration_seconds_bucket[5m])) | 视镜像大小而定 |
基线要覆盖闲时与业务高峰两个时段,并保留至少 7 天数据用于趋势对比。
1.2.2 压测控制平面:kubemark 简介
kubemark 是 Kubernetes 官方的空洞节点(hollow node)仿真工具:用容器运行"假 kubelet",向 apiserver 注册成百上千个"节点",从而在小规模物理资源上模拟数千节点集群对控制平面的压力。
- 适用场景:验证 apiserver/etcd/scheduler/controller-manager 在 N 节点、M Pod 规模下的容量与延迟(SLI/SLO 验证,对应官方 scalability 测试)。
- 局限:hollow kubelet 不跑真实容器、不走真实 CNI,不能用于验证数据平面(网络、存储、镜像拉取)性能。
- RKE2 场景下 kubemark 需要自行编译部署(kubernetes 仓库
test/kubemark),实践中更多用于研发侧容量评估;运维侧更常用 kube-burner。
1.2.3 kube-burner 实战
kube-burner 是面向真实集群的工作负载压测工具,直接对目标集群批量创建/删除对象,测量 apiserver 延迟与 Pod 就绪时间,输出 indexer 后的指标(可推 ES/Prometheus)。
安装(二进制方式):
bash
# 以 v1.9.x 为例,实际版本以 release 页为准
curl -sSL https://github.com/kube-burner/kube-burner/releases/download/v1.9.0/kube-burner-v1.9.0-Linux-x86_64.tar.gz | tar xz
sudo mv kube-burner /usr/local/bin/
kube-burner version最小压测配置(cluster-density.yaml):
yaml
global:
gc: true
measurements:
- name: podLatency
jobs:
- name: cluster-density
jobIterations: 10 # 迭代次数,每轮创建一批
qps: 20 # 对 apiserver 的客户端 QPS
burst: 20
namespace: kube-burner-test
namespacedIterations: true
objects:
- objectTemplate: deployment.yaml
replicas: 30 # 每轮 30 个 Deployment
- objectTemplate: service.yaml
replicas: 10执行与观察:
bash
export KUBECONFIG=/etc/rancher/rke2/rke2.yaml
kube-burner init -c cluster-density.yaml --timeout 30m压测期间同步观察:
bash
# 另开终端:apiserver 延迟与 etcd 延迟
watch -n2 'curl -sk https://127.0.0.1:6443/metrics | grep -E "apiserver_request_duration_seconds_bucket" | tail -5'解读要点:
podLatency中PodReady的 P99 反映"调度 + 拉镜像 + 启动"全链路;- 若 apiserver 写延迟随对象数线性恶化而 CPU 未满 → 大概率 etcd 磁盘瓶颈;
- 若 apiserver CPU 先打满 → 考虑 inflight/APF 与 watch 缓存(见第 3 章)。
1.3 瓶颈定位方法论
1.3.1 USE 方法
Brendan Gregg 的 USE 方法:Utilization(利用率)、Saturation(饱和度)、Errors(错误),对每个资源问三个问题。
针对 RKE2 集群的 USE 检查表:
| 资源 | Utilization | Saturation | Errors |
|---|---|---|---|
| CPU | node_cpu_seconds_total | node_pressure_cpu_waiting_seconds_total / runqueue | — |
| 内存 | node_memory_MemAvailable_bytes | node_vmstat_pgmajfault | OOMKill 事件 |
| 磁盘 | node_disk_io_util(util%) | node_disk_io_time_weighted | node_filesystem_device_error |
| 网络 | node_network_transmit_bytes_total | 重传率、conntrack 占用 | node_network_receive_errs_total |
| etcd | — | etcd_server_slow_apply_total、slow_read_indexes_total | etcd_server_leader_changes_seen_total |
| apiserver | CPU/Goroutine | inflight、apiserver_flowcontrol_current_executing_requests | 5xx、timeout |
口诀:先查饱和度(排队),再查错误,最后才看利用率——利用率低不代表没瓶颈(可能并发上限卡住了)。
1.3.2 从指标到根因:逐层下钻路径
遇到"集群慢",推荐下钻顺序:
用户报障:创建 Pod 慢
│
├─ 1. apiserver 慢? → apiserver_request_duration_seconds P99
│ ├─ 慢且 etcd 慢 → 第 2 章(磁盘/DB 大小/碎片)
│ ├─ 慢且 CPU 打满 → 第 3 章(APF/watch 缓存/大 LIST)
│ └─ 只有某类请求慢 → 看 verb/resource 维度拆分
├─ 2. scheduler 慢? → scheduling_attempt_duration / pending_pods
│ └─ 第 8 章(percentageOfNodesToScore、插件配置)
├─ 3. kubelet 慢? → PLEG relist 延迟、pod_start_duration
│ └─ 第 4 章(PLEG、并发、驱逐)
├─ 4. 拉镜像慢? → podLatency 拆解到 ImagePull 阶段
│ └─ 第 7 章(预拉取、P2P、containerd 调优)
└─ 5. 网络慢? → DNS 延迟、Service 转发延迟
└─ 第 5、6 章单变量原则:每次只改一个参数并回归验证,否则无法归因。
1.4 注意事项与最佳实践
- 压测必须在与生产同规格、同 CNI、同存储的演练集群进行;在空集群压出的数字不代表生产容量。
- 所有"官方推荐值"(如 inflight 400/200)只是默认值,不是上限,但是否能调大取决于 etcd 与 API 副本的承载力。
- 保留每次调优的变更记录 + 前后指标截图,这是复盘与汇报的核心资产。
1.5 面试题
Q:如何向面试官描述你定位一次"集群整体变慢"的过程?
要点:先定义"慢"的量化口径(哪个 SLI 恶化);用 USE 方法从 apiserver → etcd → 节点资源逐层下钻;强调饱和度优先于利用率;说明当时发现的根因与单变量验证过程;最后补充建立了什么基线与告警防止复发。
第 2 章 etcd 性能优化
etcd 是 RKE2 控制平面的"心脏",它慢则全集群慢。本章是全书优化部分的重中之重。
2.1 问题现象
典型症状组合:
kubectl操作间歇性变慢,创建/删除资源偶发超时(context deadline exceeded);- apiserver 日志出现
etcdserver: request timed out、*http2...TLS handshake timeout*(需注意后者多为网络问题); - controller 日志出现大量 relist:
Watch close - * total N items received频繁; - 监控:
etcd_disk_wal_fsync_duration_secondsP99 持续 > 50ms;etcd_server_slow_apply_total增长;集群规模大时etcd_mvcc_db_total_size_in_bytes逼近 quota; - 严重时:
etcd_server_leader_changes_seen_total频繁增加(etcd 频繁选主),伴随 apiserver 大面积 503。
2.2 根因分析
| 根因 | 机理 | 判据 |
|---|---|---|
| 磁盘延迟高 | etcd 每次写入都要 WAL fsync,云盘/共享存储 P99 延迟高直接拖慢所有写 | wal_fsync P99 > 10ms |
| DB 过大 | 历史对象堆积,backend 读写放大、碎片多 | DB 接近 quota(默认 2GB+);defrag 后显著缩小 |
| 碎片未整理 | compaction 只删 MVCC 旧版本,空间不还磁盘 | db_total_size - db_total_size_in_use 差值大 |
| 未配置 compaction | 旧版本无限累积 | etcd_debugging_mvcc_db_compaction_total 长期为 0 |
| 网络延迟高(跨机房) | raft 心跳超时导致频繁选主 | leader changes 增多;成员间 RTT > heartbeat-interval 量级 |
| 大对象/大事务 | 单个对象(如巨型 ConfigMap/Endpoints)写放大 | etcd_debugging_mvcc_slow_watcher_total、请求延迟按 resource 拆分 |
| etcd 与业务争抢磁盘 | 与其他 IO 密集型进程共用数据盘 | node_disk_io_util 与 etcd 延迟同步尖峰 |
2.3 优化方法
2.3.1 磁盘:NVMe + 独立磁盘
最有效的优化没有之一:etcd 数据目录使用本地 NVMe SSD,且与 OS、容器数据盘物理隔离。
bash
# 挂载独立 NVMe 盘到 etcd 数据目录
mkfs.ext4 /dev/nvme1n1
mkdir -p /var/lib/rancher/rke2/server/db
mount /dev/nvme1n1 /var/lib/rancher/rke2/server/db
# /etc/fstab 添加持久化挂载(建议在安装 RKE2 之前完成)先用 fio 验证磁盘是否达标(etcd 的参考标准是 8KB 随机写 P99 fsync 延迟):
bash
fio --name=etcd-fsync --filename=/var/lib/rancher/rke2/server/db/fio.test \
--rw=randwrite --bs=8k --ioengine=sync --fdatasync=1 \
--size=1G --runtime=60 --time_based --direct=1 \
--output-format=json | jq '.jobs[0].sync.lat_ns.percentile'验证标准:P99 < 10ms(保守标准;理想 < 5ms)。 达不到就换盘,不要在慢盘上做任何其他 etcd 调优——都是徒劳。
2.3.2 quota-backend-bytes:按需放大但不贪大
RKE2 下在 server 配置中设置:
yaml
# /etc/rancher/rke2/config.yaml
etcd-arg:
- "quota-backend-bytes=8589934592" # 8GB,大规模集群建议值- 默认 2GB 对数千节点集群偏小(对象多 + history 未 compact 时易触发
etcdserver: mvcc: database space exceeded告警,进入只读保护); - 建议 8GB;不建议超过 8GB——更大的 DB 意味着更长的 defrag 与恢复时间;
- 调整时建议滚动执行:逐个 server 节点改配置重启,保持 quorum。
2.3.3 自动压缩(auto-compaction)
yaml
etcd-arg:
- "auto-compaction-mode=periodic"
- "auto-compaction-retention=8h"periodic模式按时间周期 compact,简单可靠;- retention 取 8h 是常用折中:既保证近期 watch/relist 可用,又控制 DB 增长;
- 若上层组件明确需要更长 revision 回溯,再考虑调大。
注意:compaction 只标记旧版本可删除,磁盘空间不会自动回收,必须配合 defrag。
2.3.4 计划性 defrag(碎片整理)
判断是否需要 defrag:
bash
export ETCDCTL_API=3
export ETCDCTL_ENDPOINTS=https://127.0.0.1:2379
export ETCDCTL_CACERT=/var/lib/rancher/rke2/server/tls/etcd/server-ca.crt
export ETCDCTL_CERT=/var/lib/rancher/rke2/server/tls/etcd/server-client.crt
export ETCDCTL_KEY=/var/lib/rancher/rke2/server/tls/etcd/server-client.key
etcdctl endpoint status --cluster -w table
# 关注 DB SIZE 与 DB SIZE IN USE 的差值当 DB SIZE - IN USE > 30% 或 DB 接近 quota 的 70% 时执行 defrag:
bash
# 必须逐成员执行,且 leader 放最后(defrag 期间该成员读阻塞)
etcdctl defrag --command-timeout=60s最佳实践:
- 写成 systemd timer / cronjob,错峰对三个成员滚动 defrag(间隔 ≥ 5 分钟);
- 每次 defrag 前确认集群健康:
etcdctl endpoint health --cluster; - defrag 会短暂阻塞该成员的读,务必在业务低谷执行。
2.3.5 心跳与选举参数(跨机房/大规模场景)
默认值 heartbeat-interval=100ms、election-timeout=1000ms 适用于同机房(成员 RTT < 几 ms)。跨机房部署(RTT 10~50ms)必须调整:
yaml
etcd-arg:
- "heartbeat-interval=500" # ≥ 成员间 RTT,建议 RTT 的 1.5~2 倍
- "election-timeout=5000" # 一般取 heartbeat 的 10 倍原则:
- heartbeat-interval 应大于成员间往返 RTT,否则正常网络抖动即触发选主;
- election-timeout 建议 heartbeat 的 10 倍,且所有成员必须一致;
- 同机房 SSD 集群不要为了"更稳定"而盲目调大——调大会延长故障发现时间。
2.3.6 其他 RKE2 相关要点
- RKE2 server 节点默认运行 embedded etcd(
disable-etcd: false)。超大规模或需要独立伸缩 etcd 时,可评估 external etcd(disable-etcd: true+datastore-endpoint),代价是运维复杂度上升; - etcd 快照备份策略不能省:
etcd-snapshot-schedule-cron、etcd-snapshot-retention保持默认或按 RPO 调整; - 控制面节点避免部署业务负载(taints),防止 IO/CPU 争抢。
2.4 验证方法
bash
# 1. 磁盘基线(见 2.3.1 fio):P99 < 10ms
# 2. etcd 内置压测(在演练环境执行,勿在生产高峰跑)
etcdctl check perf --load=s
# 输出各操作延迟与通过率;观察是否有 fail/超时
# 3. 观测指标回归
# wal_fsync P99、backend_commit P99、slow_apply_total 应显著下降
# leader_changes 应归零(稳态)
# 4. 端到端:kubectl 写操作延迟
time kubectl create ns perf-test-$(date +%s)2.5 注意事项与最佳实践
- 永远先解决磁盘,再谈参数。 机械盘/高延迟云盘上的 etcd 无法通过参数调优达标。
- defrag 必须滚动进行,且确认集群有 quorum;一次 defrag 多个成员可能引发不可用。
- DB 超过 quota 触发告警后,流程是:解除告警 → compact → defrag → 恢复写。不要直接重启。
- 跨机房三中心部署时,优先把 etcd 成员放在 RTT 最低的两个机房(2+1),并接受第三机房为"少数派"。
2.6 面试题
Q:etcd 写延迟突然升高,你的排查顺序是什么?
要点:先看 wal_fsync P99(磁盘)→ 看 DB 大小与碎片(quota/compact/defrag)→ 看 slow_apply 与 leader changes(raft/网络)→ 看是否有大事务(按 resource 拆 apiserver 延迟)→ 最后看宿主机 IO 争抢。强调"磁盘是第一嫌疑"。
第 3 章 kube-apiserver 性能优化
3.1 问题现象
- 高峰期
kubectl get nodes都变慢;apiserver P99 延迟飙升; - apiserver 进程 CPU 打满(单实例或全部);
- 日志出现
Too many requests、APF排队、Rejecting request; - 502/503 增多;watch 频繁断开重连(relist 风暴);
- 通过 LB 访问时,后端 apiserver 负载不均:某一个实例 CPU 远高于其他。
3.2 根因分析
| 根因 | 说明 |
|---|---|
| inflight 上限不足或被打爆 | 默认 mutating 200 / readonly 400,大 LIST 洪峰可耗尽 |
| 大 LIST 查询 | 无 label selector 的全量 LIST(如无缓存的 Dashboard、失控的 controller)单次消耗数百 MB 内存与大量 CPU |
| watch 缓存未命中 | 资源未被缓存或缓存大小不够,穿透到 etcd |
| Event 风暴 | 异常 Pod 反复重启产生海量 event 写入 |
| Audit 日志过重 | 全量 RequestResponse 级别,apiserver 大量 CPU 花在序列化与写盘 |
| LB 长连接不均 | HTTP/2 长连接导致连接固定在某个 apiserver 实例 |
| 反序列化/序列化开销 | json vs protobuf 客户端、巨型对象 |
3.3 优化方法
3.3.1 inflight limits 与 APF
RKE2 server 配置:
yaml
# /etc/rancher/rke2/config.yaml
kube-apiserver-arg:
- "max-requests-inflight=800" # mutating,默认 400(v1.28 默认 400)
- "max-mutating-requests-inflight=400"注意:APF(API Priority and Fairness,v1.20+ 默认开启)启用后,这两个参数作为整体兜底;精细治理应通过 APF 的
FlowSchema/PriorityLevelConfiguration实现。
APF 观测与调优:
bash
kubectl get flowschemas
kubectl get prioritylevelconfigurations
# 关键指标
# apiserver_flowcontrol_rejected_requests_total —— 被拒请求(超限)
# apiserver_flowcontrol_request_wait_duration_seconds —— 排队时间调优思路:
- 给控制面组件(kubelet、scheduler、controller-manager)预留足够
assuredConcurrencyShares(内置system/workload-high通常够用); - 对批量任务控制器(CronJob 风暴、Argo Workflow)单独建低优先级 FlowSchema,防止挤占正常流量;
- 只有当
rejected_requests_total持续增长且确认是合法流量时,才考虑扩大对应 PL 的并发份额或全局 inflight。
3.3.2 watch 缓存调优
apiserver 的 watch 缓存默认按资源自动估算。数千节点集群中,Pod/Node/Endpoints 量大,应显式调大:
yaml
kube-apiserver-arg:
- "watch-cache-sizes=pods#20000,nodes#10000,endpoints#10000,services#10000"判断依据:
bash
# watch 缓存命中率低(miss 高)说明穿透 etcd
# 指标:apiserver_watch_cache_sizes / etcd_request_duration_seconds(按 resource 拆)原则:让绝大多数 LIST/WATCH 命中内存缓存,不穿透到 etcd。
3.3.3 Event 治理
Event 写入走 etcd,风暴时直接拖垮控制面:
yaml
kube-apiserver-arg:
- "event-ttl=30m" # 默认 1h,缩短保留时间配套措施:
- 排查 event 大户:
kubectl get events -A --sort-by=.lastTimestamp | tail -30; - 根治反复重启的 Pod(CrashLoopBackOff 是 event 风暴头号来源);
- 高版本可评估
events.k8s.io/v1+ 聚合器(如 eventrouter/exporter)将 event 旁路到 ES/Loki,减轻对 apiserver 的依赖查询压力。
3.3.4 Audit 裁剪
RKE2 默认开启审计。全量审计在大规模集群 CPU 开销可观。裁剪策略(/etc/rancher/rke2/audit-policy.yaml):
yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: None # 不审计 get/list/watch
verbs: ["get", "list", "watch"]
- level: Metadata # 其余只记元数据
- level: RequestResponse # 仅对敏感资源记全量
resources:
- group: ""
resources: ["secrets"]挂载并在 config 中引用:
yaml
kube-apiserver-arg:
- "audit-policy-file=/etc/rancher/rke2/audit-policy.yaml"
- "audit-log-maxsize=200"
- "audit-log-maxbackup=5"3.3.5 LB 连接均衡:goaway-chance
多 apiserver 前面挂 LB 时,HTTP/2 长连接会让客户端"粘"在单个实例。调大 GOAWAY 概率让连接定期重平衡:
yaml
kube-apiserver-arg:
- "goaway-chance=0.01" # 默认 0;0.01 表示每个请求 1% 概率要求客户端重连验证方式见 3.4。若 LB 支持按连接/请求级负载均衡(如 Envoy 的 least_request),也可在 LB 层解决。
3.3.6 pprof 排查
apiserver CPU/内存异常时,profiling 是金标准:
bash
# RKE2 apiserver 默认开启 profiling(--profiling 默认 true)
# CPU profile(采样 60s)
curl -sk --cert /var/lib/rancher/rke2/server/tls/kube-apiserver-client-ca.crt \
--key <对应 key> --cacert <ca> \
"https://127.0.0.1:6443/debug/pprof/profile?seconds=60" -o apiserver-cpu.pprof
# 更简单:kubectl proxy + 有权限的 admin kubeconfig
kubectl proxy &
curl -s "http://127.0.0.1:8001/debug/pprof/heap" -o heap.pprof # 路径视代理方式而定
go tool pprof -http=:9999 apiserver-cpu.pprof常见发现:序列化(encode/decode)占比高 → 大对象/大 LIST;client-go cache 相关 → watch 风暴。
3.4 验证方法
- 延迟回归:优化前后对比
apiserver_request_duration_secondsP99(按 verb/resource 拆)。 - 容量验证:用 kube-burner(第 1 章)在优化后的集群重跑同一压测,确认拐点后移。
- watch 缓存:大 LIST(如
kubectl get pods -A)时观察 apiserver CPU 增量与etcd_request_duration_seconds是否同步——不同步说明命中缓存。 - LB 均衡:
bash
# 观察各 apiserver 实例的连接数是否趋于均匀
ss -tnp | grep :6443 | awk '{print $5}' | cut -d: -f1 | sort | uniq -c- APF:确认
apiserver_flowcontrol_rejected_requests_total归零或仅出现在预期的低优先级队列。
3.5 注意事项与最佳实践
- 调整 inflight 前先确认 etcd 扛得住:inflight 放大的是 etcd 的写压力。
- watch-cache-sizes 调大会增加 apiserver 内存占用,同步调整节点内存预留与 apiserver 的 memory limit(RKE2 中 apiserver 为 static pod,注意节点可用内存)。
- audit 裁剪前先与合规确认审计要求,别为性能牺牲合规。
- 多 apiserver 副本的扩缩容:RKE2 中每个 server 节点一个 apiserver,扩 server 节点即扩 apiserver。
3.6 面试题
Q:apiserver CPU 打满但 etcd 很闲,可能是什么原因?怎么处理?
要点:穿透缓存的大 LIST/watch 风暴(客户端 relist)、序列化开销(大对象、JSON 客户端)、audit 全量、Event 风暴。处理:pprof 定位热点;按 verb/resource 拆延迟找元凶;调大 watch 缓存;治理 event 与 audit;APF 限制低优先级流量。
第 4 章 kubelet 大规模优化
4.1 问题现象
- 节点上 Pod 启动慢,
kubelet_pod_start_duration_secondsP99 高; - 节点 NodeReady 但 Pod 大量 Pending/ContainerCreating;
- kubelet CPU 高、
PLEG is not healthy事件; - 高并发拉镜像时节点网络/磁盘打满,镜像拉取互相拖死;
- 节点磁盘被镜像占满触发 image GC 抖动,或 eviction 误杀业务。
4.2 根因分析
| 根因 | 说明 |
|---|---|
| kubelet 访问 apiserver 默认 QPS 太低 | 默认 kubeAPIQPS=5/Burst=10,大节点(>100 Pod)状态上报与 watch 不够用 |
| 串行拉镜像 | serialize-image-pulls=true 时大镜像排队 |
| registry 并发限制 | registry-qps/burst 与并发拉取数限制 |
| PLEG 延迟 | 容器 runtime(containerd)慢、存储驱动慢、节点负载高导致 relist 周期拉长 |
| GC 阈值不合理 | 镜像占满磁盘引发抖动;GC 过于激进导致重复拉镜像 |
| 驱逐阈值不合理 | 过早驱逐影响业务;过晚触发节点 OOM |
4.3 优化方法
RKE2 中通过 /etc/rancher/rke2/config.yaml 的 kubelet-arg 或 KubeletConfiguration 下发:
yaml
kubelet-arg:
# —— 密度与 API 访问 ——
- "max-pods=250" # 视节点规格与 CNI IP 容量而定
- "kube-api-qps=50"
- "kube-api-burst=100"
# —— 镜像拉取 ——
- "serialize-image-pulls=false" # 并行拉取(配合 registry 限制)
- "registry-qps=10"
- "registry-burst=20"
- "max-parallel-image-pulls=5" # v1.27+ 支持,限制并发保护磁盘/带宽
# —— 镜像 GC ——
- "image-gc-high-threshold=80"
- "image-gc-low-threshold=70"
# —— 驱逐 ——
- "eviction-hard=memory.available<500Mi,nodefs.available<10%,imagefs.available<15%"
- "eviction-soft=memory.available<1Gi,nodefs.available<15%"
- "eviction-soft-grace-period=memory.available=30s,nodefs.available=60s"
- "eviction-pressure-transition-period=30s"逐项说明:
4.3.1 max-pods
- 默认 110。提升前必须确认:CNI 的节点 IP 池容量(Canal/Calico 每节点 block 大小、Cilium IPAM)、节点 PID/文件句柄/端口范围、以及单节点 Pod 密度对 apiserver 的连锁压力(每 Pod 的 watch/status 流量)。
- 常见取值:200~250(大规格节点 + IP 充裕);保守起步,压测验证后再上调。
4.3.2 kube-api-qps/burst
- 单节点 Pod 数多、状态变化频繁(Job 型负载)时默认 5/10 明显不足,表现为 status 更新延迟、探针事件滞后;
- 注意连锁影响:N 个节点 × QPS 提升 = apiserver 总负载上升,需与第 3 章联动评估。
4.3.3 镜像拉取:并行 vs 串行
serialize-image-pulls=true(RKE2 默认false,即并行):串行适合机械盘;NVMe 节点并行收益明显;max-parallel-image-pulls限制并发上限,防止批量调度时把磁盘 IO/带宽打满;- 配合第 7 章的镜像预拉取与 P2P 分发。
4.3.4 镜像 GC
imageGCHighThreshold(imagefs 使用率)触发 GC,降到 low 为止;- 阈值过低 → 频繁 GC + 重复拉镜像;过高 → 磁盘满风险。80/70 是常用折中;
- 使用独立 imagefs(containerd 数据目录独立挂载)可让 image GC 与 nodefs 驱逐解耦,推荐。
4.3.5 驱逐阈值
memory.available过低(如 <100Mi)会导致节点先 OOM 再驱逐,失去意义;500Mi~1Gi 是常见值;- soft eviction + grace-period 给业务优雅退出时间;
eviction-pressure-transition-period防止节点在压力状态间抖动(flapping)。
4.3.6 PLEG 延迟治理
PLEG is not healthy 的根因多数是 runtime 侧:containerd 版本 bug、存储快照器慢、节点 IO 饱和、或单节点容器数过多。
排查与治理:
bash
# 1. 看 PLEG relist 延迟
# 指标 kubelet_pleg_relist_duration_seconds P99(健康应 < 1s)
# 2. 看 runtime 操作延迟
# 指标 kubelet_runtime_operations_duration_seconds
# 3. containerd 侧
crictl --runtime-endpoint unix:///run/k3s/containerd/containerd.sock ps | wc -l
journalctl -u rke2-agent --since "10 min ago" | grep -iE "pleg|runtime" | tail处理:升级 containerd(跟随 RKE2 版本升级)、降低单节点容器密度、把 imagefs 迁移到更快的盘、排查是否有 hang 住的 exec/日志读取。
4.3.7 staticPodPath 说明
RKE2 的组件以 static pod 运行(/var/lib/rancher/rke2/agent/pod-manifests)。自定义 kubelet 的 staticPodPath 一般不建议改动;如需在节点上投放静态 Pod,直接向该目录写 manifest 即可,kubelet 会自动拉起。注意:静态 Pod 不受调度器管理,大规模使用会带来管控盲区。
4.4 验证方法
bash
# 1. Pod 启动延迟回归
# 指标 kubelet_pod_start_duration_seconds P99(优化前后对比)
# 2. 批量创建压测(配合 kube-burner)观察单节点 100+ Pod 同时启动的耗时曲线
# 3. PLEG 健康
kubectl get events -A --field-selector reason=NodeNotReady # 应无 PLEG 相关
# 指标 kubelet_pleg_relist_duration_seconds P99 < 1s
# 4. 镜像 GC 行为:造盘压测试 GC 是否在 high 阈值触发、业务是否受影响
df -h /var/lib/rancher/rke2/agent/containerd # imagefs 使用率
# 5. 驱逐演练:对测试节点施加内存压力,确认按阈值与 grace period 驱逐4.5 注意事项与最佳实践
- kubelet 参数修改后需重启 rke2-agent;批量滚动修改用节点分批 + drain。
max-pods上调必须与 CNI IPAM 容量评估一起做(见第 8 章连锁影响分析)。- 驱逐参数不要照抄:有状态负载、批任务的容忍度完全不同。
4.6 面试题
Q:节点出现 PLEG is not healthy,如何定位?
要点:确认 relist 延迟指标;检查 containerd 健康(crictl、containerd 日志、goroutine);排查节点 IO 饱和与容器密度;检查是否有卡死的 exec/attach 会话;短期可重启 rke2-agent 恢复,长期靠升级 runtime 与降密度。
第 5 章 网络性能优化
5.1 问题现象
- Service 访问延迟随服务数增长而上升;
- 节点 CPU 软中断高(ksoftirqd)、conntrack 表满报错:
nf_conntrack: table full, dropping packet; - 跨节点 Pod 通信吞吐低于宿主机直连;VXLAN 环境下有效 MTU 缩小引发分片/性能下降;
- NetworkPolicy 规则多后,新建连接延迟明显增加;
- 会话亲和(sessionAffinity)开启后个别后端热点。
5.2 根因分析
| 根因 | 说明 |
|---|---|
| iptables 规则数爆炸 | Service/Endpoints 数量大时,iptables 规则 O(N) 匹配,每包遍历成本随规则数上升 |
| conntrack 表过小 | 默认 nf_conntrack_max 对高并发节点偏小,丢包且无应用层报错 |
| MTU 开销 | VXLAN 封包占用 50 字节,MTU 未下调导致分片 |
| NetworkPolicy 实现差异 | iptables 实现(Calico 默认模式)规则数随 policy×pod 膨胀 |
| 会话亲和成本 | ClientIP 亲和需要额外规则/状态,且破坏负载均衡 |
5.3 优化方法
5.3.1 kube-proxy:iptables → IPVS → eBPF 的演进
关系量级:Service 数 S、Endpoints 数 E 时,iptables 规则数近似 O(S+E),且每条链线性匹配——数千 Service 时单包匹配成本显著上升,规则更新(Endpoints 变化)还是全量刷新,导致更新延迟与 CPU 尖峰。
| 模式 | 匹配复杂度 | 规则更新 | 适用规模 | 备注 |
|---|---|---|---|---|
| iptables | O(N) 线性 | 全量刷新 | < 数千 Service | 默认,简单稳定 |
| IPVS | O(1) 哈希 | 增量 | 数千~上万 Service | 需加载 ipvs 内核模块 |
| eBPF(Cilium kube-proxy 替代) | O(1) | 增量、Per-CPU map | 大规模/高 policy 密度 | 完全去掉 kube-proxy,需内核 ≥ 4.19(保守建议 5.x) |
RKE2 下切 IPVS(kube-proxy 以独立组件运行):
yaml
# /etc/rancher/rke2/config.yaml
kube-proxy-arg:
- "proxy-mode=ipvs"
- "ipvs-scheduler=lc" # least connection;默认 rrbash
# 节点预置模块
modprobe ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrackRKE2 也支持直接部署 Cilium(
cni: cilium)并启用其 kube-proxy replacement(eBPF),这是大规模 + 高密度 NetworkPolicy 场景的推荐路线,但属于架构级变更,需在新建集群或严格演练后迁移。
5.3.2 conntrack 表调优
bash
# 查看当前使用量
sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
# 调优(/etc/sysctl.d/99-rke2.conf)
net.netfilter.nf_conntrack_max = 1048576
# hashsize 一般取 max 的 1/4(通过内核模块参数设置)
echo "options nf_conntrack hashsize=262144" > /etc/modprobe.d/nf_conntrack.conf
sysctl --system判据:conntrack_count / conntrack_max 长期 > 70% 就该扩容;同时关注 node_nf_conntrack_entries_limit(若采集)。UDP/DNS 高并发节点尤其需要关注。
5.3.3 MTU 与 VXLAN 开销
- VXLAN 封装头 50 字节:宿主机 MTU 1500 → overlay MTU 应设 1450(RKE2 Canal 默认已处理,自定义 CNI 需确认);
- 若底层网络支持巨帧(MTU 9000),overlay 设 8950,吞吐与 CPU 效率显著提升;
- 验证:
ping -M do -s <size> <对端PodIP>逐步逼近确认无分片。
5.3.4 NetworkPolicy 大规模性能
- Calico(iptables 数据面):policy 生成 iptables 规则,规模 = policy 数 × 选中 Pod 数,数千 Pod 高密度策略时规则膨胀、更新抖动;
- Cilium(eBPF):policy 编译进 eBPF map,匹配 O(1),大规模下性能优势明显;
- 优化手段(不切换 CNI 时):合并碎 policy、避免大范围
podSelector: {}+ 大量 ingress/egress 规则、用 CIDR 规则替代 selector 规则(视 CNI 支持)。
5.3.5 会话亲和性成本
yaml
spec:
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 3600- 开启后 iptables/IPVS 需要为每个 Service 维护
-m recent/亲和状态,规则与内存开销上升; - 且 ClientIP 经过 SNAT/代理后可能退化为"所有流量打到同一后端";
- 建议:只在确有需要的 Service 上开启,超时时间按业务会话长度设置,避免默认值滥用。
5.3.6 带宽管理
- CNI 带宽插件(bandwidth plugin)可对 Pod 限速:
yaml
metadata:
annotations:
kubernetes.io/ingress-bandwidth: 100M
kubernetes.io/egress-bandwidth: 100M- 节点级:防止单个 Pod 打满节点网卡影响同节点其他业务( noisy neighbor );
- 大规模集群建议在 QoS 层面优先保障控制面流量(apiserver/etcd 节点网卡不受业务洪峰影响)。
5.4 验证方法
bash
# 1. Service 转发延迟:优化前后用 perf 工具对比
# 从 Pod 内压测 ClusterIP:
kubectl run -it --rm perf --image=nicolaka/netshoot -- bash
# 内部:fortio/wrk 压 Service,对比 iptables vs IPVS 的延迟与 QPS
# 2. 规则数与更新时间
iptables -t nat -L KUBE-SERVICES -n | wc -l # iptables 模式
ipvsadm -Ln | wc -l # IPVS 模式
# 观察 Endpoints 变更时的规则生效延迟
# 3. conntrack:压测期间监控 count/max 与丢包计数
conntrack -S | grep -i drop # 应为 0
# 4. MTU:跨节点大包连通性
ping -M do -s 1472 <pod-ip> # MTU1500 环境;不通则逐减确认实际路径 MTU
# 5. 吞吐基线:iperf3 Pod-to-Pod 跨节点,对比宿主机直连(开销应 < 10~20%,视 CNI)5.5 注意事项与最佳实践
- IPVS 切换先在单集群金丝雀验证;注意 IPVS 与某些依赖 iptables 细粒度规则的安全组件的兼容性。
- eBPF/Cilium 路线收益大但学习成本高,团队需具备 eBPF 排障能力(cilium CLI、Hubble)。
- conntrack 扩容会占用内核内存(每条约几百字节),百万级表约占用数百 MB,需纳入节点内存预算。
5.6 面试题
Q:为什么大规模集群推荐 IPVS 或 eBPF 而不是 iptables?
要点:iptables 规则线性匹配 O(N),Service/Endpoints 多时转发变慢;规则全量更新导致变更延迟与 CPU 尖峰。IPVS 哈希 O(1)、增量更新;eBPF 更进一步去掉 conntrack 依赖与 kube-proxy,支持更高效的负载均衡与 policy 实现。
第 6 章 DNS 优化
6.1 问题现象
- 应用间歇性 DNS 解析超时(
lookup ... i/o timeout),尤其高峰期; - CoreDNS Pod CPU 打满、重启或 OOM;
- 短连接服务(如大量微服务互调)DNS 延迟占总延迟比例高;
- conntrack 表满(UDP DNS 高并发加剧);
- 某些应用解析外部域名异常慢。
6.2 根因分析
| 根因 | 说明 |
|---|---|
| CoreDNS 副本数不足 | 默认 2~3 个副本无法承载数千节点 QPS |
| ndots:5 惩罚 | 少于 5 个点的名字会先遍历所有 search 域(默认 svc/cluster/local 域),单查询放大为多次 |
| 每请求都走集群 CoreDNS | 无本地缓存,同节点重复查询消耗 CoreDNS 与 conntrack |
| 负缓存缺失/过短 | 解析失败被高频重试 |
| 上游/外部域名路径不合理 | 所有外部解析都走默认 forward,无 stubDomain 分流 |
6.3 优化方法
6.3.1 NodeLocal DNSCache:大规模集群必开
RKE2 默认已部署 NodeLocal DNSCache(rke2-coredns-rke2-coredns-local DaemonSet,监听 169.254.20.10)。确认与强制使用:
bash
kubectl -n kube-system get ds | grep local
kubectl -n kube-system get cm rke2-coredns-rke2-coredns-local -o yaml # 确认配置收益:同节点 DNS 查询本地命中(TCP 上 CoreDNS 失败时兜底),显著降低 CoreDNS 负载与 UDP conntrack 压力。数千节点集群应视为强制开启项,不要禁用。
6.3.2 CoreDNS 自动扩缩容:cluster-proportional-autoscaler
按集群规模线性调整 CoreDNS 副本:
yaml
# 关键 ConfigMap 参数示例
data:
linear: |-
{
"coresPerReplica": 256,
"nodesPerReplica": 16,
"min": 3,
"max": 30,
"preventSinglePointFailure": true
}经验:每 16 个节点或 256 核一个副本(社区常用起点),数千节点集群 max 设高些但注意单副本要 anti-affinity 分散。RKE2 可通过 HelmChartConfig 定制 rke2-coredns chart 接入 autoscaler。
6.3.3 ndots:5 优化
默认 ndots:5:名字中点数 <5 时先按 search 域展开。kubernetes.default 会产生 4~5 次无效查询。
优化一(应用侧,精准):对确定性内部服务名写全 FQDN(结尾加点):
my-service.my-ns.svc.cluster.local.优化二(Pod 侧):
yaml
spec:
dnsConfig:
options:
- name: ndots
value: "2" # 外部域名多的服务受益明显注意:调低 ndots 可能让短内部域名解析行为变化,按工作负载评估,勿全局一刀切(全局修改 kubelet 的 resolv-conf/dnsConfig 模板影响面大)。
6.3.4 负缓存与缓存时长
CoreDNS Corefile(NodeLocal 或 CoreDNS 本体):
.:53 {
cache 30 {
success 9984 30
denial 9984 30 # 负缓存:NXDOMAIN 等缓存 30s
prefetch 20
}
}denial缓存减少 NXDOMAIN 风暴对上游的冲击;prefetch对热点记录提前刷新,平滑过期抖动。
6.3.5 stubDomain 分流
把特定域(如内部 IDC 域名)指向专用 DNS,避免全量转发给默认上游:
cluster.local:53 { ... }
internal.example.com:53 {
errors
cache 30
forward . 10.10.0.2 10.10.0.3
}收益:减少跨域递归延迟、隔离外部 DNS 故障域。
6.4 验证方法
bash
# 1. 解析延迟基线:Pod 内循环 dig 统计
kubectl run -it --rm dnstest --image=nicolaka/netshoot -- bash
for i in $(seq 100); do dig kubernetes.default.svc.cluster.local @169.254.20.10 | grep "Query time"; done | sort -n | tail
# 2. CoreDNS 负载:优化前后 QPS/副本
# 指标 coredns_dns_requests_total、CPU 使用率
# 3. ndots 效果:对比 dnsConfig 调整前后的查询次数(tcpdump 抓 53 端口计数)
# 4. 压测:dnsperf 对 NodeLocal IP 打 QPS,观察无超时、无丢包6.5 注意事项与最佳实践
- NodeLocal DNSCache 与 conntrack 的交互是历史高发坑(UDP 竞争条件导致丢包),新版 NodeLocal 已通过 NOTRACK 等规则规避,保持组件版本更新。
- ndots 调整先在重 DNS 负载的 namespace 试点。
- CoreDNS 副本分散:topologySpreadConstraints / anti-affinity 必配。
6.6 面试题
Q:什么是 ndots 问题?如何优化?
要点:Kubernetes 默认 resolv.conf 中 ndots:5 + 多个 search 域,导致非 FQDN 查询被多次展开重试,DNS QPS 放大数倍。优化:FQDN 加点、按需调低 ndots、NodeLocal 本地缓存兜底。
第 7 章 镜像与启动优化
7.1 问题现象
- 大版本发布/批量 Job 时,大量节点同时拉同一镜像,registry 打满、Pod 长时间 ImagePullBackOff/Pending;
- Pod 启动总耗时长但不知道慢在哪一环;
- containerd 磁盘 IO 高、snapshotter 相关报错;
- 节点磁盘被镜像层撑满。
7.2 根因分析
Pod 启动延迟全链路拆解:
调度完成 → ImagePull(未命中本地) → 创建 sandbox(pause) → CNI 分配网络 →
挂载卷/密钥 → 拉取并解压镜像层 → 启动容器 → 探针就绪主要耗时项:镜像拉取与解压(常占 70%+)、CNI 调用、以及卷挂载。
7.3 优化方法
7.3.1 镜像瘦身
- 多阶段构建、distroless/alpine 基础镜像;删除构建期依赖;
- 合并层、按变更频率排序层(不变的层靠前,利于节点间缓存复用);
- 目标:业务镜像 < 500MB(保守经验值),超大镜像(>2GB)在任何优化下启动都快不了。
7.3.2 镜像预拉取
对热点大镜像做 DaemonSet 预拉取(sleep 容器):
yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: image-prepuller
namespace: kube-system
spec:
selector:
matchLabels: {app: image-prepuller}
template:
metadata:
labels: {app: image-prepuller}
spec:
initContainers:
- name: prepull
image: registry.example.com/big-app:v2.3.1 # 热点镜像
command: ["sh", "-c", "true"]
containers:
- name: pause
image: registry.k8s.io/pause:3.9新版本发布前先滚动 prepuller,再发布业务——Pod 启动时镜像已在本地。
7.3.3 P2P 分发:Dragonfly
数千节点同时拉同一镜像时,registry 带宽是硬瓶颈。Dragonfly 让节点间 P2P 互传镜像层:
- 架构:dragonfly-dfdaemon(每节点)+ scheduler + seed peer;
- 与 containerd 集成:通过 registry mirror 配置指向本地 dfdaemon;
- 收益(保守表述):批量拉取场景 registry 出口带宽与总耗时显著下降,官方与社区案例中常见数倍提升,实际数值以自测为准。
RKE2 containerd 侧配置 mirror(/etc/rancher/rke2/registries.yaml):
yaml
mirrors:
registry.example.com:
endpoint:
- "http://127.0.0.1:65001" # dfdaemon 代理端口
- "https://registry.example.com" # 回源兜底7.3.4 containerd 配置调优
RKE2 通过 containerd 配置模板调优(/var/lib/rancher/rke2/agent/etc/containerd/config.toml.tmpl 方式定制,谨慎操作):
| 参数 | 建议 | 说明 |
|---|---|---|
| snapshotter | overlayfs(默认) | 已是事实标准;关注底层盘 IO |
| 并发拉取/解压 | 限制并发(如 max_concurrent_downloads 默认 3,视磁盘能力调) | NVMe 可适当上调;机械盘保守 |
| 镜像存储盘 | 独立 NVMe 挂载 /var/lib/rancher/rke2/agent/containerd | 与 etcd/系统盘隔离 |
| GC | 与 kubelet imageGC 阈值联动(第 4 章) | 避免双 GC 打架 |
修改 containerd 模板属于深度定制,升级 RKE2 时需回归验证模板兼容性。
7.3.5 启动延迟拆解与逐段优化
用 kube-burner podLatency 或以下方式拆解:
bash
# Pod 事件时间线
kubectl get events -n <ns> --field-selector involvedObject.name=<pod> --sort-by=.lastTimestamp
# 关注 Scheduled→Pulling→Pulled→Created→Started 的间隔逐段对策:
| 阶段 | 慢的典型原因 | 对策 |
|---|---|---|
| ImagePull | 镜像大、registry 慢、并发限制 | 瘦身/预拉取/Dragonfly/调并发 |
| sandbox 创建 | pause 镜像未缓存、CRI 抖动 | 确保 pause 镜像预置;查 runtime 延迟指标 |
| CNI | IPAM 慢(IP 池耗尽、CNI 插件调用链长) | IP 池预留、CNI 调优(第 5/8 章) |
| 容器启动 | 应用初始化慢、探针配置保守 | 优化应用;startupProbe 替代过严的 readiness |
| 卷挂载 | 网络存储 attach 慢 | 第 9 章 |
7.4 验证方法
- 端到端:冷节点(无镜像缓存)与热节点分别测量 Pod 从创建到 Ready 的 P50/P99;
- 批量场景:kube-burner 一次性拉起 500+ Pod,观察总耗时与 registry 带宽曲线(优化前后对比);
- Dragonfly 生效确认:dfdaemon 日志中出现 peer 下载记录;registry 出口流量下降。
7.5 注意事项与最佳实践
- prepuller 的 image tag 要与业务发布 tag 严格同步(纳入 CI/CD)。
- Dragonfly 引入新的运维面(dfdaemon 健康、seed peer 容量),小集群不必引入。
- containerd 独立盘 + 独立 imagefs 是 GC 与驱逐行为可控的前提。
7.6 面试题
Q:一次批量发布 2000 个 Pod,镜像 1.2GB,如何保证启动速度?
要点:发布前 DaemonSet 预拉取(滚动完成);P2P 分发(Dragonfly)分担 registry 带宽;containerd 并发拉取按磁盘能力调优;镜像长期瘦身;用 podLatency 拆解验证各环节。
第 8 章 调度与密度优化
8.1 问题现象
- 集群节点多后,Pod 调度延迟上升(
scheduler_scheduling_attempt_duration_secondsP99 变差); - 资源碎片:整体利用率 30% 却频繁
Insufficient cpu/memory; - 节点负载严重不均:热点节点 OOM,冷节点闲置;
- 提升单节点 Pod 密度后,出现 IP 耗尽、kubelet 超时、CNI 报错等连锁问题。
8.2 根因分析
| 根因 | 说明 |
|---|---|
| 调度器全量打分 | 默认对所有可行节点打分,节点数多时每轮调度成本上升 |
| requests 不准 | 乱填 requests 导致调度视图失真:要么碎片化,要么超售过度 |
| 缺少再均衡 | 调度是一次性决策,运行后负载漂移无人纠正 |
| 密度提升的连锁瓶颈 | IP 池、kubelet 上限、CNI 性能、每 Pod 的 apiserver 流量 |
8.3 优化方法
8.3.1 scheduler:percentageOfNodesToScore
yaml
# RKE2: /etc/rancher/rke2/config.yaml
kube-scheduler-arg:
- "percentage-of-nodes-to-score=20"- 含义:找到可行节点后,只对其中 20% 打分即选出最优,牺牲少量调度质量换取吞吐;
- 大规模(>1000 节点)建议 5~20;值越小调度越快但节点选择质量略降;
- 验证:
scheduler_scheduling_attempt_duration_secondsP99 下降,且节点分布无恶化。
8.3.2 descheduler 再均衡
部署 descheduler(CronJob 方式运行),常用策略组合:
yaml
# 策略片段示例
profiles:
- name: default
pluginConfig:
- name: RemovePodsViolatingNodeAffinity
args: {nodeAffinityType: ["requiredDuringSchedulingIgnoredDuringExecution"]}
- name: LowNodeUtilization
args:
thresholds: {cpu: 20, memory: 20, pods: 20}
targetThresholds: {cpu: 50, memory: 50, pods: 50}
- name: RemovePodsViolatingInterPodAntiAffinity
plugins:
balance:
enabled: [RemovePodsViolatingTopologySpreadConstraint, LowNodeUtilization]
deschedule:
enabled: [RemovePodsViolatingNodeAffinity, RemovePodsViolatingInterPodAntiAffinity]注意:descheduler 只驱逐,重调度交给 scheduler;必须为业务配置 PDB,并避开有状态/不可中断负载(用 label 排除)。
8.3.3 拓扑分布
yaml
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector: ...- 大规模多 AZ 集群:优先
ScheduleAnyway(软约束)避免约束过死导致 Pending; - 与 descheduler 的
RemovePodsViolatingTopologySpreadConstraint联动修正漂移。
8.3.4 每节点 Pod 密度上限提升的连锁影响
把 max-pods 从 110 提到 250,需要同步评估:
| 维度 | 连锁影响 | 对策 |
|---|---|---|
| CNI IPAM | 每节点 IP 需求翻倍;Calico block / 子网耗尽 | 扩大 pod CIDR 或调 block size;Cilium 检查 IPAM 池 |
| kubelet | 每 Pod watch/status/探针开销 | 第 4 章 QPS、PLEG 治理 |
| apiserver/etcd | Pod 对象与 event 写入量随总 Pod 数上升 | 第 2/3 章联动 |
| 内核 | conntrack、fd、PID、端口范围 | sysctl 预算(第 5 章) |
| 密度风险 | 单节点故障爆炸半径变大(一次宕 250 Pod) | 副本数/PDB/反亲和策略相应收紧 |
结论:密度提升是系统工程,不是改一个参数。
8.3.5 节点利用率提升
- requests 准确性治理:以历史 P95 用量 × 安全系数回填 requests;对长期 requests/usage 偏离 > 3 倍的负载重点治理;
- VPA 推荐模式:只挂 recommender(不自动改)输出建议值,供 CI 定期更新 requests:
bash
kubectl get vpa <name> -o jsonpath='{.status.recommendation.containerRecommendations}'- 超售策略:CPU 可适度超售(limits > requests,突发型业务);内存严禁激进超售(OOM 不可压缩)。保守起点:节点级 CPU 超售比 1.5~2,内存不超售;
- 在离线混部(进阶):批任务填谷,需配合优先级、驱逐与 QoS 体系,属于平台级工程。
8.4 验证方法
- 调度延迟 P99 与每小时调度吞吐(
scheduler_schedule_attempts_totalrate)回归; - 集群利用率(
sum(requests)/sum(allocatable)与实际 usage 双口径)周环比; - descheduler 干跑(
--dry-run)观察驱逐计划是否合理,再启用; - 密度提升后做节点故障演练:drain 一台高密度节点,确认恢复时间与控制面压力可接受。
8.5 注意事项与最佳实践
- percentageOfNodesToScore 调整后关注"调度质量":热点节点是否增多。
- descheduler 策略一次只启用一个,观察一周再加。
- VPA 自动模式(auto/recreate)会引发 Pod 重建,生产慎用。
8.6 面试题
Q:集群资源"碎片化"(有余量却调度不出)怎么治理?
要点:requests 失真与一次性调度是根因。手段:requests 治理(VPA 推荐)、descheduler LowNodeUtilization 再均衡、拓扑软约束、超大 Pod 与节点规格的 Binpack 匹配(节点规格归一化)。
第 9 章 存储性能
9.1 问题现象
- 有状态 Pod 创建慢:长时间
Waiting for volume to be attached; - CSI 相关 Pod(provisioner/attacher)日志大量重试、超时;
- 业务 IO 延迟高,但应用自身无异常;
- 大规模下 PVC 数量多,控制面(对云厂商/存储网关)调用被限流。
9.2 根因分析
| 根因 | 说明 |
|---|---|
| 网络存储固有延迟 | NFS/云盘/分布式存储的 RTT 与吞吐上限 |
| CSI 控制面瓶颈 | provision/attach 操作串行队列、外部 API 限流 |
| 本地盘 vs 网络盘选型错误 | 高 IOPS 业务用了共享网络存储 |
| metadata 压力 | 海量小文件/高频元数据操作打满存储 metadata 服务(NFS/对象网关尤甚) |
| mount 选项不当 | 缺 noatime、NFS 未调 rsize/wsize 等 |
9.3 优化方法
- 选型优先:etcd 类/数据库类高 IOPS 负载用本地 NVMe(local-path-provisioner / TopoLVM)+ 应用层复制;网络存储用于共享与归档场景。
- CSI 控制面扩容:provisioner/attacher 副本数与
--worker-threads上调;确认后端 API 限流配额。 - 挂载优化:
noatime;NFS 调rsize/wsize=1048576,hard,noresvport;块存储确认队列深度与多路径。 - metadata 减压:小文件合并、目录分片(hash 分桶)、避免单目录百万级文件;对象存储场景用前缀打散。
- StorageClass 分层:fast(NVMe 本地/高性能池)/ standard(网络盘)/ archive(对象),用 default SC 引导业务选对层。
9.4 验证方法
bash
# PVC 供给耗时:创建计时
time kubectl apply -f test-pvc.yaml
kubectl get events --field-selector involvedObject.kind=PersistentVolumeClaim --sort-by=.lastTimestamp | tail
# IO 基线:Pod 内 fio(随机读写 + 延迟)
fio --name=randrw --rw=randrw --bs=4k --size=1G --numjobs=4 --runtime=60 --time_based \
--filename=/data/fio.test --direct=1 --output-format=json | jq '.jobs[0] | {read: .read.lat_ns.percentile, write: .write.lat_ns.percentile}'
# 对比宿主机直测同盘,量化 CSI/虚拟化开销9.5 注意事项与最佳实践
- 本地盘方案必须回答"节点故障数据怎么办":应用层复制(如数据库主从)是前提。
- 网络存储关注重连与 hang 住的故障模式(hard mount 会无限重试阻塞 Pod 终止)。
- 存储压测必须在挂载点内做,测宿主机盘不代表 Pod 视角。
9.6 面试题
Q:大规模集群中本地盘和网络存储怎么选?
要点:按可用性模型选——应用层有复制能力(DB 主从、分布式队列)用本地 NVMe 拿性能;无复制能力的通用有状态负载用网络存储换取节点故障后的重挂载能力。同时考虑 metadata 压力、CSI 控制面容量与故障域。
第 10 章 优化清单汇总与面试题
10.1 优化清单汇总表
| 层级 | 优化项 | 关键参数/手段 | 预期收益(保守) | 风险/成本 |
|---|---|---|---|---|
| OS | etcd 独立 NVMe | 独立挂载 /var/lib/rancher/rke2/server/db | 写延迟降至 ms 级,全集群稳定性质变 | 硬件成本 |
| OS | conntrack 扩容 | nf_conntrack_max=1048576 | 消除 table full 丢包 | 数百 MB 内核内存 |
| OS | MTU 校准 | overlay 1450 / 巨帧 8950 | 吞吐提升、消除分片 | 需底层网络配合 |
| 控制平面 | etcd quota+compact+defrag | 8GB quota / periodic 8h / 滚动 defrag | 避免 DB 写满只读;延迟稳定 | defrag 窗口管理 |
| 控制平面 | etcd 心跳参数(跨机房) | heartbeat=500 / election=5000 | 消除误选主 | 故障发现变慢 |
| 控制平面 | APF 与 inflight | FlowSchema 分级;inflight 适度上调 | 高峰不雪崩、公平性 | 需先确认 etcd 容量 |
| 控制平面 | watch 缓存 | watch-cache-sizes 按资源调大 | LIST 不穿透 etcd | apiserver 内存上升 |
| 控制平面 | Event/audit 治理 | event-ttl=30m;audit 裁剪 Metadata 级 | 写压力与 CPU 下降 | 审计粒度需合规确认 |
| 控制平面 | LB 均衡 | goaway-chance=0.01 | 多实例负载均匀 | 重连开销极小 |
| 节点 | kubelet QPS | kube-api-qps=50/burst=100 | 大节点状态同步及时 | apiserver 负载上升 |
| 节点 | 并行拉镜像 | serialize-image-pulls=false + max-parallel-image-pulls | 启动加速 | IO/带宽争抢需限流 |
| 节点 | 驱逐/GC 阈值 | eviction-hard/soft、imageGC 80/70 | 磁盘与 OOM 行为可控 | 阈值需按负载调校 |
| 网络 | IPVS / eBPF | proxy-mode=ipvs 或 Cilium | Service 规模扩展性 O(1) | 迁移验证成本 |
| 网络 | NetworkPolicy 优化 | 合并规则 / Cilium eBPF | 高密度策略下转发稳定 | 策略梳理工作量 |
| DNS | NodeLocal DNSCache | 保持开启并验证 | 解析延迟与 CoreDNS 负载显著下降 | 版本更新维护 |
| DNS | autoscaler + ndots | CPA 线性扩容;按需 ndots=2 | QPS 弹性、查询放大消除 | ndots 需按负载试点 |
| 镜像 | 预拉取 + P2P | prepuller DaemonSet / Dragonfly | 批量发布提速数倍(自测为准) | 新组件运维面 |
| 镜像 | 镜像瘦身 | 多阶段构建 | 拉取与解压时间线性下降 | 构建流程改造 |
| 调度 | percentageOfNodesToScore | 20(>1000 节点) | 调度吞吐提升 | 调度质量略降 |
| 调度 | descheduler + VPA 推荐 | LowNodeUtilization 等 | 利用率提升、碎片收敛 | 驱逐风险需 PDB 兜底 |
| 存储 | SC 分层 + 本地 NVMe | fast/standard/archive | IO 型业务延迟数量级改善 | 数据可用性靠应用层 |
10.2 常见面试题(10 题)
1. 大规模 RKE2 集群优化的第一步是什么? 答:建立性能基线与监控(apiserver/etcd/kubelet/scheduler 核心 SLI),用压测(kube-burner)找到容量拐点,再按 USE 方法定位瓶颈。没有基线的优化无法验证。
2. etcd 性能优化最有效的一招是什么? 答:本地 NVMe 独立磁盘。fio 验证 8K 随机写 fsync P99 < 10ms;其次才是 quota(8GB)、periodic compaction(8h)、滚动 defrag、跨机房调心跳选举参数。
3. apiserver 被打爆如何应急与根治? 答:应急:确认 APF 拒绝的是否低优先级流量、扩容 server 节点;根治:治理大 LIST 客户端、调大 watch 缓存、裁剪 audit、治理 event 风暴、按 verb/resource 拆延迟定位元凶。
4. iptables 模式为什么在数千 Service 时不可行? 答:规则线性匹配 O(N),且 Endpoints 变更触发全量规则刷新,转发延迟与更新 CPU 尖峰随规模恶化;IPVS(哈希 O(1)、增量更新)或 Cilium eBPF 是演进方向。
5. NodeLocal DNSCache 解决了什么问题? 答:同节点 DNS 查询本地缓存命中,降低 CoreDNS 负载与解析延迟;同时用 TCP 上行规避 UDP conntrack 竞争丢包;配合 ndots 治理消除 search 域查询放大。
6. 如何加速 2000 个 Pod 的批量发布? 答:镜像瘦身是长期项;发布前 DaemonSet 预拉取;P2P 分发(Dragonfly)解决 registry 带宽瓶颈;containerd 并发拉取按磁盘能力调优;用 podLatency 拆解验证。
7. kubelet 的 PLEG is not healthy 怎么排查? 答:看 relist 延迟指标 → 查 containerd 健康与版本 → 查节点 IO 饱和与容器密度 → 查卡死的 exec/日志会话;短期重启 agent,长期升级 runtime、降密度、imagefs 上快盘。
8. 提升单节点 Pod 密度有哪些连锁影响? 答:CNI IP 池容量、kubelet QPS/PLEG、apiserver/etcd 的对象与 event 压力、conntrack/fd/PID 内核预算、以及单点故障爆炸半径——必须联动评估,不能单改 max-pods。
9. 集群有资源却调度不出 Pod(碎片化)怎么办? 答:requests 治理(VPA 推荐回填)、descheduler LowNodeUtilization 再均衡、拓扑软约束、节点规格归一化配合 Binpack 思维。
10. percentageOfNodesToScore 是什么?怎么取值? 答:调度器找到可行节点后只对该比例节点打分,换取调度吞吐。>1000 节点建议 5~20;取值后在演练集群验证调度延迟 P99 下降且节点负载分布无恶化,再上线。
本部分完。优化的终点不是"参数调完",而是建立基线—压测—告警—回归的闭环,让每一次变更都有数据背书。