主题
第三部分 大规模集群运维体系
"千台规模看架构,万台规模看运维。"——当 RKE2 集群跨越千节点门槛后,决定平台稳定性的不再是某一项调优参数,而是体系化的运维能力:SLO 驱动的目标管理、分级灰度的变更流水线、可演练的应急预案、以及把每一次故障转化为组织记忆的复盘文化。本部分以真实生产经验为蓝本,覆盖升级、节点生命周期、etcd、证书安全、资源治理、发布变更与应急演练七大运维域,并给出可直接落地的 Runbook 与命令。
适用版本:RKE2 v1.28+(示例命令在 v1.28 ~ v1.35 上验证通过) 读者对象:Kubernetes/RKE2 平台 SRE、平台架构师、运维团队负责人
目录
- 第 1 章 大规模运维的挑战与 SRE 方法论
- 第 2 章 大规模升级体系
- 第 3 章 节点生命周期管理
- 第 4 章 etcd 运维
- 第 5 章 证书与安全运维
- 第 6 章 资源治理
- 第 7 章 变更与发布管理
- 第 8 章 应急预案与演练
- 第 9 章 常见面试题
第 1 章 大规模运维的挑战与 SRE 方法论
1.1 数千节点运维 vs 百节点运维的本质差异
很多团队把大规模运维理解为"把一百节点的流程重复三十遍",这是最常见的认知误区。规模带来的不是线性工作量增长,而是质变:
| 维度 | 百节点集群 | 数千节点集群 |
|---|---|---|
| 故障常态化 | 节点故障是"事件" | 每天硬件故障是"常态",3000 节点按年故障率 3% 计,平均每天 0.25 次磁盘/内存故障 |
| 手工运维 | kubectl drain 手动执行可行 | 必须有批量工具链 + 审批流,单人 SSH 逐台操作 = 自杀式运维 |
| 控制平面压力 | apiserver QPS < 500,etcd DB < 2GiB | LIST/WATCH 风暴、etcd DB 逼近 8GiB 上限、kubelet 心跳洪流 |
| 变更风险 | 升级失败回滚代价小 | 一次错误变更影响数千节点,回滚窗口以小时计 |
| 网络效应 | 局部故障影响有限 | 雪崩级联:DNS 抖动 → 全集群健康检查失败 → 大规模驱逐 |
| 组织形态 | 1~2 人兼职 | 专职 SRE 团队 + oncall 轮换 + 分级响应 |
| 观测数据量 | GB/天 | TB/天,观测系统本身需要容量规划 |
本质差异总结为三句话:
- 概率问题变成确定性问题:小概率故障 × 大基数 = 必然发生。不能靠"小心操作",要靠"假设故障随时发生"的容错设计。
- 人成为瓶颈:任何依赖"某人会做"的操作都是单点,必须工具化、Runbook 化、可交接。
- 爆炸半径控制成为第一设计目标:所有变更的第一问不是"能不能做",而是"最坏情况下炸多大"。
百节点思维模式 数千节点思维模式
┌─────────────────┐ ┌─────────────────────────┐
│ 故障 → 人去修 │ │ 故障 → 系统自愈 → 人复盘 │
│ 变更 → 直接做 │ ───► │ 变更 → 分级 → 灰度 → 回滚 │
│ 容量 → 不够再加 │ │ 容量 → 预测 → 缓冲水位 │
│ 稳定性 = 不出事 │ │ 稳定性 = SLO 达标率 │
└─────────────────┘ └─────────────────────────┘1.2 SLO 驱动运维
大规模运维的核心抓手是 SLO(Service Level Objective)。没有 SLO 的运维团队只会在"过度维稳"(不敢做任何变更)和"裸奔"(什么都敢动)之间摇摆。
1.2.1 RKE2 平台层 SLO 参考定义
| 指标(SLI) | SLO 目标 | 测量方式 |
|---|---|---|
| kube-apiserver 可用性 | 99.95%(月度) | LB 层探测 /readyz,剔除维护窗口 |
| apiserver 请求时延 p99(非 LIST) | < 1s | apiserver_request_duration_seconds |
| Pod 创建到 Running 时延 p99 | < 60s | 金丝雀 Pod 定时创建探测 |
| 节点 Ready 比例 | ≥ 99.5% | kube_node_status_condition{condition="Ready"} |
| 集群 DNS 解析成功率 | ≥ 99.99% | 黑盒探测 kubernetes.default.svc |
| etcd leader 稳定性 | 每日 leader 变更 ≤ 2 次 | etcd_server_leader_changes_seen_total |
| 平台组件升级成功率 | ≥ 99%(单批次) | 升级流水线统计 |
1.2.2 错误预算(Error Budget)
以 apiserver 可用性 99.95% 为例,月度错误预算 = 30 天 × 24h × 0.05% ≈ 21.6 分钟。
┌──────────────────────────────────────────────────────┐
│ 错误预算消耗规则(团队共识,写入运维章程) │
├──────────────────────────────────────────────────────┤
│ 预算剩余 > 50% → 正常节奏推进变更(含升级窗口) │
│ 预算剩余 20~50% → 冻结高风险变更,只做紧急修复 │
│ 预算剩余 < 20% → 全面冻结,进入稳定性专项治理 │
└──────────────────────────────────────────────────────┘最佳实践:错误预算是"用来花的",不是"用来守的"。长期预算消耗为 0 说明团队过于保守、迭代太慢;连续两月透支说明风险管控失效。
1.3 变更管理体系
1.3.1 变更分级
| 级别 | 定义 | 示例 | 审批 | 执行方式 |
|---|---|---|---|---|
| C0 紧急 | 止血类操作 | 证书过期紧急轮换、etcd 磁盘写满抢救 | oncall 自行决策 + 事后补审 | 立即执行 |
| C1 标准低风险 | 已验证、可自动回滚 | 节点下线、业务 namespace 配额调整 | 自动化系统免检 | 流水线自动执行 |
| C2 常规 | 影响单批次节点 | agent 节点滚动升级(单批 ≤ 5%) | 值班 TL 审批 | 灰度 + 观察期 |
| C3 高风险 | 影响控制平面/全局网络 | RKE2 版本升级、CNI 升级、etcd 参数变更 | 变更评审会(CAB)+ SRE Lead 双签 | 维护窗口 + 金丝雀 + 完整回滚预案 |
| C4 架构级 | 不可逆或大范围 | 集群拆分、CIDR 调整、证书体系重构 | 架构委员会 | 专项项目组 |
1.3.2 灰度环(Ring)模型
数千节点集群的通用灰度模型,任何节点级变更都遵循:
Ring0 金丝雀池(1%~2%,常驻低优先级业务) ──观察 24h──┐
▼
Ring1 早鸟池(5%,配合度高的业务团队) ──观察 12h──┐
▼
Ring2 常规池(每批 10%,自动推进) ──观察 4h───┐
▼
Ring3 尾批(含敏感业务,人工确认后放行)每个 Ring 之间设置自动健康门禁(Health Gate),任一指标恶化即自动暂停:
yaml
# 灰度门禁判定指标(示例)
health_gates:
- name: node_ready_ratio
expr: sum(kube_node_status_condition{condition="Ready",status="true"}) / count(kube_node_info) >= 0.995
- name: pod_restart_burst
expr: increase(kube_pod_container_status_restarts_total[10m]) <= 100
- name: apiserver_p99
expr: histogram_quantile(0.99, rate(apiserver_request_duration_seconds_bucket[5m])) <= 1.0
- name: custom_business_slo
expr: up{job="canary-probe"} == 11.3.3 回滚设计三原则
- 回滚路径必须在变更前验证,而不是变更失败后现想。
- 回滚决策人 ≠ 变更执行人,避免沉没成本心理导致硬撑。
- 设定明确的"回滚触发线":例如"单批次失败率 > 10% 或门禁连续 2 次不通过 → 无条件回滚"。
1.4 运维组织架构与 oncall
1.4.1 参考组织形态(3000 节点 / 3~5 集群规模)
┌──────────────┐
│ 平台负责人 │
└──────┬───────┘
┌──────────────────┼──────────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ SRE 运行组 │ │ 平台工程组 │ │ 架构/容量组 │
│ (oncall×N) │ │ (工具/CI) │ │ (规划/评审) │
└────────────┘ └────────────┘ └────────────┘
值班/应急/巡检 自动化/发布平台 容量/标准/预算- SRE 运行组:一线 oncall,Follow-the-sun 或 7×24 轮换,建议单人连续值班不超过 1 周。
- 平台工程组:维护升级流水线、节点生命周期工具、Runbook 代码化,不承担日常值班。
- 架构/容量组:版本节奏规划、容量预测、变更评审会组织。
1.4.2 oncall 制度要点
| 项目 | 建议做法 |
|---|---|
| 告警分级 | P1(电话)≤ 3 条/周,否则告警噪音会摧毁 oncall;P2(IM)当日处理;P3 进工单池 |
| 值班交接 | 周一晨会口头交接 + 书面值班日志(进行中事件、近期变更、遗留风险) |
| 升级机制 | P1 15 分钟未响应自动升级到二线,30 分钟到 TL |
| 值班津贴与补休 | 制度化,否则人员流失率会反噬稳定性 |
| 复盘文化 | Blameless Postmortem,48h 内出报告,action item 必须进跟踪系统 |
注意事项:oncall 的痛苦指数与告警数量成正比,与告警"质量"成反比。大规模集群第一要务是告警治理——所有不能导出"需要人立刻做什么"的告警,都应该降级为仪表盘指标。
第 2 章 大规模升级体系
2.1 RKE2 升级基本约束
动手之前必须刻进脑子里的三条铁律:
- 不可跨 minor 版本升级:v1.27 → v1.29 不允许,必须 v1.27 → v1.28 → v1.29。
- 升级顺序不可颠倒:先控制平面(etcd + server),后 agent。控制平面版本 ≥ agent 版本,apiserver 不能比 kubelet 老超过 3 个 minor(Kubernetes 版本偏差策略)。
- RKE2 没有官方降级路径:升级前不打快照 = 裸奔。
2.2 system-upgrade-controller 深度实战
RKE2 官方推荐的升级方式是 rancher/system-upgrade-controller(SUC),它通过 Plan CRD 驱动节点滚动升级。
2.2.1 部署 SUC
bash
kubectl apply -f https://github.com/rancher/system-upgrade-controller/releases/download/v0.14.2/system-upgrade-controller.yaml
# 生产建议:镜像先同步进私有 registry,再修改 Deployment 镜像地址
kubectl -n system-upgrade set image deploy/system-upgrade-controller \
system-upgrade-controller=registry.internal/rancher/system-upgrade-controller:v0.14.2注意:SUC 命名空间
system-upgrade在 RKE2 中默认豁免 PSA(pod-security.kubernetes.io/enforce=privileged),迁移命名空间时别丢了 label。
2.2.2 Plan CRD 解剖
yaml
apiVersion: upgrade.cattle.io/v1
kind: Plan
metadata:
name: rke2-server-upgrade
namespace: system-upgrade
spec:
concurrency: 1 # server 必须串行(见 2.2.4)
nodeSelector:
matchExpressions:
- key: node-role.kubernetes.io/control-plane
operator: Exists
serviceAccountName: system-upgrade
cordon: true # 升级前 cordon
drain:
force: true
deleteEmptyDirData: true # v1.28+ 字段名,旧版为 delete-local-data
ignoreDaemonSets: true
gracePeriod: 60 # 秒;-1 表示使用 Pod 自身 terminationGracePeriodSeconds
upgrade:
image: registry.internal/rancher/system-upgrade-controller
version: v1.28.15+rke2r1
secrets:
- name: system-upgrade-registries
path: /etc/rancher/rke2/registries.yaml # 私有镜像仓库配置注入
prepare:
image: registry.internal/rancher/system-upgrade-controller
args: ["prepare", "rke2-agent-upgrade"]关键字段说明:
| 字段 | 作用 | 大规模注意事项 |
|---|---|---|
concurrency | 同时升级的节点数 | server=1 或 ≤ (etcd quorum 容忍数-1);agent 按池设 5%~10% |
cordon | 升级时先 cordon | 防止升级期间新 Pod 调度上来 |
drain | 升级前驱逐业务 | 必须配合 PDB,否则有状态业务可能被打穿 |
channel / version | 二选一,指定目标版本 | 生产只用 version 显式指定,禁用 channel(避免意外升到新版本) |
prepare | server 升级前预拉镜像 | 大幅减少 server 升级窗口内的拉镜像耗时 |
2.2.3 按节点池分批:多 Plan 策略
3000 节点集群不可能用一个 Plan 一梭子打完。推荐按节点池(label)拆分 Plan,用 concurrency + 分批 label 控制节奏:
bash
# 打批次标签(示例:每批 150 台)
kubectl get nodes -l node-role.kubernetes.io/worker=,pool=general -o name \
| shuf --random-source=<(yes 42) > /tmp/workers.txt
split -n l/20 -d /tmp/workers.txt /tmp/batch-
for i in $(seq -w 0 19); do
for n in $(cat /tmp/batch-$i); do
kubectl label ${n#node/} upgrade-batch=ring2-b$((10#$i))
done
doneyaml
# 每批一个 Plan,只在通过上一批门禁后 apply
apiVersion: upgrade.cattle.io/v1
kind: Plan
metadata:
name: rke2-agent-ring2-b00
namespace: system-upgrade
spec:
concurrency: 30 # 批内 30 并发
nodeSelector:
matchLabels:
upgrade-batch: ring2-b00
serviceAccountName: system-upgrade
cordon: true
drain:
force: true
deleteEmptyDirData: true
ignoreDaemonSets: true
gracePeriod: 120
version: v1.28.15+rke2r1
upgrade:
image: registry.internal/rancher/system-upgrade-controller流水线伪代码:
bash
for batch in ring0 ring1 ring2-b00 ring2-b01 ... ring3; do
kubectl apply -f plans/agent-${batch}.yaml
wait_plan_complete "rke2-agent-${batch}" # 轮询 Plan status
run_health_gates || { pause_and_page_oncall; break; }
sleep ${OBSERVE_WINDOW} # ring0: 24h, ring2: 4h
done2.2.4 控制平面与 etcd 升级顺序
对于 3(或 5)节点控制平面 + 内嵌 etcd 的 RKE2:
┌─────────────────────────────────────────────────────────┐
│ 控制平面升级顺序(concurrency: 1,SUC 串行执行) │
├─────────────────────────────────────────────────────────┤
│ 1. 升级前:etcd 快照 + 校验 leader 分布 │
│ 2. 先升级 follower server 节点(非首节点) │
│ 3. 首节点(cluster-init 那个)放最后 │
│ 4. 每台升级后验证: │
│ - rke2-server Active │
│ - etcd endpoint health 全 OK │
│ - kubectl get --raw=/readyz?verbose 全 [+]ok │
│ 通过后再放下一台 │
└─────────────────────────────────────────────────────────┘验证命令:
bash
# etcd 健康(RKE2 内嵌 etcdctl)
export ETCDCTL_API=3
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 --endpoints=https://127.0.0.1:2379 endpoint status --cluster -w table
etcdctl endpoint health --cluster -w table注意事项:etcd 集群版本(
etcdctl endpoint status的DB VERSION)在升级后可能停留在旧版本,需要所有成员升级完成并滚动重启后才会推进 cluster version。这期间不要慌,属于正常现象。
2.2.5 drain 策略在大规模下的调优
| 参数 | 小集群常用值 | 大规模建议值 | 原因 |
|---|---|---|---|
gracePeriod | 30 | 120~300 | 大集群 Pod 迁移慢、镜像拉取排队 |
disableEviction | false | 视业务而定 | 业务强依赖 PDB 时设 true(走 eviction API,尊重 PDB) |
| PodDisruptionBudget | 无 | 每个关键负载必配 | 见第 3 章 |
| 预拉镜像 | 无 | Plan prepare + DaemonSet 预拉 | 3000 节点同时拉镜像会打爆 registry |
2.3 版本追赶策略
Kubernetes 每 ~4 个月发布一个 minor 版本,社区支持最近 3 个 minor。落后太多会陷入"跨版本追赶地狱"。
时间线(推荐节奏:每 4 个月升级一次,始终落后社区 1~2 个 minor 求稳)
K8s 发布 v1.28 v1.29 v1.30 v1.31 v1.32
│ │ │ │ │
RKE2 稳定 ├──+2月──┤ │ │ │
我们升级 v1.28.x──►v1.29.x──►v1.30.x──►...
踩坑期观望 灰度一个月 灰度一个月要点:
- 永远选
rke2r1之后的 patch:例如 v1.28.15+rke2r1,等社区出到+rke2r1后 2~4 周、看完 GitHub issue 无重大回归再进生产。 - patch 升级走快速通道:同 minor 内 patch 升级(v1.28.14→v1.28.15)可压缩为 Ring0 观察 4h 后全量。
- 追赶多版本时的纪律:v1.27→v1.29 追赶,每个中间 minor 在生产至少停留 2 周(一个完整业务周期),不要一天连跳两级。
- 年度版本日历写入运维章程,提前 6 周启动兼容性评估(CNI/Ingress/CSI 版本矩阵)。
2.4 升级前检查清单(Checklist)
text
[ ] 1. 目标版本 RKE2 release notes / known issues 通读完毕
[ ] 2. 组件兼容矩阵确认(CNI/Ingress/CSI/cert-manager/监控 agent)
[ ] 3. 全量 etcd 快照完成且已验证可恢复(最近一次恢复演练 ≤ 30 天)
[ ] 4. 快照已异地备份(S3)
[ ] 5. 所有节点磁盘空闲 ≥ 20%,/var/lib/rancher 无 inode 告警
[ ] 6. 节点无 NotReady、无 Pending 升级任务残留
[ ] 7. etcd DB size < 6GiB,fragmentation < 40%(否则先 compact+defrag)
[ ] 8. 私有 registry 容量与带宽确认(预拉镜像流量估算)
[ ] 9. 所有关键负载 PDB 配置审计通过
[ ] 10. 证书剩余有效期 > 升级周期 + 90 天
[ ] 11. 维护窗口已公告,业务方 oncall 已知会
[ ] 12. 回滚预案演练过(含快照恢复路径验证)
[ ] 13. 升级期间告警静默规则与白名单已配置(只静默预期噪音,不静默核心 SLO)
[ ] 14. 升级批次表与责任人表已签字其中第 7 项检查命令:
bash
etcdctl --endpoints=https://127.0.0.1:2379 endpoint status --cluster -w table
# 关注 DB SIZE 与 DB SIZE IN USE;碎片率 = 1 - IN_USE/SIZE2.5 升级失败回滚:快照兜底方案
RKE2 官方不支持降级。rke2 server --cluster-reset 仅用于 etcd 灾难恢复,不是常规回滚手段。因此回滚体系 = 预防(快照) + 恢复(重置) 两层。
2.5.1 升级前强制快照
bash
# 控制平面每台(或至少首节点)执行
rke2 etcd-snapshot save --name pre-upgrade-v1.28.15
rke2 etcd-snapshot list确认 S3 异地备份(server config):
yaml
# /etc/rancher/rke2/config.yaml(升级窗口期间临时加密保留策略)
etcd-snapshot-schedule-cron: "0 */1 * * *" # 窗口期加密为每小时
etcd-snapshot-retention: 48
etcd-s3: true
etcd-s3-endpoint: s3.internal.example.com
etcd-s3-bucket: rke2-etcd-backup
etcd-s3-folder: prod-cluster-01
etcd-s3-access-key: xxx
etcd-s3-secret-key: xxx2.5.2 失败场景与处置矩阵
| 失败场景 | 处置 |
|---|---|
| 单台 agent 升级后 NotReady | 回退该节点二进制:/var/lib/rancher/rke2/bin 中由 SUC 生成的旧版本软链恢复,或重装旧版本;不行则下线该节点、重建 |
| 单台 server 升级失败 | rke2-killall.sh → 重装旧版本 → 若 etcd 成员异常则 member remove 后重新 join |
| 控制平面大面积异常 | cluster-reset:选一台健康节点 rke2 server --cluster-reset --cluster-reset-restore-path=<快照>,其余 server 清理数据目录重新 join(流程同第 4 章 quorum 恢复) |
| 业务层面不兼容(API 废弃) | 回滚业务应用,而非回滚集群;升级前的兼容性扫描(pluto/kubent)就是为了堵住这个 |
注意事项:
--cluster-reset会丢失快照之后的所有写操作。它是"灾难恢复"不是"回滚",决策必须升级到 C0 级别事件指挥官。
2.6 真实案例:3000 节点 RKE2 升级 Runbook(v1.27.16 → v1.28.15)
背景:单集群 5 控制平面 + 2987 worker,3 个节点池(general / gpu / bigmem),全程 3 周。
text
W-6周 版本评估:通读 v1.28 changelog,pluto 扫描废弃 API(发现 2 个业务仍用
flowcontrol.apiserver.k8s.io/v1beta1,下发整改工单)
W-2周 兼容性矩阵验证:calico v3.27 / ingress-nginx v1.10 / longhorn v1.6 测试环境验证
W-1周 Ring0 池(30 台)升级 + 7×24h 观察;etcd 快照恢复演练(借测试集群验证第 4 章流程)
W0-D1 控制平面 5 台串行升级(SUC server Plan, concurrency=1),耗时 3.5h
每台间隔 20 分钟观察:etcd leader_changes、apiserver p99、业务探测
W0-D2 Ring1:gpu 池 50 台(低峰期),concurrency=10,耗时 4h
W0-D3 门禁复盘会:确认 Ring0/Ring1 无回归,错误预算消耗 0
W1 Ring2:general 池 20 批 × 120 台,每日 4 批(09:00/14:00/19:00/23:00)
每批 concurrency=30,批间健康门禁自动判定
W2-D1 Ring3:bigmem 敏感池 8 批,业务方逐批确认后放行
W2-D3 全量完成;清理 upgrade-batch 标签;升级报告归档;etcd 快照策略恢复常态踩坑记录(真实经验):
- D2 gpu 池某批 drain 卡住 40 分钟——某训练任务 PDB 配成
minAvailable: 100%,等于禁止驱逐。教训:升级前审计"绝对 PDB"(minAvailable == replicas)。 - W1 第 7 批门禁触发暂停:registry 带宽打满导致镜像拉取 p99 飙升。教训:预拉镜像 DaemonSet 必须先行。
- 控制平面第 3 台升级后 etcd 出现一次 leader 切换——正常,但触发了告警风暴。教训:升级窗口的预期告警要进静默白名单,且白名单要带过期时间。
第 3 章 节点生命周期管理
节点生命周期 = 上线 → 服役(维护)→ 下线 → 善后。3000 节点集群中,每周都有节点进出,必须把每一步做成幂等、可审计的流水线。
3.1 节点上线 SOP
装机/交付 ──► 基线检查 ──► 安装 RKE2 agent ──► 冒烟验证 ──► 打标签/污点 ──► 放行调度
(硬件/内核) (安装脚本) (金丝雀Pod) (CMDB同步)3.1.1 上线前基线检查脚本(节选)
bash
#!/bin/bash
# precheck.sh —— 节点上线基线检查,全部通过才允许 join
set -e
fail() { echo "[FAIL] $1"; exit 1; }
# 1. OS 与内核版本
. /etc/os-release
[[ "$ID" == "rocky" && "$VERSION_ID" == 9.* ]] || fail "OS 非 Rocky 9"
kernel=$(uname -r); echo "kernel: $kernel"
# 2. 时钟同步(etcd 对时钟敏感)
chronyc tracking | grep -q "System time" || fail "chrony 未同步"
timedatectl | grep -q "synchronized: yes" || fail "NTP 未同步"
# 3. 防火墙/iptables(参考 k8s-issues/rke2-master-join-fails-iptables-firewall-blocking)
systemctl is-active firewalld | grep -q inactive || \
echo "[WARN] firewalld 运行中,确认已按 RKE2 端口矩阵放行或禁用"
# 4. 关闭 swap
[[ $(swapon --show | wc -l) -eq 0 ]] || fail "swap 未关闭"
# 5. 内核参数与模块
for m in br_netfilter overlay; do lsmod | grep -q $m || modprobe $m; done
sysctl net.bridge.bridge-nf-call-iptables | grep -q "= 1" || fail "bridge-nf-call-iptables"
# 6. 磁盘:/var/lib/rancher 独立挂载、inode 充足
df -h /var/lib/rancher | tail -1
df -i /var/lib/rancher | awk 'NR==2 {gsub(/%/,"",$5); if($5>70) exit 1}' || fail "inode 使用率过高"
# 7. 到 server:9345 与 registry 连通性
curl -sk --connect-timeout 5 https://192.168.122.190:9345/ping || fail "server:9345 不可达"
curl -s --connect-timeout 5 http://192.168.122.156:30000/v2/ || fail "registry 不可达"
echo "[OK] 基线检查通过"3.1.2 安装与 join
bash
# 参考 rke2/install-agent.sh 的生产化改造
curl -sfL https://get.rke2.io | INSTALL_RKE2_VERSION="v1.28.15+rke2r1" \
INSTALL_RKE2_TYPE="agent" sh -
# 或使用离线安装包 + 私有 registry(大规模必选,见 rke2/install-source.md)
mkdir -p /etc/rancher/rke2
cat > /etc/rancher/rke2/config.yaml <<EOF
server: https://192.168.122.190:9345
token: ${NODE_TOKEN}
node-ip: ${NODE_IP}
node-name: ${NODE_NAME}
kube-proxy-arg:
- "proxy-mode=ipvs"
system-default-registry: 192.168.122.156:30000
node-label:
- "pool=general"
- "zone=az1"
- "onboard-batch=2026w29"
EOF
systemctl enable --now rke2-agent3.1.3 上线后冒烟与放行
bash
# 节点 Ready 后先打上维护污点,冒烟通过再去除
kubectl taint nodes ${NODE_NAME} node.cilium.io/agent-not-ready=true:NoExecute --overwrite 2>/dev/null || true
kubectl taint nodes ${NODE_NAME} onboarding=true:NoSchedule
# 冒烟:DaemonSet 金丝雀 Pod(网络/DNS/挂载/镜像拉取)
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: DaemonSet
metadata: {name: onboard-smoke, namespace: platform-smoke}
spec:
selector: {matchLabels: {app: onboard-smoke}}
template:
metadata: {labels: {app: onboard-smoke}}
spec:
tolerations: [{key: onboarding, operator: Exists, effect: NoSchedule}]
containers:
- name: smoke
image: registry.internal/base/smoke:1.0
command: ["/bin/sh","-c","nslookup kubernetes.default && curl -s https://kubernetes.default.svc -k && sleep 3600"]
EOF
# 冒烟通过后放行
kubectl wait --for=condition=ready pod -l app=onboard-smoke --field-selector spec.nodeName=${NODE_NAME} -n platform-smoke --timeout=300s \
&& kubectl taint nodes ${NODE_NAME} onboarding=true:NoSchedule-最佳实践:上线批次的节点统一打
onboard-batch=YYYYwWW标签。某批次集中出问题(如同一批次网卡固件 bug)时可一键圈定影响面。
3.2 节点维护:批量 cordon/drain 工具化
3.2.1 为什么手动 kubectl drain 不可行
3000 节点集群每周维护窗口可能涉及上百节点,手工操作的问题:无并发控制、无视 PDB 熔断、无进度跟踪、不可审计。必须工具化。
3.2.2 批量维护工具设计(内部平台 drainctl 逻辑)
python
#!/usr/bin/env python3
# drainctl —— 批量节点维护工具核心逻辑(伪代码,Python + kubernetes client)
MAX_CONCURRENT = 20 # 全局并发 drain 数
MAX_UNAVAILABLE_RATIO = 0.01 # 集群不可用节点比例熔断线
def drain_batch(nodes):
for node in nodes:
wait_for_capacity() # 并发信号量
if cluster_unready_ratio() > MAX_UNAVAILABLE_RATIO:
pause_and_alert("熔断:集群 NotReady 比例超阈值"); return
if not pdb_precheck(node): # 驱逐预演算
skip_and_ticket(node); continue
cordon(node)
drain(node, grace_period=120, timeout=1800, ignore_daemonsets=True)
mark_maintenance(node) # 打上 maintenance=true 标签 + 注释
notify_owner(node)
def pdb_precheck(node):
"""预演算:该节点上的 Pod 驱逐后,是否所有 PDB 仍满足 minAvailable"""
for pod in pods_on_node(node):
pdb = find_pdb(pod)
if pdb and pdb.status.disruptionsAllowed < 1:
return False
return True3.2.3 PodDisruptionBudget 体系
大规模集群下,PDB 是"防止运维操作打死业务"与"防止业务卡死运维操作"的双向契约:
yaml
# 标准 PDB 模板(无状态服务)
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata: {name: web-pdb, namespace: app-a}
spec:
minAvailable: 85% # 允许同时驱逐 15%,权衡运维效率与业务容量
selector:
matchLabels: {app: web}
---
# 有状态服务(如 Kafka、Elasticsearch)
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata: {name: kafka-pdb, namespace: middleware}
spec:
maxUnavailable: 1 # 有状态一次只允许动 1 个副本
selector:
matchLabels: {app: kafka}| 反模式 | 后果 | 治理手段 |
|---|---|---|
minAvailable: 100% 或 == replicas | drain 永远卡死 | 准入校验(OPA/Kyverno)直接拒绝 |
| 不配 PDB | 批量维护可能打穿业务 | CI 卡点:Deployment/StatefulSet 必须带 PDB |
| 单副本业务配 PDB | 自我矛盾,drain 卡死 | 准入校验提示改为 maxUnavailable: 1 且接受中断 |
yaml
# Kyverno 策略示例:禁止"绝对 PDB"
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata: {name: pdb-sanity}
spec:
validationFailureAction: Enforce
rules:
- name: no-absolute-pdb
match: {any: [{resources: {kinds: ["PodDisruptionBudget"]}}]}
validate:
message: "minAvailable 不允许为 100% 或等于副本数"
pattern:
spec:
X(minAvailable): "100%"3.3 节点下线 SOP
下线申请 ──► 长灰度 cordon(24h) ──► drain ──► 应用迁移验证 ──► 清理 ──► 删除 Node ──► CMDB 销户bash
# 1. cordon 观察 24h(防止 drain 后发现遗漏)
kubectl cordon ${NODE}
# 2. drain
kubectl drain ${NODE} --ignore-daemonsets --delete-emptydir-data \
--grace-period=120 --timeout=30m
# 3. 停止 agent 并清理
ssh ${NODE} "systemctl stop rke2-agent && rke2-killall.sh && systemctl disable rke2-agent"
# 彻底清理(重用该机器前必须执行)
ssh ${NODE} "rm -rf /var/lib/rancher /etc/rancher /var/lib/kubelet /var/lib/cni"
# 4. 从集群移除(删除 Node 对象前先确认无残留引用)
kubectl delete node ${NODE}
# 5. 善后检查:证书/静态 Pod/etcd 成员(若为 server 节点,必须先 member remove!)
etcdctl member list -w table # server 节点下线后此处不应再有它注意事项:下线 control-plane/etcd 节点时顺序相反——先
etcdctl member remove,再停服务,最后删 Node。直接关机走人会导致 etcd quorum 长期"带病运行"(期望成员数没变,实际少一个)。
3.4 硬件故障自愈:node-problem-detector + draino + descheduler
3000 节点 = 硬件故障常态化。自愈体系三层:
┌──────────────────────────────────────────────────────────┐
│ L1 检测:node-problem-problem-detector (NPD) DaemonSet │
│ 内核日志/硬件事件 → NodeCondition + Event │
│ L2 决策:draino(按 condition 自动 cordon+drain) │
│ 只处理"明确不可修复"的 condition,其余进工单 │
│ L3 治理:descheduler(处理非故障类失衡,见第 6 章) │
└──────────────────────────────────────────────────────────┘3.4.1 NPD 关键配置
yaml
# node-problem-detector config(节选):监控磁盘、内存、网卡
{
"plugin": "kmsg",
"logPath": "/dev/kmsg",
"conditions": [
{"type": "KernelDeadlock", "reason": "KernelHasNoDeadlock", "message": "kernel has no deadlock"},
{"type": "ReadonlyFilesystem", "reason": "FilesystemIsNotReadOnly", "message": "Filesystem is not read-only"},
{"type": "MemoryPressureHW", "reason": "NoMCE", "message": "no machine check exception"},
{"type": "NicLinkFlap", "reason": "NicStable", "message": "nic link stable"}
],
"rules": [
{"type": "permanent", "condition": "ReadonlyFilesystem",
"reason": "FilesystemIsReadOnly", "pattern": "Remounting filesystem read-only"},
{"type": "permanent", "condition": "MemoryPressureHW",
"reason": "MCEHardwareError", "pattern": ".*Hardware Error.*memory.*"},
{"type": "temporary", "condition": "NicLinkFlap",
"reason": "NicFlapping", "pattern": ".*link is down.*"}
]
}3.4.2 draino 配置与护栏
bash
# draino:检测到指定 condition 后自动 cordon+drain(部署在控制面)
draino \
--node-condition="ReadonlyFilesystem,MemoryPressureHW" \
--node-condition-expr="DiskPressure" \
--drain-buffer=30m \
--max-grace-period=8m \
--eviction-drain-grace-period=120s \
--eviction-headroom=30s护栏设计(缺一不可):
| 护栏 | 说明 |
|---|---|
| condition 白名单 | 只自动处置"确定性硬件故障"(只读盘、MCE),网络抖动类只告警 |
| drain 速率限制 | draino 层限流 + PDB 兜底,防止误判批量驱逐 |
| 故障 node 保留尸检窗口 | cordon 后保留 24h 再 drain,供硬件团队抓取 dmesg |
| 与升级流水线互斥 | 升级窗口内 draino 降级为只告警(全局开关 label) |
3.4.3 典型故障处置矩阵
| 故障 | 检测信号 | 自动动作 | 人工动作 |
|---|---|---|---|
| 磁盘只读/坏道 | NPD ReadonlyFilesystem + smartctl | draino cordon+drain | 工单换盘,机器下线 |
| 内存 MCE/EDAC 报错 | NPD MemoryPressureHW | 报错速率 > 阈值 → cordon | 换内存条;单条偶发可清零观察 |
| 网卡 flap/降速 | NPD + node_exporter node_network_speed_bytes | 连续 5 分钟降速 → cordon | 排查光模块/交换机端口 |
| GPU 掉卡(Xid 错误) | dcgm-exporter | 打 gpu-fault 污点 | 重置/返修 |
| 整机失联 | Node Ready→Unknown | 不自动驱逐(防脑裂!) | 确认电源/网络后再 force delete |
注意事项:节点失联(NotReady/Unknown)绝不自动 force delete + 驱逐。你不知道它是真死了还是只是网络分区——后者自动驱逐会造成双写脑裂。只有硬件团队确认"该机已断电/不可恢复"后,才允许执行:
bashkubectl delete node <node> --force kubectl delete pod <pod> -n <ns> --force --grace-period=0 # 逐 Pod,带审计
3.5 僵尸节点清理
僵尸节点 = Node 对象或底层资源处于"半死"状态的节点:NotReady 超期、kubelet 证书过期静默脱离、重复注册(同名不同机)、云实例已释放但 Node 对象残留。
bash
# 1. 找出 NotReady 超过 7 天的节点
kubectl get nodes -o json | jq -r '
.items[] | select(.status.conditions[] | select(.type=="Ready" and .status!="True")) |
.metadata.name'
# 2. 找出 Ready 但长时间未更新心跳(僵尸嫌疑)的节点
kubectl get --raw '/api/v1/nodes' | jq -r '
.items[] | .metadata.name as $n |
(.status.conditions[] | select(.type=="Ready")) |
[$n, .lastHeartbeatTime] | @tsv' | \
awk -v cutoff="$(date -d '3 days ago' -Is)" '$2 < cutoff'
# 3. 孤儿 lease 检查(kubelet 心跳 lease 与 node 不一致)
kubectl -n kube-node-lease get leases -o wide
# 4. 清理流水线:确认底层实例不存在 → 审计记录 → 删 Node → 删 lease → 回收 IP/标签最佳实践:僵尸节点清理做成每周定时任务 + 报表,而不是出事再捞。同时把"节点 Ready 比例"和"僵尸节点数"纳入周报 SLO 看板。
第 4 章 etcd 运维
etcd 是 RKE2 集群的心脏。3000 节点集群的 etcd 写入 QPS 可达数千,DB size 常年在 4~7GiB 区间游走,etcd 运维是整个运维体系中 SLA 要求最高的一环。
4.1 日常巡检(建议自动化为每日巡检脚本)
bash
#!/bin/bash
# etcd-daily-check.sh —— 在任一 control-plane 节点执行
export ETCDCTL_API=3
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
EP="https://127.0.0.1:2379"
echo "=== 1. 成员与健康 ==="
etcdctl --endpoints=$EP member list -w table
etcdctl --endpoints=$EP endpoint health --cluster -w table
echo "=== 2. 状态(DB size / leader / raft index) ==="
etcdctl --endpoints=$EP endpoint status --cluster -w table
echo "=== 3. leader 变更频率(应 ≤ 2次/天) ==="
curl -sk --cert $ETCDCTL_CERT --key $ETCDCTL_KEY --cacert $ETCDCTL_CACERT \
https://127.0.0.1:2381/metrics | grep -E 'etcd_server_leader_changes_seen_total|etcd_server_has_leader'
echo "=== 4. 慢请求与磁盘 WAL fsync 时延 ==="
curl -sk --cert $ETCDCTL_CERT --key $ETCDCTL_KEY --cacert $ETCDCTL_CACERT \
https://127.0.0.1:2381/metrics | grep -E 'etcd_disk_wal_fsync_duration_seconds_(sum|count)|etcd_server_slow_apply_total'
echo "=== 5. 告警(etcd 自身 alarm,如 NOSPACE) ==="
etcdctl --endpoints=$EP alarm list巡检指标红线:
| 指标 | 健康 | 警告 | 危险 |
|---|---|---|---|
| DB SIZE | < 4GiB | 4~6GiB | > 6GiB(逼近 8GiB quota) |
| 碎片率 | < 20% | 20~40% | > 40% |
| leader 变更 | ≤ 1 次/天 | 2~5 次/天 | > 5 次/天(查时钟/网络/磁盘) |
| WAL fsync p99 | < 10ms | 10~25ms | > 25ms(磁盘 IO 瓶颈) |
| alarm list | 空 | — | NOSPACE 出现即 C0 事件 |
4.2 compaction 与 defrag 例行操作
RKE2 内嵌 etcd 由 kube-apiserver 做自动 compaction(默认每 5 分钟,压缩窗口可通过 kube-apiserver-arg: ["etcd-compaction-interval=..."] 调整),但 defrag 必须人工/脚本触发,且碎片整理有讲究:
bash
# 1. 查看碎片情况
etcdctl --endpoints=$EP endpoint status --cluster -w table
# 碎片率 = 1 - (DB SIZE IN USE / DB SIZE)
# 2. defrag:先 defrag 所有 follower,最后 defrag leader
# (defrag 会锁定该成员,阻塞请求;逐个来,集群始终有可用 leader)
LEADER=$(etcdctl --endpoints=$EP endpoint status --cluster -w json | \
jq -r '.[] | select(.Status.header.member_id == .Status.leader) | .Endpoint')
for ep in $(etcdctl --endpoints=$EP member list -w json | jq -r '.members[].clientURLs[0]'); do
[[ "$ep" == "$LEADER" ]] && continue
etcdctl --endpoints=$ep defrag
done
etcdctl --endpoints=$LEADER defrag
# 3. 整理后验证
etcdctl --endpoints=$EP endpoint status --cluster -w table注意事项:
- defrag 先 follower 后 leader,顺序反了等于主动制造 leader 切换。
- 出现
NOSPACEalarm 时,先 compact + defrag 释放空间,然后必须etcdctl alarm disarm解除告警,否则 etcd 仍拒绝写入。- defrag 纳入月度维护窗口,与升级操作错开(不要在升级当天 defrag,出问题时不好归因)。
4.3 备份策略
本地快照(rke2 定时) ──┐
├──► S3 异地(跨机房/跨 region)
按需快照(变更前) ────┘生产配置(RKE2 server config,参考 rke2/server-config.yaml 增强版):
yaml
etcd-snapshot-schedule-cron: "0 */6 * * *" # 每 6 小时
etcd-snapshot-retention: 20 # 本地保留 20 份(5 天)
etcd-s3: true
etcd-s3-endpoint: s3.internal.example.com
etcd-s3-bucket: rke2-etcd-backup
etcd-s3-folder: prod-cluster-01
etcd-s3-region: cn-north-1
etcd-s3-access-key: ${S3_AK}
etcd-s3-secret-key: ${S3_SK}
etcd-s3-timeout: 5m| 备份层 | 频率 | 保留 | 用途 |
|---|---|---|---|
| 本地快照 | 6h | 5 天 | 快速恢复(误删资源、短窗口回滚) |
| S3 异地 | 每次快照同步 | 90 天 | 机房级灾难恢复 |
| 变更前按需快照 | 手动 | 7 天 | 升级/重大变更兜底 |
| 月度归档 | 每月 1 号快照打标 | 1 年 | 合规审计 |
bash
# 备份有效性自动校验(每日 CI 任务):随机抽取快照校验完整性
rke2 etcd-snapshot list
etcdctl --write-out=table snapshot status /var/lib/rancher/rke2/server/db/snapshots/<snapshot-name>4.4 恢复演练制度
没有演练过的备份等于没有备份。制度要求:
- 每月一次:在隔离测试集群完整走一遍"快照 → 全新集群恢复"流程,记录 RTO(目标 < 2h)与 RPO(≤ 6h)。
- 每季度一次:包含 control-plane 重建 + 业务抽样验证的完整演练。
- 每次重大升级前:最近一次演练记录不得超过 30 天。
演练检查单:
text
[ ] 从 S3 下载任意历史快照成功
[ ] cluster-reset 恢复流程执行成功(见 4.5)
[ ] apiserver /readyz 通过
[ ] 抽样 10 个 namespace 资源完整(deployment/pvc/secret/configmap)
[ ] 抽样业务 Pod 重建成功
[ ] 全程耗时记录并归档4.5 quorum 丢失恢复流程
完整真实案例见仓库文档:
k8s-issues/rke2-disaster-recovery-control-plane-quorum-loss.md(3 control-plane 全部宕机后的灾后恢复实录),本节提炼为标准流程。
场景:3(或多数)个 etcd 成员同时丢失,quorum 无法达成,所有 rke2-server 起不来,报错 etcdserver: request timed out。
多数 etcd 成员丢失
│
▼
选择一台数据最新的节点(比较 raft index)作为恢复种子
│
▼
rke2-killall.sh 清理所有 server 节点残留进程
│
▼
┌─── 路径 A:有可用的 etcd 数据目录 ───────────────────────┐
│ 1. 备份 /var/lib/rancher/rke2/server/db/etcd │
│ 2. 编辑 db/etcd/config 追加 force-new-cluster: true │
│ 3. 启动 rke2-server(强制单节点集群) │
│ 4. etcdctl member remove 移除所有失效成员 │
└──────────────────────────────────────────────────────────┘
┌─── 路径 B:数据目录也损坏,用快照恢复 ────────────────────┐
│ rke2 server \ │
│ --cluster-reset \ │
│ --cluster-reset-restore-path=/path/to/snapshot │
└──────────────────────────────────────────────────────────┘
│
▼
验证单节点集群:kubectl get nodes / etcdctl endpoint health
│
▼
其余 server 节点:清理数据目录 → 以 join 模式重新加入
(参考 k8s-issues/rke2-master-join-fails-critical-configuration-mismatch:
重新 join 的节点配置必须与集群现存配置一致,否则 join 失败)
│
▼
恢复业务验证 + 复盘(为什么多数成员同时丢?电源?存储?误操作?)关键命令摘录(与真实案例一致):
bash
# 所有 server 节点
rke2-killall.sh
# 种子节点:备份后强制单节点
mkdir -p /var/lib/rancher/rke2/server/db/etcd.bak.$(date +%Y%m%d%H%M%S)
cp -a /var/lib/rancher/rke2/server/db/etcd/* /var/lib/rancher/rke2/server/db/etcd.bak.*/
echo "force-new-cluster: true" >> /var/lib/rancher/rke2/server/db/etcd/config
systemctl restart rke2-server
# 移除失效成员
etcdctl --endpoints https://127.0.0.1:2379 member list -w table
etcdctl --endpoints https://127.0.0.1:2379 member remove <旧成员ID>
# 恢复后记得把 force-new-cluster 从 config 中删掉!(否则下次重启又是单节点重置)
sed -i '/force-new-cluster/d' /var/lib/rancher/rke2/server/db/etcd/config
# 其余节点重新 join:清空数据目录 + 保持 config.yaml 与集群一致
rm -rf /var/lib/rancher/rke2/server/db /var/lib/rancher/rke2/server/tls/etcd
systemctl start rke2-server注意事项(血泪教训):
force-new-cluster是临时配置,恢复后必须删除,这是最常见的二次事故源。- 重新 join 的节点,
/etc/rancher/rke2/config.yaml中cluster-cidr/service-cidr等关键参数必须与集群一致,否则触发 "critical configuration mismatch" 拒绝加入(详见 k8s-issues/rke2-master-join-fails-critical-configuration-mismatch.md)。- join 后节点长期 NotReady 先查镜像导入(参考 rke2-master-join-notready-image-import-delay.md)与 iptables/防火墙(rke2-master-join-fails-iptables-firewall-blocking.md)。
第 5 章 证书与安全运维
5.1 RKE2 证书轮换计划
RKE2 集群内部证书(apiserver、etcd、kubelet client 等 CA 签发的证书)默认有效期 1 年,CA 本身 10 年。3000 节点集群绝不能等到证书过期才处理——证书过期 = 全集群瘫痪(见第 8 章应急预案)。
5.1.1 证书台账与监控
bash
# 查看各证书到期时间(控制平面节点)
for crt in /var/lib/rancher/rke2/server/tls/*/server-*.crt \
/var/lib/rancher/rke2/server/tls/client-*.crt; do
echo "== $crt"
openssl x509 -in "$crt" -noout -subject -enddate
done
# agent 节点 kubelet 证书
openssl x509 -in /var/lib/rancher/rke2/agent/client-kubelet.crt -noout -enddatePrometheus 告警规则(提前 90/30/7 天三级预警):
yaml
- alert: CertExpiringSoon
expr: (probe_ssl_earliest_cert_expiry{job="rke2-certs"} - time()) / 86400 < 30
labels: {severity: P2}
annotations: {summary: "证书 {{ $labels.instance }} 30 天内到期"}5.1.2 轮换操作
bash
# 控制平面证书轮换(每台 server 滚动执行,C3 变更)
rke2 certificate rotate
systemctl restart rke2-server
# agent 证书:删除本地证书文件后重启 agent 即可重新签发
rm -rf /var/lib/rancher/rke2/agent/*.crt /var/lib/rancher/rke2/agent/*.key
systemctl restart rke2-agent| 计划项 | 安排 |
|---|---|
| 轮换节奏 | 每 9 个月例行轮换(留 3 个月缓冲),与版本升级窗口错开 2 周 |
| 顺序 | follower server → 首节点 server → agent 分批(每批 ≤ 5%) |
| 回滚 | 证书轮换失败:恢复 /var/lib/rancher/rke2/server/tls 备份(操作前必须整目录备份) |
| 特殊证书 | 自定义 CA / tls-san 变更需要重建证书体系,属 C4 变更,须专项评估 |
5.2 kubeconfig 权限治理与 RBAC 体系
3000 节点集群通常对应数百个用户与几十个自动化系统。kubeconfig 满天飞是安全事故的头号温床。
5.2.1 治理原则
- 禁止分发 admin kubeconfig:
/etc/rancher/rke2/rke2.yaml只允许留在控制平面节点 + 堡垒机应急柜(双人授权取用)。 - 人走 SSO,机器走 ServiceAccount:用户统一经 OIDC(dex/Keycloak)接入 apiserver;CI/CD 等机器身份用带 namespace 限定的 SA + 短期 token。
- RBAC 分层模板化:
┌─────────────────────────────────────────────────────────┐
│ Cluster 层(极少数人) │
│ cluster-admin → 平台 SRE Lead(≤3 人,双人复核)│
│ cluster-viewer → 架构/审计(全集群只读) │
│ Namespace 层(大多数) │
│ ns-admin → 业务负责人(本 ns 全权,不可动 RBAC 本身) │
│ ns-dev → 开发(读写工作负载,不可 secret/不可 exec) │
│ ns-viewer → 只读 │
│ 特殊约束 │
│ 平台保留 ns(kube-system/cattle-* 等)仅 cluster-admin │
└─────────────────────────────────────────────────────────┘yaml
# 模板示例:namespace 级开发角色(禁止读 secret、禁止 exec)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: {name: ns-dev, namespace: app-a}
rules:
- apiGroups: ["", "apps", "batch"]
resources: ["deployments","statefulsets","jobs","pods","pods/log","services","configmaps"]
verbs: ["get","list","watch","create","update","patch","delete"]
# 注意:刻意不包含 secrets 和 pods/exec- 季度权限审计:列出所有 ClusterRoleBinding,清理离转人员;
kubectl auth can-i --list抽查高危权限。
bash
# 审计:所有拥有 cluster-admin 的主体
kubectl get clusterrolebindings -o json | jq -r '
.items[] | select(.roleRef.name=="cluster-admin") |
.subjects[] | "\(.kind)\t\(.name)"'
# 审计:能读 secret 的非系统主体
kubectl get clusterroles -o json | jq -r '
.items[] | select(.metadata.name | startswith("system:") | not) |
select(.rules[]? | .resources[]? == "secrets") | .metadata.name'5.3 API 审计日志:采集与成本权衡
3000 节点集群 apiserver 审计日志可达 每小时数十 GB。全量采集 = 存储成本爆炸 + 淹没有效信号。大规模下必须裁剪策略:
yaml
# /etc/rancher/rke2/audit-policy.yaml —— 大规模裁剪版
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# 1. 高频只读请求:直接丢弃(kubelet/list/watch 是流量大头)
- level: None
verbs: ["get", "list", "watch"]
# 2. 系统组件的心跳类写入:丢弃
- level: None
userGroups: ["system:nodes"]
resources: [{group: "", resources: ["nodes/status", "pods/status"]}]
- level: None
users: ["system:kube-scheduler", "system:kube-controller-manager"]
verbs: ["update", "patch"]
resources: [{group: "coordination.k8s.io", resources: ["leases"]}]
# 3. 敏感操作:Metadata 级(谁、何时、对什么资源)
- level: Metadata
verbs: ["create", "update", "patch", "delete"]
# 4. 高危操作:Request 级(含请求体,不带响应体)
- level: Request
resources:
- {group: "", resources: ["secrets"]}
- {group: "rbac.authorization.k8s.io"}
# 5. exec/portforward/attach:RequestResponse 级(完整取证)
- level: RequestResponse
resources: [{group: "", resources: ["pods/exec","pods/portforward","pods/attach"]}]yaml
# /etc/rancher/rke2/config.yaml
kube-apiserver-arg:
- "audit-policy-file=/etc/rancher/rke2/audit-policy.yaml"
- "audit-log-path=/var/lib/rancher/rke2/server/logs/audit.log"
- "audit-log-maxage=7"
- "audit-log-maxbackup=30"
- "audit-log-maxsize=200"
# 超大集群可选 webhook 模式直送 Kafka,避免本地落盘 IO
# - "audit-webhook-config-file=/etc/rancher/rke2/audit-webhook.yaml"
# - "audit-webhook-batch-buffer-size=10000"最佳实践:裁剪后的审计日志经 filebeat/vector → Kafka → 热存储 7 天(ES/Loki 用于实时调查)+ 冷存储 1 年(S3 用于合规)。安全告警规则基于流式消费(Falco/自研),而不是事后查库。
5.4 漏洞响应流程
CVE 披露/情报订阅 ──► 影响评估 ──► 分级 ──► 修复路径选择 ──► 灰度修复 ──► 验证关闭
(NVD/GHSA/厂商通告) (是否命中?) (patch版本/镜像/缓解配置)| 严重度 | 判定参考 | 响应时限 | 典型动作 |
|---|---|---|---|
| Critical(如可远程 RCE、容器逃逸) | CVSS ≥ 9 + 集群暴露面命中 | 24h 内缓解,72h 内修复 | 先缓解(准入策略/网络策略/临时补丁),再升级 |
| High | CVSS 7~9 且需认证/特定条件 | 7 天内 | 纳入最近维护窗口 patch 升级 |
| Medium/Low | 其余 | 30 天 | 随季度版本节奏 |
修复路径优先级(侵入性从小到大):
- 缓解配置:NetworkPolicy 隔离、PSA 加固、禁用受影响 feature-gate。
- 镜像修复:业务/组件镜像重打(基础镜像 CVE),走正常发布流水线。
- patch 版本升级:RKE2 同 minor 内 patch 升级(快速通道,见 2.3)。
- 临时自编译/hotfix:仅限 Critical 且无官方修复,事后必须回归官方版本。
bash
# 镜像漏洞扫描流水线(trivy 示例,CI 门禁)
trivy image --severity CRITICAL,HIGH --exit-code 1 registry.internal/app/web:v2.3.1
# 集群存量镜像扫描(starboard/trivy-operator 持续扫描报告)
kubectl get vulnerabilityreports -A --sort-by=.report.summary.criticalCount | head -205.5 CIS 基线持续合规
RKE2 默认部署即较接近 CIS 基线(profile: cis 可进一步强化),但漂移检测必须持续化:
bash
# 使用 rancher 的 kube-bench 适配版
docker run --rm --pid=host -v /etc:/etc:ro -v /var:/var:ro \
registry.internal/aquasec/kube-bench:latest run --targets master,node,etcd,policies \
--config-dir /etc/kube-bench/cfg --benchmark rke2-1.28| 持续合规手段 | 说明 |
|---|---|
| kube-bench 月度扫描 | 纳入巡检流水线,diff 报告进工单 |
| 准入控制(OPA/Kyverno) | 防止"运行中漂移":privileged 容器、hostPath、latest 镜像等在入口拦截 |
| 配置版本化 | /etc/rancher/rke2/config.yaml 全部进 Git,节点 Agent 定期 diff |
| 例外管理 | 不 compliant 项必须有书面例外单 + 到期复审日期 |
第 6 章 资源治理
无治理的 3000 节点集群会在半年内退化为"谁也说不清资源去哪了"的黑洞。资源治理四件套:配额、默认值、优先级、周期性整理。
6.1 Namespace/租户配额体系
6.1.1 租户模型
平台 ──► 业务线(BU)──► 环境(prod/staging)──► namespace
│ │
BU 级配额预算(CMDB) ResourceQuota + LimitRange6.1.2 ResourceQuota 基线模板
yaml
apiVersion: v1
kind: ResourceQuota
metadata: {name: tenant-quota, namespace: app-a}
spec:
hard:
requests.cpu: "100"
requests.memory: 400Gi
limits.cpu: "200"
limits.memory: 800Gi
persistentvolumeclaims: "50"
count/services.loadbalancers: "5" # 防止滥用 LB
count/services.nodeports: "10" # NodePort 是稀缺资源,必须限制
count/ingresses.networking.k8s.io: "20"注意事项:
- 一旦 namespace 设了 cpu/memory 配额,所有 Pod 必须带 requests/limits 否则创建被拒——所以 LimitRange(下节)必须先于 Quota 落地。
- 大规模集群慎用
count/pods类对象数量配额,控制器重试风暴可能瞬间打满配额并伴随大量事件,压垮 etcd。优先用资源量配额。- 配额调整走审批流 + 变更系统记录,杜绝"口头加配额"。
6.2 LimitRange 基线
yaml
apiVersion: v1
kind: LimitRange
metadata: {name: tenant-defaults, namespace: app-a}
spec:
limits:
- type: Container
default: # 未写 limits 时的默认值
cpu: "1"
memory: 2Gi
defaultRequest: # 未写 requests 时的默认值
cpu: 100m
memory: 256Mi
max: # 单容器上限:防止单 Pod 吞掉半个节点
cpu: "16"
memory: 64Gi
min:
cpu: 10m
memory: 64Mi
maxLimitRequestRatio: # limit/request 比值上限:防止过度超卖放大节点压力
cpu: "4"
- type: PersistentVolumeClaim
max: {storage: 2Ti}
min: {storage: 1Gi}最佳实践:
maxLimitRequestRatio是大规模集群稳定性的隐形护栏。超卖比失控时,节点负载尖峰会把 kubelet 打进 NotReady 旋涡。配合节点级超卖水位监控(sum(requests)/allocatable ≤ 1.2)一起用。
6.3 PriorityClass 体系设计
大规模混部集群必须回答"资源不够时谁先死"。设计原则:系统组件永不饿死,业务分级可预期驱逐。
| PriorityClass | 值 | 使用者 | 说明 |
|---|---|---|---|
| system-node-critical | 2000001000 | kubelet/网络/CNI DaemonSet | K8s 内置,勿动 |
| system-cluster-critical | 2000000000 | DNS、metrics-server 等 | K8s 内置 |
| platform-critical | 1000000 | 监控/日志 agent、ingress controller | 平台自建 |
| biz-critical | 900000 | 核心交易类业务 | 需审批 |
| biz-normal | 500000 | 普通业务(default) | 默认 |
| biz-batch | 100000 | 离线任务/CI Job | 可被抢占 |
| biz-besteffort | 1000 | 探索性/测试 | 随时牺牲 |
yaml
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata: {name: biz-batch}
value: 100000
preemptionPolicy: PreemptLowerPriority # 离线任务允许抢占 besteffort
globalDefault: false
description: "离线批处理任务,高峰可被抢占"
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata: {name: biz-normal}
value: 500000
preemptionPolicy: Never # 普通业务之间互不抢占,避免连环驱逐
globalDefault: true注意事项:除离线池外,
preemptionPolicy尽量设Never。大规模集群中抢占级联(A 抢 B、B 抢 C)一次可能驱逐几百个 Pod,引发业务雪崩。
6.4 descheduler 治理
调度器只看"调度那一刻",长期运行后必然失衡(节点碎片、负载不均、亲和性漂移)。descheduler 周期性纠偏:
yaml
# descheduler policy(v0.30+,大规模生产参考)
apiVersion: descheduler/v1alpha2
kind: DeschedulerPolicy
profiles:
- name: default
pluginConfig:
- name: RemovePodsViolatingNodeTaints # 清理落在错误污点节点上的 Pod
args: {}
- name: RemovePodsViolatingInterPodAntiAffinity
args: {}
- name: LowNodeUtilization # 利用率均衡:压高填低,削峰
args:
thresholds: {cpu: 20, memory: 20} # 低于 20% 的节点视为"欠载"
targetThresholds: {cpu: 70, memory: 70} # 高于 70% 的节点开始迁出
- name: RemovePodsHavingTooManyRestarts # 异常重启 Pod 集中清理
args:
podRestartThreshold: 100
includingInitContainers: true
plugins:
balance: {enabled: [LowNodeUtilization]}
deschedule:
enabled: [RemovePodsViolatingNodeTaints, RemovePodsViolatingInterPodAntiAffinity, RemovePodsHavingTooManyRestarts]大规模使用红线:
- 只对无状态业务开启(label 过滤
governance.io/deschedule=true白名单制),有状态中间件禁用。 - descheduler 尊重 PDB,但批量整理仍可能短时间造成大量迁移——运行窗口限低峰期,并与 draino/升级流水线互斥。
- 每次运行驱逐量设上限(
--max-pods-to-evict-per-node/ 命名空间级限制),防止整理本身变成故障源。
6.5 节点标签与污点体系规范
标签和污点是大规模调度的"交通规则",必须成文规范,否则三个月后无人敢动任何节点。
| 类别 | 命名 | 示例 | 谁负责 |
|---|---|---|---|
| 硬件拓扑 | topology.kubernetes.io/zone、node.kubernetes.io/instance-type | az1 / m5.16xlarge | 云平台/CMDB 自动打 |
| 节点池 | pool | general/gpu/bigmem/edge | 平台组 |
| 硬件特性 | hardware.io/gpu-model、network.io/nic-speed | a100-80g / 25g | 上线 SOP |
| 运维状态 | onboard-batch、maintenance | 2026w29 / true | 运维流水线 |
| 调度约束污点 | dedicated=gpu:NoSchedule | GPU 池专用 | 平台组 |
| 故障隔离污点 | gpu-fault、hardware-fault | 由 NPD/draino 自动打 | 自愈系统 |
| 上线防护污点 | onboarding:NoSchedule | 冒烟通过即摘除 | 上线 SOP |
bash
# 规范审计:找出没有任何 pool 标签的"野节点"
kubectl get nodes -l '!pool' -o wide
# 规范审计:污点使用是否符合白名单
kubectl get nodes -o json | jq -r '
.items[] | .metadata.name as $n | (.spec.taints // [])[] |
"\($n)\t\(.key)=\(.value):\(.effect)"' | sort | uniq -c | sort -rn第 7 章 变更与发布管理
7.1 集群组件变更灰度方案(CNI / Ingress 等)
平台组件(CNI、Ingress Controller、CSI、CoreDNS)的变更影响面是全局的,比业务发布危险一个数量级,必须按 C3 级变更管理。
7.1.1 CNI 升级(以 Calico 为例)
text
1. 版本兼容矩阵确认(Calico ↔ K8s 版本 ↔ RKE2 内置组件是否冲突)
2. 金丝雀:升级控制平面相关组件(calico-kube-controllers 等单点组件)
3. 节点金丝雀:先通过 Helm/Addon 配置仅对 ring0 节点池生效
(RKE2 中通过 HelmChartConfig 自定义 rancher 管理的 calico chart)
4. DaemonSet 滚动策略调参:maxUnavailable 从默认 1 调整为节点池的 2%
5. 每环观察:BGP session 状态 / 跨节点连通性探测 / conntrack 表压力
6. 回滚预案:chart 版本 pin 住旧版 + 滚动回退 DaemonSetyaml
# RKE2 中定制 CNI 的标准姿势:HelmChartConfig(勿直接改 chart)
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: rke2-calico
namespace: kube-system
spec:
valuesContent: |-
installation:
calicoNetwork:
mtu: 1450
felixConfiguration:
prometheusMetricsEnabled: true
typha:
replicas: 9 # 3000 节点规模 typha 副本数 ≈ 每 300~400 节点 1 个注意事项:RKE2 管理的组件(calico/coredns/ingress-nginx/metrics-server)由内置 helm-controller 维护,直接
kubectl edit会被调谐回滚。所有定制必须通过HelmChartConfig,且文件入 Git。
7.1.2 Ingress Controller 升级
text
滚动策略:DaemonSet 模式 → maxUnavailable 调小(如 2%)+ surge 不可用时加临时节点
金丝雀方案:
A. 部署新版 ingress-nginx 为独立 Deployment(不同 IngressClass: nginx-canary)
B. 通过 DNS/LB 权重将 1% 流量切到金丝雀实例
C. 观察错误率/延迟后逐步放量
D. 旧实例保留下线窗口 7 天,随时可切回bash
# 切流验证
dig +short app.example.com
curl -H "Host: app.example.com" http://<canary-lb-ip>/healthz -v参考本仓库真实案例 k8s-issues/rke2-ingress-nginx-controller-ContainerCreating.md:ingress controller Pod 长期 ContainerCreating 时,排查顺序为 镜像拉取(registry 连通)→ 挂载卷 → CNI 分配 IP → 资源配额。
7.2 业务发布平台对接
SRE 与业务发布的边界约定(大规模下防止互相踩踏):
| 关注点 | 平台侧职责 | 业务发布平台职责 |
|---|---|---|
| 节点维护窗口 | 发布节点维护日历 API(GET /maintenance/nodes) | 发布前查询,避开节点正在 drain 的时间窗 |
| 容量 | 提供集群水位 API(allocatable/requests 水位) | 大促扩容提前 2 周提容量工单 |
| 配额 | Quota 审批流 | 配额内自助发布 |
| 优先级 | PriorityClass 定义与审计 | 按规范选择业务等级 |
| 熔断 | 集群级故障时冻结业务发布(全局开关) | 尊重冻结信号,发布流水线集成检查 |
bash
# 发布平台对接示例:发布前置检查
cluster_freeze=$(curl -s https://platform-api/v1/freeze-status | jq -r .frozen)
[[ "$cluster_freeze" == "true" ]] && { echo "集群处于冻结窗口,禁止发布"; exit 1; }7.3 维护窗口制度
| 窗口类型 | 时间 | 允许的变更 |
|---|---|---|
| 日常低风险窗口 | 工作日 10:00–17:00 | C1/C2 级,自动化执行 |
| 夜间维护窗口 | 周二/周四 00:00–06:00 | C3 级组件升级、节点批量维护 |
| 月度大窗口 | 每月第三个周六 00:00–08:00 | 版本升级、etcd 碎片整理、证书轮换 |
| 冻结窗口 | 大促前 7 天 ~ 后 3 天、重大节假日 | 仅 C0 紧急修复 |
制度要点:
- 变更日历统一入口:所有窗口登记在共享日历,冲突由变更管理员仲裁。
- 窗口内必配"观察员":执行人之外必须有第二人盯大盘告警,执行人无权自行宣布"没问题"。
- 窗口结束即复盘:窗口内所有动作、异常、耗时自动记录进变更系统(流水线落库),周会过一遍。
第 8 章 应急预案与演练
8.1 应急预案库
预案的写法要求:判断条件 → 影响评估 → 处置步骤(带命令)→ 验证 → 升级路径,任何人拿到都能执行。
预案 1:控制平面全挂(API 不可用)
text
判断:LB 探测 apiserver /readyz 全部失败
第一步:确认是 apiserver 问题还是 etcd 问题
- ssh 任一 server 节点:systemctl status rke2-server; journalctl -u rke2-server -n 200
- etcdctl endpoint health --cluster
分支 A(etcd quorum 丢失):走第 4.5 节恢复流程(参考
k8s-issues/rke2-disaster-recovery-control-plane-quorum-loss.md)
分支 B(apiserver 进程崩溃但 etcd 健康):
- 检查磁盘(/var/lib/rancher 写满?audit 日志打爆?)
- 检查 OOM:dmesg | grep -i oom
- 修复后 systemctl restart rke2-server,逐台恢复
验证:kubectl get --raw=/readyz?verbose 全部 [+]ok;业务探测恢复预案 2:etcd 数据损坏 / NOSPACE
text
判断:etcdctl alarm list 出现 NOSPACE,或日志报 mvcc: database space exceeded
处置:
1. 获取当前 revision:etcdctl endpoint status -w json | jq '.[].Status.header.revision'
2. compact:etcdctl compact <revision>
3. defrag(先 follower 后 leader):见 4.2
4. 解除告警:etcdctl alarm disarm
5. 根因排查:是什么对象暴涨?(常见:事件风暴、ConfigMap 大数据、CRD 失控)
kubectl get --raw /api/v1/events | wc -c
预防:etcd quota 监控告警(>70% 即介入);事件保留收敛;CRD 写入限流预案 3:网络分区(机房/可用区级)
text
判断:某 zone 节点批量 NotReady,但节点本身健康(可达)
处置原则:
- 不驱逐!不驱逐!不驱逐!(分区≠死亡,自动驱逐=脑裂)
- 确认分区范围:zone 拓扑标签统计 NotReady 分布
- 若控制平面 quorum 仍在线:保持现状,等网络恢复,Pod 自动重连
- 若分区导致 etcd quorum 风险:评估是否将分区侧 server 节点主动关机
(保 quorum 侧),再按 member remove 流程收缩
- 网络恢复后:关注 Pod 大规模 reschedule 风暴,必要时分批 uncordon预案 4:证书过期
text
判断:kubectl 报 x509: certificate has expired;kubelet 日志 TLS handshake error
处置:
1. 确认过期范围:openssl x509 -enddate 逐个检查(5.1.1)
2. 若 admin kubeconfig 过期但集群内部证书未过期:
控制平面节点上 /etc/rancher/rke2/rke2.yaml 仍可用(supervisor 端口)
3. 证书轮换:rke2 certificate rotate && systemctl restart rke2-server(逐台)
4. agent:删除 /var/lib/rancher/rke2/agent/*.crt *.key 后重启 rke2-agent
5. 全部恢复后:复盘为什么 90/30/7 天三级告警全部失效预案 5:私有镜像仓库宕机
text
判断:大面积 Pod ImagePullBackOff;registry /v2/ 探测失败
影响评估:存量 Pod 运行不受影响;新调度/驱逐重建受影响
处置:
1. 切换备用 registry:registries.yaml 中配置多 endpoint(参考 rke2/registries.yaml)
mirrors:
"192.168.122.156:30000":
endpoint:
- "http://192.168.122.156:30000"
- "http://192.168.122.157:30000" # 备用
修改后逐台 systemctl restart rke2-agent(纳入节点池分批)
2. 立即冻结节点维护操作(drain 会触发大量镜像拉取)
3. registry 恢复后解冻
预防:registry 双活/主备 + 存储层共享;关键基础镜像(pause/cni)节点本地预置预案 6:集群 DNS 故障
text
判断:业务大面积解析失败;coredns Pod CrashLoop 或被打满
处置:
1. 定位:kubectl -n kube-system get pods -l k8s-app=kube-dns -o wide
2. 若 CoreDNS 资源不足被打爆:紧急扩容 replicas + 提 requests
(3000 节点集群 CoreDNS 建议 ≥ 10 副本 + HPA + cluster-proportional-autoscaler)
3. 若上游 DNS 异常:检查 Corefile forward 配置,切备用上游
4. 临时缓解:确认 nodelocaldns(NodeLocal DNSCache)是否已部署;
未部署则作为事后改进项(大规模集群强烈建议部署,
参考 metaLB/coredns-nodelocaldncache-from-beginner-to-master.md)8.2 混沌工程演练(大规模下的谨慎使用)
混沌工程的价值毋庸置疑,但在 3000 节点生产集群上"随机搞破坏"是危险的。原则:先测试环境练熟,生产只打"已知可控"的故障。
| 工具 | 适用 | 大规模注意事项 |
|---|---|---|
| Chaos Mesh | Pod/网络/IO 故障注入,有 Dashboard 和审批流 | 生产必选 mode: fixed-number 限定影响 Pod 数;全部实验走 C2 审批 |
| kube-monkey | 随机杀 Pod 验证业务自愈 | 只在预发/影子集群跑;生产禁用或限定"自愿参加"的 namespace |
| 节点级混沌(关机/断网) | 验证自愈与预案 | 只打 ring0 维护池节点;必须在维护窗口内 |
yaml
# Chaos Mesh 示例:限定范围的 Pod 故障注入(演练"业务对单 Pod 死亡的容忍")
apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata: {name: drill-kill-web, namespace: chaos-testing}
spec:
action: pod-kill
mode: fixed-number
value: "1" # 每次只杀 1 个,绝不按比例
selector:
namespaces: [app-a]
labelSelectors: {app: web}
scheduler:
cron: "0 3 * * 3" # 每周三凌晨低峰8.3 GameDay 制度
┌─────────────────────────────────────────────────────────┐
│ GameDay = 有导演、有剧本、有观众的"计划内故障日" │
├─────────────────────────────────────────────────────────┤
│ 频率:每季度 1 次,半天 │
│ 角色:导演(注入故障)、防守方(oncall 轮值团队)、观察员 │
│ 剧本:从预案库抽 2~3 个场景(如 etcd 快照恢复 + DNS 故障) │
│ 环境:首选 1:1 影子集群;生产只做"读路径"类演练 │
│ 产出: │
│ - 每个预案的实测 RTO vs 目标 RTO │
│ - 预案文档的偏差修订(命令过期、步骤缺失) │
│ - 新发现的监控盲区 → 告警补建工单 │
└─────────────────────────────────────────────────────────┘最佳实践:GameDay 的 KPI 不是"故障恢复多快",而是"预案文档有多少地方是错的"。第一次 GameDay 预案命中率低于 50% 是正常的,这正是它的价值。
第 9 章 常见面试题
1. 问:RKE2 集群升级为什么不能跨 minor 版本?多版本落后时如何追赶?
答:Kubernetes 官方只保证相邻 minor 版本间的兼容性(组件版本偏差策略),跨版本升级可能遇到 API 废弃、存储版本迁移跳步、etcd 数据格式不兼容等未验证路径。追赶策略:逐级升级(v1.27→v1.28→v1.29),每个中间版本在生产停留至少一个业务周期(2 周+),先控制平面后 agent,agent 按节点池分批灰度,每批间设健康门禁。
2. 问:system-upgrade-controller 的 Plan 中 concurrency 对 server 和 agent 分别应该怎么设?为什么?
答:server(控制平面/etcd)必须设 1(或小于 etcd quorum 容忍数),因为同时升级多个 etcd 成员可能丢失 quorum,且 apiserver 版本跳变需要逐个验证。agent 可按节点池大小设 5%~10% 并发,配合 drain + PDB 与健康门禁,兼顾效率与爆炸半径控制。
3. 问:RKE2 没有官方降级方案,升级失败怎么回滚?
答:核心思路是"快照兜底":升级前对 etcd 打全量快照(rke2 etcd-snapshot save 并同步 S3)。单节点失败回退二进制或重建;控制平面大面积失败走 --cluster-reset --cluster-reset-restore-path=<快照> 恢复单节点后其余节点重新 join。代价是快照之后的写操作丢失,所以决策级别是 C0。真正的防线是升级前的灰度验证和废弃 API 扫描,让回滚永远用不上。
4. 问:节点失联(NotReady)后能不能自动驱逐上面的 Pod?为什么?
答:不能。失联≠死亡,可能是网络分区,此时 Pod 其实还在正常运行。自动 force delete + 重建会造成新旧 Pod 双写(脑裂),对有状态服务是灾难。正确做法:失联只告警,由人工/硬件侧确认节点彻底不可恢复(断电、已下电)后,再带审计地 force delete。能自动化的只有"确定性硬件故障"信号(如磁盘只读、内存 MCE),走 NPD + draino。
5. 问:etcd 出现 NOSPACE 告警怎么处理?如何预防?
答:处理四步:etcdctl compact <current-revision> → 按 follower 优先、leader 最后的顺序 defrag → etcdctl alarm disarm 解除告警 → 排查根因(事件风暴、大对象、CRD 失控写入)。预防:监控 DB SIZE(红线 6GiB)与碎片率(>40% 即整理)、月度例行 defrag、收敛事件保留、apiserver 审计策略裁剪减少 etcd 无关写入。
6. 问:大规模集群的 API 审计日志为什么要裁剪?怎么裁?
答:3000 节点集群审计日志可达每小时数十 GB,全量采集存储成本失控且淹没有效信号。裁剪原则:kubelet 心跳、list/watch 等高频只读流量置 None;系统组件的 lease/status 更新置 None;写操作记 Metadata;secret/RBAC 变更记 Request;exec/portforward 记 RequestResponse 用于取证。落地为 audit-policy.yaml 分级规则 + Kafka 流式消费 + 冷热分层存储。
7. 问:etcd quorum 丢失的恢复流程是什么?有哪些坑?
答:选数据最新的节点为种子,rke2-killall.sh 清理 → 备份数据目录 → db/etcd/config 加 force-new-cluster: true 强制单节点启动(或用快照 --cluster-reset 恢复)→ etcdctl member remove 移除失效成员 → 其余节点清数据目录重新 join。三大坑:①恢复后忘删 force-new-cluster 导致重启再次重置;②重新 join 节点配置与集群不一致触发 critical configuration mismatch;③join 后 NotReady 未排查镜像导入/防火墙问题。
8. 问:PriorityClass 体系怎么设计?preemptionPolicy 有什么讲究?
答:分层:系统组件用内置 system-node/cluster-critical;平台组件(监控/ingress)自建 platform-critical;业务分 critical/normal/batch/besteffort 四级。关键讲究是 preemptionPolicy:除离线批处理池外一律设 Never,否则大规模集群中抢占级联一次可驱逐数百 Pod 引发雪崩;同时配 PDB 与节点超卖水位监控(sum(requests)/allocatable ≤ 1.2)共同兜底。
9. 问:drain 被 PDB 卡死怎么处理?如何从根本上避免?
答:临时处理:定位卡住的 Pod 所属 PDB(kubectl describe pod 看 eviction 报错),与业务确认后临时调低 minAvailable 或人工终止 Pod。根本避免:①准入控制禁止"绝对 PDB"(minAvailable=100% 或等于副本数);②CI 卡点要求无状态服务必须配 PDB 但留有 disruption 余量;③批量维护工具 drain 前做 PDB 预演算(disruptionsAllowed ≥ 1 才驱逐)。
10. 问:设计一套 3000 节点集群的变更灰度体系,关键要素有哪些?
答:五要素:①变更分级(C0~C4,决定审批层级与窗口);②灰度环模型(ring0 金丝雀 1~2% → ring1 5% → ring2 每批 10% → ring3 尾批人工确认),环间设观察期(24h/12h/4h 递减);③自动健康门禁(节点 Ready 率、Pod 重启突增、apiserver p99、业务探测,任一恶化自动暂停);④回滚预案前置(回滚路径变更前验证、回滚决策人与执行人分离、明确触发线如批次失败率 >10%);⑤错误预算联动(预算剩余 <20% 时冻结非紧急变更)。
附录 A:本部分引用的仓库资料索引
| 资料 | 位置 | 用途 |
|---|---|---|
| etcd quorum 丢失灾后恢复实录 | k8s-issues/rke2-disaster-recovery-control-plane-quorum-loss.md | 第 4.5 节、预案 1 |
| master join 配置不匹配故障 | k8s-issues/rke2-master-join-fails-critical-configuration-mismatch.md | 第 4.5 节注意事项 |
| master join 被防火墙阻断故障 | k8s-issues/rke2-master-join-fails-iptables-firewall-blocking.md | 第 3.1 节基线检查 |
| master join 镜像导入延迟 NotReady | k8s-issues/rke2-master-join-notready-image-import-delay.md | 第 4.5 节注意事项 |
| 节点添加操作手册 | k8s-issues/rke2-node-addition-operation-manual.md | 第 3.1 节上线 SOP |
| ingress-nginx ContainerCreating 排查 | k8s-issues/rke2-ingress-nginx-controller-ContainerCreating.md | 第 7.1 节 |
| RKE2 server/agent 配置样例 | rke2/server-config.yaml、rke2/agent-config.yaml、rke2/registries.yaml | 第 2、3、4、8 章 |
| NodeLocal DNSCache 教材 | metaLB/coredns-nodelocaldncache-from-beginner-to-master.md | 预案 6 延伸阅读 |
附录 B:运维节奏一页纸(建议打印贴墙)
| 频率 | 动作 |
|---|---|
| 每日 | 巡检报表(etcd/SLO/僵尸节点/证书倒计时);oncall 交接 |
| 每周 | 僵尸节点清理;配额与 RBAC 变更复核;低峰节点维护窗口 |
| 每两周 | patch 版本评估与灰度 |
| 每月 | etcd defrag + 快照恢复演练;kube-bench 扫描;月度大维护窗口 |
| 每季度 | 权限审计;GameDay;descheduler/准入策略回顾;配额水位评审 |
| 每 4 个月 | minor 版本升级(跟随社区节奏,落后 1~2 个 minor) |
| 每 9 个月 | 证书轮换 |
| 每年 | 容量规划评审;预案库全面刷新;灾备架构演练 |
下一部分预告:第四部分《大规模集群观测体系》将深入 metrics/logs/traces 三层架构、Prometheus 联邦与Thanos 长存储、万级告警治理与 SLO 看板工程化。