腾空节点并制定升级计划
目标
亲手执行安全排空并恢复一台节点的流程,并把版本偏差规则与金丝雀发布计划制作成可验证的产物,而不仅是文档。
为什么重要
大多数升级事故并不是因为不知道命令,而是因为不知道顺序和范围。在不可逆操作(kubeadm upgrade apply)之前,必须先创建 etcd 快照。控制平面不支持降级,因此出错时的逃生路线不是“降回去”,而只能是“从快照恢复”。范围问题由版本偏差规则负责约束。kubelet 可以低于 apiserver,却不能高于它;只有 kubectl 允许高一个小版本。如果不了解这种不对称性,就会卡在“kubectl 可以用最新版,为什么 kubelet 不行”之类的问题上。最后,节点操作的三步(cordon → drain → uncordon)必须与 PDB 配合。没有 PDB,drain 可能使整个服务停机;PDB 过于严格,drain 又可能永远无法结束。
步骤
- 创建
/root/ops/upgrade/out/versions.json。顶层键为server(格式为v1.x.y的服务器版本字符串)和nodes。nodes以lab-node-0、lab-node-1、lab-node-2为三个键,其值必须是各节点实际的 kubelet 版本字符串。 - 只 cordon
lab-node-1,立即将节点列表保存到/root/ops/upgrade/out/cordon.txt。文件中必须出现lab-node-1和SchedulingDisabled,而lab-node-0不得被禁止调度。再在/root/ops/upgrade/out/cordon-note.txt中用一行说明 cordon 不会影响已经运行的 Pod。 - 创建命名空间
ops-upgrade,并在其中创建replicas: 3的 Deploymentweb(Pod 标签app=web)。接着在同一命名空间创建 PodDisruptionBudgetweb-pdb,设置spec.minAvailable: 2、spec.selector.matchLabels.app: web,不要使用maxUnavailable。 - drain
lab-node-1,将输出保存到/root/ops/upgrade/out/drain.txt。drain 完成后,把ops-upgrade命名空间中的 Pod 分布在哪些节点保存到/root/ops/upgrade/out/after-drain.txt,至少三行。该文件中不得出现lab-node-1,并且必须出现lab-node-0或lab-node-2。 - uncordon
lab-node-1,将输出保存到/root/ops/upgrade/out/uncordon.txt。三个节点都必须恢复为可调度状态,Deploymentweb的就绪副本数也必须重新达到 3。 - 将
/opt/lab/fixtures/k8s/skew-template.yaml复制到/root/ops/upgrade/skew.yaml,填写answers下的问号。apiserver: "1.34"是题目给定条件,保持不变。答案格式为"1.31"之类的小版本字符串,只有downgrade_supported使用"yes"或"no"。需要填写的键为min_kubelet、max_kubelet、max_kubectl、min_kubectl、max_controller_manager、next_upgrade_target、downgrade_supported。 - 编写不少于 500 字节的
/root/ops/upgrade/runbook.md。每一项必须放在不同的行,并遵循以下顺序:(1) 备份 etcd 快照,(2)kubeadm upgrade plan,(3) 在第一个控制平面执行kubeadm upgrade apply,(4) 在其余节点执行kubeadm upgrade node;节点操作部分中,drain必须先于uncordon出现。还要用句子说明不能跨越小版本升级的限制(例如“一次一个”)。 - 为三个节点添加
upgrade.labhub.io/stage标签:lab-node-0为canary,lab-node-1为batch1,lab-node-2为batch2。然后在/root/ops/upgrade/out/rollout.json中编写计划。stages是长度为 3 的数组,每个元素包含stage(canary/batch1/batch2)和nodes(节点名称数组)。第一阶段必须是canary,且恰好包含 1 个节点;三个节点都必须被分配到某个阶段。顶层还要包含verify_between_stages键,写明阶段之间要验证什么。
参考
- 服务器版本位于
kubectl version -o json的.serverVersion.gitVersion,节点 kubelet 版本位于kubectl get nodes -o json的.items[].status.nodeInfo.kubeletVersion。使用jq的from_entries可以方便地构造以名称为键的对象。 - 如果存在 DaemonSet Pod 或使用 emptyDir 的 Pod,drain 默认会拒绝执行。请添加
--ignore-daemonsets、--delete-emptydir-data,必要时再加--force。 - 使用
kubectl get pods -n ops-upgrade -o wide --no-headers可以逐行查看 Pod 所在节点。 - 标签中包含点和斜杠时,需要在 jsonpath 中转义。最简单的检查方式是
kubectl get nodes -L upgrade.labhub.io/stage。 - 常见错误 1:第 7 步 runbook 将
kubeadm upgrade apply和kubeadm upgrade node写在同一行。系统根据两个命令首次出现的行号判定顺序,写在同一行无法通过。drain和uncordon也是如此。 - 常见错误 2:第 6 步把 kubectl 的上限写成与 apiserver 相同。只有 kubectl 允许高一个小版本。
- 每次实验都会启动新的实验 Pod,因此前一个实验中的集群状态不会保留。请在第 3 步自行创建
ops-upgrade命名空间和 Deployment。这正是必须把运维流程写入 runbook 和 manifest,而不能依靠记忆的原因。
收集控制平面与节点版本
创建 /root/ops/upgrade/out/versions.json。顶层键为 server(格式为 v1.x.y 的服务器版本字符串)和 nodes。nodes 以 lab-node-0、lab-node-1、lab-node-2 为三个键,其值必须是各节点实际的 kubelet 版本字符串。
升级从准确了解当前版本开始。服务器版本和各节点的 kubelet 版本需要从不同位置读取。节点信息位于 status.nodeInfo 下。
只禁止一台节点调度
只 cordon lab-node-1,立即将节点列表保存到 /root/ops/upgrade/out/cordon.txt。文件中必须出现 lab-node-1 和 SchedulingDisabled,而 lab-node-0 不得被禁止调度。再在 /root/ops/upgrade/out/cordon-note.txt 中用一行说明 cordon 不会影响已经运行的 Pod。
原则是一次只处理一台。cordon 只阻止今后的调度,不会影响已经运行的 Pod;请把这一点记录下来。
创建保障可用性的 PDB
创建命名空间 ops-upgrade,并在其中创建 replicas: 3 的 Deployment web(Pod 标签 app=web)。接着在同一命名空间创建 PodDisruptionBudget web-pdb,设置 spec.minAvailable: 2、spec.selector.matchLabels.app: web,不要使用 maxUnavailable。
声明三个副本中至少必须存活多少个。最小可用数和最大不可用数不能同时设置。
排空节点并确认 Pod 迁移
drain lab-node-1,将输出保存到 /root/ops/upgrade/out/drain.txt。drain 完成后,把 ops-upgrade 命名空间中的 Pod 分布在哪些节点保存到 /root/ops/upgrade/out/after-drain.txt,至少三行。该文件中不得出现 lab-node-1,并且必须出现 lab-node-0 或 lab-node-2。
drain 在 cordon 的基础上还会驱逐现有 Pod。如果没有处理 DaemonSet 和 emptyDir 的选项,命令会被拒绝。排空后请保存 Pod 所在节点的信息。
恢复完成维护的节点
uncordon lab-node-1,将输出保存到 /root/ops/upgrade/out/uncordon.txt。三个节点都必须恢复为可调度状态,Deployment web 的就绪副本数也必须重新达到 3。
如果忘记 uncordon,该节点就会默默闲置。三个节点都应恢复可调度状态,工作负载也应回到原来的数量。
填写版本偏差表
将 /opt/lab/fixtures/k8s/skew-template.yaml 复制到 /root/ops/upgrade/skew.yaml,填写 answers 下的问号。apiserver: "1.34" 是题目给定条件,保持不变。答案格式为 "1.31" 之类的小版本字符串,只有 downgrade_supported 使用 "yes" 或 "no"。需要填写的键为 min_kubelet、max_kubelet、max_kubectl、min_kubectl、max_controller_manager、next_upgrade_target、downgrade_supported。
基准始终是 apiserver。kubelet 可以低 3 个小版本,控制器可以低 1 个小版本,只有 kubectl 可以上下相差 1 个小版本。小版本必须一次升级一个。
编写升级 runbook
编写不少于 500 字节的 /root/ops/upgrade/runbook.md。每一项必须放在不同的行,并遵循以下顺序:(1) 备份 etcd 快照,(2) kubeadm upgrade plan,(3) 在第一个控制平面执行 kubeadm upgrade apply,(4) 在其余节点执行 kubeadm upgrade node;节点操作部分中,drain 必须先于 uncordon 出现。还要用句子说明不能跨越小版本升级的限制(例如“一次一个”)。
不可逆操作之前必须先备份。还要写明第一个控制平面与其余节点使用的 kubeadm 子命令不同。
制定金丝雀发布计划并添加标签
为三个节点添加 upgrade.labhub.io/stage 标签:lab-node-0 为 canary,lab-node-1 为 batch1,lab-node-2 为 batch2。然后在 /root/ops/upgrade/out/rollout.json 中编写计划。stages 是长度为 3 的数组,每个元素包含 stage(canary/batch1/batch2)和 nodes(节点名称数组)。第一阶段必须是 canary,且恰好包含 1 个节点;三个节点都必须被分配到某个阶段。顶层还要包含 verify_between_stages 键,写明阶段之间要验证什么。
不要让计划只停留在文档中,要通过节点标签将它写入集群。第一阶段必须只有一台节点,而阶段之间验证什么,才是金丝雀策略的核心。