☰
虚拟机部署K8s与Docker容器云实训环境避坑指南
2026/10/22 13:45:34 网站建设 项目流程

1. 为什么要在虚拟机里折腾K8s和Docker这套组合

很多人第一次接触容器云实训环境时,都会卡在同一个地方:手头只有一台笔记本,系统是日常在用的,既不敢直接在上面装Docker和K8s把环境搞乱,又不想额外买服务器。这时候虚拟机就成了最务实的选择。我自己带过几批做容器云实训的同学,几乎所有人一开始都会问同一个问题——为什么不能直接在物理机上装?答案其实很朴素:K8s集群搭建过程中要改内核参数、要动网络转发规则、要反复重装,一旦把宿主机搞崩,恢复成本远高于在虚拟机里推倒重来。

这套基于先电2.4容器云手册的部署方案,核心思路就是在一台物理机上用VMware开出若干台虚拟机,分别扮演master节点、node节点以及必要的辅助角色,然后在上面完成Docker运行时和K8s集群的安装。它解决的是"没有真实服务器集群,但又要完整走一遍容器云部署流程"这个矛盾。适合的人群很明确:正在做容器云相关实训的学生、想在自己电脑上搭一套K8s练手环境的运维初学者,以及需要反复演示集群部署流程的讲师。

我先把这套方案的几个关键前提说清楚,避免你走弯路。第一,宿主机内存建议不低于16GB,因为一台master加两台node,每台至少2GB,再算上系统开销,8GB的机器会非常吃力。第二,VMware Workstation版本建议15以上,网络模式的选择直接决定后面集群能不能通。第三,先电2.4手册里的步骤是有顺序依赖的,跳步操作大概率会在某个环节卡住,而且报错信息往往指向不了真正的原因。

下面我会按照真实的部署顺序,把每一步背后的逻辑、容易踩的坑、以及手册里没写但实际会遇到的问题,完整拆一遍。你照着做,基本可以一次跑通;就算中途出错,也能根据我给的排查思路定位到问题。

2. 虚拟机规划与网络模式选型:决定成败的第一步

2.1 节点数量与资源配置的取舍逻辑

先电2.4手册通常要求一个master加两个node的最小集群。为什么是三个节点而不是一个?因为K8s的核心价值在于调度和容错,单节点集群虽然能跑起来,但体现不出pod跨节点调度的效果,实训里很多验证步骤会失去意义。三个节点是能演示完整功能的最小规模。

资源配置上,我实测下来的建议是:master给2核4GB,两个node各给2核2GB。这里有个容易被忽略的点——master节点虽然不跑业务pod,但API Server、etcd、Controller Manager这些控制面组件对内存的占用并不低,给2GB很容易在集群初始化后出现组件被OOM Killer干掉的情况。我见过不止一个同学因为master只给2GB,导致kubectl get nodes时好时坏,查半天查不出原因。

磁盘方面,每台虚拟机建议不低于40GB,采用单个文件存储即可。这里要提醒一句:不要用"拆分多个文件"的选项,虽然理论上性能差异不大,但在快照和克隆时单文件更省心。另外,虚拟磁盘类型选SCSI而不是IDE,这个在安装某些Linux发行版时会影响磁盘识别。

节点角色CPU核心内存磁盘说明
master24GB40GB控制面组件内存占用高,不建议低于4GB
node122GB40GB跑业务pod,可按需调整
node222GB40GB同上

2.2 NAT与桥接模式的选择,以及静态IP的必要性

VMware的网络模式有三种:桥接、NAT、仅主机。做K8s集群,我的建议是NAT模式配合静态IP。原因在于桥接模式会让虚拟机直接暴露在所在局域网里,IP由路由器分配,换一个网络环境IP就变了,集群配置全部失效;仅主机模式虽然隔离性好,但虚拟机访问不了外网,安装Docker和K8s组件时会很麻烦。NAT模式兼顾了外网访问和网络隔离,是实训环境的最优解。

选好NAT之后,关键动作是给每台虚拟机配置静态IP。K8s集群的节点之间通过IP互相通信,如果IP是DHCP动态分配的,重启后IP一变,集群直接瘫痪。具体做法是在虚拟机的网络配置文件里手动指定IP、网关和DNS。以常见的Linux发行版为例,需要修改网卡配置文件,把BOOTPROTO改成static,然后填入规划好的IP地址。

这里有个细节:VMware NAT模式的默认网段通常是192.168.x.0/24,网关是192.168.x.2。你可以通过VMware的"虚拟网络编辑器"查看具体网段。规划IP时,master用.10,node1用.11,node2用.12,这样整齐好记。配完之后一定要用ping互相测试连通性,别等到装K8s时才发现网络不通。

注意:修改网络配置后需要重启网络服务,不同发行版命令不同。改完先用ip addr确认IP生效,再测试节点间互通。

2.3 主机名与hosts文件的统一规划

这一步看起来简单,但它是后面很多问题的根源。K8s在初始化集群时会把当前主机名注册为节点名,如果主机名是默认的localhost或者重复的,集群里就会出现节点名冲突。所以每台虚拟机都要设置唯一的主机名,比如master、node1、node2。

设置完主机名后,要在所有节点的/etc/hosts文件里加上三行映射,把三个主机名和对应IP绑定。为什么必须做这一步?因为K8s组件之间通信时,有些配置用的是主机名而不是IP,如果DNS解析不了主机名,就会出现节点之间能ping通IP但集群组件连不上的诡异现象。我遇到过同学反馈"明明网络通,但node就是加入不了集群",最后查出来就是hosts文件没配全。

另外,建议把防火墙和SELinux先关掉。实训环境里这两个东西带来的麻烦远大于安全收益,它们会拦截K8s组件之间的端口通信。关闭命令执行后要确认状态,别只是执行了命令却没生效。SELinux要改配置文件并重启才能彻底关闭,临时命令重启后就恢复了。

3. Docker运行时的安装与关键配置调整

3.1 安装源选择与版本锁定

K8s从1.24版本开始不再默认支持Docker作为容器运行时,改用containerd。但先电2.4手册对应的K8s版本通常还在支持Docker的范围内,所以这套方案里Docker依然是主角。安装Docker时,我强烈建议锁定版本,不要用latest。因为Docker版本更新频繁,新版本可能和手册里的K8s版本存在兼容性问题,用最新版很容易在集群初始化时报一些莫名其妙的错。

安装步骤上,先配置好软件源,然后指定版本号安装。安装完成后用docker version确认客户端和服务端都正常。这里有个常见问题:安装完Docker后服务没有自动启动,需要手动systemctl start docker并设置开机自启。如果忘了设开机自启,虚拟机重启后Docker没起来,K8s节点就会显示NotReady。

3.2 cgroup驱动对齐:最容易被忽略的致命细节

这是整套部署里最关键的一个配置点,也是最多人踩坑的地方。K8s和Docker都需要使用cgroup来管理资源,但两者默认使用的cgroup驱动可能不一致。Docker默认用cgroupfs,而K8s的kubelet默认用systemd。如果两者不一致,集群初始化后kubelet会不断报错,节点状态不稳定。

解决办法是让Docker也使用systemd驱动。具体操作是修改Docker的配置文件,在exec-opts里加上native.cgroupdriver=systemd,然后重启Docker服务。改完之后用docker info查看Cgroup Driver那一行,确认显示的是systemd。

我为什么把这个点单独拎出来讲?因为它的症状很有迷惑性。集群能初始化成功,kubectl get nodes也能看到节点,但过一会儿节点就变成NotReady,或者pod一直起不来。很多人会去查网络、查镜像,绕一大圈才发现是cgroup驱动的问题。记住一句话:装完Docker第一件事就是改cgroup驱动,别等出问题再回头改。

3.3 镜像加速与私有仓库的提前准备

K8s初始化时需要拉取大量镜像,这些镜像默认从境外仓库拉取,速度慢且容易失败。所以要在Docker的配置文件里配置镜像加速地址。配置完成后重启Docker,用docker info确认加速地址已生效。

除了加速,还要提前规划好镜像的存放方式。实训环境里通常会把需要的镜像提前导入到各个节点,避免初始化时现拉。导入镜像用docker load命令,导入后用docker images确认。这里要注意:master节点和node节点需要的镜像不完全一样,node节点需要pause镜像和网络插件镜像,这些要提前准备好,否则节点加入集群后会因为拉不到镜像而卡住。

提示:镜像导入是个耗时操作,建议在集群初始化前统一完成,不要在初始化过程中临时导入,容易和初始化流程冲突。

4. K8s集群初始化:参数背后的门道

4.1 kubeadm初始化命令的参数拆解

集群初始化用kubeadm init命令,这条命令的参数决定了集群的基本形态。手册里通常会给出完整的命令,但很多人是照着敲完就完事,不知道每个参数是干什么的。我挑几个关键参数说一下。

--apiserver-advertise-address指定API Server对外通告的地址,一般填master节点的IP。这个参数如果填错,node节点就找不到master。--pod-network-cidr指定pod网段,这个网段不能和宿主机网段、节点网段重叠,否则会出现路由冲突。--kubernetes-version指定K8s版本,要和安装的kubelet、kubectl版本一致,版本不匹配会导致初始化失败。

初始化命令执行后,如果成功,会输出一段join命令,这段命令是node节点加入集群用的,一定要保存下来。如果没保存,可以用kubeadm token create --print-join-command重新生成。我建议初始化成功后立刻把join命令复制到一个文本文件里,别指望自己记得住。

4.2 初始化失败的常见原因与排查顺序

初始化失败是常态,一次就成功反而是运气好。我总结了一个排查顺序,按这个顺序查基本能覆盖大部分问题。

先看kubelet的状态,用systemctl status kubelet查看。如果kubelet没起来,集群肯定初始化不了。kubelet起不来的常见原因是cgroup驱动没对齐,或者配置文件有语法错误。再看journalctl -u kubelet的日志,里面会有具体的报错信息。

如果kubelet正常,但初始化还是失败,就要看是不是镜像没拉全。用kubeadm config images list列出需要的镜像,然后逐个用docker images核对,缺哪个补哪个。还有一种情况是端口被占用,K8s需要用到6443、10250等端口,如果这些端口被其他程序占了,初始化也会失败,用netstat查一下。

最后一种比较隐蔽的问题是时间不同步。集群节点之间的时间差如果太大,证书校验会失败。实训环境里虚拟机长时间挂起再恢复,时间容易漂移,建议装个时间同步工具,或者手动校准一下。

4.3 网络插件的选择与安装时机

K8s本身不提供网络功能,需要安装网络插件才能让pod之间通信。常用的有Flannel、Calico等。先电2.4手册里一般指定了某一种,照着装就行。这里要强调的是安装时机:必须在master初始化成功之后、node加入之前安装网络插件。如果顺序反了,node加入后网络不通,pod之间ping不通,排查起来很麻烦。

安装网络插件就是应用一个yaml文件,用kubectl apply -f执行。执行后等一会儿,用kubectl get pods -n kube-system查看插件pod是否Running。如果一直是Pending或者CrashLoopBackOff,多半是镜像没拉下来或者网段配置和初始化时的--pod-network-cidr不一致。

网络插件装好后,用kubectl get nodes确认master状态是Ready。注意,刚初始化完master可能是NotReady,装了网络插件后才变Ready,这是正常的。

5. node节点加入集群与状态验证

5.1 join命令执行前后的检查清单

node节点加入集群前,有几件事必须先确认。第一,node节点上的Docker已经装好且cgroup驱动已改为systemd。第二,node节点能ping通master的IP。第三,node节点的时间与master同步。第四,node节点上已经导入了必要的镜像。这四条任何一条不满足,join都会失败或者加入后状态异常。

确认无误后,在node节点上执行之前保存的join命令。命令执行过程中会拉取配置、启动kubelet,最后输出"This node has joined the cluster"。看到这行字不代表万事大吉,还要回到master上用kubectl get nodes查看节点状态。刚加入的节点通常是NotReady,需要等一会儿让网络插件完成配置。

如果节点加入后一直NotReady,先在master上kubectl describe node 节点名查看事件,再到node节点上systemctl status kubelet和journalctl -u kubelet查日志。最常见的还是cgroup驱动和镜像问题。

5.2 节点Ready之后必须做的几项验证

节点变成Ready只是第一步,还要验证集群是否真的可用。我通常会做三个验证。第一,用kubectl run起一个测试pod,看它能不能正常调度到node上并Running。第二,用kubectl get pods -o wide查看pod被调度到了哪个节点,确认调度器工作正常。第三,如果起了多个pod,测试pod之间的网络连通性。

还有一个容易被忽略的验证是DNS。K8s集群内的服务发现依赖CoreDNS,用kubectl get pods -n kube-system确认CoreDNS的pod是Running。如果CoreDNS起不来,后面部署应用时服务之间互相访问会失败。

5.3 集群重置与快照策略

实训环境里集群经常需要推倒重来,所以掌握重置方法很重要。master节点用kubeadm reset,node节点也用同样的命令,然后清理残留的配置文件和网络规则。重置后可以重新初始化,不用重装系统。

但更高效的做法是用VMware快照。在集群初始化成功、验证通过之后,给每台虚拟机打一个快照。后面如果环境搞乱了,直接恢复到快照点,几分钟就能回到干净状态,比重新部署快得多。我建议至少打两个快照:一个是系统装好、Docker配好但还没初始化集群的状态;另一个是集群完全就绪的状态。这样无论是想重做集群还是想跳过基础配置,都有对应的恢复点。

注意:快照会占用磁盘空间,虚拟机运行时间越长快照越大。建议在关键节点打快照,不要频繁打,用完及时清理不需要的快照。

6. 部署过程中那些手册不会告诉你的坑

6.1 虚拟机克隆导致的IP和主机名冲突

很多人为了省事,装好一台虚拟机后直接克隆出另外两台。克隆本身没问题,但克隆出来的虚拟机网卡MAC地址会变,而网络配置文件里如果绑定了MAC地址,网卡就起不来。另外,克隆机的IP和主机名和源机一样,直接启动会导致IP冲突。

正确的做法是:克隆后先修改网络配置文件里的MAC地址绑定(或者删掉这行),再改IP和主机名,然后重启网络。主机名修改后要重启虚拟机才能完全生效,因为有些服务在启动时读取主机名并缓存。我见过同学改了主机名但没重启,结果K8s里注册的节点名还是旧的,怎么都对不上。

6.2 内存不足引发的连锁反应

前面提过master节点内存不能低于4GB,这里再展开说一下内存不足的表现。内存不足时,系统会触发OOM Killer,随机杀掉占用内存高的进程。在K8s环境里,被杀掉的可能是etcd、可能是API Server,表现就是集群时好时坏,kubectl命令有时响应有时超时。这种间歇性故障最难排查,因为等你去看的时候进程可能已经被重启了。

除了master,node节点内存不足会导致pod被驱逐。如果你发现pod莫名其妙消失,先查一下节点内存。实训环境里跑几个nginx pod就占不少内存,2GB的node跑不了太多东西,心里要有数。

6.3 时间漂移与证书过期

虚拟机有个特性:挂起后再恢复,系统时间可能还停留在挂起那一刻。如果挂起时间长了,恢复后时间差可能达到几小时甚至几天。K8s的证书和token都是有有效期的,时间差太大会导致证书校验失败,节点掉线。

解决办法是装时间同步服务,让虚拟机定期和外部时间源同步。如果环境不允许访问外部时间源,至少在每次恢复虚拟机后手动校准一下时间。这个问题在短期实训里不明显,但如果你的虚拟机放几天再打开,大概率会遇到。

6.4 镜像拉取失败的几种伪装

镜像拉取失败的表现不一定是明确的"pull failed",有时候是pod一直Pending,有时候是pod反复重启。排查时先用kubectl describe pod看事件,如果事件里提到ImagePullBackOff或者ErrImagePull,就是镜像问题。

镜像问题分几种:一是镜像名写错了,这种最好查;二是镜像在仓库里不存在,可能是版本号写错;三是网络不通拉不下来,这种要检查节点的网络和镜像加速配置;四是镜像存在但架构不匹配,比如在ARM机器上拉了AMD64的镜像。实训环境一般是AMD64,但如果你用的是某些特殊硬件,要注意这一点。

7. 环境跑通之后的延伸玩法

集群跑通只是起点,这套环境还能做很多事。最直接的是部署一个实际应用,比如用Deployment起几个副本的web服务,再用Service暴露出来,完整体验一遍K8s的部署流程。这比单纯看节点状态有意义得多,也能暴露更多配置问题。

再进一步可以玩滚动更新和回滚。改一下Deployment的镜像版本,观察pod是如何逐个替换的,然后回滚到上一个版本。这个过程中能直观看到K8s的调度和自愈能力。如果时间充裕,还可以试试配置ConfigMap和Secret,把应用的配置和敏感信息从镜像里剥离出来。

对于做实训的同学,我建议把整个部署过程整理成文档,把每一步的命令、输出、遇到的问题和解决办法都记下来。这份文档的价值远大于集群本身,因为部署命令网上到处都有,但你自己踩过的坑和总结的排查思路是独一无二的。下次再搭环境,或者换一套环境搭,这份文档就是你的操作手册。

最后说一个我自己的习惯:每次部署完成后,用kubectl get all --all-namespaces把集群里所有资源列一遍,看看有没有异常的pod或者服务。这个命令能让你对集群的整体状态有个全局把握,比逐个namespace查要高效。养成这个习惯,很多问题能在萌芽阶段就被发现。

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

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

立即咨询