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

CKA — Kubernetes 管理员

kubeadm 不替你做的事 — 准备、版本偏差、接口

在 TT Lab 中继续学习

一句话总结

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 年的补丁支持。组件之间允许的差异如下。

升级顺序就是从这些规则中得出的。先升级 kube-apiserver(不能跳过 minor),然后是 controller-manager 和 scheduler,最后是 kubelet 和 kube-proxy。如果反过来,就会经过“kubelet 比 apiserver 新”这种被禁止的状态。文档建议,升级之前先升到当前 minor 的最新补丁,再升到目标 minor 的最新补丁。

升级 kubeadm 集群 文档规定的流程分为三步——第一个控制平面节点、其余控制平面节点、工作节点。

  1. 在第一个控制平面上用 kubeadm upgrade plan 确认可升级的版本和组件配置状态,然后执行 kubeadm upgrade apply v1.X.y。这个命令会检查 API 服务器是否可达、所有节点是否 Ready、控制平面状态,强制执行偏差策略,准备镜像,替换控制平面组件(只要有一个没起来就会回退),并应用新的 CoreDNS 和 kube-proxy 清单。由 kubeadm 管理的证书也会在此时更新。
  2. 在其余控制平面节点上使用 kubeadm upgrade node。它会获取集群的 ClusterConfiguration,并升级 static Pod 清单和 kubelet 配置。
  3. 对各节点的 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 插件。