VMware+CentOS7.9+kubeadm搭建K8s集群:亲试无坑实操指南 从标题说起。这篇文章不是给你讲 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内存给 4GCPU 给 2 核。这里有一个容易忽略的细节在自定义硬件时记得把网络适配器设置为 NAT 模式。NAT 模式的好处是宿主机和虚拟机处于同一虚拟网段虚拟机之间也能互通而且不依赖你公司或家里的路由器 DHCP 设置隔离性好不会和局域网里其他人的 IP 冲突。系统安装界面选择最小化安装然后在安装位置里把分区需求改一下默认自动分区就行不用手工分。装完后进入系统第一件事是配置网络。CentOS 7 默认网卡名是 ens33编辑/etc/sysconfig/network-scripts/ifcfg-ens33把BOOTPROTOdhcp改成static然后追加 IP、网关、DNS。我在这台基础模板上分配的是192.168.68.100网关是 VMware NAT 模式默认的192.168.68.2DNS 写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/SELINUXenforcing/SELINUXdisabled/g /etc/selinux/config setenforce 0 # 关闭 swapkubeadm 初始化时会强制检查 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.cgroupdriversystemd] } 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] nameKubernetes baseurlhttps://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled1 gpgcheck0 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.101worker 是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-versionv1.23.17 # 根据列出的镜像名用 docker pull 逐个拉取逐个拉取太麻烦可以直接用脚本循环拉。但更简单的办法是查询kubeadm config images list后把输出结果做成一个数组然后 for 循环 pullimages$(kubeadm config images list --kubernetes-versionv1.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-address192.168.68.101 \ --image-repository registry.cn-hangzhou.aliyuncs.com/google_containers \ --kubernetes-versionv1.23.17 \ --pod-network-cidr10.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 是入门首选轻量、简单对性能和排错要求不高的场景完全够用。执行下面的命令安装 Flannelkubectl 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 --imagenginx:latest kubectl expose deployment nginx --port80 --typeNodePort kubectl get svc如果 Pod 能处于 RunningService 能分配到一个 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.cgroupdriversystemd。改完 Docker 配置后要重启 Docker再重置 kubeletsystemctl 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 给 4Gworker 给 2G同时把 VMware 里虚拟机的“启动时分配内存”选项关掉让内存按需分配减轻宿主机压力。再分享一个快照技巧每完成一个阶段基础环境、Docker 安装、集群初始化、网络插件都拍一个 VMware 快照。当你折腾出奇怪问题时直接回滚到上一个快照比重新排错快得多。我基本就是靠快照把“无坑”做到极致的。7. 集群搭建完下一步怎么扩展集群能跑通之后别急着删掉虚拟机这是一套性价比极高的本地学习沙箱。你可以在它上面继续折腾一些真实业务场景比如部署一个简单的 Web 应用、配置 Ingress 控制器、搭建监控体系、甚至测试存储持久化。因为整个集群环境都在你本机随便怎么玩都不会影响业务这比在公司生产环境练手要踏实太多。我个人在实际操作中比较建议的顺序是先在这套集群上把 Deployment、Service、Ingress 这一套发布流程走通然后加一个 Dashboard 看看可视化界面再尝试做一个 PV/PVC 的存储测试。每个部分踩过的坑都值得单独记下来。这套 VMware 方案的好处是你随时可以快照、销毁、重建成本基本为零但获得的实操经验和工作流上的手感要比纸上谈兵有价值得多。说到底K8s 这东西最忌讳的就是只看不练。