在学习和使用 Kubernetes 的过程中,很多人的第一道坎不是概念理解,而是“没有一套真实的多节点集群环境”。手边只有一台 Windows 笔记本,用 Minikube 又觉得不够接近生产环境,买三台云服务器对个人来说成本偏高。基于 VMware 虚拟机搭建三节点 Kubernetes 集群是目前性价比最高、也最接近真实服务器操作路径的方案。
本文将以 Kubernetes 1.29.14 为例,从 VMware 创建虚拟机开始,完成 Linux 系统安装、基础环境初始化、containerd 容器运行时配置、kubeadm 初始化控制平面、Worker 节点加入集群、部署测试应用这一整条链路,最后整理高频报错的排查思路和工程化建议。整个流程不需要额外购买云资源,一台 16GB 内存的宿主机就能跑起来。
1. 为什么选择 VMware + 三节点 Kubernetes
1.1 三节点集群能做什么
三节点集群是 Kubernetes 里最常见的“最小生产拓扑”,由 1 个控制平面节点和 2 个工作节点组成。
控制平面节点运行 kube-apiserver、kube-controller-manager、kube-scheduler 和 etcd 等核心组件,负责集群状态管理、资源调度和 API 请求处理。工作节点运行 kubelet、kube-proxy 和容器运行时,真正承载业务 Pod。
单机方案无法模拟的问题,比如 Pod 调度到不同节点、节点故障后 Pod 重建、多节点网络互通、NodePort 访问等,在三节点集群里都能真实复现。这些都是面试和实际工作中绕不开的场景。
1.2 与 Minikube、kind 的定位差异
Minikube 适合在本地快速启动一个单节点集群,用来跑通 kubectl 命令和简单实验,但它很难模拟多节点的网络模型和调度行为。kind 则是把 Kubernetes 跑在 Docker 容器里,适合 CI 场景,和物理服务器部署差别较大。
VMware 方案里,每一台虚拟机都是独立的操作系统实例,有独立的 systemd、独立的网络栈。你在上面的操作与真实服务器几乎一致,将来把集群迁移到物理机或云服务器时,部署流程和经验可以直接复用。
1.3 版本号 1.29.14 的含义
Kubernetes 的版本号由主版本、次版本、补丁版本组成。1.29.14 表示 1.29 这个稳定版本线中的第 14 个补丁版本,主要包含 bugfix 和安全更新。
在部署时,kubelet、kubeadm、kubectl 这三个组件必须保持版本一致,否则可能出现 API 兼容问题或节点注册失败。这也是为什么后面安装时要把版本号固定为 1.29.14。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与整体规划
2.1 宿主机与 VMware 版本
宿主机建议使用 16GB 内存,最低不要低于 12GB。三台虚拟机各分配 4GB 内存,虚拟机开机后宿主机还要保留 2-4GB 运行 VMware 和操作系统。
VMware Workstation Pro 17.x 是当前常用版本,如果你只有 VMware Workstation Player,操作流程基本一致。宿主机需要打开 BIOS 中的 Intel VT-x 或 AMD-V 虚拟化功能,否则创建虚拟机时会提示无法启动或性能异常。
磁盘空间建议预留 150GB 左右,每台虚拟机分配 40GB,还需要给系统镜像、Kubernetes 组件日志和容器镜像留出余量。
2.2 节点规划
| 节点角色 | 主机名 | CPU | 内存 | 磁盘 | 操作系统 |
|---|---|---|---|---|---|
| 控制平面 | k8s-master | 2 核 | 4GB | 40GB | Rocky Linux 9.x |
| 工作节点 | k8s-node1 | 2 核 | 4GB | 40GB | Rocky Linux 9.x |
| 工作节点 | k8s-node2 | 2 核 | 4GB | 40GB | Rocky Linux 9.x |
操作系统使用 Rocky Linux 9 作为示例。CentOS 7 已经停止维护,不建议新环境使用。如果你熟悉其他发行版,比如 Ubuntu 22.04,部署步骤大同小异,只是包管理命令从 dnf 换成 apt。
2.3 网络规划
虚拟机网络选择 VMware 的 NAT 模式。NAT 模式下虚拟机通过宿主机访问外部网络,即使宿主机切换 Wi-Fi,虚拟机之间依然可以稳定通信,适合学习环境。
规划以下固定 IP:
| 节点 | IP 地址 |
|---|---|
| k8s-master | 192.168.x.10 |
| k8s-node1 | 192.168.x.11 |
| k8s-node2 | 192.168.x.12 |
x 是你 VMware NAT 网段中的具体值,可以在 VMware 的“虚拟网络编辑器”里查看,不要照抄 192.168.x.x 的前三段。
同时规划 Pod 网段为 10.244.0.0/16,Service 网段为 10.96.0.0/12。Pod 网段必须和宿主机虚拟机网段错开,否则后面网络插件会路由混乱。
3. 在 VMware 中创建虚拟机
3.1 创建第一台虚拟机
打开 VMware Workstation,选择“创建新的虚拟机”,选择“自定义”安装方式。下载 Rocky Linux 9 的 Minimal ISO 镜像作为安装源,分配 2 核 CPU、4GB 内存、40GB 磁盘。
网络类型选择 NAT。这里不建议选桥接模式,因为桥接模式在宿主机网络变化时容易导致虚拟机 IP 漂移,而 NAT 模式从 VMware 层面管理了网络,稳定性更好。
硬盘分配选择默认的“将虚拟磁盘拆分成多个文件”,不要选择“立即分配所有磁盘空间”,否则创建速度慢且浪费空间。
3.2 安装系统并配置静态 IP
安装过程中选择 Minimal 最小化安装,这样可以避免桌面环境占用过多内存。
安装完成后,先用 nmcli 配置静态 IP。Rocky Linux 9 默认使用 NetworkManager 管理网卡,网卡名一般是 ens33,可以通过 ip addr 确认。
bash复制# 将 ens33 的 IP 地址、网关、DNS 写入网卡配置
nmcli con mod ens33 \
ipv4.addresses 192.168.x.10/24 \
ipv4.gateway 192.168.x.2 \
ipv4.dns "8.8.8.8 223.5.5.5" \
ipv4.method manual
# 重启网卡使配置生效
nmcli con up ens33
# 查看配置结果
ip addr show ens33
这里网关通常是 VMware NAT 网段的 .2 地址,具体以“虚拟网络编辑器”里的 NAT 设置为准。DNS 可以使用公共 DNS,企业内网则换成自己的 DNS 服务器。
安装完系统后,建议安装 open-vm-tools,方便后续复制命令和调整虚拟机分辨率:
bash复制dnf install -y open-vm-tools
3.3 克隆两个 Worker 节点
第一台 Master 节点配置完成后,不需要重新装两台系统,直接在 VMware 中右键虚拟机,选择“管理 -> 克隆”,创建两个完整克隆。
这里有一个关键点:要选择“创建完整克隆”,而不是“创建链接克隆”。链接克隆会依赖父虚拟机磁盘,后面如果父虚拟机改动或损坏,子克隆也可能出问题。
克隆完成后,需要修改三处配置:
bash复制# 1. 修改主机名
hostnamectl set-hostname k8s-node1
# 2. 修改静态 IP 地址
nmcli con mod ens33 ipv4.addresses 192.168.x.11/24
nmcli con up ens33
# 3. 克隆后的 machine-id 是一样的,建议重新生成
rm -f /etc/machine-id
systemd-machine-id-setup
不要漏掉 machine-id。克隆虚拟机后 machine-id 相同,可能导致 systemd 一些依赖 machine-id 的服务异常。
4. 集群基础环境初始化
4.1 统一主机名、Hosts 和时间同步
三台节点的主机名分别设置为 k8s-master、k8s-node1、k8s-node2,然后在三台节点的 /etc/hosts 中写入相同内容,保证节点之间可以通过主机名互相访问:
bash复制cat <<EOF >> /etc/hosts
192.168.x.10 k8s-master
192.168.x.11 k8s-node1
192.168.x.12 k8s-node2
EOF
时间同步对 Kubernetes 集群非常重要,特别是 etcd 对时钟偏差非常敏感。使用 chrony 进行时间同步:
bash复制dnf install -y chrony
systemctl enable --now chronyd
chronyc sources
chronyc sources 输出中如果出现 ^* 前缀,说明已经同步到时间源。
4.2 关闭防火墙、SELinux 和 swap
kubeadm 初始化过程中,控制平面和 kubelet 之间需要互相访问多个端口。实验环境最简单的方式是关闭防火墙,生产环境则应该按官方文档精确放行端口。
bash复制systemctl stop firewalld
systemctl disable firewalld
setenforce 0
sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config
swapoff -a
sed -i '/ swap / s/^/#/' /etc/fstab
SELinux 从 enforcing 改为 permissive 而不是 disabled,是为了保留 SELinux 的安全机制,同时避免 kubelet 和容器运行时的权限问题。
swapoff 是必须的操作,Kubernetes 从设计上不支持在 swap 开启的状态下稳定运行调度。修改 /etc/fstab 是为了防止重启后 swap 自动挂载。
4.3 配置内核参数与模块加载
Kubernetes 要求网络桥接流量经过 iptables,这需要 br_netfilter 模块。同时还需要 overlay 模块支持容器层文件系统。
bash复制cat <<EOF | tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
modprobe overlay
modprobe br_netfilter
cat <<EOF | 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
sysctl --system
net.ipv4.ip_forward 开启后,Pod 之间的请求才能被正确转发。
4.4 安装并配置 containerd
kubeadm 支持 containerd、CRI-O 和 Docker 等多种容器运行时,目前官方默认推荐 containerd。它轻量、稳定,也是 Kubernetes 社区最常用的运行时。
安装 containerd 有多种方式,可以使用 Docker 官方仓库安装 containerd.io,也可以从 containerd 官方 Release 页面下载二进制包。以 Rocky Linux 为例,启用 Docker 仓库后安装:
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 | tee /etc/containerd/config.toml
Kubernetes 从 kubelet 到容器运行时使用 systemd 的 cgroup 驱动,containerd 默认配置里使用的是 cgroupfs,必须改为 systemd:
bash复制sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
启动 containerd:
bash复制systemctl enable --now containerd
ctr version
如果 ctr version 能正常输出版本号,说明 containerd 已经可以工作。后面还需要根据 kubeadm 需要的 pause 镜像版本调整沙箱镜像,这个放到下一节处理。
5. 安装 Kubernetes 组件并初始化控制平面
5.1 配置 YUM 源并安装 kubeadm、kubelet、kubectl
Kubernetes 官方提供 RPM 仓库,添加 1.29 版本的仓库:
bash复制cat <<EOF | tee /etc/yum.repos.d/kubernetes.repo
[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
安装指定版本,三个组件版本必须一致:
bash复制dnf install -y kubelet-1.29.14 kubeadm-1.29.14 kubectl-1.29.14
为了防止后面系统更新时误升级组件,可以加锁固定版本:
bash复制dnf install -y python3-dnf-plugin-versionlock
dnf versionlock kubelet kubeadm kubectl
配置 kubelet 的 cgroup 驱动为 systemd:
bash复制cat <<EOF | tee /etc/sysconfig/kubelet
KUBELET_EXTRA_ARGS=--cgroup-driver=systemd
EOF
然后设置开机自启。此时 kubelet 启动会报错,因为没有集群配置,这是正常的,等 kubeadm init 之后就会恢复正常:
bash复制systemctl enable kubelet
5.2 使用 kubeadm init 初始化 Master
在 Master 节点上执行 kubeadm init 之前,可以先确认需要拉取哪些镜像。如果直连默认镜像仓库网络不稳定,国内环境可以使用公共镜像仓库:
bash复制kubeadm config images list --image-repository=registry.aliyuncs.com/google_containers
确认无误后执行初始化命令:
bash复制kubeadm init \
--kubernetes-version=v1.29.14 \
--control-plane-endpoint=k8s-master \
--apiserver-advertise-address=192.168.x.10 \
--pod-network-cidr=10.244.0.0/16 \
--service-cidr=10.96.0.0/12 \
--image-repository=registry.aliyuncs.com/google_containers
参数说明:
--kubernetes-version:指定 Kubernetes 组件版本。--control-plane-endpoint:控制平面访问地址,这里使用 Master 主机名,之后 kubelet 会通过 /etc/hosts 解析到对应 IP。--apiserver-advertise-address:API Server 对外广播的 IP 地址。--pod-network-cidr:Pod 网段,这里配置为 10.244.0.0/16,需要和后面安装的 Calico 保持一致。--service-cidr:Service 网段,默认 10.96.0.0/12 即可。--image-repository:镜像拉取仓库。
初始化时间取决于镜像拉取速度,正常情况几分钟内完成。成功后,末尾会详细输出两段关键信息:
- 配置 kubectl 的命令。
- 让 Worker 节点加入集群的 kubeadm join 命令,以及一个额外的 CA 证书 Hash。
把控制台的输出完整保存下来,后面用得上。
5.3 配置 kubectl 并安装 Calico 网络插件
初始化完成后,用输出里的命令配置 kubectl:
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
此时只能看到 k8s-master 一个节点,状态是 NotReady,原因是集群还没有安装网络插件。
这里选择 Calico 作为 CNI 网络插件。Calico 使用 BGP 协议在三层网络转发流量,支持 NetworkPolicy,应用广泛。从 Calico 官方获取与 Kubernetes 1.29 兼容的 release manifest,安装前需要确认里面的 Pod 网段配置与 kubeadm init 时一致。
yaml复制# 在 calico.yaml 中搜索 CALICO_IPV4POOL_CIDR
# 默认值通常是 192.168.0.0/16,需要改成下面这样
- name: CALICO_IPV4POOL_CIDR
value: "10.244.0.0/16"
修改完成后应用 manifest:
bash复制kubectl apply -f calico.yaml
查看系统组件运行状态:
bash复制kubectl get pods -n kube-system -w
等 calico-node 和 coredns 都变成 Running,Master 节点状态也会变为 Ready。
如果访问 Calico 官方地址不方便,也可以选择 Flannel,初始化时使用 --pod-network-cidr=10.244.0.0/16,Flannel 默认就使用这个网段,配置更简单,但功能上不支持 NetworkPolicy。
6. Worker 节点加入集群
6.1 获取加入命令
如果保存了 kubeadm init 的输出,可以直接使用其中的 join 命令。命令包含两部分信息:APIServer 地址和 token,以及 CA 证书的 Hash。
如果没有保存输出,或者 token 已经过期,可以在 Master 上重新生成:
bash复制kubeadm token create --print-join-command
输出类似:
bash复制kubeadm join 192.168.x.10:6443 --token xxxxxx --discovery-token-ca-cert-hash sha256:xxxxxx
token 默认有效期 24 小时,过期后需要重新生成。discovery-token-ca-cert-hash 用来校验控制平面证书,防止加入方连接到错误的集群。
6.2 在两个 Worker 节点上执行 join
重要提醒:Worker 节点也需要先完成第 4 节的所有基础环境初始化,包括 /etc/hosts、关闭防火墙和 swap、安装 containerd、安装 kubeadm/kubelet/kubectl,否则 join 后节点会一直处于 NotReady 状态。
确认环境就绪后,分别登录 k8s-node1 和 k8s-node2,执行 join 命令:
bash复制kubeadm join 192.168.x.10:6443 \
--token xxxxxx \
--discovery-token-ca-cert-hash sha256:xxxxxx
join 过程会安装节点所需组件并向 API Server 注册节点。回到 Master 上查看节点状态:
bash复制kubectl get nodes
等待一分钟后,三台节点都应该处于 Ready 状态:
text复制NAME STATUS ROLES AGE VERSION
k8s-master Ready control-plane 10m v1.29.14
k8s-node1 Ready <none> 3m v1.29.14
k8s-node2 Ready <none> 3m v1.29.14
6.3 部署 Nginx 验证集群功能
集群跑通后,用最简单的 Nginx Deployment 验证调度和网络。
创建 Deployment:
bash复制kubectl create deployment nginx-demo --image=nginx:1.25
暴露 NodePort 服务:
bash复制kubectl expose deployment nginx-demo --port=80 --type=NodePort
查看 Service:
bash复制kubectl get svc
输出中 NodePort 端口类似 30000-32767 之间的一个值。用浏览器访问任意节点的 IP 加 NodePort 端口,比如 http://192.168.x.11:32000,就能看到 Nginx 默认欢迎页。
再把副本数扩展到 3:
bash复制kubectl scale deployment nginx-demo --replicas=3
kubectl get pods -o wide
可以看到三个 Pod 被调度到不同的节点上,这是多节点集群调度能力的第一次直观验证。
7. 常见问题与排查思路
7.1 高频问题速查表
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| kubeadm init 卡在等待 kubelet 启动 | kubelet 未启动或配置了错误的 cgroup 驱动 | 查看 journalctl -u kubelet,确认 containerd SystemdCgroup=true |
| CoreDNS 一直 Pending | 网络插件未安装或 Pod 网段配置不一致 | 安装 Calico/Flannel,确认 init 参数和 CNI 配置一致 |
| 节点 join 后长期 NotReady | 节点未关闭 swap、SELinux 未配置、镜像拉取失败 | 检查节点基础环境,查看 kubelet 日志 |
| 镜像拉取失败 | 默认仓库访问慢或网络限制 | 使用 --image-repository 指定镜像仓库 |
| 虚拟机启动提示虚拟化相关错误 | BIOS 未开启 VT-x/AMD-V | 重启进入 BIOS 开启硬件虚拟化 |
| 克隆虚拟机后 IP 不通或主机名不对 | 克隆后 machine-id 重复或 NetworkManager 配置残留 | 重新生成 machine-id,重新配置静态 IP |
7.2 通用排查命令
遇到集群组件异常,最忌讳的是不看日志凭感觉操作。下面是几个最常用的排查命令:
查看节点状态和节点事件:
bash复制kubectl get nodes
kubectl describe node <节点名>
查看系统组件的 Pod 状态和日志:
bash复制kubectl get pods -n kube-system -o wide
kubectl logs -n kube-system <pod名称>
如果节点状态不对,登录对应节点查看 kubelet 日志:
bash复制journalctl -u kubelet -f
kubelet 日志里如果出现 “CRI v1 runtime API is not implemented” 这类字样,说明 containerd 的版本和 kubelet 期望的 CRI 版本不一致,优先检查 containerd 是否需要更新,以及 cgroup 驱动配置是否修改成功。
8. 工程化建议与扩展方向
8.1 让集群更稳定运行
整个集群搭建完成后,建议做几件操作,避免以后“跑着跑着就挂了”。
首先是不要在主节点上运行业务 Pod。Master 初始化时自带污点(taint),正常情况下业务 Pod 不会被调度到 Master 上,不要为了节省资源手动删掉这个污点,否则一旦日志或监控组件挤占控制平面资源,可能导致集群不稳定。
然后是给系统做快照。在 VMware 中关机状态下对 Master 和两个 Worker 分别拍摄快照,之后做实验出现问题可以一键回滚。这个习惯比任何高级运维技巧都实用。
第三是监控磁盘空间。容器镜像和日志增长很快,40GB 磁盘很快就会满。建议定时清理不用的镜像,或者把 Docker 数据目录迁移到大磁盘。
8.2 证书续期与 etcd 备份
kubeadm 部署的集群各组件证书默认有效期一年。等一年后证书过期,整个集群会突然失联,这是很多初学 kubeadm 的同学踩过的坑。
查看证书过期时间:
bash复制kubeadm certs check-expiration
续期命令:
bash复制kubeadm certs renew all
续期之后需要重启 kubelet 和 API Server 相关组件才能生效。
etcd 中保存了整个集群的全部状态,包括所有资源对象和配置,备份 etcd 比备份某台虚拟机更可靠。定时执行 snapshot 保存:
bash复制mkdir -p /backup
ETCDCTL_API=3 etcdctl snapshot save \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
/backup/etcd-snapshot-$(date +%Y%m%d).db
生产环境里应该将备份文件再转移到其他机器或对象存储,避免宿主机磁盘损坏导致备份一起丢失。
8.3 下一步学习路线
三节点集群起来之后,可以按下面路线继续深入:
- 练习 Namespace、ConfigMap、Secret、PVC 等基础资源对象的使用。
- 安装 Ingress Controller,用域名访问集群内部服务。
- 使用 Helm 管理应用包,熟悉 chart 的打包和发布。
- 部署 Prometheus + Grafana,把集群监控搭起来。
- 学习 RBAC,理解不同账号的权限隔离方式。
- 有更高需求时,研究 kubeadm 高可用部署,把控制平面扩展到 3 个节点。
这套基于 VMware 的集群环境虽然跑在笔记本或台式机上,但操作思路和生产环境基本一致。遇到问题先看日志、按顺序排查,比盲目重启节点靠谱得多。先动手把这套环境完整跑一遍,后面的学习才会顺。
