TT Lab
开始
学习 学习路径 课程

用 Kubespray 与 Terraform 搭建集群

一次一步,从 inventory 开始 — Kubespray 升级的规则

在 TT Lab 中继续学习

一句话总结

kubespray 的升级,就是把 inventory 中的版本向上升一格,然后运行 upgrade-cluster.yml;它会逐个节点执行 cordon → drain → 升级 → uncordon,所以在单节点集群中,这段时间就是停机时间。

为什么需要它

Kubernetes 项目只为最近的三个次版本提供补丁。因此一年需要升级两三次,如果不升级一直撑着,就要一次跨越好几格,而这是不被允许的。Kubernetes 的版本偏差策略(Version Skew Policy)规定,kube-apiserver 每次只能升级一个次版本,kubeadm 升级文档也要求不要跳过次版本。kubespray 在此之上又加了一条自己的规则——kubespray 的标签也要一格一格地升级。因为每个标签中 role 的默认值和要下载的文件清单都会改变,如果从旧标签直接跳到最新标签,会坏掉什么,没有人测试过。

工作原理

版本要在 inventory 中升级。 升级文档的要点是这样的:如果在 inventory 中写了 kube_version,就要在运行 upgrade-cluster.yml 之前修改这个值,或者用 -e kube_version=... 传入新版本,否则会“停留在 inventory 中所写的同一个版本上”。两种方法中,修改文件更好。如果只用 -e 升级,集群是新版本,而 inventory 仍是旧版本,下次有人用这个 inventory 运行 cluster.yml 的那一刻,就会从不一致的状态开始。

upgrade-cluster.yml 会遵守顺序。 这个 playbook 只用于已经存在的集群,先升级控制平面和 etcd,后升级工作节点。对每个节点,pre-upgrade role 会执行 cordon 和 drain,用 kubeadm upgrade 把静态 Pod 清单换成新版本,升级 kubelet 之后,由 post-upgrade role 执行 uncordon。一次升级的节点数由 serial(默认 20%)决定,文档还介绍了通过 upgrade_node_confirm 和 upgrade_node_pause_seconds 在每个节点处暂停确认的方法。如果要分批升级节点,它要求先不加限制地运行 facts.yml,再用 --limit "kube_control_plane:etcd" 先升级控制平面。

并不是所有东西都会跟着升级。 kubespray 按 Kubernetes 次版本对应的表(etcd_supported_versions、coredns_supported_versions 等)来选择 etcd、CoreDNS 和 pause 镜像的版本。如果两个次版本在表中指向同一个值,那个组件就保持不变。这次的实测就是这样——1.35.8 和 1.36.4 用的都是 etcd 3.6.14,反过来,CoreDNS 在表中指向的版本不同,从 1.12.4 变成了 1.14.2。“升级了 Kubernetes,etcd 应该也升级了吧”,在确认之前只是一个假设。

下一格来自下一个标签。 标签所接受的最高版本是校验和列表的第一个键,在 v2.32.0 中是 1.36.4。如果已经升到这个版本,就无法再用这个 kubespray 继续升级了。像文档中 “Multiple upgrades” 一节那样,把 kubespray 升级到下一个标签(并用该标签的 requirements.txt 重新安装 Ansible),再运行 upgrade-cluster.yml,这才是下一格。

在现场相遇的样子

制作本课程时,在 medium VM 上实测了 1.35.8 → 1.36.4。upgrade-cluster.yml 整体用了 368 秒,其中 kubeadm 升级第一个控制平面的任务用了 94 秒,drain 用了 16 秒。内存最高升到 1.56GiB,比安装时(最高 1.36GiB)略高,但在 4GiB 之内很宽裕。节点 UID 保持不变,被 drain 的 web Pod 在 uncordon 之后以新的 UID 重新启动。也就是说,在单节点集群中,drain 不是“迁移”,而是“停下再重新启动”。

现场常见的事故有两种。第一,如果 PodDisruptionBudget 用 minAvailable 绑住了所有副本,drain 就不会结束。pre-upgrade role 的默认值是 drain_timeout: 360s,尝试三次(drain_retries: 3)之后失败,并且由于 upgrade_node_uncordon_after_drain_failure: true,它会让节点重新 uncordon,然后停下 playbook。节点仍然是旧版本。第二,drain 已经通过,但在之后的步骤停住,节点就会保持 cordon 状态。如果不重新运行就下班,第二天就会发现有一个无法调度的节点。所以在升级后的检查清单最上面,是“有没有 unschedulable 的节点”。

下一项实验要做什么

用 1.35.8 搭建集群并启动工作负载,然后拍下升级前的对照照片(before/after picture)。把 inventory 中的版本改为 1.36.4 并运行 upgrade-cluster.yml,再拍一张同样的对照照片,比较节点、工作负载、etcd 和 CoreDNS 各自发生了什么变化。从日志中读取 cordon、drain 和 uncordon,并判断用这个 kubespray 是否能升级到下一格。