1. 从节点安装 Docker、kubeadm、kubelet、kubectl:写在动手之前的准备事项
在 Kubernetes 集群的部署流程里,第四步通常就是把新节点真正接入集群。这一步的内容看起来就是装几个软件包的事,但很多人在这个环节翻车。原因不在于命令本身有多复杂,而在于对组件之间的关系没概念:Docker 负责把容器跑起来,kubelet 是真正注册到集群的“代理人”,kubeadm 负责把控制平面和节点初始化串起来,kubectl 则是对外操作集群的命令行入口。四者各管一段,缺一个,节点都起不来。
我在接手这类搭建任务时,习惯先把环境确认放在第一位。这个第四步并不是从执行命令开始的,而是从上一步的架构规划就决定了。如果你在控制平面节点上已经初始化过集群,那么从节点这边要做的事情其实高度统一:安装同样的容器运行时、同样的 kubeadm 版本、同样的 kubelet 版本,然后拿着控制平面初始化时输出的 token 和证书哈希,执行一条 join 命令即可。
但这只是理想情况。实际工作中,我看到最多的问题集中在版本不一致、cgroup 驱动不匹配、iptables 配置干扰、swap 没有关闭这四类。这篇文章把每一步的坑提前标出来,再给出一套可以直接复制的操作流程,适合已经完成控制平面初始化、正在准备往集群里添加更多节点的场景。如果你是第一次搭 Kubernetes,建议把控制平面那边的安装过程也同步理顺,两边的环境基准保持统一,后面所有排查都会轻松得多。
另外说一句版本问题。kubeadm、kubelet、kubectl 这三个组件的版本,在同一个集群里应当保持相同的小版本,比如都停在某个具体的 Kubernetes 版本上。容器运行时本身不太受 Kubernetes 版本约束,但它的 cgroup 驱动和 kubelet 的配置要能对得上。这个我会在下一节展开讲,它是我遇到过最隐蔽的故障来源之一。
还有一件事值得提前说:从节点不一定要安装 kubectl。严格意义上,节点加入集群靠的是 kubelet,kubectl 只是客户端工具。但在实操里,很多运维同学习惯在节点上直接查看集群状态,所以从节点也一起安装。考虑到这是一篇完整的从节点安装流程,我会把 kubectl 的安装也纳入进来,但会说明它真正的职责边界。
整体执行顺序建议是:先做完系统层优化和依赖检查,再装 Docker,再装 Kubernetes 三个组件,最后执行 join。任何一步失败,都不要直接往下走,先把报错解决掉,否则后面所有的排查都会指向错误的方向。
2. 环境初始化:内核参数、swap、模块加载,一个都不能少
很多人在安装 Docker 和 kubeadm 之前,会把系统准备工作当成“可选优化”跳过。实际上,系统层配置直接决定容器运行时能不能正常上报状态,决定 kubelet 会不会因为资源统计错误而被节点隔离。下面这几项,我在每次搭节点时都会逐一过一遍。
2.1 关闭 swap 并调整 swappiness
Kubernetes 的 kubelet 默认不允许宿主机开启 swap。原因不复杂:kubelet 要做的是精确掌握节点上每个 Pod 的资源使用情况,然后基于这些数据做调度和驱逐决策。如果内存被换到磁盘上,资源统计就会出现偏差,Pod 的管理行为也会跟着错乱。所以标准做法是永久关闭 swap。
sudo swapoff -a sudo sed -i 's/^\(.*swap.*\)$/#\1/' /etc/fstab先执行swapoff -a让当前会话立即生效,再修改/etc/fstab把开机自动挂载 swap 的条目注释掉。这里最容易漏的是第二步,因为swapoff -a只在重启前有效,重启后 swap 又会被挂载回来,kubelet 直接启动失败。我见过有人反复排查 kubelet 日志,最后发现只是 fstab 没有处理干净。
如果你不确定系统里有没有 swap,可以用free -h和cat /proc/swaps两个命令确认。云厂商提供的镜像通常默认不带 swap,但自建虚拟机或某些定制发行版就不一定了。
2.2 加载 br_netfilter 并开启流量转发
Kubernetes 集群内部的 Pod 通信依赖 Linux 网桥和 iptables。为了让桥接流量也能被 iptables 规则处理,需要加载br_netfilter内核模块,并把桥接场景下的 IPv4 转发打开。这和 Docker 的网络模式配合得尤其紧密,因为容器之间、Pod 之间大量流量会走 bridge。
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter同时要写一组 sysctl 配置,让 reload 之后永久生效:
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 EOF sudo sysctl --system这里面net.ipv4.ip_forward尤其关键。没有它,集群内的 Pod 无法做正常的南北向通信,Service 的 ClusterIP 转发也会出问题。检查是否生效也很简单:sysctl net.bridge.bridge-nf-call-iptables返回 1 就说明配置加载了。
2.3 可选的资源限制调优
不是必须,但我通常会顺手加上一组文件句柄和进程数的限制。Kubernetes 节点上运行的容器数量多时,kubelet、containerd、dockerd 都会拉起大量进程和文件描述符。默认值虽然够用,但在高密度部署场景下,早做准备没坏处。
cat <<EOF | sudo tee /etc/security/limits.d/k8s.conf * soft nofile 65536 * hard nofile 65536 * soft nproc 65536 * hard nproc 65536 EOF这一条不是必须的,但如果你打算在这个节点上跑比较多的 Pod,或者容器经常出现“too many open files”之类的报错,可以回头检查这一项。
系统层准备做完后,建议重启一次节点再继续。不是所有场景都要求重启,但重启能确认持久化配置都生效了。特别是modules-load.d和sysctl.d这两处,如果开机后没有自动加载,后面 kubelet 起来时会很被动。
3. 安装 Docker:版本、cgroup 驱动和 kubelet 的协同关系
容器运行时是 Kubernetes 节点的引擎。这篇流程里我们用 Docker,但要注意:Kubernetes 从某个版本开始已经不再直接调用 Docker 的底层能力,而是通过dockershim来兼容。这个细节在实操中的直接体现是,Kubernetes 侧的配置通常指向/run/containerd/containerd.sock或/var/run/dockershim.sock,取决于集群版本和运行时选型。
3.1 安装 Docker 并固定版本
不同发行版的 Docker 安装方式略有差异,我以 Debian/Ubuntu 系为例。官方源安装的好处是能够拿到最新的稳定版,同时也能用apt-mark hold把版本锁住,防止后面系统更新时不小心把 Docker 升上去。
sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin装完之后,建议确认一下 Docker 和 containerd 的版本号。因为 Kubernetes 的 kubelet 需要和容器运行时做 gRPC 通信,版本跳跃太大偶尔会出现握手失败。固定版本的做法是:
sudo apt-mark hold docker-ce docker-ce-cli containerd.io这样即使以后执行apt-get upgrade,Docker 和 containerd 也不会被意外替换成不兼容的版本。踩过坑的人都知道,集群稳定运行半年后,最怕的就是某次例行更新把运行时版本悄悄改了。
3.2 配置 cgroup 驱动为 systemd
这一步是整个安装过程中最容易埋雷的地方。简单解释一下背景:systemd 是绝大多数 Linux 发行版的 init 进程,它已经接管了系统的 cgroup 管理。Docker 默认使用的 cgroup 驱动可能是cgroupfs或systemd,取决于安装方式。而 kubelet 在 Kubernetes 1.20 之后的版本里,强烈建议使用systemd驱动。两边如果不一致,节点加入集群后,kubelet 在检查 cgroup 驱动时就会报错,Pod 起不来,或者状态反复进入 CrashLoopBackOff。
正确做法是在 Docker 配置里显式指定 systemd 驱动,同时让 kubelet 那边也用 systemd 驱动。Docker 的配置文件在/etc/docker/daemon.json,如果文件不存在就自己建一个:
{ "exec-opts": ["native.cgroupdriver=systemd"], "log-driver": "json-file", "log-opts": { "max-size": "100m" }, "storage-driver": "overlay2" }native.cgroupdriver=systemd让 Docker 容器统一使用 systemd 作为 cgroup 管理器。overlay2是推荐的存储驱动,性能表现比aufs或vfs好得多。日志限制max-size和max-file也是我强烈建议加的,因为 Kubernetes 节点上的容器日志默认会无限增长,一旦磁盘写满,kubelet 会直接进入 NotReady 状态,那是相当被动的场景。
改完配置后重启 Docker:
sudo systemctl restart docker sudo docker info | grep "Cgroup Driver"看到输出是systemd就说明配置生效了。
3.3 确认 kubelet 侧的 cgroup 驱动参数
kubelet 的配置文件通常位于/var/lib/kubelet/kubeadm-flags.env或/etc/systemd/system/kubelet.service.d/10-kubeadm.conf。在 join 之后,kubeadm 会自动生成 kubelet 的配置,但里面默认的 cgroup 驱动不一定和 Docker 一致。为了保险起见,我会在安装 kubeadm 之前先建好一个 kubelet 的默认配置目录,避免 join 时自动生成的信息和 Docker 配置冲突。
实际上,更稳妥的做法是:在初始化从节点之前,就先把 kubelet 默认使用的 systemd cgroup 驱动写入/var/lib/kubelet/config.yaml。但这个文件通常由 kubeadm 生成,如果提前写容易和后面 join 的数据打架。所以我通常这样处理:先运行一次kubeadm join,然后立刻检查 kubelet 配置文件里的 cgroupDriver 字段。如果发现是 cgroupfs,再手动改成 systemd 并重启 kubelet。这个排查思路留到第四节细说,里面有一段完整的排错链路,直接照着做就可以。
顺带补一句:在较新的 Kubernetes 版本中,Docker 作为运行时已经不是唯一选择,containerd 甚至比 Docker 更轻。但既然标题要求装 Docker,我依然把配置调整到“Kubernetes 能顺利接受”的状态来演示。如果未来你打算从 Docker 切换到 containerd,思路是一样的,只是把daemon.json换成/etc/containerd/config.toml。
4. 安装 kubeadm、kubelet、kubectl:仓库、版本锁定与各自分工
从节点和控制平面节点在这一步的操作基本一致:配置 Kubernetes 的 apt/yum 仓库,然后安装指定版本的 kubeadm、kubelet、kubectl。这三个工具的作用要继续分清楚:kubeadm 是集群引导工具,负责生成证书、配置组件、执行 join;kubelet 是真正运行在每个节点上的守护进程,负责管理 Pod 生命周期并向控制平面汇报节点状态;kubectl 是命令行客户端,主要用来对集群下发操作指令。
4.1 添加 Kubernetes 官方软件源
Kubernetes 官方源在国内访问经常不稳定,可以根据自己的网络环境选择一个可用镜像源。为了演示通用流程,这里仍以官方源的方式为例:
sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl gpg curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list sudo apt-get update这里用 v1.28 做示例。如果你控制平面初始化时的版本是 v1.29 或 v1.30,那仓库路径里的版本号要跟着改,保持两边一致。注意仓库路径里的版本号只决定“你会看到哪些可用版本”,真正安装哪个版本,还是要在下一步明确指定。
4.2 安装指定版本并锁定
直接装 latest 看起来省事,但对集群运维来说风险很大。控制平面节点和从节点的 kubeadm、kubelet 如果差了一个小版本,join 时可能因为配置结构不匹配报错。所以我的习惯是查一下当前可用的稳定版本,然后统一安装:
sudo apt-get install -y kubelet=1.28.2-1.1 kubeadm=1.28.2-1.1 kubectl=1.28.2-1.1版本号后面的-1.1是 Debian/Ubuntu 打包的修订号,不同仓库可能不一样,最好先用apt-cache policy kubeadm查看准确的候选版本再填。装完执行版本检查:
kubeadm version kubelet --version kubectl version --client三个输出应该都是 v1.28.2,至少也要在同一个次版本号范围内。
安装完之后,立刻用apt-mark hold锁住这三个包:
sudo apt-mark hold kubelet kubeadm kubectl这样系统在自动更新时不会把这三个组件升级到其它版本。Kubernetes 集群和正常软件不一样,它不是越新越好,它讲究的是跟控制平面保持一致。我在多个环境里见过因为 kubelet 被自动升级、而控制平面还是旧版本导致的节点失联,排查起来特别头疼。
4.3 为什么不建议在从节点上反复执行 kubectl 命令
既然安装了 kubectl,很多人会忍不住在从节点上执行kubectl get nodes,但这时通常还没有拿到控制平面的 kubeconfig。别急,这个很正常。kubectl 要靠/etc/kubernetes/admin.conf或你自己维护的 kubeconfig 去连接 API Server。从节点上用 kubectl 有两种场景:
一种是在kubeadm join之后,把控制平面的 kubeconfig 拷贝到从节点,然后用它查看集群状态。这个操作没问题,方便是挺方便的。另一种是作为普通运维入口,在任意一台机器上配置好 kubeconfig 之后,远程指挥集群。这个其实更常见,相当于 kubectl 是个瘦客户端。
所以从节点的 kubectl 安装并不是集群工作的必需项,但它确实能让排错变得更顺手。比如你用kubectl get nodes -o wide一看节点状态,就知道 kubelet 是不是真的注册成功了。
4.4 启动 kubelet 但不要期望它立刻工作
安装完 kubelet 之后,需要先把服务设为开机自启:
sudo systemctl enable --now kubelet这时候 kubelet 其实会尝试启动,但因为没有集群配置,它会一直处于一种“等待 join”的状态。这不是故障。我用一句话概括:在 kubeadm join 之前,kubelet 只是空转,它没有 API Server 地址、没有 token、也没有 CA 证书;在 join 之后,kubeadm 会把配置写进去,这个进程才会真正进入工作状态。
很多新手看到这里会慌:systemctl status kubelet显示 dead 或者 failed,就以为装错了。实际上,安装完直接systemctl enable kubelet但不执行 join,这个状态是正常的。等到第四节执行 join 之后,kubelet 才会被真正拉起。如果真的不放心,可以看一眼日志:journalctl -u kubelet -f,里面大概率会反复提示缺少 bootstrap 配置,那就说明我们还没走到 join 那一步。
5. kubeadm join:token、证书哈希及完整的排错链路
到此,Docker、kubeadm、kubelet、kubectl 都装完,系统准备和 cgroup 驱动也处理好了。这一步就是把节点真正加入集群。如果你在控制平面初始化完成时,已经保存了kubeadm init输出的 join 命令,那直接复制过来用就行。如果你把它弄丢了,也不慌,可以在控制平面节点上重新生成一个新的 join 凭证。
5.1 在控制平面生成新的 token 和证书哈希
我用最简单的方式来说明:控制平面节点上执行:
kubeadm token create --print-join-command这条命令会输出类似这样的内容:
kubeadm join 192.168.1.10:6443 --token abcdef.0123456789abcdef --discovery-token-ca-cert-hash sha256:...复制整条命令,到从节点上执行。这里面的 token 有有效期,默认是 24 小时。如果超过时间,重新执行kubeadm token create就好。
如果集群重启过,或者 CA 证书发生过变更,有可能需要同时检查证书哈希。更稳妥的写法是:
kubeadm token create --print-join-command --ttl 2h--ttl 2h表示这个 token 只保留两小时。新加的节点比较少的时候,设置一个短暂的有效期可以有效降低 token 泄露带来的风险。
5.2 执行 join 并从节点侧验证
在从节点上拿到 join 命令后,直接执行。这里要求从节点可以正常访问控制平面的 API Server 地址和端口。如果中间有防火墙或安全组,需要确认 6443 端口是放开的,否则 join 会一直卡在连接超时。
执行完成后,较新版本 kubeadm 会在最后打印类似This node has joined the cluster的提示。验证是否真正加入,可以回到控制平面节点上执行:
kubectl get nodes如果从节点状态是NotReady,先不要急着把锅甩给网络。先检查 kubelet 日志:
journalctl -u kubelet -f最典型的问题,就是我前面反复强调的 cgroup 驱动不一致。日志里会出现failed to run Kubelet: failed to validate kubelet flags或者与cgroup driver相关的错误。这时候去检查/var/lib/kubelet/config.yaml里的cgroupDriver字段,同时检查 Docker 的实际驱动。
有次我在现场排查一个节点反复 NotReady 的问题,现象很奇怪:Docker 明明已经配置了 systemd 驱动,docker info也显示正确,但 kubelet 日志就是报 cgroup 错误。最后发现是 kubeadm join 时自动生成的 kubelet 配置覆盖了手动预设的参数,把 cgroupDriver 又写成了空值。处理办法是编辑 kubelet 的配置,显式补上cgroupDriver: systemd,然后重启 kubelet。这是一个典型的、容易反复踩的坑。
5.3 常见的 join 失败分支及快速定位
我做了一个简单的分类梳理,遇到 join 失败时可以按表排查:
| 现象 | 大概率原因 | 查看位置 |
|---|---|---|
| 连接 API Server 超时 | 防火墙/安全组阻断了 6443 | 控制平面kubectl get nodes、从节点ping控制平面 IP |
| token 无效或过期 | token 超过有效期 | 控制平面重新kubeadm token create |
| CA 证书哈希不匹配 | 控制平面升级过或证书轮换 | 重新获取--discovery-token-ca-cert-hash |
| kubelet 反复重启 | cgroup 驱动不一致、swap 未关 | journalctl -u kubelet |
| 节点已存在但状态异常 | 之前的 join 残留数据 | /etc/kubernetes、/var/lib/kubelet清理后重新 join |
| Pod 网络无法通信 | CNI 插件未安装 / 网段冲突 | 控制平面的kubectl get pods -A |
最容易被忽视的是“残留数据”这一项。如果一个节点第一次 join 失败,之后你重新执行 join,但它的/etc/kubernetes和/var/lib/kubelet已经写入了旧的证书或配置,新数据可能覆盖不干净。遇到这种情况,正确操作是把相关目录清理掉再重新 join。我用下面这条命令做过很多次:
sudo kubeadm reset -f sudo rm -rf /etc/kubernetes /var/lib/kubelet /var/lib/docker /etc/cni/net.d注意最后两个目录的清理要看实际情况,如果你在节点上还跑着其它容器应用,或者节点后续要复用,就不能随随便便全删。至少要保留对系统本身无影响的目录。
5.4 节点加入后立即要做的事
节点状态变为 Ready 之后,工作其实还没有完全结束。第一件事是确认节点上的角色标签。如果这是纯从节点,通常角色是<none>或worker,如果你希望它在拓扑里被规划成工作节点,可以手动打 label:
kubectl label node <node-name> node-role.kubernetes.io/worker=第二件事是确认 Pod 网络是否正常。新加入的节点上,CNI 插件会自动为这个节点创建网络命名空间。如果集群里用的 Flannel 或 Calico,节点加入后会自动同步。一旦发现节点虽然是 Ready 但内部 Pod 之间互相 ping 不通,优先检查 CNI 的 DaemonSet 有没有在这个节点上正常调度。
第三件事是及时观察资源压力。用kubectl top node看 CPU 和内存占用,确认 kubelet 的驱逐阈值没有被触发。如果节点内存已经满负荷,Kubernetes 可能因为安全原因把节点标成 MemoryPressure 或 DiskPressure,导致新 Pod 无法调度上去。这些都是 join 成功之后的“次生问题”,但比 join 失败更隐蔽。
6. 从节点重复加入的清理套路:reset 之后如何恢复洁净状态
在实际运维中,一个节点可能会因为硬件更换、IP 变化、或者集群重建,需要反复加入多次。如果你只是顺利 join 了一次,这个章节可以当作备用知识收藏。但如果你遇到节点反复重新拉起的场景,这一节能帮你省下大量时间。
6.1 哪些数据会被写入节点
kubeadm join 会在节点上写入如下内容:/etc/kubernetes/kubelet.conf,这是 kubelet 对自己身份和 API Server 的认证配置;/etc/kubernetes/bootstrap-kubelet.conf,这是引导阶段的临时配置;/etc/kubernetes/pki下的部分证书;/var/lib/kubelet下的运行时数据、volume plugin 数据、Pod 状态数据;/etc/cni/net.d下的网络插件配置。
如果只是 token 过期,其实不需要清理所有文件。kubeadm join 本身支持重新换一个 token 再执行。但如果你发现节点加入后状态错乱,比如新旧证书混在一起、Pod CIDR 冲突、kubelet 报身份认证失败,那说明集群给这个节点的旧配置已经失效了。这种情况下,彻底 reset 是更高效的做法。
6.2 手动清理的边界
kubeadm reset是个好命令,但它并不会清掉所有东西。它负责的部分是把 kubelet、containerd/docker 对这些目录的引用关系松开,然后停止相关的 systemd 服务。但它不会删除/etc/kubernetes下所有目录,也不会自动清空/var/lib/docker。所以在我们确实要重新加入集群时,通常执行:
sudo kubeadm reset -f sudo systemctl stop kubelet sudo rm -rf /etc/kubernetes sudo rm -rf /var/lib/kubelet sudo rm -rf /var/lib/docker sudo rm -rf /var/lib/containerd sudo rm -rf /etc/cni/net.d执行完之后,检查ip link看有没有遗留的 CNI 虚拟网卡,比如cni0、flannel.1或calico相关的接口。如果有,直接删除对应的虚拟网络接口。不然新旧网络配置叠加在一起,会导致 Pod 之间出现路由黑洞。
6.3 重新加入时版本不匹配的回归问题
清理完之后重新 join,如果控制平面已经升级了版本,而节点上的 kubelet 还是旧版本,join 依然会失败。报错可能出现在 API Server 拒绝 kubelet 的 TLS 握手,也可能出现在 kubelet 解析集群配置时字段不认识。
所以“先升级节点组件版本,再执行 join”是比较稳妥的顺序。操作其实很简单:解锁 kubelet/kubeadm/kubectl 的版本锁定,然后用 apt 安装指定版本,再重新锁定:
sudo apt-mark unhold kubelet kubeadm kubectl sudo apt-get update sudo apt-get install -y kubeadm=<new-version> kubelet=<new-version> kubectl=<new-version> sudo apt-mark hold kubelet kubeadm kubectl从 Load Balancer 或 HA 集群的视角看,节点短暂脱离集群再回来,只要控制平面还有其它可用节点,业务不会受到太大影响。但如果节点本身承载了有状态应用或单副本工作负载,建议先做排空:
kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data排空之后再做升级和重新 join。理论上讲,节点重新加入后会重新调度 Pod,但如果之前的数据因为清理目录而丢了,有状态服务的恢复会变成另外一个复杂的系统问题。
7. 我从多次节点部署里总结的几条实操经验
聊到这儿,整个流程已经把从节点安装 Docker、kubeadm、kubelet、kubectl 以及最终 join 集群的路径走完了。最后分享几条我自己的习惯,不一定都写在官方文档里,但确实能减少无效工单。
第一条,永远把版本号公差先对齐。控制平面是什么 Kubernetes 版本,从节点就装什么版本。哪怕从节点晚装一个月,也不要用“最新稳定版”的思维去装。Kubernetes 官方的版本支持策略是,小版本之间的差异主要靠 API 兼容性兜底,但 kubelet 和 kubeadm 这种本地组件一旦出现字段不匹配,报错信息往往又长又晦涩,排查成本非常高。先固定版本,再考虑更新,集群稳定性会好很多。
第二条,一定要在生产环境里验证 swap 关闭和 cgroup 驱动。“测试环境没问题、生产环境炸了”的大部分原因,都集中在宿主机默认配置不同。云厂商给的基础镜像,有的默认开启 swap,有的默认 cgroup 驱动是 cgroupfs。我的做法是在加入集群前把这两项写成检查脚本,任何一个不满足条件就拒绝往下执行。可以简单用 shell 写一个判断:
if [ "$(docker info 2>/dev/null | grep 'Cgroup Driver' | awk '{print $3}')" != "systemd" ]; then echo "cgroup driver mismatch" exit 1 fi虽然写脚本有点原始,但它能挡住相当一部分低级错误。
第三条,join 后不要急着部署业务负载。先让节点自己跑几分钟,观察 kubelet 是否稳定、CNI 是否正常、日志里有没有异常堆栈。我见过不少案例,开发同学一看到节点 Ready 就立刻把 Pod 调度上去,结果 kubelet 其实还在反复重试,Pod 一下被创建,一下又被驱逐,状态来回跳。新节点的“养熟”过程,通常需要让系统组件先把网络、证书、运行时配置稳定下来。
第四条,如果你在一个多云或混合云环境里,节点间的时间同步也要纳入检查。kubelet 和 API Server 之间有证书校验和心跳机制,如果节点时间偏离太多,TLS 握手会失败,Node 状态会显示 NotReady。这不算 Kubernetes 本身的问题,但确实是我在跨地域集群里见过多次的坑。
这一整套流程讲下来,你会发现从节点安装本身不复杂,真正决定集群稳定性的,是对版本、cgroup、网络、交换分区的理解和统一管理。把每一步背后的“为什么”想清楚,再回去看那些报错日志,你会觉得 Kubernetes 的报错并没有想象中那么不可理喻。