从标题说起。这篇文章不是给你讲 K8s 理论的,是一套在 VMware 上从零到一、能跑通的完整集群搭建实录。我自己在这条路上来回折腾了几天,重装过系统、翻过墙(指网络问题)、排查过各种玄学报错,最后把每一步、每个坑都记了下来。所以这篇内容的核心关键词就是三个:VMware、K8s 集群、无坑实操。适合谁看?想在一台 Windows 笔记本上低成本拥有完整多节点集群的开发者,准备考认证需要练手的人,以及被各种博客教程带偏、急需一份能照着敲完就成功的部署指南的朋友。
我先把结论放前面:这套方案用的工具是 VMware Workstation + CentOS 7.9 + kubeadm,为什么不做二进制手动部署、为什么不用 minikube,后面会详细说。整体下来,只要你的电脑内存不低于 16G、硬盘有 80G 以上剩余空间,跟着这篇文章一步步来,大概率能一次成功。
1. 为什么选 VMware + CentOS 7.9 + kubeadm 这套组合
先说说选型逻辑,这是很多教程不讲的,但恰恰决定了你会不会半路翻车。
为什么用 VMware 而不是 VirtualBox?我最早其实是用 VirtualBox 试的,结果在嵌套虚拟化和网络桥接上折腾了很久。VMware Workstation 在网络配置上要省心得多,尤其是 NAT 模式的稳定性、克隆虚拟机时的网卡配置、以及虚拟化性能损耗,它都做了很好的优化。你在这台电脑上跑 3-4 台虚拟机做集群,内存和 CPU 的调度效率很重要,VMware 在这方面实测比 VirtualBox 稳。
为什么用 CentOS 7.9 而不是 8 或者更新的系统?这是我在踩坑后切回 7.9 的关键决定。CentOS 8 出来后,由于生命周期和政策调整,很多镜像源不可用,装个基础软件包都能卡半天。而 CentOS 7.9 的生态非常成熟,市面上 90% 的 K8s 教程都基于它,三方组件的兼容性最好。特别是你要用 kubeadm 初始化集群,某些内核参数和防火墙规则,7.9 的处理方式和 K8s 官方文档几乎一致,遇到问题找答案也方便。
为什么用 kubeadm 而不是二进制手动部署?手动部署当然能让你更懂原理,但它的时间成本和错误率太高。kubeadm 是官方推荐的集群引导工具,它做的事情就像婚礼登记处一样,把 master 和 worker 之间该走的流程都结构化封装了。我的观点是:先用 kubeadm 把集群跑起来,再回去看它生成的静态 Pod 清单和证书文件,去理解组件之间的关系,这才是效率最高的学习路径。
硬件要求先确认一下:我这里说的节点规划基于两台虚拟机:一台 master、一台 worker。如果你还想加一台 worker,那就是三台。建议每台虚拟机分配 2 核 CPU、4G 内存,master 可以稍微给多点。如果你只有 8G 内存,那跑两台 2G 内存节点勉强行,但 K8s 系统组件比较多,很容易 OOM,实测不舒服。
2. 虚拟机创建与系统基础配置:克隆前必须做的三件事
很多人喜欢一台一台地装系统,我建议反过来,先做一台基础虚拟机,配好所有公共环境,然后直接克隆出多个节点。这样能省至少一半时间,而且能保证每台机器环境一致性,后续排错会非常舒服。
2.1 创建基础虚拟机并最小化安装系统
在 VMware 里新建虚拟机,操作系统选择 CentOS 7 64 位,磁盘给 40G,内存给 4G,CPU 给 2 核。这里有一个容易忽略的细节:在自定义硬件时,记得把网络适配器设置为 NAT 模式。NAT 模式的好处是宿主机和虚拟机处于同一虚拟网段,虚拟机之间也能互通,而且不依赖你公司或家里的路由器 DHCP 设置,隔离性好,不会和局域网里其他人的 IP 冲突。
系统安装界面选择最小化安装,然后在安装位置里把分区需求改一下,默认自动分区就行,不用手工分。装完后进入系统,第一件事是配置网络。CentOS 7 默认网卡名是 ens33,编辑/etc/sysconfig/network-scripts/ifcfg-ens33,把BOOTPROTO=dhcp改成static,然后追加 IP、网关、DNS。我在这台基础模板上分配的是192.168.68.100,网关是 VMware NAT 模式默认的192.168.68.2,DNS 写8.8.8.8或者114.114.114.114都行。
2.2 基础环境初始化脚本
等这台基础机可以正常联网后,执行下面这组命令,把所有公共环境都配好。这个步骤里最容易踩坑的是 yum 源和包版本,后面细说。
# 更新系统 yum update -y # 安装基础工具 yum install -y vim wget curl net-tools lsof # 关闭防火墙,K8s 组件会自己管理端口 systemctl stop firewalld && systemctl disable firewalld # 关闭 SELinux sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config setenforce 0 # 关闭 swap,kubeadm 初始化时会强制检查 swapoff -a sed -i '/swap/s/^/#/' /etc/fstab这里每个操作都有明确的“为什么”。关闭防火墙不是偷懒,而是 K8s 的 Pod 网络插件(比如 Flannel、Calico)和 Service 会动态创建大量 iptables 规则,和 firewalld 的规则管理冲突会让你排查到怀疑人生。关闭 SELinux 同理,它会对容器文件系统访问做强制访问控制,不仅仅是你命令行里能感知到的。关闭 swap 是因为 K8s 的 kubelet 在 1.8 版本之后对 swap 支持不完整,你不关掉,kubeadm init直接报[ERROR Swap]: running with swap on is not supported。
2.3 桥接内核模块与系统参数
K8s 要求开启两个内核模块:br_netfilter和overlay,同时调整几个网络转发参数。这一部分虽然写起来快,但漏掉任何一个,后面初始化就会卡在配置阶段。
# 加载内核模块 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.bridge.bridge-nf-call-iptables是让桥接的流量也能被 iptables 规则过滤,K8s 的 Service 转发和服务发现依赖这个;net.ipv4.ip_forward是开启路由转发,Pod 之间的跨节点通信必须有它。
到这里,基础虚拟机的准备工作就完了。建议此时在 VMware 里拍一个快照,名字叫“基础模板”,后面克隆出的节点如果出问题,你可以回到这个干净状态重新来。
3. 容器运行时与 kubeadm 工具链安装:版本对齐是成败关键
K8s 需要节点上有一个容器运行时,最常见的就是 Docker 和 containerd。kubelet 通过 CRI (Container Runtime Interface) 和它通信。这里我选择 Docker,稳定、好排查,不过你要是追求更轻量的资源占用,直接用 containerd 也行,操作上会有些差异,但思路完全一致。
3.1 安装 Docker 并配置镜像加速
先配置 Docker 的 yum 源,然后用 yum 安装。注意指定版本,不要默认装最新版,因为太新的 Docker 和 K8s 的适配可能存在问题,我用的是20.10.12这个版本,实测配合 K8s 1.23 系列非常稳。
# 配置阿里源(国内环境实测速度快很多) yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # 安装指定版本 yum install -y docker-ce-20.10.12 docker-ce-cli-20.10.12 containerd.io # 启动并设置开机自启 systemctl start docker && systemctl enable docker接着配置 Docker 镜像加速。为什么要配?因为 K8s 要拉取的镜像大多在 Docker Hub 上,在国内网络环境下直接拉非常慢,且经常超时。这里我用阿里云镜像加速器,你可以在自己阿里云账号的控制台里申请免费加速地址。改完配置重启 Docker 后会好很多。
cat <<EOF | tee /etc/docker/daemon.json { "registry-mirrors": ["https://你的专属加速地址.mirror.aliyuncs.com"], "exec-opts": ["native.cgroupdriver=systemd"] } EOFexec-opts里那句非常重要。Docker 默认的 cgroup 驱动是cgroupfs,而 kubelet 使用的驱动是systemd,两者不一致,kubelet 就会一直报failed to run Kubelet之类的错。你提前把它统一成 systemd,就少一个坑。
3.2 安装 kubeadm / kubelet / kubectl
这三个工具的版本必须和 K8s 集群版本严格一致。我装的是 1.23.17,因为这是一个相对稳定且资料充足的老版本,资源占用也不高。你要是想装更新的版本,注意对应调整命令里的版本号,并且确认和 Docker 版本的兼容性。
# 配置 K8s 的 yum 源 cat <<EOF | tee /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled=1 gpgcheck=0 EOF # 安装指定版本 yum install -y kubelet-1.23.17 kubeadm-1.23.17 kubectl-1.23.17 # 启动 kubelet 并设置开机自启 systemctl enable kubelet注意,这里只需要enable,不需要立刻start。你手动启动 kubelet 它会因为没初始化而报错,等kubeadm init成功后再启动才是正确顺序。这个细节如果你照着某些教程直接 start,会看到一个红色的失败状态,先别慌,这是正常的。
提示:三台机器上关于 Docker、kubeadm、kubelet、kubectl 的安装步骤完全一样,所以先把基础模板打好,克隆后这些环境天然一致。
3.3 在基础模板上克隆节点
当上述步骤都在基础虚拟机上完成后,先关机,然后右键虚拟机,选择管理 → 克隆。克隆类型选择“创建完整克隆”,链接克隆虽然省空间,但性能和独立性都差一些。克隆出来的机器开机后,你需要做两件事:
第一是改主机名:
hostnamectl set-hostname k8s-master # master 机 hostnamectl set-hostname k8s-worker1 # worker 机第二是改固定 IP。打开ifcfg-ens33,把 IP 从基础模板的192.168.68.100改成你规划好的地址,比如 master 是192.168.68.101,worker 是192.168.68.102。同时需要修改/etc/hosts,把三台机器的 IP 和主机名映射写进去,因为 K8s 节点间通信会通过主机名解析。
这一步还有一个容易翻车的地方:VMware 克隆出来的虚拟机,网卡 MAC 地址会自动改变,但/etc/sysconfig/network-scripts/ifcfg-ens33里的HWADDR字段可能还是旧的。所以你最好把HWADDR这行直接注释掉或者删掉,然后重启网络,否则即使你改了 IP 也可能不生效。
4. Master 节点初始化与网络插件:两个关键命令决定集群能否高可用
基础环境就绪后,进入核心环节:在 master 节点执行kubeadm init,完成控制平面组件(API Server、etcd、Controller Manager、Scheduler)的启动,然后安装 Pod 网络插件。
4.1 提前拉取镜像,避免初始化超时
kubeadm init过程中会去拉取一批系统镜像,包括kube-apiserver、kube-controller-manager、kube-scheduler、kube-proxy、etcd、coredns、pause等。如果在国内网络环境下直接 init,大概率会卡在拉取镜像这一步,然后超时失败。我建议先执行下面的命令,把镜像拉下来:
kubeadm config images list --kubernetes-version=v1.23.17 # 根据列出的镜像名,用 docker pull 逐个拉取逐个拉取太麻烦?可以直接用脚本循环拉。但更简单的办法是查询kubeadm config images list后,把输出结果做成一个数组,然后 for 循环 pull:
images=$(kubeadm config images list --kubernetes-version=v1.23.17) for image in $images; do docker pull $image done如果你没有配镜像加速,这一步会很痛苦。配置了加速后,速度和本地一样流畅。有个小技巧:如果某个镜像拉取失败,可以先去镜像加速器页面看看到底能不能用,或者换一个加速地址,多试几个。
4.2 kubeadm init 初始化
镜像就绪后,直接在 master 节点上执行初始化命令。需要注意--apiserver-advertise-address填你 master 的 IP,--pod-network-cidr根据你安装的网络插件来决定。我选择 Flannel,对应的 Pod 网段是10.244.0.0/16。如果你选 Calico,网段一般是192.168.0.0/16,这个不能混,因为插件内部会检查网段。
kubeadm init \ --apiserver-advertise-address=192.168.68.101 \ --image-repository registry.cn-hangzhou.aliyuncs.com/google_containers \ --kubernetes-version=v1.23.17 \ --pod-network-cidr=10.244.0.0/16--image-repository这个参数把官方镜像仓库切换成了国内镜像仓库,配合前面的 Docker 加速,几乎不会出现拉镜像超时。初始化过程大约 1-3 分钟,中途你会看到它创建证书、生成 kubeconfig、安装 etcd 等步骤。如果一切顺利,最后会输出一段Your Kubernetes control-plane has been initialized successfully!的提示。
初始化完成后,要按提示配置 kubectl,否则你执行任何kubectl命令都会提示无法连接集群:
mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config到这里,master 的控制平面已经起来了,但节点状态还是NotReady,因为还没有网络插件。
4.3 安装 Flannel 网络插件
网络插件是 K8s 集群里负责 Pod 间通信的核心组件。Flannel 是入门首选,轻量、简单,对性能和排错要求不高的场景完全够用。执行下面的命令安装 Flannel:
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml由于这个 yaml 文件里的镜像存放在 ghcr.io 或者 docker hub 上,国内拉取可能很慢,可以考虑用代理,或者提前把flannel镜像手动拉取到本地。这块如果卡在ImagePullBackOff,先检查 docker 镜像和网络,再考虑换一个 yaml 源。
安装完成后,等几秒钟,检查所有节点状态:
kubectl get pods -n kube-system kubectl get nodes当kube-apiserver、etcd、coredns等 Pod 全部处于Running状态,master 的STATUS变成Ready,集群的控制面就彻底通了。
5. Worker 节点加入集群:一条 join 命令背后的流程
master 就绪后,把 worker 节点加进集群是相对轻松的一步,但也是最容易因为 token 过期、cert 问题而卡壳的环节。
5.1 获取 join 命令
在 master 上执行:
kubeadm token create --print-join-command这条命令会输出一个完整的kubeadm join命令,比如:
kubeadm join 192.168.68.101:6443 --token abc123.xyz789 --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxx如果你之前初始化时没有保存输出的 join 指令,用这个动态生成的方式最稳妥。注意 token 有时间限制,默认 24 小时过期,你要是隔了很久才去加节点,建议重新token create。
5.2 在 worker 节点执行 join
登录 worker 节点,直接粘贴刚才的命令运行。如果出现This node has joined the cluster,就代表成功加入。此时回到 master,执行:
kubectl get nodes正常情况会看到两个节点,名字分别是k8s-master和k8s-worker1,状态是Ready。
这里有个小细节:worker 节点上也需要安装 Docker 和 kubeadm/kubelet,原因很简单,kubelet 是每台节点都必须运行的守护进程,它和容器运行时配合才能启动 Pod。如果你是用基础模板克隆的,这些都已经存在,不用额外处理。
5.3 验证集群可用性
节点 Ready 后,别急着收工,跑一个简单的 Nginx Deployment 验证集群真的能用:
kubectl create deployment nginx --image=nginx:latest kubectl expose deployment nginx --port=80 --type=NodePort kubectl get svc如果 Pod 能处于 Running,Service 能分配到一个 NodePort 端口(比如 30080),然后通过任意节点 IP 加端口访问 Nginx 页面,整个集群链路(调度、拉取镜像、网络代理、DNS 解析)就算是完全打通了。这一步测完,你才算是真正搞定了这套集群。
6. 我踩过的五个坑,以及对应排查命令
最后这部分是我觉得对读者最有价值的,因为我整理这篇博客时,特意回忆了那些让我崩溃过的报错。我把它们连同排查思路一起列出来,你可以直接对照处理。这也是标题“亲试无坑”的来源,不是真的没有坑,而是每个坑我都提前替你踩了一遍。
6.1 kubeadm init 报错:swap 未关闭
报错信息非常直接:[ERROR Swap]: running with swap on is not supported。原因就是/etc/fstab里的 swap 项没有被注释掉,或者你只执行了swapoff -a但没有改 fstab,导致重启后又挂上了。处理方式:注释掉 fstab 中的 swap 行,重启或者重新swapoff -a,确认free -m输出中 Swap 全为 0。
6.2 kubelet 状态显示 failed
你在没有 init 之前手动启动了 kubelet,它肯定报 failed。初始化完成之后,如果 kubelet 仍然不是 active,先看日志:
journalctl -u kubelet -f常见错误是failed to load Kubelet config file或者cgroup driver mismatch。前者是 init 没成功,后者按我前面说的,检查 Docker 的exec-opts是否已经配置成native.cgroupdriver=systemd。改完 Docker 配置后要重启 Docker,再重置 kubelet:
systemctl daemon-reload systemctl restart kubelet6.3 Pod 一直 Pending 或 CrashLoopBackOff
如果你发现kubectl get nodes显示 master 是 Ready,但kube-system里的coredns或 Flannel Pod 起不来,先不要急着重装集群。两个排查方向:第一,kubectl describe pod <pod名> -n kube-system看 Events,确认是镜像拉取失败还是节点资源不足;第二,查看docker logs <容器ID>,如果是网络插件报错,大概率是 Pod 网段和插件配置不一致。我遇到过一次 Flannel 镜像没拉下来,改成手动docker pull后再删掉旧 Pod 重新创建就解决了。
6.4 集群重启后 IP 变了,kubectl 失联
如果你的虚拟机之前配置的是 DHCP,重启后 IP 变了,kubectl 连接自然就断了。解决方法非常简单:把集群内所有节点都改成静态 IP。改完后,如果有需要,再更新/etc/hosts映射。建议从一开始就固定 IP,不要贪图方便用 DHCP。
6.5 内存不足导致虚拟机卡死
这是硬件层面的坑。跑着 K8s 控制平面的 master 节点,最低建议 4G 内存,实际运行时经常看到内存使用量接近 3.5G,系统都开始用 swap 了。如果你只有 8G 物理内存,跑两台虚拟机(一台 4G、一台 4G),加上宿主机开销,会很卡。我个人的做法是:master 给 4G,worker 给 2G,同时把 VMware 里虚拟机的“启动时分配内存”选项关掉,让内存按需分配,减轻宿主机压力。
再分享一个快照技巧:每完成一个阶段(基础环境、Docker 安装、集群初始化、网络插件),都拍一个 VMware 快照。当你折腾出奇怪问题时,直接回滚到上一个快照,比重新排错快得多。我基本就是靠快照把“无坑”做到极致的。
7. 集群搭建完,下一步怎么扩展
集群能跑通之后,别急着删掉虚拟机,这是一套性价比极高的本地学习沙箱。你可以在它上面继续折腾一些真实业务场景,比如部署一个简单的 Web 应用、配置 Ingress 控制器、搭建监控体系、甚至测试存储持久化。因为整个集群环境都在你本机,随便怎么玩都不会影响业务,这比在公司生产环境练手要踏实太多。
我个人在实际操作中比较建议的顺序是:先在这套集群上把 Deployment、Service、Ingress 这一套发布流程走通,然后加一个 Dashboard 看看可视化界面,再尝试做一个 PV/PVC 的存储测试。每个部分踩过的坑都值得单独记下来。这套 VMware 方案的好处是,你随时可以快照、销毁、重建,成本基本为零,但获得的实操经验和工作流上的手感,要比纸上谈兵有价值得多。说到底,K8s 这东西最忌讳的就是只看不练。