kubeadm 不替你做的事 — 准备、版本偏差、接口
一句话总结
kubeadm 是组装控制平面的工具,而不是准备节点的工具。swap、IP 转发、容器运行时、cgroup 驱动和端口需要人来设置好;升级必须遵循版本偏差策略(version skew policy)所规定的顺序(kube-apiserver 先升级);CNI、CSI、CRI 是把网络、存储、运行时从核心中分离出来、使其可替换的接口。
为什么需要它
kubeadm init 一开始就会运行 preflight 检查。kubeadm 安装 文档把要求写成这样——每台机器至少 2GB RAM,控制平面机器至少 2 个 CPU,所有机器之间网络完全连通,每个节点拥有唯一的 hostname、MAC 地址和 product_uuid,以及开放特定端口。二进制文件动态链接到 glibc,所以在 Alpine 这类没有 glibc 的发行版上需要兼容层,而 kubeadm 会通过 SystemVerification 检查确认是否是受支持的内核版本。
这些检查失败时,kubeadm 会停下来,但不会替你修复使其通过。所以如果不了解准备事项,就会从“为什么不行”开始,靠搜索一项项补上,而在考场上没有这个时间。升级也是如此。kubeadm 会强制执行偏差策略,但要按什么顺序升级哪些节点,必须由人来掌握。
工作原理
安装前的基础设施准备
swap。kubelet 的默认行为是,如果检测到节点上有 swap,就拒绝启动。所以要用 swapoff -a 关闭,并且为了重启后仍然保持关闭,要从 /etc/fstab 或 systemd.swap 配置中移除。如果非要保留 swap,就要在 kubelet 配置中指定 failSwapOn: false,即便如此,由于默认的 swapBehavior 是 NoSwap,工作负载也不会使用 swap。
内核网络设置。根据 容器运行时 文档,Linux 内核默认不允许在接口之间路由 IPv4 数据包。所以要在 /etc/sysctl.d/k8s.conf 中写入 net.ipv4.ip_forward = 1,并用 sysctl --system 应用。文档补充说,大多数网络实现会在需要时自行修改这个值,但有些则交给管理员,并且“也有实现期望其他 sysctl 值或加载内核模块,请参阅网络实现的文档”。旧指南中的 overlay、br_netfilter 模块加载以及 bridge-nf-call 系列 sysctl,已不在当前官方页面的必需列表中,而是取决于 CNI 实现是否要求。在考试或现场,与其照搬旧流程去背,不如区分清楚当前文档要求的内容(ip_forward)和网络实现要求的内容,这样更稳妥。
容器运行时。Kubernetes 通过 CRI 与运行时通信。如果不指定运行时,kubeadm 会扫描已知的套接字路径自动检测;如果有多个或一个都没有,就会报错并要求指定。Docker Engine 没有实现 CRI,所以需要另外安装 cri-dockerd,kubelet 对 Docker 的内置支持(dockershim)已在 1.24 中移除。
| 运行时 | Unix 套接字 |
|---|---|
| containerd | unix:///var/run/containerd/containerd.sock |
| CRI-O | unix:///var/run/crio/crio.sock |
| Docker Engine (cri-dockerd) | unix:///var/run/cri-dockerd.sock |
cgroup 驱动。kubelet 和运行时都使用 cgroup 来限制资源,这时所用的驱动有 cgroupfs 和 systemd 两种。文档甚至称之为“critical”的规则是,kubelet 和运行时必须使用同一个驱动。kubelet 的默认值是 cgroupfs,但在 init 系统为 systemd 的发行版上,cgroup 管理器会变成两个,在资源压力下节点可能变得不稳定,所以要使用 systemd 驱动。如果使用 cgroup v2,答案就是 systemd 驱动。kubelet 一侧是 KubeletConfiguration 中的 cgroupDriver: systemd,运行时一侧是各运行时文档中的设置。在 Kubernetes 1.37 中,KubeletCgroupDriverFromCRI 特性门控是开启的,并且如果运行时支持 RuntimeConfig CRI RPC,kubelet 会自动从运行时获知驱动;但像 containerd 1.y 这样没有该 RPC 的运行时,仍然使用 kubelet 自己的设置。
端口。原样转录 端口与协议 文档中的表,如下。
| 位置 | 端口 | 用途 |
|---|---|---|
| 控制平面 | 6443 | kube-apiserver |
| 控制平面 | 2379-2380 | etcd 客户端 API |
| 控制平面 | 10250 | kubelet API |
| 控制平面 | 10259 | kube-scheduler |
| 控制平面 | 10257 | kube-controller-manager |
| 工作节点 | 10250 | kubelet API |
| 工作节点 | 10256 | kube-proxy |
| 工作节点 | 30000-32767 | NodePort Service(TCP、UDP) |
集群生命周期——偏差决定顺序
版本偏差策略 写明,项目维护最近三个 minor 发布分支,1.19 之后的版本可获得约 1 年的补丁支持。组件之间允许的差异如下。
- HA 集群中的各个 kube-apiserver 之间,必须在 1 个 minor 以内。
- kubelet 不能比 kube-apiserver 新,最多可以旧 3 个 minor。kube-proxy 也一样。
- kube-controller-manager、kube-scheduler、cloud-controller-manager 不能比 kube-apiserver 新。
- kubectl 支持在 kube-apiserver 的 ±1 个 minor 范围内使用。
升级顺序就是从这些规则中得出的。先升级 kube-apiserver(不能跳过 minor),然后是 controller-manager 和 scheduler,最后是 kubelet 和 kube-proxy。如果反过来,就会经过“kubelet 比 apiserver 新”这种被禁止的状态。文档建议,升级之前先升到当前 minor 的最新补丁,再升到目标 minor 的最新补丁。
升级 kubeadm 集群 文档规定的流程分为三步——第一个控制平面节点、其余控制平面节点、工作节点。
- 在第一个控制平面上用
kubeadm upgrade plan确认可升级的版本和组件配置状态,然后执行kubeadm upgrade apply v1.X.y。这个命令会检查 API 服务器是否可达、所有节点是否 Ready、控制平面状态,强制执行偏差策略,准备镜像,替换控制平面组件(只要有一个没起来就会回退),并应用新的 CoreDNS 和 kube-proxy 清单。由 kubeadm 管理的证书也会在此时更新。 - 在其余控制平面节点上使用
kubeadm upgrade node。它会获取集群的 ClusterConfiguration,并升级 static Pod 清单和 kubelet 配置。 - 对各节点的 kubelet 做 minor 升级时,要先 drain。然后升级 kubelet 和 kubectl 软件包,在工作节点上用
kubeadm upgrade node更新 kubelet 配置后重启 kubelet,再用kubectl uncordon恢复。
文档预先告知,由于容器 spec 哈希会改变,升级之后所有容器都会重启。失败时可以重新执行同一个命令(幂等),也可以在不改变版本的情况下用 kubeadm upgrade apply --force 对齐状态,并且在 /etc/kubernetes/tmp 下会留有 etcd 和清单的备份。
根据 安全地清空节点 文档,kubectl drain 会优雅终止 Pod,并遵守 PodDisruptionBudget,如果存在 DaemonSet Pod,就需要 --ignore-daemonsets。drain 成功结束,意味着(除被排除的系统 Pod 外)所有 Pod 都已被安全驱逐,之后用 kubectl uncordon 重新开放调度。每次只对一个节点执行,但即使在多个终端中并行运行,PDB 也会一并得到遵守。
CNI、CSI、CRI——分离出了什么
这三个接口名字相似,但所分离出的层不同。
| 接口 | 分离出的部分 | 谁调用谁 |
|---|---|---|
| CRI | 容器运行时 | kubelet 作为 gRPC 客户端调用运行时 |
| CNI | Pod 网络实现 | 容器运行时加载 CNI 插件,实现 Pod 网络模型 |
| CSI | 存储系统 | CSI 驱动以标准接口暴露存储,Pod 通过 csi 卷类型使用 |
CRI 文档写明,CRI 是 kubelet 与运行时之间的主要 gRPC 协议,在 v1.23 中稳定,从 1.26 开始 kubelet 要求 v1 CRI API,所以在不支持的运行时上,节点无法注册。端点通过 kubelet 的 --container-runtime-endpoint 指定。网络插件 文档说明,CNI 插件是实现 Kubernetes 网络模型所必需的,必须与 CNI 规范 v0.4.0 及以上(推荐 v1.0.0)兼容,并且在 1.24 中 kubelet 的 cni-bin-dir 和 network-plugin 标志被移除,CNI 的管理由运行时负责,而不是 kubelet。卷 文档的 CSI 一节写明,CSI 是把任意存储系统暴露给容器工作负载的标准接口,可以通过引用 PVC、generic ephemeral 卷和 CSI ephemeral 卷三种方式使用,而旧方式 FlexVolume 从 1.23 起已被弃用。
在现场相遇的样子
只有在资源压力下节点才会不稳。平时好好的,一到内存占满,kubelet 就重启或 Pod 莫名其妙地终止,这样的节点要怀疑是 cgroup 驱动不一致。文档恰好写了这个症状——init 是 systemd,而 kubelet 和运行时使用 cgroupfs 时,cgroup 管理器会变成两个,资源视图因此分裂。文档还警告,修改已经加入集群的节点的驱动也是一项敏感操作,所以在加入之前对齐要便宜得多。
没有 drain 就升级了 kubelet。只替换软件包然后重启 kubelet,该节点上的容器会全部重启,而由于没有做 drain,PDB 和优雅终止都没有得到遵守。这就是文档明确要求“minor 升级要先 drain”的原因。
下一项测验要确认什么
测验会考查 kubelet 对 swap 的默认行为、把 cgroup 驱动对齐为 systemd 的原因、当前官方文档要求的内核设置、升级顺序、kubelet 允许的偏差,以及是谁在加载 CNI 插件。