最近在整理内部测试环境,需要在虚拟化平台上搭一套能日常练手和跑demo的Kubernetes集群。原本以为K8s集群部署已经是很成熟的流程,闭着眼都能装完,结果真正从VMware里创建虚拟机开始走了一遍才发现,坑全藏在细节里:网络模式选错导致节点之间互不通、镜像拉不下来卡在初始化、cgroup驱动不一致导致kubelet起不来、CNI插件没装对节点一直NotReady。这篇文章就是我把整套流程从头到尾亲手验证之后整理出来的,用VMware Workstation创建了三台Ubuntu 22.04虚拟机,一台master两台node,采用kubeadm方式部署Kubernetes 1.28版本,最后还装上了K8s Dashboard并跑通了一个测试应用。每一步都给出具体命令和操作理由,按这个顺序做下来可以少走很多弯路,适合正在准备本地集群环境、想快速跑通一套可用K8s的开发者参考。
1. 虚拟机创建前的规划:网络模式、资源分配和IP设计
1.1 网络模式决定集群搭建的成败
在VMware里搭建K8s集群,第一个要决定的不是装什么系统,而是虚拟机使用哪种网络模式。VMware Workstation提供桥接模式、NAT模式和仅主机模式,不同模式直接影响三台节点之间能否互通、外网能否访问镜像仓库。
我在第一次尝试时直接用了桥接模式,想着让三台机器跟宿主机处于同一局域网,IP由家里的路由器DHCP分配,结果第二天重新开机后发现节点IP全变了,集群直接分崩离析。桥接模式下IP取决于所连接网络的DHCP租约,在办公环境或频繁切换Wi-Fi的场所非常不稳定。
如果只是本机自己练习、不要求集群被局域网内其他机器访问,最稳妥的方案是用NAT模式,配合VMware自带的虚拟网络编辑器固定网段,然后在虚拟机内配置静态IP。NAT模式的宿主机网络结构比较稳定,所有虚拟机共享一个虚拟NAT网关,三台机器天然处于同一个二层网络,这正好满足K8s集群对节点网络的基本要求。
1.2 节点资源规划:最低配置和推荐配置
K8s集群对硬件资源不算太苛刻,但如果虚拟机配置得太低,初始化阶段镜像拉取、组件启动都会变得非常慢,甚至直接超时失败。我这次给每个节点分配了2核CPU、4GB内存和40GB磁盘,三台虚拟机一共占用宿主机6核、12GB以上的内存。
资源规划上有一条经验之谈:master节点建议内存不要低于4GB,否则etcd和kube-apiserver在系统负载上来之后容易出现内存压力;node节点2GB是下限,但如果还要在上面跑业务容器,4GB会更从容。磁盘方面20GB装完系统外加容器镜像会比较紧张,建议40GB起步,避免后续拉几个镜像就把根分区写满。
| 节点角色 | CPU | 内存 | 磁盘 | 数量 |
|---|---|---|---|---|
| master | 2核 | 4GB | 40GB | 1 |
| node工作节点 | 2核 | 4GB | 40GB | 2 |
创建虚拟机时有个容易被忽略的细节:内存分配需要在创建向导里一次性设置好,虚拟机创建完成后再改内存,需要关机操作。系统镜像选择Ubuntu 22.04 Server版,安装过程比较直观,选择默认的LVM分区方案即可,不需要额外定制。
1.3 IP规划与主机名设计
我采用NAT模式,在VMware的虚拟网络编辑器中确认了当前NAT网络的网段。这里要说明一下,不同电脑上VMware生成的NAT网段可能不一样,实际操作时以自己机器上显示为准。我的三台虚拟机IP规划如下:
| 主机名 | IP地址 | 角色 |
|---|---|---|
| master | 192.168.137.10 | 控制平面节点 |
| node1 | 192.168.137.11 | 工作节点 |
| node2 | 192.168.137.12 | 工作节点 |
安装系统时可以在安装向导里预设主机名,也可以在系统安装完成后通过命令设置。比较推荐在安装阶段就分别命名为master、node1、node2,避免后面混淆。
系统装好第一件事,是把三台机器的IP地址从DHCP改为静态。Ubuntu 22.04使用netplan管理网络配置,编辑/etc/netplan/00-installer-config.yaml文件:
network: ethernets: ens33: dhcp4: false addresses: - 192.168.137.10/24 routes: - to: default via: 192.168.137.2 nameservers: addresses: - 192.168.137.2 version: 2网卡名称以实际环境为准,可能是ens33、ens160或eth0,用ip addr命令查看确认即可。配置完成后执行sudo netplan apply使配置生效,然后用ip addr验证IP是否变成预期值。
关于/etc/hosts解析,三台节点都要配置相同的条目,保证节点之间通过主机名可以互相访问:
192.168.137.10 master 192.168.137.11 node1 192.168.137.12 node2这一步经常被忽略,但kubeadm初始化之后,kubelet会通过主机名或API服务器地址来访问Master节点,解析不通会造成各种莫名奇妙的连接问题。
2. 系统基础配置:关闭swap、防火墙和调整内核参数
2.1 更新软件源并安装基础工具
三台虚拟机系统装好后,先别急着部署K8s,先把操作系统本身做一遍基础配置。先用root身份登录,或者用创建用户加sudo提权。
首先更新软件包索引并升级系统:
sudo apt-get update sudo apt-get upgrade -y升级时间取决于网络状况,可能需要几分钟到十几分钟不等。然后安装一些后面会用到的工具:
sudo apt-get install -y apt-transport-https ca-certificates curl gpg这里安装apt-transport-https是为了让apt支持从HTTPS源下载软件包,ca-certificates则用于信任HTTPS证书。如果跳过这一步,后面在添加K8s软件源时很可能会报证书或传输协议相关的错误。
2.2 关闭swap和防火墙:为什么K8s明确要求
Kubernetes对节点有个硬性要求:必须关闭swap分区。kubelet默认情况下不允许在swap内存上工作,因为K8s的资源模型是基于CPU和内存配额管理的,swap的存在会让资源使用量统计出现偏差,影响调度判断。不关闭swap的情况下启动kubelet,服务会直接报错退出。
关闭swap:
sudo swapoff -a sudo sed -i '/ swap / s/^/#/' /etc/fstab第一条命令立刻卸载当前系统里已经启用的swap,第二条命令注释掉fstab里的swap挂载项,防止重启之后swap卷土重来。很多人只执行第一条,重启后swap又回来了,然后一脸疑惑地开始排查各种奇怪问题。
Ubuntu默认的防火墙是ufw,K8s集群内部组件需要互相访问大量端口。为了本地测试环境的便利,直接关闭ufw并把iptables的转发策略调整为允许:
sudo systemctl stop ufw sudo systemctl disable ufw在生产环境中防火墙策略通常比这个复杂,但本地VMware环境里,开防火墙带来的麻烦远大于收益,尤其当你对K8s各组件的端口要求还不熟悉时。
2.3 内核模块和sysctl参数的设置
K8s网络插件(CNI)需要Linux内核支持overlay网络和bridge过滤功能。overlay是一种容器网络层叠技术,简单理解就是让容器可以拥有跨主机的扁平虚拟网络;br_netfilter模块则确保桥接的流量也能通过iptables规则进行过滤。
创建内核模块加载配置:
sudo tee /etc/modules-load.d/k8s.conf <<EOF overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter然后配置sysctl参数,确保包转发和桥接流量处理正常工作:
sudo tee /etc/sysctl.d/k8s.conf <<EOF net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 EOF sudo sysctl --system这里面的net.ipv4.ip_forward尤其关键。Kubernetes的Pod网络和Service代理依赖IP转发来实现数据包在节点之间的路由,如果不开启,容器之间、不同节点上的Pod之间无法正常通信,即使集群初始化成功,后续也会出现Pod访问不通的问题。
2.4 设置主机名并检查时区
前面提到安装时最好把主机名设置好,如果当时没有设置,现在需要手动补上:
sudo hostnamectl set-hostname master sudo hostnamectl set-hostname node1 sudo hostnamectl set-hostname node2注意,三台机器分别执行对应的主机名命令,设置完成后最好重启一次系统,让所有内核参数、主机名配置彻底生效。时区方面保持系统默认即可,不需要额外配置统一时间,K8s组件自己会协调时间偏差,只要系统时间没有大的差异一般不会出问题。
3. 安装容器运行时与Kubernetes核心组件
3.1 安装Docker并调整cgroup驱动
Kubernetes需要容器运行时来启动Pod里的容器,目前最主流的选择还是Docker,通过containerd作为底层运行时来工作。Ubuntu 22.04的软件源里直接有Docker包,使用docker.io这个包名安装:
sudo apt-get install -y docker.io sudo systemctl enable --now docker安装好之后先用docker version检查Docker守护进程是否正常。这里有个非常重要的步骤:修改Docker的cgroup驱动为systemd。
sudo tee /etc/docker/daemon.json <<EOF { "exec-opts": ["native.cgroupdriver=systemd"], "log-driver": "json-file", "log-opts": { "max-size": "100m" } } EOF sudo systemctl daemon-reload sudo systemctl restart dockercgroup是Linux用来限制进程资源使用的机制,Docker和Kubelet各自管理cgroup,如果两者的驱动不一致,在节点运行一段时间后就会被kubelet判定为资源管理方式不匹配而触发崩溃。把Docker的cgroup驱动改成systemd,与kubelet保持一致,是官方推荐的标准配置。
关于镜像加速的问题,如果你的网络环境访问Docker Hub比较慢,可以在这个daemon.json里额外配置registry-mirrors字段,填入一个网络可达的镜像加速地址。不同地域、不同网络环境下可用的加速地址并不相同,建议根据自己实际网络情况配置,填错反而拖慢拉取速度。
3.2 添加Kubernetes软件源并安装三件套
Kubernetes的核心组件是kubeadm、kubelet、kubectl。kubeadm负责初始化集群和管理节点加入流程,kubelet是运行在每个节点上的核心代理服务,kubectl是操作集群的命令行工具,三者缺一不可。
添加Kubernetes官方APT源:
sudo curl -fsSL <K8s官方APT源的Release.key地址> | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] <K8s官方APT源地址> /' | sudo tee /etc/apt/sources.list.d/kubernetes.list sudo apt-get update这里我没有直接写出具体URL,因为不同引导文档可能推荐不同地址,而且软件源地址也可能随版本调整。实际操作时,以Kubernetes官方文档的说明为准。重点在于,源配置完成后执行apt-get update能看到Kubernetes相关软件包列表。
然后安装指定版本的kubeadm、kubelet和kubectl:
sudo apt-get install -y kubelet kubeadm kubectl安装完成后执行apt-mark hold kubelet kubeadm kubectl锁定这三个包的版本,防止意外升级导致集群组件版本不一致。Kubernetes版本升级有时会引入不兼容的API变化,在正式升级之前,锁版本是惯例操作。
安装完成后可以查看版本号:
kubeadm version kubectl version --client如果kubelet服务没有自动启动,手动执行sudo systemctl enable --now kubelet,这时候kubelet可能处于异常状态,这是正常的,因为集群还没有初始化,等kubeadm init之后它才能正常工作。
3.3 预先拉取需要的系统镜像
kubeadm init过程中会从容器镜像仓库拉取Kube API Server、etcd、CoreDNS等镜像,如果网络不稳定,初始化会卡在镜像拉取阶段很久。可以在初始化之前先手动查看需要哪些镜像:
kubeadm config images list这条命令会列出该版本K8s组件所需的全部镜像及版本号。如果默认镜像仓库访问困难,可以使用kubeadm config images pull --image-repository <可访问的镜像仓库地址>预先拉取,也可以直接在这里换成可访问的镜像仓库地址。手动先拉一遍镜像,既能验证网络是否通畅,也能让后续初始化流程更加顺畅。
4. 集群初始化与工作节点加入
4.1 master节点初始化命令详解
在master节点上执行初始化命令:
sudo kubeadm init \ --apiserver-advertise-address=192.168.137.10 \ --pod-network-cidr=10.244.0.0/16两个参数详解一下:
--apiserver-advertise-address指定API Server对外广播的IP地址,这里填master节点的静态IP。如果不指定,kubeadm会尝试自动探测本机IP,在多网卡环境下有可能探测到错误的网卡,导致工作节点无法连接API Server。
--pod-network-cidr是Pod网络的CIDR网段,这个网段必须和接下来安装的CNI插件配置保持一致。这里用10.244.0.0/16,是flannel网络插件的默认网段。
初始化过程会执行一段时间,成功后会看到类似这样的输出:
Your Kubernetes control-plane has initialized successfully!注意重点在最后两段输出:第一段是在master节点上配置kubectl的命令,不执行的话无法在本机使用kubectl命令管理集群;第二段是给工作节点加入集群用的kubeadm join命令,里面包含token和证书哈希值。
配置kubectl:
mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config这里一定要用root或者有sudo权限的用户去复制admin.conf文件到当前用户目录下,否则后面执行kubectl get nodes会因为缺少集群连接信息而报错。
4.2 镜像拉取失败和初始化中断的处理方式
初始化过程中最常见的失败点是镜像拉取超时。如果碰到这种情况,错误信息会比较明确地指出是哪个镜像拉不下来。处理思路是检查网络,弄清楚默认镜像仓库是否可达,如果不可达,就手动执行镜像预拉取,换一个可达的镜像仓库地址。
还有一种情况是初始化到一半因为参数错误失败,比如Pod网段与后面要装的CNI冲突。这时候不需要销毁虚拟机重来,先执行重置命令:
sudo kubeadm reset -f sudo rm -rf /etc/cni/net.d sudo rm -rf $HOME/.kube然后检查系统状态,再重新执行kubeadm init。重置操作不会删除已经拉取的镜像,所以第二次初始化时会快很多。
4.3 工作节点加入集群与token过期处理
master节点初始化成功后,在node1和node2上分别执行初始化完成时输出的kubeadm join命令,命令格式类似这样:
sudo kubeadm join 192.168.137.10:6443 \ --token <token> \ --discovery-token-ca-cert-hash sha256:<hash>其中<token>和<hash>是初始化时生成的真实值。执行后等待几秒钟,如果输出显示节点已加入集群,那就成功了。
如果初始化时没有来得及保存join命令,或者token过期了,不用重新初始化集群。在master节点上重新生成一条join命令:
kubeadm token create --print-join-command这条命令会生成新的token并直接打印出完整的join命令,复制到工作节点执行即可。
节点加入后,在master节点上查看集群状态:
kubectl get nodes此时大概率会看到master和两个node都处于NotReady状态,原因是没有安装CNI网络插件。这是正常的,下一步就是装网络插件。
5. 部署CNI网络插件:让节点从NotReady变成Ready
5.1 flannel和calico怎么选
CNI(Container Network Interface)是Kubernetes容器网络的接口规范,负责给每个Pod分配IP并打通跨节点通信。没有CNI插件的集群,节点之间的Pod网络是断裂的,kubelet会一直报告节点条件不满足,所以节点状态停留在NotReady。
社区里最常见的两种CNI方案是flannel和calico。flannel实现简单、资源占用低、配置方便,适合本地开发和小规模集群;calico功能更强,支持网络策略,适合对网络安全隔离要求较高的场景。
我这次本地环境选择的是flannel,原因很直接:部署只需要一条命令,对硬件要求低,默认监听端口也少,不容易踩坑。如果你的生产环境需要精细控制Pod之间的访问策略,再去考虑calico。
5.2 安装flannel并验证Pod网络
获取flannel的部署清单文件,然后应用到集群:
kubectl apply -f <flannel部署清单文件地址>部署清单文件地址以Kubernetes文档推荐或flannel项目官方发布的为准。应用前可以先查看清单内容,确认其中的Network网段是否为10.244.0.0/16,如果不是就需要下载后修改为与初始化参数一致,否则flannel会给Pod分配与预期不一致的网段,导致网络混乱。
应用完成后,flannel会以DaemonSet方式在集群每个节点上启动一个Pod,检查flannel Pod是否正常运行:
kubectl get pods -n kube-flannel -o wide等所有flannel Pod进入Running状态后,再次查看节点状态:
kubectl get nodes正常情况下,master、node1、node2都会从NotReady变为Ready。节点状态变为Ready意味着kubelet已经正常上报参数,网络插件也已就绪,Pod的调度和启动条件都满足了。
如果节点长时间停留在NotReady,查看kubelet日志是排查的第一步:
journalctl -u kubelet -f5.3 跨节点Pod通信验证
集群Ready之后,可以先简单验证一下跨节点通信能力。部署一个测试Pod并进入其中ping另一个节点的Pod IP,比如在master上执行:
kubectl run test-pod --image=nginx kubectl get pods -o wide拿到Pod的IP后,进入Pod内部访问集群的另一个节点地址。更简单的验证方式是在某个节点上部署一个Pod,然后通过Service将流量转发到另一个节点上的Pod,这个放在仪表盘部署之后再验证也不迟。
6. 安装Dashboard并跑通一个测试应用
6.1 部署Dashboard控制台
Kubernetes Dashboard是官方提供的Web管理界面,可以直观地查看节点状态、工作负载和日志。部署Dashboard用官方推荐清单文件:
kubectl apply -f <dashboard推荐部署清单文件地址>部署完成后,Dashboard相关的Pod会运行在kubernetes-dashboard命名空间下。等待Pod全部运行正常:
kubectl get pods -n kubernetes-dashboardKubernetes Dashboard从较新版本开始,默认不开放匿名访问,需要使用token或者kubeconfig文件登录。为了拿到管理员的登录权限,需要单独创建一个具有管理员权限的ServiceAccount。
创建管理员账号的YAML文件:
apiVersion: v1 kind: ServiceAccount metadata: name: admin-user namespace: kubernetes-dashboard --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: admin-user roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - kind: ServiceAccount name: admin-user namespace: kubernetes-dashboard保存为dashboard-admin.yaml后执行:
kubectl apply -f dashboard-admin.yaml然后获取登录token:
kubectl -n kubernetes-dashboard create token admin-user输出的长字符串就是登录Dashboard的访问令牌。
Dashboard默认监听的是443端口,想从宿主机浏览器直接访问,最简单的办法用kubectl proxy转发:
kubectl proxy然后在浏览器访问:
http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/打开页面后选择token登录方式,粘贴刚才获取的token,即可进入控制台。
6.2 部署一个带Service的测试应用
为了确认集群确实可以承载业务流量,部署一个简单的nginx应用并暴露Service。
创建Deployment和Service:
kubectl create deployment nginx-test --image=nginx kubectl expose deployment nginx-test --type=NodePort --port=80 --target-port=80查看Service详情:
kubectl get svc nginx-testNodePort类型会为Service分配一个30000-32767范围内的端口,例如31567。此时在宿主机浏览器访问http://192.168.137.10:31567,如果能看到nginx的欢迎页面,说明整个集群工作正常:请求经过Service转发到了Pod里的nginx容器,跨节点网络也处于通联状态。
如果想验证跨节点调度,可以扩容副本数:
kubectl scale deployment nginx-test --replicas=3然后查看Pod分布在哪些节点上:
kubectl get pods -o wide正常情况下Pod会分布在不止一个节点上,说明调度器正常工作,两个工作节点也能正常运行业务容器。
6.3 集群日常排查的常用命令
集群搭建完成后,后续维护中也经常会遇到各种状态问题。把我实际使用中比较高频的命令整理一下:
- 查看节点整体状态:
kubectl get nodes - 查看所有命名空间下的Pod状态:
kubectl get pods -A - 查看某个Pod的详细信息和事件:
kubectl describe pod <pod名> -n <命名空间> - 查看容器实时日志:
kubectl logs -f <pod名> -n <命名空间> - 查看集群所有组件状态:
kubectl get componentstatus(部分版本可能不再支持) - 查看Control Plane相关Pod状态:
kubectl get pods -n kube-system - 检查网络插件Pod状态:
kubectl get pods -n kube-flannel
如果某个节点状态变为NotReady,先把网络插件Pod和kubelet日志翻一遍,基本能定位80%以上的问题。
这次搭建过程中我最常踩的坑集中在两处:一是镜像拉取问题,二是cgroup驱动不一致。镜像问题在本地环境里通过预拉取和配置可用镜像源解决掉,cgroup驱动则在配置Docker daemon.json这一步就提前规避了。整个流程走完,从空的虚拟机到可用的K8s集群,总共用了一下午时间。如果你也是准备在VMware里搭一套集群练手,建议每完成一步就做一个VMware虚拟机快照,后续想快速重建环境或者回退到正常状态,不用从头再装一遍系统和软件。