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

Kubernetes 发行版 — 自己搭

如果跟随 stable 通道,就会跳过两个次版本

在 TT Lab 中继续学习

本实验在真正的 k3s 中运行

VM 中有一台 k3s v1.34.11+k3s1。你要把这个集群向上升一格到 1.35,并实际完成升级之前的准备和升级之后的对照。首次启动大约需要 2 分钟。工作目录是 /root/upgrade。

目标

在把 k3s 的次版本升一格的过程中,依次完成记录起始版本、检查已弃用 API、备份、判断 drain,并在升级之后用 UID 证明同一个节点和同一个工作负载仍然存活。最后把下一格写成 system-upgrade-controller Plan。

为什么重要

重新运行安装脚本就完成了升级,所以选择版本这件事容易被看得很轻。然而通道(stable)并不知道当前集群是什么版本,而是直接指向最新推荐版本。如果从 1.34 升级到 stable,在 stable 为 1.36 的那天就会跳过两个次版本,而 Kubernetes 的 skew 策略即使对只有一个实例的集群也不允许这样做。没有任何工具会阻止跳级,所以必须由人来固定版本并一格一格地升级。

而且“升上去了”本身并不是证据。如果在升级之前没有记下版本和 UID,升级之后就没有办法确认同一个集群是否带着同样的工作负载升上来了。

步骤

  1. 把 API 服务器版本、kubelet 版本、节点名称、节点 UID 保存到 /root/upgrade/before.json。
  2. 在 upg 命名空间中创建 Deployment web(nginx 1.27-alpine,副本数 2)和 PDB web(minAvailable 1),并把 UID 写入 /root/upgrade/workload.json。
  3. 把 apiserver_requested_deprecated_apis 指标抄写到 /root/upgrade/deprecated.txt,并写入 removed_by_target=。
  4. 把 SQLite DB 和令牌备份到 /root/upgrade/backup/,并写入 /root/upgrade/backup.json。
  5. 尝试 drain,把输出保存到 /root/upgrade/drain.txt,uncordon 之后再追加 decision= 这一行。
  6. 升级到 v1.35.8+k3s1,并写入 /root/upgrade/upgrade.json。
  7. 把升级之后的状态和 skew 允许范围写入 /root/upgrade/after.json。
  8. 安装 Plan CRD,并把固定了下一格版本的 Plan k3s-server-next 写入 /root/upgrade/plan.yaml 并应用。
  9. 在 /root/upgrade/report.md 中写入五行内容和说明。

参考

先写下起始版本

把 API 服务器版本、kubelet 版本、节点名称、节点 UID 以 server_version、kubelet_version、node_name、node_uid 为键保存到 /root/upgrade/before.json。

升级之后,在任何地方都再也看不到之前的版本了。请查看 kubectl version -o json 的 serverVersion,以及节点对象的 status.nodeInfo 和 metadata.uid。UID 是后面步骤用来核对“升上去的是不是同一个节点”的值。

部署带有 PDB 的工作负载

在 upg 命名空间中创建 Deployment web(镜像 public.ecr.aws/docker/library/nginx:1.27-alpine,副本数 2)和选择 app=web 的 PodDisruptionBudget web(minAvailable 1),并把 Deployment 的 UID 以 deployment_uid、pdb_min_available 为键保存到 /root/upgrade/workload.json。

PDB 是“在自愿中断(eviction)时,请至少保留几个”的承诺。可以用 kubectl create deployment 和 kubectl create pdb 创建,等两个 Pod 都变为 Ready 之后再写入 UID。不要使用 Docker Hub 的镜像。

还有谁在调用旧 API

把 API 服务器指标中的 apiserver_requested_deprecated_apis 行原样抄写到 /root/upgrade/deprecated.txt,并以 removed_by_target=<수>(占位符为数量)这一行,写出其中在目标版本 1.35 之前(removed_release 小于等于 1.35)被移除的条目的个数。

可以用 kubectl get --raw /metrics 查看 API 服务器的指标。如果标签中的 removed_release 为空,就表示只是被弃用,没有移除计划。这个集群即使什么都不做,也会出现一行。

同时备份 DB 和令牌

把 SQLite 存储备份为 /root/upgrade/backup/state.db,把服务器令牌备份为 /root/upgrade/backup/token,并把所备份的版本和令牌哈希以 k3s_version、token_sha256 为键写入 /root/upgrade/backup.json。

k3s 的 SQLite 位于 /var/lib/rancher/k3s/server/db/。对于正在使用的 DB,sqlite3 的在线备份命令比复制文件更安全。为什么需要令牌,请重新阅读理论部分的备份一节。

drain 结束不了

用 kubectl drain --ignore-daemonsets --delete-emptydir-data --timeout=40s 尝试清空节点,把完整输出保存到 /root/upgrade/drain.txt,然后对节点执行 uncordon。在最后一行用 decision=upgrade-without-drain 或 decision=add-node-first 写出决定。

drain 会先 cordon,再通过 eviction API 驱逐 Pod,而 eviction 会遵守 PDB。如果只有一个节点,请想一想被驱逐的 Pod 的替代 Pod 应该去哪里。即使 drain 以失败结束,输出也必须保存到文件中。

只升一个次版本

从目标版本的标签下载安装脚本,用 INSTALL_K3S_VERSION=v1.35.8+k3s1 升级,并把结果以 from、to、node_uid 为键写入 /root/upgrade/upgrade.json。

固定版本,而不是使用通道。安装脚本会从 raw.githubusercontent.com 的 k3s-io/k3s 仓库下载该版本标签的 install.sh。这台 VM 的配置在 /etc/rancher/k3s/config.yaml 中,所以不需要再次给出。结束后,请等待 API 响应,再写入值。

对照升上来的是不是同一个东西

把升级之后的状态以 server_version、deployment_uid、web_ready、web_restarts(web Pod 的重启次数之和)、traefik_deployed、kubelet_allowed、kubectl_allowed 为键写入 /root/upgrade/after.json。最后两项是 skew 策略针对当前 API 服务器版本所允许的次版本列表("1.xx" 字符串)。

工作负载用 UID 来对照,配置则用 traefik 是否复活来对照。在 skew 策略中,kubelet 最多可以比 API 服务器低几个版本,kubectl 可以上下相差几个版本,请看理论部分的表。

把下一格写成 Plan

应用 system-upgrade-controller v0.20.1 的 crd.yaml,并在 system-upgrade 命名空间中编写 Plan k3s-server-next,保存为 /root/upgrade/plan.yaml 并应用。把当前版本之后紧接着的下一个次版本的 k3s 版本,固定到 version 中,只选择服务器节点,并设为逐台 cordon 后升级。

CRD 从 GitHub release 的固定版本地址下载。控制器不会启动,所以 Plan 不会被执行,只会被保存。CRD 会原样接受既没有 version 也没有 channel 的 Plan,所以应用成功并不意味着它是正确的 Plan。请想一想,在 k3s 官方文档的服务器 Plan 示例中,channel 应该改成什么。

如果跟随 stable,会是几格

在 /root/upgrade/report.md 中写入 initial=、current=、next_plan=、stable_channel=(当前 k3s stable 通道所指向的版本)、stable_minor_jump=(从起始版本到该版本相差的次版本格数)这五行,以及不少于 150 个字符的说明,解释为什么固定版本而不是使用通道。

通道指向的版本由 https://update.k3s.io/v1-release/channels 以 JSON 形式给出。格数就是次版本数字的差。说明中请写入 skew 策略,以及是什么没有阻止那次跳级。