kubeadm部署Kubernetes v1.23.1集群实战:VMware多节点环境搭建与避坑指南 1. 为什么还要聊kubeadm部署K8s这件事kubeadm部署Kubernetes-v1.23.1的集群这个组合放在今天来看不算最新但绝对算得上经典。我之所以想把这套流程完整梳理一遍是因为在过去很长一段时间里无论是自己搭测试环境还是帮朋友排查集群问题kubeadm始终是我最愿意推荐给入门者的方式。它不像二进制手工部署那样每一步都要自己拼装证书和systemd单元也不像某些封装过度的发行版把细节全藏起来出了问题无从下手。kubeadm刚好卡在一个舒服的位置核心流程标准化关键环节可干预学完一遍能真正理解一个K8s集群是怎么从零长出来的。这次选择v1.23.1这个版本也有实际考量。它属于1.23的早期补丁版本API相对稳定容器运行时接口已经全面转向containerd对Docker的依赖彻底成为过去式。同时这个版本对系统内核、cgroup驱动、镜像仓库配置的要求都比较明确踩坑点集中非常适合拿来练手。配合VMware Workstation做虚拟化一台宿主机就能模拟出完整的多节点集群网络、存储、调度都能真实跑起来成本低可复现性强。这篇文章适合谁看如果你是刚接触K8s的运维或开发想在自己电脑上搭一套能用的集群来学习Pod、Service、Deployment这些概念那这套流程可以直接抄作业。如果你已经用过minikube或kind但觉得它们太“玩具”想体验更接近生产环境的多节点部署那kubeadmVMware的组合会让你对节点通信、网络插件、证书管理有更具体的感知。甚至如果你只是好奇“一个K8s集群到底需要哪些组件”跟着走一遍答案自然就出来了。我下面会从整体设计思路开始拆然后逐个环节讲清楚每一步在干什么、为什么这么干、容易在哪里翻车。所有操作都基于我在VMware环境里反复验证过的流程参数和命令可以直接复制使用但背后的逻辑我会尽量说明白这样你遇到变体场景也能自己调整。2. 集群整体设计与环境规划思路2.1 为什么选kubeadm而不是其他部署方式Kubernetes的部署方式大致可以分成几类二进制手工部署、kubeadm、以及各种托管或封装发行版。二进制部署最灵活但门槛极高光是证书签发和组件启动顺序就能劝退大部分人。托管发行版最省心但你把集群交给别人管学习价值就打折了。kubeadm的设计目标很明确用标准化的步骤完成集群引导同时把关键配置暴露出来让你能改。具体来说kubeadm帮你做了这几件事生成集群所需的各类证书和kubeconfig文件、拉起控制平面静态Pod、生成bootstrap token让工作节点加入、安装核心插件CoreDNS、kube-proxy。它不负责的事也很清楚不装容器运行时、不装网络插件、不装存储插件。这种“半自动”的边界感恰恰是它适合学习的原因——你既不用从零造轮子又必须理解每个外部依赖的作用。v1.23.1这个版本还有一个特点就是它对容器运行时的要求已经明确为containerd或CRI-ODocker Shim的过渡期基本结束。这意味着你在部署时必须先装好containerd并配置好CRI插件这个环节在早期版本里经常被跳过但在1.23里是硬性前提。我选择containerd而不是CRI-O主要是因为containerd的配置更直观和kubeadm的集成文档也更丰富排查问题时社区资料多。2.2 VMware环境下的节点规划与资源分配在VMware Workstation里搭集群第一件事是决定节点数量和角色分配。最小可用集群是1个控制平面1个工作节点但这样你没法体验调度和故障转移。我建议至少1个控制平面2个工作节点这样能跑一些带副本的Deployment观察Pod在不同节点上的分布。如果宿主机内存够控制平面给2核4G工作节点各给2核4G总共12G内存现代笔记本基本能扛住。如果内存紧张控制平面可以降到2核3G工作节点2核2G但跑稍重的应用会吃力。网络规划是第二个关键点。VMware默认的NAT网络能让虚拟机访问外网但宿主机访问虚拟机需要端口转发多节点之间通信倒是没问题。我习惯用仅主机模式Host-Only加一个NAT网卡仅主机网段用于节点间通信和宿主机管理NAT网卡用于拉取镜像和安装包。这样网络边界清晰也不会因为宿主机网络变化导致集群失联。具体网段我一般用192.168.100.0/24作为集群内网网关和DNS按VMware虚拟网络编辑器的默认配置走。节点主机名和IP的对应关系必须提前规划好因为kubeadm初始化时会用主机名注册节点后期改起来很麻烦。我通常这样分配控制平面叫k8s-masterIP是192.168.100.10工作节点叫k8s-node1和k8s-node2IP分别是192.168.100.11和192.168.100.12。所有节点都要配置静态IP不能用DHCP否则重启后IP变化会导致集群异常。主机名解析通过/etc/hosts文件完成不依赖外部DNS这样即使断网集群内部也能正常通信。2.3 系统层面的前置准备清单在装任何K8s组件之前有三件事必须做关闭Swap、配置内核参数、统一时间同步。关闭Swap是因为K8s的调度器需要准确的内存信息Swap会让内存统计失真而且kubelet默认不允许Swap开启。操作上就是swapoff -a临时关闭然后注释掉/etc/fstab里的swap行永久关闭。内核参数主要涉及网桥流量转发和iptables对桥接流量的处理需要加载br_netfilter模块并设置net.bridge.bridge-nf-call-iptables1否则Pod跨节点通信会出问题。时间同步容易被忽略但在多节点集群里很关键。证书有效期、Token过期、日志时间戳都依赖系统时间一致。VMware虚拟机挂起后时间容易漂移我一般装chrony或ntpdate配置一个可靠的时间源确保所有节点时间差在秒级以内。另外防火墙规则要放行K8s所需端口或者干脆在实验环境里关掉firewalld和ufw减少干扰。SELinux在CentOS系上也要设为permissive或disabled否则containerd挂载卷时会遇到权限拒绝。这些准备工作看起来琐碎但每一步没做到位后面都会以奇怪的方式报错。比如Swap没关kubelet直接起不来内核参数没设CoreDNS的Pod会一直Pending时间不同步节点加入集群时Token验证失败。我踩过最坑的一次是主机名里有大写字母kubeadm初始化时报错说主机名不符合DNS规范排查了半天才发现。所以下面我会把每一步的命令和验证方法都写清楚你照着做就能避开这些雷。3. 核心组件安装与配置细节3.1 containerd的安装与CRI配置要点containerd是v1.23.1版本默认推荐的容器运行时安装方式有两种用系统包管理器装或者下载官方二进制包手动配置。我倾向于用包管理器因为依赖关系自动处理升级也方便。在Ubuntu上就是apt install containerd在CentOS上需要先加Docker的repo再yum install containerd.io。装完之后别急着启动先改配置因为默认配置里CRI插件是禁用的SystemdCgroup也是关的这两个不改kubelet连不上containerd。配置文件在/etc/containerd/config.toml如果没有就执行containerd config default /etc/containerd/config.toml生成一份。然后找到[plugins.io.containerd.grpc.v1.cri]这一段把sandbox_image改成国内能访问的镜像地址比如registry.aliyuncs.com/google_containers/pause:3.6。这个pause镜像很重要每个Pod都会用它做沙箱如果拉不下来Pod永远起不来。接着找到SystemdCgroup这一项设为true因为kubelet默认用systemd管理cgroupcontainerd如果不一致资源限制会失效。改完配置后重启containerd然后用crictl ps检查一下能不能正常通信。crictl需要单独装或者用containerd自带的ctr命令但ctr的命名空间和K8s不一致容易混淆所以我建议装crictl并配置/etc/crictl.yaml指向unix:///run/containerd/containerd.sock。这一步验证通过说明容器运行时层面没问题了可以进入K8s组件的安装。3.2 kubelet、kubeadm、kubectl的版本锁定与安装这三个组件的版本必须和你要部署的K8s版本匹配v1.23.1对应的就是1.23.1-00这个包版本。安装时一定要指定版本号否则包管理器会装最新版导致版本偏差。在Ubuntu上先加K8s的apt源然后apt install kubelet1.23.1-00 kubeadm1.23.1-00 kubectl1.23.1-00。在CentOS上加yum源后类似操作。装完后用systemctl enable kubelet设成开机自启但先别start因为kubelet需要kubeadm初始化后生成的配置文件才能正常运行。这里有个细节kubelet的包安装后会尝试启动但因为缺少/etc/kubernetes/kubelet.conf而失败这是正常的。你可以用systemctl status kubelet看到它处于activating或failed状态不用管等kubeadm init跑完再来看。另外kubelet默认会从kubeadm生成的配置里读取容器运行时端点所以containerd的sock路径必须是/run/containerd/containerd.sock如果你改过containerd的sock位置kubelet这边也要对应调整。kubectl是命令行工具装在控制平面节点上就行工作节点可以不装。但为了方便排查我习惯所有节点都装kubectl配置好kubeconfig后可以直接在工作节点上查集群状态。kubeconfig文件在kubeadm init之后会生成在/etc/kubernetes/admin.conf复制到~/.kube/config就能用。注意权限普通用户读不了admin.conf需要sudo cp并改属主。3.3 镜像预拉取与国内源加速策略kubeadm init默认会去k8s.gcr.io拉取控制平面组件的镜像这个地址在国内基本不可达。解决办法有两种一是初始化时用--image-repository参数指定国内镜像仓库比如registry.aliyuncs.com/google_containers二是提前用kubeadm config images pull拉取但同样要指定仓库。我推荐第一种直接在init命令里带上省事。具体命令是kubeadm init --image-repositoryregistry.aliyuncs.com/google_containers --kubernetes-versionv1.23.1这样所有镜像都从阿里云拉速度快且稳定。containerd本身也需要配置镜像加速否则拉业务Pod的镜像会很慢。在config.toml里找到[plugins.io.containerd.grpc.v1.cri.registry.mirrors]这一段添加docker.io的镜像地址比如https://registry.cn-hangzhou.aliyuncs.com。注意containerd 1.x的配置格式和2.x不同1.23.1配套的containerd通常是1.5.x或1.6.x配置写法是registry.mirrors.docker.io.endpoint。改完重启containerd用crictl pull拉个nginx镜像测试一下速度。镜像预拉取还有一个好处就是能提前发现网络问题。如果拉取失败你可以从容排查是DNS问题、证书问题还是仓库地址问题而不是在kubeadm init跑到一半时卡住。我一般会在所有节点上先手动拉取pause镜像和几个常用镜像确认containerd工作正常再执行集群初始化。4. 控制平面初始化与工作节点加入4.1 kubeadm init关键参数解析与执行kubeadm init是整个部署过程中最核心的一步参数选对了后面顺风顺水参数错了可能要推倒重来。除了前面说的--image-repository和--kubernetes-version还有几个参数值得关注。--pod-network-cidr用来指定Pod网段这个必须和后面要装的网络插件匹配。我习惯用10.244.0.0/16这是Flannel的默认网段如果你用Calico可以用192.168.0.0/16但要注意别和宿主机网段冲突。--apiserver-advertise-address指定API Server对外通告的地址多网卡机器上必须显式指定否则kubeadm可能选错网卡。在VMware环境里如果你有仅主机和NAT两块网卡一定要指定仅主机网段的IP这样工作节点才能通过内网访问API Server。--control-plane-endpoint用于高可用场景单控制平面可以不写但写了也不影响可以指向控制平面的IP或一个解析到该IP的域名。完整的初始化命令大概长这样kubeadm init --image-repositoryregistry.aliyuncs.com/google_containers --kubernetes-versionv1.23.1 --pod-network-cidr10.244.0.0/16 --apiserver-advertise-address192.168.100.10 --cri-socketunix:///run/containerd/containerd.sock。执行后你会看到一系列检查项如果前面准备到位应该能顺利跑完最后输出kubeadm join命令和配置kubectl的提示。初始化完成后按照提示执行mkdir -p $HOME/.kube、sudo cp /etc/kubernetes/admin.conf $HOME/.kube/config、sudo chown $(id -u):$(id -g) $HOME/.kube/config。然后kubectl get nodes应该能看到控制平面节点处于NotReady状态这是正常的因为网络插件还没装。kubectl get pods -n kube-system能看到CoreDNS和kube-proxy的Pod但CoreDNS可能还在Pending同样是因为网络没通。4.2 网络插件的选择与Flannel部署实操网络插件是K8s集群的血管没有它Pod之间无法通信CoreDNS也起不来。常见的选择有Flannel、Calico、Weave Net等。Flannel最简单配置少适合学习和测试环境。Calico功能更强支持网络策略但配置稍复杂。在VMware环境里我推荐先用Flannel把集群跑通理解基本网络模型后再尝试Calico。Flannel的部署就是apply一个yaml文件kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml。但这个地址在国内可能访问不了可以先把文件下载下来或者用国内镜像源。apply之后Flannel会在每个节点上以DaemonSet形式跑一个Pod负责配置路由和VXLAN隧道。等所有Flannel Pod变成RunningCoreDNS也会跟着变成Running这时候kubectl get nodes应该显示所有节点Ready。验证网络是否正常可以起一个测试Pod然后从另一个Pod ping它。我一般用kubectl run test1 --imagebusybox --command -- sleep 3600再run一个test2然后kubectl exec -it test2 -- ping test1的IP。如果能通说明Pod网络没问题。如果ping不通先检查Flannel Pod日志常见问题是网卡选错、VXLAN端口被防火墙拦截、或者pod-network-cidr和Flannel配置不一致。4.3 工作节点加入集群与Token管理工作节点加入集群靠的是kubeadm init输出的kubeadm join命令里面包含一个bootstrap token和CA证书的哈希值。这个token默认24小时有效过期后需要重新生成。重新生成用kubeadm token create --print-join-command它会输出一条新的join命令直接复制到工作节点执行即可。注意如果CA证书哈希也变了一般不会需要重新获取。在工作节点上执行join命令前确保containerd已装好并配置正确kubelet、kubeadm已安装且版本匹配。join命令执行后会做一系列检查包括网络连通性、容器运行时可用性、版本兼容性等。如果卡在某个检查项根据提示排查。常见问题是工作节点无法访问控制平面的6443端口或者containerd的sock路径不对。join成功后在控制平面执行kubectl get nodes就能看到新节点状态先是NotReady等Flannel Pod跑起来后变成Ready。Token管理还有一个细节如果节点需要重新加入比如重装了系统要先在控制平面删除旧节点记录kubectl delete node k8s-node1然后再执行join。否则会出现节点名冲突新节点无法正常注册。另外如果集群里节点多了建议用kubeadm token list查看所有有效token及时清理不用的减少安全风险。5. 集群验证与常见故障排查5.1 集群健康状态检查清单集群搭好后别急着跑应用先做一轮全面体检。第一步是kubectl get nodes -o wide确认所有节点Ready版本号一致内网IP正确。第二步是kubectl get pods -n kube-system确认所有系统Pod都是Running没有反复重启的。重点看CoreDNS、kube-proxy、Flannel这三个它们直接决定集群能不能用。第三步是kubectl cluster-info确认API Server地址和DNS服务地址正常。第四步是功能验证起一个Deployment副本数设为2看Pod能不能分布到不同节点能不能正常拉镜像、启动、就绪。然后创建一个Service用ClusterIP访问再试试NodePort从宿主机访问。如果这些都通了说明集群基本可用。第五步是持久化验证如果你装了存储插件可以创建一个PVC挂到Pod里写点数据删Pod重建后看数据还在不在。这一步能暴露存储配置的问题。我习惯把检查命令整理成一个脚本每次集群有变动就跑一遍。比如kubectl get componentstatuses虽然1.23里已经废弃但可以用kubectl get --raw/readyz?verbose替代、kubectl top nodes需要metrics-server、kubectl get events --sort-by.lastTimestamp看最近事件。这些命令能帮你快速定位异常不用一个个Pod去翻日志。5.2 典型报错与快速定位方法部署过程中最常见的报错集中在几个环节。第一个是kubelet起不来报错“failed to run Kubelet: misconfiguration: kubelet cgroup driver: cgroupfs is different from docker cgroup driver: systemd”。这是containerd的SystemdCgroup没设成true导致的改配置重启containerd即可。第二个是Pod一直Pendingdescribe一下看Events如果是“no nodes available”说明节点没Ready如果是“insufficient memory”说明资源不够如果是“failed to pull image”说明镜像拉取有问题。第三个是CoreDNS一直CrashLoopBackOff日志里报“connection refused”或“no route to host”这通常是网络插件没装好或网段冲突。检查Flannel的pod-network-cidr和kubeadm init时指定的--pod-network-cidr是否一致检查节点间VXLAN端口8472是否通。第四个是节点加入失败报“failed to request cluster-info”一般是Token过期或API Server地址不可达。用kubeadm token create重新生成确认工作节点能curl通控制平面的6443端口。第五个是kubectl命令报“The connection to the server localhost:8080 was refused”说明kubeconfig没配好。检查~/.kube/config是否存在内容是否指向正确的API Server地址。如果是普通用户确认权限没问题。这些报错我几乎都遇到过每次排查完都会记下来下次再遇到直接查表效率高很多。5.3 节点NotReady的排查路径节点NotReady是最常见也最让人头疼的问题因为原因可能有很多。我的排查路径是这样的先kubectl describe node 节点名看Conditions部分找到Ready为False的原因。如果是“KubeletNotReady”往下看Message通常会提示具体问题比如“container runtime not ready”或“network plugin not ready”。如果是“NetworkUnavailable”说明网络插件没配好。然后上到该节点systemctl status kubelet看服务状态journalctl -u kubelet -f看实时日志。日志里常见的错误有无法连接containerdsock路径错或containerd没起、无法加载证书证书过期或路径不对、无法注册节点API Server不可达或Token无效。如果是containerd问题crictl ps看容器状态crictl logs看容器日志。如果是网络问题ip addr看cni0和flannel.1接口是否存在ip route看路由表是否正常。还有一个隐蔽的问题节点时间不同步导致证书验证失败。用date命令对比各节点时间如果差超过几秒就装chrony同步。另外如果节点是克隆出来的虚拟机machine-id可能重复导致kubelet注册时冲突。解决办法是清空/etc/machine-id并重新生成或者用sysprep工具处理。这个坑我在VMware里踩过好几次克隆完虚拟机一定要改machine-id和主机名。5.4 常见问题速查表问题现象可能原因排查命令解决方法kubelet启动失败cgroup驱动不一致journalctl -u kubelet设containerd的SystemdCgrouptruePod一直Pending节点未Ready或资源不足kubectl describe pod检查节点状态和资源配额CoreDNS CrashLoop网络插件未就绪kubectl logs -n kube-system重装Flannel检查网段节点加入失败Token过期或网络不通kubeadm token list重新生成Token检查6443端口kubectl连接拒绝kubeconfig未配置ls ~/.kube/config复制admin.conf并改权限镜像拉取失败仓库地址不可达crictl pull配置镜像加速或换仓库节点NotReady多种原因kubectl describe node按Conditions逐项排查Pod间不通VXLAN端口被拦ping测试放行8472端口或关防火墙这张表是我自己整理的每次遇到问题先查表能解决八成以上的常见故障。剩下的两成需要看日志深入分析但有了这张表至少能快速缩小范围不至于像无头苍蝇一样乱撞。6. 部署后的调优与日常维护建议6.1 资源限制与调度策略调整集群跑起来之后如果不做任何限制某个Pod可能吃掉整个节点的资源导致其他Pod被挤掉。所以第一步是给系统组件和业务Pod都加上资源请求和限制。系统组件比如CoreDNS、kube-proxy可以在它们的Deployment或DaemonSet里加resources字段设置requests和limits。业务Pod在yaml里也要写requests影响调度limits影响运行时上限。我一般给CoreDNS设requests 100m CPU、70Mi内存limits 200m CPU、170Mi内存这个配置在测试环境够用。调度策略方面可以给节点打标签然后用nodeSelector或affinity控制Pod分布。比如把控制平面节点标记为node-role.kubernetes.io/master:NoSchedule默认不调度业务Pod这样控制平面更稳定。如果想让某些Pod只跑在特定节点可以给节点打自定义标签比如disktypessd然后在Pod里指定nodeSelector。另外PodDisruptionBudget可以保证在节点维护时关键Pod的副本数不低于某个值避免服务中断。资源配额方面可以用ResourceQuota限制命名空间的总资源用LimitRange设置默认的requests和limits。这样即使开发人员忘了写资源限制也有兜底。我习惯给每个团队或项目建一个命名空间配上ResourceQuota防止某个项目把集群资源耗尽。这些配置在测试环境可能显得多余但养成习惯后迁移到生产环境会省很多事。6.2 证书有效期与集群备份策略kubeadm生成的证书默认有效期是一年到期后集群会不可用。查看证书有效期用kubeadm certs check-expiration续期用kubeadm certs renew all。续期后需要重启控制平面组件也就是重启kubelet和静态Pod。我建议在证书到期前一个月就做续期别等到最后一天。如果集群规模大可以写个定时任务自动检查并提醒。另外kubeconfig文件里的客户端证书也会过期续期后要重新生成kubeconfig并分发。集群备份主要备两样东西etcd数据和证书文件。etcd备份用etcdctl snapshot save指定endpoint和cacert、cert、key。备份文件要存到集群外部比如宿主机或对象存储。证书文件在/etc/kubernetes/pki目录整个目录打包备份。恢复的时候先停掉控制平面组件用etcdctl snapshot restore恢复数据再把证书放回原位重启组件。这个过程我演练过几次只要备份文件在恢复不难但一定要提前练别等真出事了才翻文档。日常维护还包括日志轮转和监控。K8s组件日志默认由journald管理可以配置journald的轮转策略避免日志占满磁盘。监控方面metrics-server是基础装上后kubectl top nodes和kubectl top pods就能用。再进一步可以上PrometheusGrafana但那是另一个话题了。对于学习环境metrics-server加简单的告警就够了。6.3 从测试环境到生产环境的差距VMware里搭的这套集群和真正的生产环境比差距主要在几个方面。第一是高可用生产环境的控制平面至少三个节点etcd也是集群模式任何单点故障都不影响集群。第二是网络生产环境通常用Calico或Cilium支持网络策略和更高效的转发Flannel的VXLAN在规模大时性能会下降。第三是存储生产环境需要分布式存储如Ceph或云厂商的块存储本地盘只能做测试。第四是安全生产环境要开RBAC、NetworkPolicy、PodSecurityPolicy还要做镜像扫描和审计日志。但话说回来在VMware里用kubeadm搭集群目的不是直接上生产而是理解K8s的运作机制。你把单节点控制平面玩透了再去看高可用方案会发现核心逻辑是一样的只是多了几个节点和负载均衡器。你把Flannel的网络模型搞清楚了再看Calico的BGP模式也能更快上手。所以别小看这套测试环境它是最佳的学习跳板。我个人在实际操作中的体会是kubeadm部署K8s最难的不是命令本身而是对每个组件作用的理解。你知道了为什么要关Swap、为什么要配cgroup驱动、为什么要指定pod-network-cidr后面遇到任何变体都能自己推导。这套流程我反复搭过很多次每次都能发现之前忽略的细节比如containerd的配置格式变化、kubelet的启动顺序、证书的依赖关系。建议你也多搭几遍第一遍照着做第二遍试着改参数第三遍尝试排错三遍下来K8s集群部署这件事就算真正掌握了。