在 VMware Workstation Pro 上搭建一套三节点 Kubernetes 1.29.14 集群,是不少开发者在学习 Kubernetes 调度、网络插件、节点状态管理和故障恢复时最先接触的完整实验环境。单机 Minikube 虽然启动快,但把节点注册、Pod 调度、跨节点网络、kubelet 心跳和镜像拉取这些环节都简化了;而三节点集群能真实暴露这些组件之间的依赖关系。本文围绕一个明确目标展开:在 VMware 虚拟机中准备三台 Linux 节点,安装 containerd、kubeadm、kubelet、kubectl,初始化一个版本为 1.29.14 的 Kubernetes 集群,并部署 nginx 验证集群可用。文章会覆盖网段规划、VMware 网络选型、系统初始化、组件安装、集群初始化、验证和常见问题排查。
1. 先确定集群形态:三节点、版本和网络模型
如果一上来就在三台虚拟机里执行安装命令,后续很容易在节点初始化阶段卡住。常见原因并不是命令写错,而是集群形态没有提前定下来。这里说的集群形态包括节点角色、节点数量、操作系统、Kubernetes 版本、容器运行时和网络插件。这些决策会直接体现在 kubeadm init 的参数、防火墙放通范围和 CNI 配置中,改动成本高,先想清楚更划算。
1.1 三节点集群能验证哪些单机环境验证不了的事情
单节点集群或 Kind 这类轻量环境更适合验证 Kubernetes API 用法,而不是验证集群行为。三节点集群至少有四个价值是单机环境难以替代的。
第一,节点注册。Worker 节点使用 kubelet 向控制平面注册自己,这个过程涉及证书、令牌、API Server 地址和 kubelet 启动参数。第二,跨节点调度。Pod 会被调度到不同节点上,需要检查资源分配、节点亲和性和污点容忍。第三,CNI 网络。Pod 网段的跨主机通信、路由规则和容器网络接口都由网络插件负责,单机环境很难看到真实的路由表变化。第四,节点故障恢复。把某个 Worker 节点关机后,控制平面如何把该节点上的 Pod 标记为 Terminating 或重新调度,需要多节点才能观察。
在 VMware 虚拟机里做这件事,比在云主机上做更可控。虚拟机的网络隔离、快照回滚、多台同时启动和模拟断电都比较方便,适合反复练习。
1.2 Kubernetes 1.29.14 的版本语义
Kubernetes 的版本号分为三段:主版本、次版本和补丁版本。1.29.14 表示主版本 1、次版本 29、补丁版本 14。次版本决定功能范围,例如 1.29 版本包含哪些新增特性和 API 变更;补丁版本主要包含 bug 修复和安全修复,一般不引入新的功能。
学习或生产环境都应该避免直接安装“最新版本”,而是在一个次版本中选择一个被验证过的补丁版本。这样做的原因有三个:一是组件版本能够统一锁定,kubeadm、kubelet、kubectl 三个组件必须与集群版本一致,否则很容易出现 API 版本不对齐的问题;二是后续升级时可以从 1.29.14 升级到 1.30 系列,路径更明确;三是排错时网上资料和官方文档都能对应上具体版本。
这里要特别强调:安装时使用的 Kubernetes 版本不是随便填写的。kubeadm init 的 --kubernetes-version 参数需要与软件仓库里实际存在的版本一致。本文示例使用 1.29.14,具体安装时要先通过 dnf list 或 apt 的可用列表确认该补丁版本存在,如果仓库已经更新,也可以选择同一系列的最后一个补丁版本。不要直接写一个仓库里不存在的版本号。
1.3 网络模型和网段规划
Kubernetes 集群里有三套网络,需要提前规划清楚。
第一套是节点网络,也就是虚拟机在 VMware 中获取的 IP 地址,用于节点之间、控制平面与 kubelet 之间的通信。第二套是 Pod 网络,每个 Pod 会得到一个独立的 IP,这个 IP 来自 Pod 网段,由 CNI 插件负责分配和路由。第三套是 Service 网络,Service 的 ClusterIP 来自 Service 网段,由 kube-proxy 和 iptables 或 IPVS 实现转发。
本文示例采用 VMware NAT 网络,节点网络使用 192.168.80.0/24 网段,Pod 网络使用 10.244.0.0/16,Service 网络使用 10.96.0.0/12。三个网段互相独立,不能在物理网络上重叠。
节点规划:master(控制平面节点)加上 node1、node2(工作节点)。如果只是验证基础功能,这一组三节点集群已经足够。更正式的 HA 集群需要至少三个控制平面节点和多个 Worker 节点,那是生产环境的事。
| 角色 | 主机名 | IP 地址 | 功能 |
|---|---|---|---|
| 控制平面 | master | 192.168.80.10 | kube-apiserver、etcd、kube-controller-manager、kube-scheduler、kubelet |
| 工作节点 | node1 | 192.168.80.11 | kubelet、kube-proxy、containerd、业务 Pod |
| 工作节点 | node2 | 192.168.80.12 | kubelet、kube-proxy、containerd、业务 Pod |
| 网络类型 | 网段 | 说明 |
|---|---|---|
| 节点网络 | 192.168.80.0/24 | VMware NAT 网段,网关通常为 192.168.80.2 |
| Pod 网络 | 10.244.0.0/16 | 与 Flannel 默认配置一致 |
| Service 网络 | 10.96.0.0/12 | kubeadm 默认 Service CIDR |
注意:NAT 模式下虚拟机可以共享宿主机网络出网,这是学习环境里拉取软件包最方便的方式。如果后续练习桥接模式,需要保证虚拟机 IP 与宿主机在同一网段,并且网络管理设备允许虚拟机访问宿主机以外的网络。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 在 VMware 中准备三台虚拟机
2.1 宿主机要求和 VMware Workstation Pro 环境
在创建虚拟机之前,先确认宿主机资源足够。三台虚拟机如果每台分配 2 核 CPU、4GB 内存、40GB 磁盘,宿主机至少要 16GB 内存,CPU 核心数不低于 6 核。如果每台虚拟机要跑构建任务、数据库或多个中间件,建议每台虚拟机提升到 4 核 6GB 或 4 核 8GB,宿主机内存相应提升到 32GB。
这里有一个容易被忽略的问题:虚拟机占用的 CPU 和内存是“同时占用”的。三台虚拟机一起启动,意味着内存几乎全部被预占,而不是按需分配。swap 在宿主机层面不能替代物理内存,否则集群运行会非常慢。
| 资源项 | 最低配置 | 建议配置 |
|---|---|---|
| 宿主机 CPU | 6 核 | 8 核以上 |
| 宿主机内存 | 16GB | 32GB |
| 虚拟机磁盘 | 每台 20GB | 每台 40GB |
| 虚拟机 CPU | 每台 2 核 | 每台 4 核 |
| 虚拟机内存 | 每台 3GB | 每台 6GB |
VMware Workstation Pro 17 的版本可以直接使用,也可以使用更新的兼容版本。对个人学习场景来说,VMware 提供了免费许可,确认许可后创建虚拟机即可。如果在 Windows 上安装后出现“无法启用虚拟机平台”或启动虚拟机蓝屏,通常是 Hyper-V、Windows 虚拟机监控程序平台与 VMware 的虚拟化功能冲突。处理前先确认本机是否需要 Hyper-V,再到“启用或关闭 Windows 功能”中调整相应组件,调整后必须重启。
2.2 创建第一台虚拟机并安装 Linux
这里采用一个省力策略:先完整安装一台 Linux 虚拟机,打好快照,再克隆出另外两台。这样能避免三台虚拟机分别安装系统时出现的网络配置差异。
创建虚拟机时需要注意以下几个选项。
网络适配器选择 NAT 模式。这是学习环境里最省事的选择。NAT 模式下虚拟机通过宿主机访问外网,并且共用一个由 VMware 生成的 NAT 网段。监听这个网段时,网关通常是 192.168.x.2,DNS 通常也是 192.168.x.2。如果想确认 NAT 网段和网关地址,打开 VMware 的“虚拟网络编辑器”,选择 VMnet8 查看子网和 NAT 设置。
磁盘大小可以根据镜像拉取需求调整。Kubernetes 系统组件镜像和后续测试镜像会占用几个 GB,如果还想在集群里部署 Kafka、Redis 等中间件,磁盘建议预留更多。创建磁盘时选择“将虚拟磁盘存储为单个文件”,对克隆和快照更友好,定位问题也更简单。
内存建议先分配 4GB 或 6GB,CPU 分配 2 核或 4 核。安装完成后如果资源不够,可以关机后再调整。
操作系统镜像选择 Linux x86_64 版本。本文示例以 Rocky Linux 9.x 为主,命令使用 dnf 包管理器。使用 Ubuntu Server 22.04 也可以,但软件包命令需要替换成 apt,容器运行时和 Kubernetes 组件的安装方式会有差异。
安装系统时有一个细节:如果安装程序允许选择交换分区,可以创建 2GB 左右的 swap;不创建也可以。无论创建与否,安装完成后都要按 Kubernetes 的要求执行 swapoff,并注释 /etc/fstab 中的 swap 行,否则 kubelet 会拒绝启动。
2.3 克隆出 Worker 节点
在 master 虚拟机正常安装完成并关机后,右键虚拟机标签,选择“管理-克隆”。克隆时选择“创建链接克隆”还是“创建完整克隆”都可以,学习环境建议创建完整克隆,避免链接克隆对母盘产生依赖。克隆出 node1 和 node2 后,再对 node1 做一次快照,后续所有 kubeadm init、CNI 安装、集群加入操作失败时,可以从快照回滚重新开始。
克隆后必须处理两个问题。第一,主机名完全相同,需要使用 hostnamectl 修改。第二,克隆生成的网卡在系统启动后可能沿用旧的连接配置,需要确认网卡名称和配置文件,避免两台机器 IP 冲突。
这里还有一个与系统服务相关的隐患:克隆出来的两台虚拟机如果和 master 使用相同磁盘分区,某些系统会因相同的 /etc/machine-id 导致 DHCP 或 systemd 行为异常。Rocky Linux 9 克隆后建议执行 dnf reinstall systemd 或主动重新生成 machine-id。不过由于本教程后续使用静态 IP,这个问题主要影响系统日志和部分服务识别,不阻塞集群创建。
注意:创建虚拟机的硬件配置不是越小越好。内存太小的控制平面节点在运行 etcd 和多个控制平面组件时可能频繁 OOM,反而比多分配内存更浪费时间。宁可给 master 多分一点内存,也不要让学习环境陷入排查资源问题的循环。
3. 三台节点统一执行系统初始化
系统初始化是整篇文章里最“机械”但最不能跳过的部分。三台节点都要执行相同的初始化脚本,只是主机名和 IP 不同。建议把这一步的检查点做完整,不要在一个节点上改了,另外两个节点没有同步。
3.1 设置主机名和 hosts 映射
在每台节点上分别执行:
bash复制hostnamectl set-hostname master
node1 和 node2 分别执行对应主机名:
bash复制hostnamectl set-hostname node1
hostnamectl set-hostname node2
然后编辑 /etc/hosts,把三台节点的 IP 和主机名写入。这样节点之间可以通过主机名互相解析,kubeadm 和后续组件访问 apiserver 时不会依赖 DNS。
bash复制cat >> /etc/hosts <<EOF
192.168.80.10 master
192.168.80.11 node1
192.168.80.12 node2
EOF
这里的 hosts 映射要在三台虚拟机上都配置。如果只配置 master,Worker 节点在 join 时就会面临主机名解析问题。
3.2 配置静态 IP
VMware NAT 模式下,DHCP 分配的 IP 可能会在虚拟机重启后变化,这对集群是非常致命的:kube-apiserver 的证书序列号、kubelet 注册地址、CNI 网络的四元组都和 IP 绑定。因此三台虚拟机都应使用静态 IP。
在 Rocky Linux 9 中,推荐使用 nmcli 修改连接配置。先查看当前网卡名称:
bash复制nmcli device status
假设网卡名为 ens33,master 节点执行:
bash复制nmcli connection modify ens33 ipv4.addresses 192.168.80.10/24
nmcli connection modify ens33 ipv4.gateway 192.168.80.2
nmcli connection modify ens33 ipv4.dns "192.168.80.2 223.5.5.5"
nmcli connection modify ens33 ipv4.method manual
nmcli connection up ens33
node1 和 node2 使用同样的命令,把 IP 分别改成 192.168.80.11 和 192.168.80.12。网关和 DNS 保持相同。修改完成后执行 ip addr show ens33 确认 IP 生效,再执行 ip route 确认默认路由指向网关。
这里的 192.168.80.2 是 VMware NAT 网段的默认网关。如果你创建的 NAT 网段不是 192.168.80.0/24,需要到 VMware 的“虚拟网络编辑器”里确认网段和网关地址。不同环境的网关可能不同,不要直接照抄。
注意:静态 IP、网关、主机名这三个值必须在三台虚拟机中一致地规划。集群排查时,很多“节点突然失联”的问题,根源都是某台虚拟机重启后 DHCP 给了不同 IP,而 kubelet 和 apiserver 仍按旧地址通信。
3.3 关闭防火墙、SELinux 和 swap
Kubernetes 组件之间需要访问大量端口,学习环境下最简洁的选择是直接关闭 firewalld。生产环境可以按端口规划放通,但这里先关掉。
bash复制systemctl disable --now firewalld
SELinux 在 Kubernetes 与网络插件配合时可能带来额外的限制。先临时设置并永久禁用:
bash复制setenforce 0
sed -i 's/^SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
swap 必须关闭。kubelet 默认要求在 swap 关闭的情况下工作,否则启动时会报 "Running with swap on is not supported" 并退出。关闭方法和持久化处理如下:
bash复制swapoff -a
sed -i '/ swap / s/^/#/' /etc/fstab
如果系统里没有 swap 分区,这两条命令不会报错,无需额外处理。
关闭防火墙和 SELinux 后,检查确认:
bash复制getenforce
systemctl is-enabled firewalld
swapon --show
期望输出分别是 Disabled、disabled,以及没有任何输出。
3.4 加载内核模块并调整 sysctl
Kubernetes 的网络插件需要 overlay 和 br_netfilter 两个内核模块。overlay 提供容器镜像分层存储的文件系统支持,br_netfilter 让经过 Linux 网桥的流量也能被 iptables 规则过滤,这是 kube-proxy 的 iptables 模式和 CNI 插件能够正确转发流量的前提。
创建模块加载配置:
bash复制cat > /etc/modules-load.d/k8s.conf <<EOF
overlay
br_netfilter
EOF
立即加载:
bash复制modprobe overlay
modprobe br_netfilter
创建 sysctl 配置:
bash复制cat > /etc/sysctl.d/k8s.conf <<EOF
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
应用配置:
bash复制sysctl --system
执行后确认关键参数:
bash复制sysctl net.bridge.bridge-nf-call-iptables net.ipv4.ip_forward
输出中两个值都应该为 1。
到这里,三台虚拟机的系统初始化已经完成。可以用下面这张清单在每台节点上做一次最终检查,再进入组件安装阶段。
| 检查项 | 目标状态 | 检查命令 |
|---|---|---|
| 主机名 | 分别为 master、node1、node2 | hostname |
| 静态 IP | 与规划一致 | ip addr show ens33 |
| 防火墙 | 已停用且禁用 | systemctl status firewalld |
| SELinux | Disabled | getenforce |
| swap | 已关闭 | swapon --show |
| 内核模块 | overlay、br_netfilter 已加载 | lsmod | grep br_netfilter |
| 转发参数 | net.ipv4.ip_forward=1 | sysctl net.ipv4.ip_forward |
4. 安装 containerd、kubeadm、kubelet、kubectl
系统初始化完成后,开始安装容器运行时和 Kubernetes 三件套。这里有三件事要理清楚:为什么选 containerd、如何配置 containerd、如何锁定 Kubernetes 组件版本。
4.1 为什么使用 containerd
Kubernetes 从 1.24 起不再内置 dockershim,想要继续使用 Docker 作为运行时,需要借助 cri-dockerd 适配器。更主流的方式是直接使用 containerd,因为 containerd 本身就是 CRI 兼容的容器运行时,kubelet 可以直接通过 CRI 协议调用它。
containerd 与 Docker 的关系需要澄清:Docker 本身使用 containerd 来管理容器生命周期,但在 Kubernetes 1.24 之后,Kubelet 不再与 Docker 通信,而是直接与 containerd 通信。学习环境选择 containerd 有三个好处:占用资源少、配置简单、与 Kubernetes 的 CRI 机制贴合紧密,也能避免额外维护适配层。
4.2 安装并配置 containerd
在 Rocky Linux 9 上,可以用 docker-ce 软件源安装 containerd。之所以选择这个源,是因为它提供的 containerd.io 包比较完整,升级路径也相对稳定。
bash复制dnf install -y yum-utils
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
dnf install -y containerd.io
安装完成后生成默认配置:
bash复制mkdir -p /etc/containerd
containerd config default > /etc/containerd/config.toml
这份默认配置里有几处需要修改。第一处是 SystemdCgroup,需要改为 true。Kubernetes 推荐让 containerd 使用 cgroup v2 和 systemd cgroup 驱动,这样 kubelet 与 containerd 的 cgroup 管理方式一致,节点资源统计和 Pod 内存限制才准确。第二处是 sandbox_image,也就是 pause 容器镜像地址。默认值指向 registry.k8s.io,如果你的网络环境无法直接访问该仓库,就需要改成可访问的公共镜像仓库。
修改方式可以直接编辑 /etc/containerd/config.toml。需要重点确认的段落如下:
toml复制[plugins."io.containerd.grpc.v1.cri"]
sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.9"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
修改后重启 containerd:
bash复制systemctl restart containerd
systemctl enable containerd
systemctl status containerd
验证 containerd 是否正常:
bash复制ctr version
这里的 pause 版本不是随意写的。不同 Kubernetes 小版本对应的 pause 镜像版本不同,可以用 kubeadm config images list --kubernetes-version=v1.29.14 在 master 节点上查看,然后再把 config.toml 里的 sandbox_image 改成对应版本。
4.3 配置 Kubernetes 软件源并锁定版本
Kubernetes 官方软件源位于 pkgs.k8s.io,不同大版本使用不同的源目录。1.29 系列的 yum 源如下:
bash复制cat > /etc/yum.repos.d/kubernetes.repo <<EOF
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.29/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.29/rpm/repodata/repomd.xml.key
EOF
注意:如果网络环境无法访问 pkgs.k8s.io,也可以使用可访问的公共 Kubernetes 软件源,但源目录必须对应 v1.29 系列。
先查看仓库里可用的版本:
bash复制dnf list kubeadm --showduplicates
确认存在 1.29.14 后,安装三件套:
bash复制dnf install -y kubeadm-1.29.14 kubelet-1.29.14 kubectl-1.29.14
系统安装后立即设置 kubelet 开机启动,但此时先不启动。因为还没有初始化集群,kubelet 启动后找不到 apiserver 和集群配置,日志会持续报错,但这种报错是暂时的,不应视为安装失败。
bash复制systemctl enable kubelet
4.4 三件套安装后的检查点
安装完成后,通过三个命令确认版本:
bash复制kubeadm version
kubelet --version
kubectl version --client
三个输出的版本号都应保持统一。这里的关键不是看版本号是否好看,而是保证这三个工具与后续 kubeadm init 的 --kubernetes-version 保持一致。
同时确认 containerd 和 kubelet 的状态:
| 组件 | 状态 | 检查命令 |
|---|---|---|
| containerd | running | systemctl is-active containerd |
| kubelet | enabled,尚未 active | systemctl is-enabled kubelet |
如果 containerd 没有运行,后续 kubeadm 在执行 preflight 检查时会直接失败。
5. 初始化控制平面并让 Worker 节点加入集群
5.1 kubeadm init 参数说明
所有系统初始化和组件安装完成后,只在 master 节点上执行 kubeadm init。kubeadm init 会完成一系列动作:生成证书、启动 etcd、生成 kubeconfig、启动 kube-apiserver、kube-controller-manager、kube-scheduler,并注册 master 节点。
在 master 上执行:
bash复制kubeadm init \
--apiserver-advertise-address=192.168.80.10 \
--control-plane-endpoint=192.168.80.10 \
--image-repository=registry.aliyuncs.com/google_containers \
--kubernetes-version=v1.29.14 \
--pod-network-cidr=10.244.0.0/16 \
--service-cidr=10.96.0.0/12
这里每个参数都有特定含义,不要盲目照抄。
| 参数 | 作用 | 说明 |
|---|---|---|
| --apiserver-advertise-address | 指定 apiserver 向外通告的 IP | 必须与 master 静态 IP 一致 |
| --control-plane-endpoint | 控制平面统一入口 | 单控制平面时可以填写 master IP |
| --image-repository | 镜像仓库地址 | 网络可达时保留默认即可 |
| --kubernetes-version | 集群版本 | 必须与安装的 kubeadm 版本一致 |
| --pod-network-cidr | Pod 网段 | 与后续 CNI 的默认网段保持一致 |
| --service-cidr | Service 网段 | 沿用 kubeadm 默认值时可不写,但建议显式声明 |
如果网络可以直连 registry.k8s.io,可以去掉 --image-repository 参数。如果你的网络环境无法直连默认仓库,再使用可访问的公共镜像仓库。镜像仓库地址不影响集群版本和功能,重点是 sandbox_image、pause 版本与 kubeadm 期望的一致。
kubeadm init 执行期间会先做 preflight 检查,可能暴露 swap 未关闭、端口占用、containerd 未运行等问题。建议把输出完整保留,遇到红色提示先解决再重试。
5.2 配置 kubectl
kubeadm init 成功后,最后一段输出会提示如何配置 kubectl。复制 kubeconfig 到当前用户目录:
bash复制mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config
验证:
bash复制kubectl get nodes
此时 master 可能显示 NotReady,这是正常的,因为还没有安装网络插件。但节点应显示为 master 角色,状态为 NotReady。
5.3 安装 Flannel 网络插件
这里选用 Flannel 作为学习环境的 CNI 插件,因为用法简单、资源占用低,也能满足跨节点 Pod 通信。下载并应用 Flannel 的资源配置:
bash复制kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
需要注意两个先决条件。第一,kubeadm init 时 --pod-network-cidr 必须为 10.244.0.0/16,因为 Flannel 配置中的默认 Network 就是这个网段。如果你改了 Pod 网段,就需要同步修改 kube-flannel.yml 里的 net-conf.json。第二,三台节点必须已经加载 br_netfilter 内核模块并设置转发参数,否则 Flannel 的 Pod 网络无法正常转发。
安装完成后,Flannel 会以 DaemonSet 形式在每个节点上运行一个 Pod。对 NAT 模式的 VMware 虚拟机来说,VXLAN 模式通常可以直接工作。
5.4 生成 join 命令并加入 Worker 节点
控制平面和网络插件就绪后,在 master 上生成 Worker 节点的加入命令:
bash复制kubeadm token create --print-join-command
输出类似:
bash复制kubeadm join 192.168.80.10:6443 --token xxxxxx --discovery-token-ca-cert-hash sha256:xxxxxx
分别在 node1 和 node2 上执行这段命令。执行完成后,回到 master 查看节点状态:
bash复制kubectl get nodes -o wide
如果一切正常,一两分钟内三个节点都会进入 Ready 状态。如果 node1 或 node2 长时间 NotReady,见后面的排查章节。
注意:join 令牌是有有效期的,默认 24 小时。如果过期,重新在 master 上执行
kubeadm token create即可。不要把 kubeadm init 输出中的旧 join 命令反复粘贴,那个命令的令牌可能已经过期。
6. 验证集群并部署 nginx 工作负载
集群初始化后,验证不能只看 kubectl get nodes 是否 Ready,还要确认系统组件、网络转发、工作负载调度和 Service 访问都可用。这一章给出一个最小验证链路。
6.1 检查系统组件状态
先看所有节点和系统组件:
bash复制kubectl get nodes -o wide
kubectl get pods -n kube-system -o wide
期望状态:
- 三个节点均 Ready,VERSION 一列显示 v1.29.14。
- kube-system 命名空间里的 coredns、flannel、kube-proxy 等 Pod 全部 Running。
如果 coredns 处于 Pending,通常是 Flannel 还没有把节点网络打通;如果 coredns 一直 CrashLoopBackOff,常见原因是 ip_forward 参数未开启或 CNI 网络冲突。
6.2 部署 nginx 验证调度
创建一个 nginx Deployment,副本数设为 3,验证 Pod 是否能被调度到不同节点:
bash复制kubectl create deployment nginx --image=nginx:1.27 --replicas=3
查看 Pod 分布:
bash复制kubectl get pods -o wide
可以看到三个副本分别落在三个节点上,IP 段为 10.244.x.x,说明 Pod 网络已经连通。
暴露 Service 并验证访问:
bash复制kubectl expose deployment nginx --port=80 --target-port=80 --type=NodePort
kubectl get svc nginx
输出的 PORT(S) 一列类似 80:30958/TCP,表示集群每个节点都会监听 30958 端口。在宿主机浏览器访问 http://192.168.80.10:30958 或任意节点 IP 加该端口,就能看到 nginx 欢迎页。
这里要解释一下 NodePort 类型:NodePort 会把 Service 端口映射到每个节点的指定端口上,数据经由 kube-proxy 转发到后端 Pod。对学习环境来说,NodePort 是验证集群对外访问最直接的方式。
6.3 验证跨节点通信和调度能力
为了确认 Pod 跨节点通信不是偶然,可以进入任意 Pod 去访问另一个节点上的 Pod IP:
bash复制kubectl exec -it <pod-name> -- curl <另一个pod-ip>
如果返回 HTTP 响应,说明 Flannel 的跨节点数据路径是通的。这一步虽然简单,但能有效排除“节点 Ready 但 Pod 网络不通”的情况。
最后做一个可重复的检查清单:
| 验证项 | 命令 | 期望结果 |
|---|---|---|
| 节点状态 | kubectl get nodes | 三个 Ready |
| 系统组件 | kubectl get pods -n kube-system | 均 Running |
| Pod 调度 | kubectl get pods -o wide | 副本分布在不同节点 |
| Service 访问 | kubectl get svc nginx | NodePort 可访问 |
| 跨节点网络 | kubectl exec pod -- curl 对端 Pod IP | HTTP 返回 |
7. 常见故障定位顺序:从节点状态到容器日志再到虚拟机层
集群搭建完成后,真正花时间的往往不是安装过程,而是故障定位。这里按出现频率整理几类问题,每类都按“现象、原因、检查方式、处理建议”的顺序展开。
7.1 节点状态一直是 NotReady
现象:kubectl get nodes 显示 node1 或 node2 一直是 NotReady。
排查顺序如下。先看节点事件:
bash复制kubectl describe node node1
再看 kubelet 日志:
bash复制journalctl -u kubelet -f --no-pager | tail -100
常见原因有三类。第一,网络插件未安装或 Flannel 的 DaemonSet 没有成功运行,此时节点上的 Pod 网络接口没建立。检查 kubectl get pods -n kube-system 里的 flannel Pod 状态。第二,kubelet 的 cgroup 驱动与 containerd 不一致,日志里出现 kubelet 与 CRI 的 cgroup 驱动不匹配,回到 containerd 配置确认 SystemdCgroup。第三,节点 IP 变化,kubelet 注册的旧 IP 和当前 IP 不一致,检查静态 IP 是否生效。
7.2 kubeadm init 拉取镜像超时
现象:kubeadm init 在 preflight 之后卡在拉取 pause、etcd 等镜像时超时。
处理方式有两个方向。第一,显式指定可访问的镜像仓库,也就是前面提到的 --image-repository。第二,在所有节点上预先拉取镜像:
bash复制kubeadm config images pull --image-repository=registry.aliyuncs.com/google_containers --kubernetes-version=v1.29.14
这里优先推荐预先执行 kubeadm config images pull,这样可以把镜像拉取问题从 init 过程中剥离出来。镜像全部拉取成功后再执行 init,排错范围会缩小很多。
7.3 coredns 持续 CrashLoopBackOff
现象:kube-system 里的 coredns 反复重启。
先查看日志:
bash复制kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50
如果是网络插件还没就绪,coredns 会反复失败。先确认 Flannel 是否 Running。如果 Flannel 正常,再检查:
bash复制sysctl net.ipv4.ip_forward
该值必须是 1。另外,如果之前使用过不同网段的 Pod CIDR 初始化集群,需要确认 Flannel 配置中的 Network 与 kubeadm init 的 --pod-network-cidr 一致。修改配置后应删除旧的 kube-flannel 再重新应用,必要时重建节点上的 cni0 接口。
7.4 虚拟机重启后集群失联
现象:所有虚拟机重启后,kubectl 无法连接 apiserver,节点之间也无法通信。
这一类问题有必然性,因为学习环境的虚拟机重启后存在多个“状态丢失”点。按以下顺序检查。
第一,检查静态 IP 是否还生效,NAT 网关和 DNS 是否恢复。第二,重启 containerd 和 kubelet,因为系统服务和内核模块顺序可能导致网络接口重建后 kubelet 没有重新注册。第三,确认 swap 仍然是关闭状态,如果 /etc/fstab 注释不彻底,重启后 swap 会重新挂载,kubelet 会拒绝工作。
临时状态下,恢复集群的顺序建议是:
bash复制systemctl restart containerd
systemctl restart kubelet
然后等待几分钟,再用 kubectl get nodes 观察。如果想避免每次重启都手动恢复,可以把 containerd 和 kubelet 都设置为开机启动,并确认 /etc/fstab 中的 swap 注释生效。
7.5 VMware 层相关的虚拟化问题
现象:启动虚拟机时提示“无法启用虚拟机平台”,或虚拟机启动后宿主机蓝屏,也可能出现 VMware Workstation 无法连接到虚拟机的提示。
这通常是 VMware Workstation 与 Windows 的虚拟化平台组件冲突。可能的处理方向包括:
- 确认本机是否同时使用 WSL 2、Hyper-V、Windows 沙盒等依赖 Hypervisor 的功能。
- 如果不需要这些功能,在“启用或关闭 Windows 功能”中关闭“Hyper-V”和“虚拟机监控程序平台”,然后重启。
- 如果 Workstation 提示“无法连接到虚拟机”,先确认虚拟机是否处于挂起状态,检查网络适配器是否已连接,区分是虚拟机服务未启动还是集群网络故障。
这类问题发生在 VMware 层,不是 Kubernetes 配置错误,排查时要先分清楚故障发生在哪一层。在宿主机事件查看器中查找关于虚拟化功能的警告,比反复重装集群更有效。
8. 从学习环境走向生产环境
到这里,基于 VMware 的三节点 Kubernetes 1.29.14 集群已经可以运行。但必须清楚:这套环境是学习环境,不是生产环境。它适合验证调度、网络、工作负载和中间件部署,却不具备生产集群需要的多数保障机制。
8.1 学习环境与生产环境的差距
| 维度 | 学习环境(本文环境) | 生产环境 |
|---|---|---|
| 控制平面 | 单 master | 三个或更多控制平面节点 |
| 节点来源 | 虚拟机克隆 | 物理机、云主机、集群管理工具 |
| 网络 | NAT 模式 | 专有网络、负载均衡、网络隔离 |
| 存储 | 本地磁盘 | 持久化存储、动态存储类 |
| 监控 | 无 | Prometheus、告警、日志中心 |
| 备份 | 快照回滚 | etcd 备份、证书备份、应用备份 |
| 认证授权 | 单管理员 kubeconfig | RBAC、审计、多租户 |
| 升级 | 可随时重装 | 滚动升级、回滚策略 |
8.2 生产环境还要补齐的四个方向
第一,把控制平面做成高可用。单点 master 的 etcd 和 apiserver 一旦故障,整个集群不可用。生产环境需要配置多个控制平面节点,并让 Worker 节点通过负载均衡地址访问 apiserver。
第二,选择更完整的网络策略。Flannel 擅长提供基本连通性,但 NetworkPolicy 能力有限。生产环境如果需要网络隔离、策略控制和更复杂的路由,可以评估 Calico 或其他方案。换成 Calico 时,注意修改 CALICO_IPV4POOL_CIDR,使其与 kubeadm init 的 Pod 网段一致。
第三,补齐持久化存储。本文环境里 Pod 删除后数据随之丢失,因为 Pod 使用的是临时存储。生产环境要为有状态工作负载配置持久卷声明和存储类,例如 NFS、Ceph 或云厂商存储。
第四,建立监控和备份机制。至少要对 Node 和 Pod 做资源监控,对 etcd 做定期备份。etcd 是集群状态的唯一权威数据源,生产环境升级或大范围变更前必须验证备份可恢复。
8.3 在这套集群上还可以做哪些练习
三节点集群已经具备继续深入的条件。可以在集群上部署 Redis 集群、Kafka 集群或 Spring Cloud Sentinel 的持久化存储场景,验证有状态应用的调度、配置和故障转移。也可以练习节点隔离、Pod 驱逐、资源配额和 RBAC,把调度器、控制器和审计日志的行为都观察一遍。
每一次练习都遵循同一套顺序:先明确要验证的现象,再准备数据和配置,然后执行变更,最后用 kubectl get、日志和访问结果确认。遇到问题时,不从最底层的系统开始怀疑,先检查输入、路径、版本和日志,这是排查 Kubernetes 问题最有效的方式。
在这套 VMware 三节点集群上跑通一次完整的初始化、部署、排查和恢复流程后,继续学习高可用集群、网络策略和持续交付就有了基础。下一步通常建议做两件事:给这一套环境打一个干净的快照,然后带着问题去重新搭建一遍,尝试不依赖文档把集群恢复到 Ready 状态。
