☰
VMware本地搭建Kubernetes集群:kubeadm+containerd+Calico全流程无坑指南
2026/10/21 4:39:52 网站建设 项目流程

如果你正打算系统学习 Kubernetes,又不想在云环境里烧钱练手,那用 VMware 在本地虚拟机里搭一套 1主2从 的 k8s 集群,是我最推荐的一条路。这套流程我前后完整跑过三轮,第一次折腾了整整一天,后面把步骤固化成文档之后,再重新搭一遍,从零到集群就绪基本一小时以内搞定。

标题写“亲试无坑”,不是说这个过程真的一帆风顺,而是说我踩过的那些坑已经全部填平了。你只要按这套流程走,注意我标注的几个关键细节,大概率能一把过。这篇文章我会把虚拟机规划、系统初始化、容器运行时、kubeadm 初始化、网络插件、节点加入到最终验证,全部按实际执行顺序重写一遍。适合已经装过 VMware、懂一点 Linux 基础命令、但没完整搭过 K8s 的读者直接照着做。

1. 用 VMware 搭集群,到底好在哪

1.1 三类环境方案横向对比

我在学 K8s 的时候,也纠结过到底用哪种环境来练。市面上常见的方案无非三类:云服务器、物理机/裸金属、本地虚拟机。各有利弊,但综合下来,本地虚拟机对于学习阶段来说是性价比最高的。

云服务器的好处是网络环境真实,公网访问方便,但成本是个绕不开的问题。一套 3 节点的集群,哪怕选最便宜的机器,一个月下来也是一笔开销,而且你只是拿来做实验,大部分时间机器都在闲着。物理机就更不用说了,三台机器放在家里,噪音、功耗、空间都是麻烦。

Minikube 或者 kind 这类单机方案倒是省事,一条命令就能起一个“伪集群”。但它们把多节点的网络通信、节点调度、Pod 调度等真实场景都屏蔽了。如果你是奔着掌握 K8s 运维去的,单机方案学到的东西其实很有限。

VMware 虚拟机则刚好卡在中间:一台宿主机就能跑三台虚拟机,网络层面可以模拟真实的多节点环境,磁盘和内存开销完全可控,最重要的是——快照功能太适合用来反复折腾了。初始化完成之后打一个快照,后面不管怎么折腾,一条命令就能回到干净状态。

1.2 虚拟机资源规划与网络模式选择

先说资源配置,这是很多人第一步就容易犯迷糊的地方。我的建议是最低按 1主2从 来搭,也就是三台虚拟机。主节点是控制面,承载 etcd、API Server、Controller Manager、Scheduler 这些核心组件,建议给到 4G 内存、2 核 CPU、50G 硬盘。两个工作节点可以稍微低一点,2G 内存、2 核 CPU、30G 硬盘就够跑了。

如果宿主机内存比较紧张,也可以压缩成 1主1从,每台 2G 内存。但注意主节点内存低于 2G 时,etcd 和系统组件跑起来会很吃力,集群很容易因为内存不足出现各种莫名其妙的异常。所以有条件的话,主节点的 4G 内存建议保留。

网络模式方面,我强烈建议用NAT 模式,不要用桥接模式。理由很简单:NAT 模式下 VMware 会自动维护一个虚拟网络,虚拟机之间天然互通,虚拟机也能正常访问外网,而且 IP 段和宿主机所在局域网是隔离的,不会跟你家里的路由器网段产生冲突。桥接模式虽然能让虚拟机直接出现在局域网里,但IP 可能漂移或冲突,对集群环境来说反而是不稳定因素。

NAT 模式下有一个关键操作:给每台虚拟机设置静态 IP。如果放任 DHCP 自动分配,重启之后 IP 一变,后面写的 /etc/hosts 解析就全废了。可以在系统安装完成后用 netplan 或者 nmcli 手动固定 IP,这个我会在下一章详细展开。

2. 三台虚拟机的创建与系统准备

2.1 镜像选择与安装建议

操作系统我建议选用Ubuntu 20.04 LTS或者 22.04 LTS,不要用带图形界面的桌面版,安装时选 Server 版本或最小化安装就可以。图形界面在虚拟机里纯属浪费内存,而且 K8s 集群的运行跟桌面环境没有任何关系。

安装虚拟机的时候有一个小细节值得注意:CPU 配置里,如果宿主机 CPU 支持,可以在 VMware 的虚拟机设置里把“虚拟化 Intel VT-x/EPT 或 AMD-V/RVI”勾上。这个选项平时用不上,但如果你后面想在集群里跑嵌套虚拟化或者用到需要硬件虚拟化支持的组件,就会需要它。提前勾上,省得后面再改配置重启一次。

三台机器装好系统之后,我建议按这样的规划来分配主机名和 IP:

角色主机名IP 地址
主节点master01192.168.31.101
工作节点1node01192.168.31.102
工作节点2node02192.168.31.103

这里的 IP 段是我在自己的 VMware NAT 网络里规划的,你实际用的时候要以自己虚拟网络编辑器里的网段为准。查看方式是 VMware 菜单栏“编辑”->“虚拟网络编辑器”,选中 NAT 模式对应的 VMnet,那里能看到当前网段和网关地址,通常是 192.168.x.0 这种形式。

2.2 统一基础设置:主机名、hosts、时间同步

三台虚拟机都要执行下面这一组基础设置,我按顺序拆开讲。

先设置主机名,三台分别执行对应的命令:

hostnamectl set-hostname master01 # node01 上执行:hostnamectl set-hostname node01 # node02 上执行:hostnamectl set-hostname node02

然后编辑 /etc/hosts,把集群内所有节点的解析关系写进去。这一步很多人会忽略,但如果不配,后面 kubeadm join 的时候会一直报主机名解析错误:

cat >> /etc/hosts <<EOF 192.168.31.101 master01 192.168.31.102 node01 192.168.31.103 node02 EOF

接下来是时间同步。K8s 集群的证书校验对时间偏差非常敏感,节点之间时间差超过几秒钟,会导致证书校验失败、组件之间无法通信。Ubuntu 20.04 默认自带 systemd-timesyncd,只需要确认它正常工作即可:

timedatectl set-timezone Asia/Shanghai timedatectl set-ntp true timedatectl status

如果 status 输出里能看到 NTP synchronized: yes,说明时间同步已经生效。这一步千万别跳过,我见过太多集群里 cert 相关的诡异报错,最后查下来都是时间不同步引起的。

2.3 内核网络模块与系统参数

K8s 集群运行依赖两个内核模块:overlay 和 br_netfilter。overlay 是容器镜像分层存储的文件系统驱动,br_netfilter 的作用是让 Linux 网桥设备上的流量也能通过 iptables 规则进行处理。不加载 br_netfilter 的话,集群内的 Pod 网络转发会出问题。

在三台机器上分别执行:

cat <<EOF | tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF modprobe overlay modprobe br_netfilter

然后是内核参数调整。K8s 官方要求网络转发相关参数必须设置为 1,否则 kubelet 启动时会警告甚至直接失败:

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 vm.swappiness = 0 EOF sysctl --system

最后是关闭 swap。这一步是硬性要求,kubeadm 检测到 swap 开启时会在 init 阶段直接报错退出。关闭并永久禁用:

swapoff -a sed -i '/ swap / s/^/#/' /etc/fstab

sed 命令的作用是把 fstab 中挂载 swap 分区的行注释掉,这样重启后 swap 不会再自动挂载。我见过有人只执行 swapoff -a 却忘了改 fstab,结果虚拟机一重启 kubelet 又起不来,非常坑。

到这里,三台虚拟机的系统基础准备就完成了。我建议在这个阶段先在 VMware 里给三台机器各拍一个快照,命名为“基础环境已配置”。后面如果再装错什么东西,直接恢复快照,十分钟就能回到这个干净状态,不需要重新装系统。

3. 容器运行时:containerd 安装与关键配置

3.1 为什么这里不装 Docker

K8s 从 1.24 版本开始,彻底移除了对 Docker 作为默认运行时的一体化支持。现在主流的容器运行时是 containerd,它本身就是当初从 Docker 项目里拆分出来的核心容器管理组件,更轻量,也更贴近 K8s 的原生接口。

我这里就直接安装 containerd.io,不装 Docker。原因很简单:集群里跑业务用的是容器,不是 Docker 的镜像构建等外围功能。装一个完整的 Docker 在节点上只会额外占用磁盘和内存,还得处理 Docker 和 containerd 之间的兼容性问题,完全没有必要。

安装命令很简单,直接使用 Docker 官方软件源里的 containerd.io 包即可:

apt-get update apt-get install -y containerd.io

装完先别急着启动,containerd 默认配置是没法直接给 K8s 用的,需要手动生成一份配置文件并修改两个关键项。

3.2 systemd cgroup 与 sandbox_image 两个坑

生成默认配置文件:

mkdir -p /etc/containerd containerd config default | tee /etc/containerd/config.toml

第一个必改项是 cgroup 驱动。containerd 默认配置里SystemdCgroup = false,但如果宿主机系统使用的是 systemd 管理进程,就必须把它改成 true,否则 kubelet 和 containerd 在 cgroup 管理上会不一致,表现为 kubelet 反复重启,日志里全是 cgroup 相关的报错。

修改方法是用 sed 直接替换:

sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

第二个必改项是 sandbox_image,也就是 pause 镜像地址。这个镜像是每个 Pod 启动时必须先拉取的基础镜像,默认指向registry.k8s.io/pause:3.x。这个地址在你的网络环境下很可能访问不到,不改的话,创建 Pod 时会一直卡在 Pending 状态,镜像拉取失败。

这里需要说明一下:具体使用哪个镜像仓库地址,取决于你所在网络环境能访问哪个源。我强烈建议你在自己网络里找到一个可用的镜像仓库,然后把 sandbox_image 替换成对应地址。配置方式如下:

sed -i 's#sandbox_image.*#sandbox_image = "你的镜像仓库地址/google_containers/pause:3.9"#' /etc/containerd/config.toml

注意,这里给的是地址格式模板,实际操作时把地址换成你自己环境里确实可访问的镜像仓库前缀即可,后面那段的路径结构保持不变。

改完配置后重载:

systemctl enable --now containerd systemctl restart containerd

验证 containerd 是否正常运行:

systemctl status containerd

能正常看到 active (running) 就说明容器运行时已经就绪。

4. 安装 kubeadm、kubelet、kubectl

4.1 添加仓库与版本固定

容器运行时就绪之后,接下来安装 K8s 的三个核心命令行组件:kubeadm(集群初始化工具)、kubelet(节点上的代理服务)、kubectl(集群操作客户端)。

首先需要添加 Kubernetes 的 apt 软件源。官方源是packages.cloud.google.com,考虑到网络可达性差异,这里同样建议换成你能访问的镜像源。写法上,把源中的域名部分替换成对应镜像地址即可:

cat <<EOF | tee /etc/apt/sources.list.d/kubernetes.list deb https://你的镜像源地址/kubernetes/apt/ kubernetes-xenial main EOF

这个源文件配好之后,先执行apt-get update。如果命令找不到 kubeadm 或者源不生效,用apt-cache madison kubeadm查一下源里是否有对应软件包。查不到基本就是源地址配置有问题。

安装时建议固定版本,不要直接装最新版。K8s 的版本升级节奏很快,不同版本之间的配置差异经常让人踩坑。我在多次实践中觉得 v1.28 这个系列比较稳定,生态兼容性好,适合学习使用:

apt-get install -y kubelet=1.28.2-1.1 kubeadm=1.28.2-1.1 kubectl=1.28.2-1.1

装完之后用 apt-mark hold 锁住版本,防止误操作触发升级导致版本不一致:

apt-mark hold kubelet kubeadm kubectl

三个组件版本必须保持一致。如果 kubelet 和 kubeadm 版本不同,集群初始化时会出现版本校验不通过的问题。

4.2 三件套的角色区别

很多初学者刚接触这三个工具时容易搞混,我这里用一句话帮你理清各自职责:kubeadm 是“施工队长”,负责把集群搭起来;kubelet 是“驻场管家”,常驻在每个节点上,负责管理本节点的容器生命周期;kubectl 是“遥控器”,你在主节点上敲的所有操作命令都通过它发给 API Server。

三台节点上都需要安装 kubelet 和 kubeadm,因为工作节点 join 集群时需要执行 kubeadm join 命令,kubelet 则会在节点加入后被 systemd 拉起。kubectl 只需要在主节点安装一份,因为所有集群操作都在主节点上执行。

5. 初始化集群:从零到主节点就绪

5.1 kubeadm init 参数详解

所有准备工作做完,终于到了初始化集群的核心环节。在 master01 上执行 kubeadm init,先看完整命令:

kubeadm init \ --kubernetes-version=v1.28.2 \ --control-plane-endpoint=192.168.31.101 \ --apiserver-advertise-address=192.168.31.101 \ --pod-network-cidr=10.244.0.0/16 \ --service-cidr=10.96.0.0/12 \ --image-repository=你的镜像仓库地址/google_containers

每个参数我拆开解释一下,理解了以后出问题也更容易排查。

--kubernetes-version必须和前面安装的 kubeadm 版本一致,这里填写 v1.28.2。

--control-plane-endpoint是控制面入口地址。单主节点环境下,直接填主节点 IP 就可以。写上这个参数的好处是以后如果扩成多主节点高可用架构,只要在这里换成负载均衡地址即可,集群配置不用大改。

--apiserver-advertise-address指定 API Server 对外广播的地址,同样填 master01 的实际 IP。

--pod-network-cidr是 Pod 网络的地址段。这里填 10.244.0.0/16,是为了和后面要装的网络插件配置对齐。这个网段不能和宿主机或虚拟机所在的物理网段重叠,否则路由会冲突。

--service-cidr是 Service 虚拟 IP 的地址段,默认就是这个 10.96.0.0/12,显式写出来只是让你知道有这个参数。同样不能和物理网段冲突。

--image-repository指定组件镜像拉取仓库。kubeadm init 会从仓库拉取 apiserver、etcd、coredns 等一组镜像,默认是 registry.k8s.io,你需要把它替换成前面配好的镜像仓库地址,路径结构保持google_containers不变。

init 执行过程中如果卡在某一步超过10分钟,大概率是镜像拉取问题。这时kubectl get pods -A看不到内容,应该先去查 kubelet 日志。

init 成功之后,终端会输出一个 join 命令,格式类似下面这样:

kubeadm join 192.168.31.101:6443 --token xxxxxx --discovery-token-ca-cert-hash sha256:xxxxxx

这是我全篇强调的最关键信息之一,建议立刻复制保存下来。token 有效期是24小时,万一过期了可以重新生成,方法后面会讲。

5.2 kubectl 使用环境配置

init 成功后在 master01 上还不能直接用 kubectl,因为当前用户的 kubeconfig 还没有配置。执行以下命令:

mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config

这三行的作用分别是创建 kubeconfig 目录、把 admin.conf 复制为用户级配置、把配置文件的属主改为当前用户。做完之后执行kubectl get nodes,正常会看到 master01 处于 NotReady 状态,这是预期的,因为网络插件还没装。

5.3 部署Calico网络插件

K8s 集群的容器网络需要额外安装 CNI 插件,我首选 Calico。它的稳定性很好,支持网络策略,是生产环境里用的最多的方案之一。Flannel 也可以,但 Calico 的功能更全面,学一次能覆盖更多场景。

Calico 的部署方式很简单:先下载官方发布的 calico.yaml 清单文件,传输到 master01 上,然后执行:

kubectl apply -f calico.yaml

这里有一个我在实操中必踩的坑:calico.yaml 默认的 Pod 网段是 192.168.0.0/16,而我们在 kubeadm init 时指定的是 10.244.0.0/16,两者不一致会直接导致网络插件失效,Pod 之间永远无法通信。

解决办法是在 apply 之前先修改 calico.yaml 里的配置。在文件中搜索192.168.0.0/16,把它改成10.244.0.0/16,再用 kubectl apply 生效。很多教程没提这个细节,跟着做就会发现所有节点都是 Ready 了,但跨节点 Pod 网络完全不通。

apply 完成之后,等待镜像拉取和组件启动:

watch kubectl get pods -n kube-system

看到所有 pod 都变成 Running 状态后,再次执行:

kubectl get nodes

master01 应该已经变成 Ready 状态。到这一步,主节点初始化全部完成。

6. 工作节点加入与集群验证

6.1 join 命令与 token 管理

主节点就绪之后,把 node01 和 node02 分别执行 join 命令加入集群。直接在每台工作节点上运行第 5.1 节保存的那条 join 命令即可:

kubeadm join 192.168.31.101:6443 --token xxxxxx --discovery-token-ca-cert-hash sha256:xxxxxx

join 过程中如果提示 token 过期,或你忘了保存初始 token,可以在 master01 上重新生成一条 join 命令:

kubeadm token create --print-join-command

这个命令输出里会带上新的 token 和 ca cert hash,复制到工作节点执行即可。

两条 join 命令都成功之后,回到 master01 查看节点状态:

kubectl get nodes

正常输出应该包含三台节点,master01 和 node01、node02 都处于 Ready 状态。如果 node 还是 NotReady,按第 7 章的排查表去定位。

6.2 部署 nginx 验证业务流量

集群搭建完成,最后一步是验证业务链路真的能跑通。部署一个简单的工作负载:

kubectl create deployment nginx-test --image=nginx --replicas=2 kubectl expose deployment nginx-test --port=80 --target-port=80 --type=NodePort kubectl get svc nginx-test

最后一条命令会输出一个 NodePort 端口号,比如 31234。这时你在浏览器里访问任意一台节点的 IP 加这个端口,比如http://192.168.31.102:31234,能看到 nginx 的欢迎页就说明整个集群从调度到网络到业务流量全链路都通了。

另外,验证一下跨节点通信:

kubectl get pods -o wide

看两个副本是否调度到了不同的节点上。如果能跑通,说明 Calico 的跨节点网络已经正常工作。

7. 实操中一定会遇到的坑与排除方法

7.1 症状、原因与解决方案速查表

我把一轮轮实操下来最频繁出现的几个状况整理成一个速查表,遇到问题按表格逐条排查效率最高。

症状根本原因解决方案
kubeadm init 一直卡住或报错swap 未关闭执行swapoff -a并注释 fstab 中的 swap 行
kubelet 反复重启,日志报 cgroupcontainerd 的 SystemdCgroup 未设置为 true修改 /etc/containerd/config.toml 配置项并重启 containerd
Pod 一直 Pending,镜像拉取失败sandbox_image 指向的镜像仓库不可访问修改 sandbox_image 和 image-repository 为可达镜像仓库地址
节点 NotReady,Calico 相关 Pod 有报错calico.yaml 中 Pod 网段与 init 参数不一致修改 calico.yaml 中的网段为 10.244.0.0/16 后重新 apply
join 提示 token 过期token 有效期只有 24 小时在 master01 执行kubeadm token create --print-join-command重新生成
节点间时间不一致,证书校验失败未同步时间或时间漂移执行 timedatectl 相关命令配置并确认 NTP 同步
kubectl 执行报 connection refusedAPI Server 未启动或端口被防火墙拦截检查 API Server 状态,放行 6443 端口或关闭防火墙
vm 重启后 IP 变化导致集群异常使用 DHCP 动态分配 IP用 netplan 或 nmcli 配置静态 IP
NodePort 访问不通虚拟机防火墙未放行放行 NodePort 端口范围或直接systemctl stop ufw(测试环境)

7.2 几个值得养成的操作习惯

最后分享几个让我少走很多弯路的小习惯,属于那种“没人提醒你根本想不到”的经验。

一个是快照习惯。前面提到过,系统基础配置完打一个快照,集群初始化成功后再打一个快照。这个习惯的价值在实操中体现得非常明显——我第三次搭集群的时候,装网络插件改错了配置,整条命令链直接废掉,如果当时没有快照,要恢复得重做三台机器的基础环境,至少折腾半小时。而恢复快照只需要一分钟。

另一个是日志优先的排错思路。K8s 集群的报错信息往往非常笼统,真正有用的信息藏在日志里。kubelet 的日志用journalctl -u kubelet -f查看,containerd 的运行时问题用crictl ps -a查看容器状态,网络插件的问题查对应 Pod 的kubectl logs。我遇到的所有诡异问题,最后都是靠这三类日志定位到的。

还有一个是清理重来的完整流程。很多人执行 kubeadm init 失败之后直接重新 init,但系统里残留的配置会导致第二次失败得更莫名其妙。正确的清理方式是:

kubeadm reset -f rm -rf /etc/cni/net.d ~/.kube

然后再重新执行 init。这套清理命令在你反复实验时几乎是必备技能。

在这个项目里,我最深的一条体会是:K8s 集群搭建的难点从来不是命令本身,而是那些藏在配置文件和网络参数里的隐性要求。只要把系统初始化、容器运行时、网络插件网段这三个关键位置把住,整个流程就是一条直线。按这套流程走完,你得到的不仅是一个能跑的集群,更是对 K8s 控制面、节点通信、容器运行时这些底层机制的一次完整理解。后续你想在上面继续学部署应用、配置存储、做监控,这套虚拟机环境就是你的实验基地。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询