系统学习 演练学习路径:35 个 lab 的难度排序、4 周基础计划与复盘方法

Lab 状态:静态审查2026-10-25 · kind 多节点 / 一次性测试集群

在 kind 预检集群和一次性 kubeadm 环境里演练升级评估、节点排空、控制面升级与失败回滚

演练目标

安全边界:本 Lab 只允许在 kind、一次性虚拟机或专用测试集群中进行。生产升级必须走变更评审,不能照抄本页命令;任何 drain、upgrade apply、快照恢复和软件包升级都要有维护窗口、备份、回滚材料和决策人。不要在业务集群上为了“练手”执行这些步骤。

环境准备

准备 Docker、kind、kubectl、与集群 etcd 版本匹配的 etcdctl,以及一台可以随意重装的一次性 kubeadm 虚拟机或测试集群。kind 用来做预检和排空演练;真正升级控制面需要 kubeadm 环境,kind 节点不适合原地升级。工具清单中 pluto、kubent 是可选的废弃 API 扫描器,没有安装时先跳过,不要临时从不明来源下载二进制。

mkdir -p /tmp/lab-upgrade/backup
docker version --format '{{.ServerVersion}}'
kind version
kubectl version --client

创建一个带两个 worker 的 kind 集群,后续排空演练需要把 Pod 从一个 worker 迁到另一个:

cat > /tmp/lab-upgrade/kind.yaml <<'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
  - role: worker
  - role: worker
EOF
timeout 300 kind create cluster --name lab-upgrade --config /tmp/lab-upgrade/kind.yaml
timeout 60 kubectl get nodes -o wide

固定升级前基线。基线不是形式主义,回滚和验证都要拿同一组指标对比:

timeout 30 kubectl version -o yaml > /tmp/lab-upgrade/backup/version-before.yaml
timeout 30 kubectl get nodes -o wide > /tmp/lab-upgrade/backup/nodes-before.txt
timeout 30 kubectl get pods -A -o wide > /tmp/lab-upgrade/backup/pods-before.txt
timeout 30 kubectl get --raw='/readyz?verbose' > /tmp/lab-upgrade/backup/readyz-before.txt
timeout 30 kubectl get ns --no-headers | wc -l > /tmp/lab-upgrade/backup/ns-count.txt
timeout 30 kubectl get crd --no-headers | wc -l > /tmp/lab-upgrade/backup/crd-count.txt
timeout 30 kubectl get pvc -A --no-headers | wc -l > /tmp/lab-upgrade/backup/pvc-count.txt

如果使用一次性 kubeadm 虚拟机,还要先做 etcd 快照和控制面文件备份,并验证快照可读。快照要复制到宿主机之外的位置,不能只留在待升级节点上:

sudo timeout 120 etcdctl snapshot save /tmp/lab-upgrade/backup/etcd-before.db \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key
sudo timeout 60 etcdctl snapshot status /tmp/lab-upgrade/backup/etcd-before.db -w table
sudo timeout 120 tar czf /tmp/lab-upgrade/backup/etc-kubernetes.tgz /etc/kubernetes
sudo timeout 30 tar tzf /tmp/lab-upgrade/backup/etc-kubernetes.tgz | head
sudo timeout 60 kubeadm certs check-expiration

目标版本必须由 kubeadm upgrade plan、官方版本偏差策略和 addon 发布说明共同确定。下面统一写成 <target-version> 占位符,不要从博客或本页复制具体版本号,也不要跨多个 minor 跳级升级。

演练步骤

1. 预检与废弃 API 扫描

先只读检查升级路径和当前组件版本,不执行任何变更:

timeout 600 sudo kubeadm upgrade plan
timeout 30 kubectl get nodes -o wide
timeout 30 kubectl get --raw='/readyz?verbose'
timeout 60 sudo kubeadm certs check-expiration

如果装有 pluto,用它扫描集群内的工作负载、CRD 和 Helm release;没有安装就人工核对即将删除的 API 版本、转换 Webhook 和聚合 API。扫描结果要落到变更记录里,不能只看一眼终端输出。随后确认 CNI、CSI、Ingress、cert-manager、metrics-server、监控日志和备份组件的目标版本兼容性;升级窗口内不要同时更换 CNI 或容器运行时。

2. 在 kind 上演练排空

部署一个两副本的演示应用,并用拓扑约束让它分散到两个 worker。排空时观察 Pod 是否能被重新调度:

cat > /tmp/lab-upgrade/web.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: lab-web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: lab-web
  template:
    metadata:
      labels:
        app: lab-web
    spec:
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: lab-web
      containers:
        - name: web
          image: nginx:stable
          ports:
            - containerPort: 80
EOF
timeout 120 kubectl apply -f /tmp/lab-upgrade/web.yaml
timeout 180 kubectl rollout status deployment/lab-web --timeout=150s
kubectl get pods -l app=lab-web -o wide

选择其中一个 worker 排空,所有会阻塞的命令都加超时。若 drain 超时,不要立刻加 --force,先看 PDB、本地存储、DaemonSet 和 finalizer:

NODE=$(timeout 30 kubectl get pods -l app=lab-web -o jsonpath='{.items[0].spec.nodeName}')
timeout 240 kubectl drain "$NODE" --ignore-daemonsets --delete-emptydir-data --timeout=180s
timeout 30 kubectl get nodes
kubectl get pods -l app=lab-web -o wide
timeout 60 kubectl uncordon "$NODE"

3. 升级控制面(仅一次性 kubeadm 环境)

kind 演练到此为止,不要在 kind 上执行 kubeadm upgrade apply。在一次性 kubeadm 虚拟机中确认快照和 /etc/kubernetes 备份都存在后,先升级 kubeadm 包,再执行计划和应用:

# 先用发行版包管理器把 kubeadm 升到目标版本(占位,按官方文档替换版本号)
timeout 600 sudo kubeadm upgrade plan
timeout 1800 sudo kubeadm upgrade apply <target-version>

命令会要求确认目标版本,必须核对它来自 upgrade plan,且与变更单一致。控制面升级后,用发行版软件源把当前节点的 kubelet、kubectl 升到同一目标版本,然后重启 kubelet。软件源和包名以实际发行版文档为准,不要直接照抄占位符:

# 示例占位:把 <target-version> 换成本次变更批准的目标版本
sudo apt-get install -y kubelet=<target-version>-* kubectl=<target-version>-*
timeout 120 sudo systemctl daemon-reload
timeout 120 sudo systemctl restart kubelet
timeout 30 kubectl get nodes -o wide
timeout 30 kubectl get --raw='/readyz?verbose'

4. 逐台升级其余控制面

HA 集群一次只处理一台控制面。先升级 kubeadm 包,再执行 kubeadm upgrade node,接着升级 kubelet、kubectl 并重启 kubelet。每台都确认 /readyz、etcd 成员健康、leader 和系统 Pod 正常后,才处理下一台:

timeout 120 kubectl cordon <control-plane-node>
# 先按发行版文档升级 kubeadm 包(占位)
timeout 600 sudo kubeadm upgrade node
# 再按发行版文档升级 kubelet、kubectl 包(占位)
timeout 120 sudo systemctl restart kubelet
timeout 30 kubectl get --raw='/readyz?verbose'
sudo timeout 60 etcdctl endpoint health --cluster \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key
timeout 60 kubectl uncordon <control-plane-node>

三成员 stacked etcd 集群绝不能同时重启两台控制面;成员更多的集群也应保持一次一台的节奏。若某一台升级后 etcd 成员异常,应停止后续批次,先恢复该成员的证书、数据目录和网络,而不是继续升级。

5. 滚动升级 worker 节点

按节点池逐台处理,先选非关键池。每台先 cordon、drain,再升级 kubeadm、kubelet 和节点镜像,重启 kubelet,最后 uncordon。对 StatefulSet、数据库和消息队列节点,要逐台确认副本和选主状态:

timeout 120 kubectl cordon <worker-node>
timeout 240 kubectl drain <worker-node> --ignore-daemonsets --delete-emptydir-data --timeout=180s
# 按发行版文档升级 kubeadm、kubelet、kubectl 包并更新节点镜像(占位)
timeout 600 sudo kubeadm upgrade node
timeout 120 sudo systemctl restart kubelet
timeout 30 kubectl get nodes -o wide
timeout 60 kubectl uncordon <worker-node>

6. 升级 addon 与收尾

按“CRD → 控制器 → 工作负载”的顺序,一次只升级一个 addon,优先用 Helm 或原有清单,不要用最新标签覆盖生产清单。每升一个就确认 APIService、Webhook 证书和控制器日志:

timeout 60 kubectl get crd
timeout 60 kubectl get apiservices
timeout 120 kubectl -n kube-system rollout status deployment/coredns --timeout=90s
timeout 60 kubectl get pods -A | grep -Ev 'Running|Completed' || true

全部批次完成后,恢复 CI/CD、自动扩缩容和定时任务,解除临时告警静默,更新版本台账,并至少观察一个完整业务高峰再宣布结束。

观察现象

排空阶段,被 drain 的节点应显示 SchedulingDisabled,演示 Pod 在另一个 worker 上重新进入 Running;如果 PDB 的 ALLOWED DISRUPTIONS 为 0,drain 会等待或超时,这是保护机制生效,不是命令坏了。DaemonSet 管理的 Pod 会被 --ignore-daemonsets 跳过,不要因为看到它们还在节点上就判断失败。

控制面升级阶段,静态 Pod 清单和镜像会被更新,kubelet 重启后节点上的 KUBELET-VERSION 才会变化;kubectl version 的 server 版本反映 API Server,不反映 kubelet。升级成功的判据是:所有节点 Ready 且 kubelet 版本等于目标版本;/readyz?verbose、/livez、/healthz 全部通过;etcd 成员健康且只有一个 leader;系统 Pod 没有持续 CrashLoopBackOff;核心业务探针成功;日志中没有大量 x509、forbidden 或版本不兼容错误。

升级失败的判据也要提前写死:API Server 持续不可用、etcd 失去 quorum、多个节点长期 NotReady、关键 addon 无法启动、核心业务持续报错,或错误率明显超出基线。触发任意一条就停止后续批次,而不是“再升一台看看”。

判断与定位

  1. 先分清版本来源。kubectl version -o yaml 看 client 和 server,kubectl get nodes -o wide 看 kubelet,kubeadm version 看升级工具。控制面升级成功但 kubelet 没升级,是常见“看起来升级完了”的假象。
  2. 用 kubectl get --raw='/readyz?verbose' 逐项看哪个健康检查失败,再用 /livez 区分“进程活着”和“能提供服务”。不要只看 kubectl get nodes。
  3. drain 卡住时,依次查 PDB、kubectl describe node 中的不可驱逐 Pod、local storage、emptyDir、finalizer 和 DaemonSet。--force 会绕过保护,只能作为明确知道后果的受控操作。
  4. 控制面异常先看 etcd:endpoint health --cluster、endpoint status --cluster -w table、member list。三成员 etcd 同时坏两台就失去多数派,此时继续重启控制面只会扩大故障。
  5. 容易误判:kind 的 control-plane 节点通常带 NoSchedule 污点,Pod 被驱逐后 Pending 不一定是升级失败;kubelet 版本没变不一定是包没装,可能只是没有重启;kubectl get pods 短暂 Pending 也可能是在等 CNI 或镜像。
  6. 废弃 API 的问题往往在对象下一次写入时才暴露。升级前用扫描器检查 CRD、Helm release、Operator 和 Webhook,升级后检查 kubectl get apiservices 和控制器日志,而不是只验证现有 Pod 还在运行。

恢复与回滚

先约定触发条件:API Server 持续不可用、etcd 失去 quorum、大面积节点 NotReady、关键 addon 不兼容,或核心业务持续错误。触发后立即停止后续批次,保留现场,由决策人确认“向前修复”还是“回滚”。

节点回滚相对简单:重新安装上一版 kubelet、容器运行时和节点镜像,重新加入集群并 uncordon。回滚后仍要满足版本偏差策略,不能长期保留过旧组件;节点上的 local storage 和 emptyDir 数据要提前确认。

addon 回滚优先用 Helm 或原清单回到上一版,恢复 CRD 和 Webhook 配置。CRD 降级可能丢字段,回滚前先备份自定义资源;回滚后重新验证 APIService、证书和控制器日志。

控制面回滚要特别谨慎。kubeadm 通常不支持控制面降级,可行路径是停止升级、修复单点问题,或从升级前的 etcd 快照和 /etc/kubernetes 备份恢复,再安装旧版本 kubelet/kubectl 和旧节点镜像。恢复必须让所有控制面成员使用同一份快照,先切断业务写入,并明确快照之后的数据会丢失。不要把 kubeadm reset 当作生产回滚,它会清掉集群状态。完整恢复步骤见 runbook-etcd-restore 和本目录的 etcd 恢复 Lab。

# 仅在一次性测试环境中演示“保留现场再恢复”的顺序
timeout 120 sudo systemctl stop kubelet
sudo mv /var/lib/etcd /var/lib/etcd.before-rollback.$(date +%Y%m%d%H%M%S)
# 之后按 runbook 用同一份快照恢复所有成员,再恢复 /etc/kubernetes 备份并启动 kubelet

kind 演练环境直接删除,不保留任何集群数据:

timeout 120 kind delete cluster --name lab-upgrade
rm -rf /tmp/lab-upgrade

恢复后的数据一致性检查不能只看 Pod Running。至少比较升级前后的 Namespace、CRD、PVC 数量,抽查 ConfigMap、Secret 和关键业务对象是否与快照时点一致;如果升级前记录了 etcd revision,恢复后核对 revision 和成员列表;业务库要单独核对行数、汇总值和最大 ID。快照时间点之后丢失的写入必须量化并同步给业务方,不能假装没有发生。

验证

按下面的顺序做最终验证,所有命令都带超时,避免在异常集群上无限等待:

timeout 30 kubectl get nodes -o wide
timeout 30 kubectl get --raw='/readyz?verbose'
timeout 30 kubectl get --raw='/livez'
timeout 60 kubectl -n kube-system get pods
timeout 60 kubectl get apiservices
timeout 60 kubectl get crd --no-headers | wc -l
timeout 60 kubectl get pvc -A --no-headers | wc -l
timeout 120 kubectl rollout status deployment/lab-web --timeout=90s

复盘

相关命令

参考链接

读完自测

  1. 控制面升级成功、kubectl version 显示新版本,为什么还不能说集群升级完成?
  2. drain 超时并提示 PDB 不允许中断,应该先检查什么?为什么不能直接用 --force?
  3. 控制面升级失败后,为什么通常不能靠 kubeadm reset 回滚?回滚前必须确认哪些数据边界?
答案
  1. 控制面只代表 API Server 等组件;kubelet、节点镜像、addon、工作负载和存储还要分别升级和验证。必须确认所有节点版本、/readyz、etcd、系统 Pod、核心业务和数据一致性都达标。
  2. 先看 PDB 的 ALLOWED DISRUPTIONS、副本数、节点上的本地存储、DaemonSet 和 finalizer。--force 会绕过 PDB 和驱逐保护,可能让关键服务副本同时下线或丢失本地数据,只能作为明确评估后的受控手段。
  3. kubeadm reset 会清理节点上的集群状态,不是降级工具,也不能恢复已经写入的新版本数据。回滚前要确认 etcd 快照时点、快照后的写入损失、/etc/kubernetes 和旧版本软件包是否可用,并确保所有控制面成员使用同一份快照。
微信小程序码:扫码打开命令速查小程序

小程序版

微信扫码,随时查命令