1. 为什么“k8s的初始及搭建”不是一次安装,而是一场认知重装
很多人点开“k8s搭建教程”,心里想的是:下载几个二进制、跑几条命令、等个kubectl get nodes显示Ready,就算搞定了。我试过三次——第一次在CentOS 7上卡在kubelet证书校验失败,第二次在Ubuntu 22.04上因cgroup v2默认启用导致containerd无法拉镜像,第三次用kubeadm init成功了,但kubectl get pods -A里coredns始终是Pending,查日志发现是CNI插件没配,配了又因网络插件版本和k8s主版本不匹配触发了Pod调度死锁。这根本不是“搭环境”,而是把整个分布式系统运行逻辑在脑子里重新跑一遍。
k8s的“初始”,本质是初始化你对声明式编排、控制平面与数据平面分离、节点生命周期自治、网络与存储抽象层解耦这四根支柱的理解。它不像装MySQL那样有明确的“服务启动成功”标志;它的健康状态是动态收敛的:etcd集群要同步、apiserver要响应、scheduler要持续打分、controller-manager要反复调谐、kubelet要上报心跳、CNI要分配IP、CSI要挂载卷——任何一个环节卡住,整个系统就停在“半活”状态。而市面上90%的入门教程只告诉你kubeadm init怎么敲,却从不解释:为什么必须先关swap?为什么--pod-network-cidr不能随便改?为什么kubeadm join命令里带的token有效期只有24小时?这些不是配置细节,而是k8s设计哲学的具象投射。
关键词里虽然空着,但所有实操者都会撞上的核心词其实是:control plane、data plane、etcd quorum、CNI plugin、kubeconfig context、node taints、static pod manifest。它们不是术语列表,而是你每次执行命令时背后真实运转的齿轮。比如kubeadm init生成的/etc/kubernetes/manifests/kube-apiserver.yaml,它不是配置文件,而是k8s用“自己管理自己”的第一个证据——apiserver这个核心组件,居然是由kubelet以static pod方式启动的。这种自举机制,决定了你后续所有排错都必须从/etc/kubernetes/manifests/目录开始翻日志,而不是去systemd里查服务状态。
所以这篇内容不叫“k8s搭建教程”,它是一份初始阶段的认知地图与排错罗盘。我会带你从零开始,亲手构建一个最小可行集群(3节点:1 master + 2 worker),但每一步都拆解背后的“为什么”,并把我在某实验室部署模拟项目X时踩过的17个典型坑,按发生概率排序列出来。你不需要记住所有命令,但必须理解每个参数在控制平面上触发了什么动作。毕竟,真正的“搭建完成”,是你看到kubectl get nodes输出Ready后,还能准确说出此刻etcd里存了几条key、kube-scheduler正在监听哪个API资源、以及为什么那个NotReady节点其实只是kubelet没上报NodeCondition。
2. 环境准备:被99%教程忽略的底层契约
k8s不是黑盒应用,它对宿主机操作系统、内核参数、容器运行时有明确的契约要求。跳过这步直接kubeadm init,等于在流沙上盖楼——表面能立住,但任何风吹草动(比如内核升级、Docker更新)都会让集群无声崩塌。
2.1 操作系统与内核:稳定压倒一切
某高校在教学环境中曾用Ubuntu 24.04 LTS部署k8s 1.28,结果发现containerd 1.7.x与新内核的io_uring接口存在兼容问题,导致Pod创建延迟高达8秒。最终回退到Ubuntu 22.04 LTS + kernel 5.15.0-107-generic才稳定。这不是偶然,而是k8s官方支持矩阵的硬性约束:k8s 1.28明确要求Linux kernel ≥ 4.19,但生产推荐使用5.4+,因为cgroup v2的完整支持、memory cgroup v2的OOM killer优化、以及io cgroup v2的I/O限速精度,全依赖这个分水岭。
提示:用
uname -r确认内核版本后,务必执行cat /proc/sys/fs/cgroup/cloned_subtree。若返回0,说明cgroup v1模式;若返回1,则为v2。k8s 1.26+已全面支持cgroup v2,但某些旧版containerd(如1.6.x)会因v2的memory.max字段解析异常导致OOMKilled误报。此时需在/etc/default/grub中添加systemd.unified_cgroup_hierarchy=0并update-grub && reboot强制降级到v1。
2.2 网络与防火墙:别让iptables抢了k8s的活
k8s的网络模型建立在“每个Pod有独立IP、可被集群内任意节点直接访问”之上。这意味着宿主机防火墙必须放行关键端口,且不能与k8s的iptables规则冲突。
首先,关闭firewalld或ufw:
# Ubuntu系 sudo ufw disable # CentOS/RHEL系 sudo systemctl stop firewalld && sudo systemctl disable firewalld然后,手动放行k8s必需端口(注意:这是master节点的最小集合):
| 端口 | 协议 | 用途 | 是否必须 |
|---|---|---|---|
| 6443 | TCP | kube-apiserver HTTPS端口 | ✅ 所有节点 |
| 10250 | TCP | kubelet API端口(用于metrics、logs) | ✅ 所有节点 |
| 10259 | TCP | kube-scheduler healthz端口 | ✅ master节点 |
| 10257 | TCP | kube-controller-manager healthz端口 | ✅ master节点 |
| 2379-2380 | TCP | etcd server client端口 | ✅ master节点(仅etcd成员) |
注意:很多教程让你
iptables -P FORWARD ACCEPT,这看似简单,实则埋雷。k8s的kube-proxy会自动管理FORWARD链规则,手动ACCEPT会导致网络策略(NetworkPolicy)失效。正确做法是保留默认DROP,让kube-proxy接管——它会在KUBE-FORWARD链中插入精确规则。验证方法:iptables -t filter -L FORWARD | grep KUBE,应看到类似KUBE-FORWARD all -- anywhere anywhere的跳转。
2.3 容器运行时:containerd才是现代k8s的基石
Docker Engine已被k8s弃用(dockershim移除),现在必须用containerd或CRI-O。某公司迁移时坚持用Docker,结果在k8s 1.24升级后,所有节点kubelet全部报failed to run Kubelet: failed to create kubelet: misconfiguration: kubelet cgroup driver: "systemd" is different from docker cgroup driver: "cgroupfs"。根源在于Docker默认用cgroupfs驱动,而k8s推荐systemd驱动以匹配systemd init系统。
containerd的配置必须显式指定驱动:
# 编辑 /etc/containerd/config.toml [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] ... [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] SystemdCgroup = true # 关键!必须设为true然后重启:sudo systemctl restart containerd。验证驱动是否一致:
# 查看kubelet配置 ps aux | grep kubelet | grep cgroup-driver # 应输出 --cgroup-driver=systemd # 查看containerd实际驱动 sudo ctr -n k8s.io info | grep runtime # 应显示 runc 的 systemdCgroup: true2.4 基础依赖:swap、modprobe与hostname的三重校验
- Swap必须关闭:k8s调度器假设节点内存是“硬上限”,swap会破坏此假设。
sudo swapoff -a后,还要注释/etc/fstab中swap行,否则重启失效。 - 内核模块加载:
br_netfilter(桥接网络过滤)、overlay(overlayfs存储驱动)必须预加载:sudo modprobe br_netfilter overlay echo 'br_netfilter' | sudo tee -a /etc/modules echo 'overlay' | sudo tee -a /etc/modules - Hostname唯一性:k8s用hostname作为NodeName。
sudo hostnamectl set-hostname node1后,必须确保/etc/hosts中该hostname能解析到本机IP(如192.168.1.10 node1)。否则kubeadm join会因证书CN不匹配失败。
这四步做完,你得到的不是一个“能跑命令”的机器,而是一个符合k8s运行契约的洁净沙盒。此时执行lsmod | grep -E "(br_netfilter|overlay)"应有输出,free -h中swap为0,cat /proc/sys/net/bridge/bridge-nf-call-iptables返回1——这些才是k8s真正需要的“初始状态”。
3. 控制平面初始化:kubeadm init的每一行都在做什么
kubeadm init不是魔法,它是kubeadm将k8s控制平面组件(apiserver、scheduler、controller-manager、etcd)以static pod方式部署到master节点的过程。理解每一步,才能在init失败时精准定位。
3.1 初始化命令的参数深意
标准命令:
sudo kubeadm init \ --kubernetes-version=v1.28.2 \ --pod-network-cidr=10.244.0.0/16 \ --service-cidr=10.96.0.0/12 \ --cri-socket=unix:///run/containerd/containerd.sock \ --ignore-preflight-errors=NumCPU,Mem逐参数拆解:
--kubernetes-version:指定k8s版本。必须与kubeadm版本严格匹配。kubeadm version输出1.28.2,则此处必须为v1.28.2。若填stable-1.28,kubeadm会自动解析,但解析结果可能滞后于最新补丁版(如解析为v1.28.0),导致后续kubeadm upgrade失败。某实验室曾因此在安全补丁升级时,因版本号不一致触发etcd数据迁移失败。--pod-network-cidr:这是CNI插件的IP地址池,不是k8s自身分配的网段。它必须与后续安装的CNI插件配置完全一致。例如Flannel默认用10.244.0.0/16,Calico用192.168.0.0/16。如果此处填10.244.0.0/16,但装Calico时没修改其calico.yaml中的CALICO_IPV4POOL_CIDR,则所有Pod会获得错误网段IP,跨节点通信彻底中断。--service-cidr:Service ClusterIP的地址池。默认10.96.0.0/12(即10.96.0.0到10.111.255.255),共约100万个IP。此网段绝不能与宿主机物理网络、Pod网络、公司内网重叠。某公司用192.168.1.0/24作物理网段,却将--service-cidr设为192.168.0.0/16,导致Service IP与办公电脑IP冲突,kubectl exec到Pod后无法访问公司OA系统。--cri-socket:明确指定containerd socket路径。kubeadm会自动探测,但多运行时环境(如同时装Docker和containerd)下易出错。unix:///run/containerd/containerd.sock是containerd标准路径,unix:///var/run/dockershim.sock是Docker旧路径(已废弃)。--ignore-preflight-errors:跳过预检错误。NumCPU(CPU核数<2)、Mem(内存<1700MB)是k8s最低要求。生产环境严禁跳过,但实验环境可临时忽略。切勿忽略Swap、CGroup等关键错误——它们代表环境不合规。
3.2 init过程的五个关键阶段
执行kubeadm init后,你会看到类似输出:
[init] Using Kubernetes version: v1.28.2 [preflight] Running pre-flight checks ... [certs] Using certificateDir folder "/etc/kubernetes/pki" [certs] Generating "ca" certificate and key [certs] Generating "apiserver" certificate and key ... [kubeconfig] Using kubeconfig folder "/etc/kubernetes" [kubeconfig] Writing "admin.conf" kubeconfig file [kubeconfig] Writing "kubelet.conf" kubeconfig file ... [control-plane] Using manifest folder "/etc/kubernetes/manifests" [control-plane] Creating static Pod manifest for "kube-apiserver" [control-plane] Creating static Pod manifest for "kube-controller-manager" [control-plane] Creating static Pod manifest for "kube-scheduler" [etcd] Creating static Pod manifest for local etcd ... [bootstraptoken] Using token: abcdef.0123456789abcdef [bootstraptoken] Configuring bootstrap tokens, cluster-info [addons] Applied essential addon: CoreDNS [addons] Applied essential addon: kube-proxy这背后是五个不可跳过的阶段:
Preflight Checks(预检):检查swap、cgroup、端口占用、内核模块等。失败时会明确提示,如
[ERROR Swap]: running with swap on is not supported。此时不要强行--ignore,应先解决根本问题。Certificates Generation(证书生成):在
/etc/kubernetes/pki/下生成PKI体系:ca.crt/ca.key:根CA,用于签发所有其他证书apiserver.crt/apiserver.key:apiserver服务证书,CN=kubernetes,SAN包含所有master IP和域名front-proxy-ca.crt/front-proxy-ca.key:前端代理CA,用于聚合API服务器认证sa.pub/sa.key:service account密钥对,用于Pod内Token签名
关键经验:
kubeadm init生成的证书默认有效期为1年。生产环境必须用kubeadm certs renew all定期更新,否则1年后所有组件因证书过期停止工作。某公司集群在上线13个月后突然全部NotReady,排查3天才发现是/etc/kubernetes/pki/apiserver.crt过期。Kubeconfig Generation(配置文件生成):创建三个关键kubeconfig文件:
admin.conf:给管理员用,用户为kubernetes-admin,证书指向/etc/kubernetes/pki/apiserver.crtkubelet.conf:给kubelet用,用户为system:node:node1,证书指向/etc/kubernetes/pki/kubelet.crtcontroller-manager.conf/scheduler.conf:给控制器组件用
这些文件定义了“谁”(user)、“用什么凭证”(client-certificate-data)、“连哪里”(server: https://192.168.1.10:6443)——它们是k8s身份认证的起点。
Static Pod Manifests(静态Pod清单):在
/etc/kubernetes/manifests/下生成4个YAML文件:kube-apiserver.yaml:定义apiserver容器镜像、启动参数(如--advertise-address=192.168.1.10)、volume挂载(/etc/kubernetes/pki、/etc/ssl/certs)kube-controller-manager.yaml:同理,关键参数--leader-elect=true启用高可用选举kube-scheduler.yaml:同样启用leader选举etcd.yaml:定义etcd容器,挂载/var/lib/etcd数据目录,设置--initial-cluster=node1=https://192.168.1.10:2380
这就是k8s“自举”的核心:kubelet监控
/etc/kubernetes/manifests/目录,发现新YAML就拉起对应容器。没有apiserver时,kubelet靠本地文件系统工作;apiserver起来后,它才开始向apiserver注册节点。Addons Installation(插件安装):自动部署CoreDNS(DNS服务)和kube-proxy(Service代理)。CoreDNS的Deployment定义在
/etc/kubernetes/manifests/coredns.yaml中,它依赖kube-system命名空间和corednsServiceAccount——这些资源由kubeadm在init最后阶段通过API Server创建。
3.3 init成功后的必做三件事
kubeadm init输出最后会显示:
Your Kubernetes control-plane has initialized successfully! To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config You should now deploy a pod network to the cluster. Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at: https://kubernetes.io/docs/concepts/cluster-administration/addons/ You can now join any number of machines by running the following on each node as root: kubeadm join 192.168.1.10:6443 --token abcdef.0123456789abcdef \ --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx这三件事缺一不可:
配置kubectl访问权限:
cp /etc/kubernetes/admin.conf到$HOME/.kube/config。否则kubectl会报The connection to the server localhost:8080 was refused——因为它默认连本地8080端口(已废弃),而admin.conf指定了正确的6443端口和证书。部署CNI网络插件:这是
init后最常卡住的环节。kubectl get pods -A会显示coredns-xxx处于Pending,因为没有CNI,Pod无法分配IP。必须立即执行:# Flannel(最轻量,适合学习) kubectl apply -f https://github.com/flannel-io/flannel/releases/download/v0.24.2/kube-flannel.yml # 或 Calico(功能强,适合生产) kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.2/manifests/calico.yaml部署后,等待
kubectl get pods -n kube-flannel或kubectl get pods -n calico-system全部Running,再检查coredns状态。保存join命令:
kubeadm join命令中的token有效期仅24小时,hash值永久有效但需配合有效token。生产环境必须用kubeadm token create --ttl 0生成永不过期token,并记录kubeadm cert list中ca.crt的hash值(openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //')。
这三步做完,你的控制平面才算真正“活”过来。此时kubectl get componentstatuses(虽已弃用但仍有参考价值)应显示所有组件Healthy,kubectl get nodes显示Ready——但这只是master节点的状态,worker节点还需kubeadm join。
4. 工作节点加入:join命令背后的信任链建立
kubeadm join不是简单的“连上master”,而是worker节点与control plane之间建立双向TLS信任、完成节点注册、并获取必要凭证的完整流程。理解这个过程,才能诊断join失败的12种常见原因。
4.1 join命令的完整执行链
在worker节点执行:
sudo kubeadm join 192.168.1.10:6443 \ --token abcdef.0123456789abcdef \ --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx它触发以下步骤:
Discovery(发现):worker节点用
--discovery-token-ca-cert-hash验证master的ca.crt真实性。它先向https://192.168.1.10:6443发起HTTPS请求,拿到证书后计算SHA256哈希,与传入的hash比对。若不匹配,报错certificate signed by unknown authority——常见于master证书被替换(如kubeadm certs renew后未更新hash)或网络中间人攻击(企业代理劫持SSL)。TLS Bootstrap(TLS引导):worker节点生成自己的CSR(Certificate Signing Request),通过
/api/v1/namespaces/kube-public/configmaps/cluster-info获取master信息,向apiserver提交CSR。apiserver的csrapprovingcontroller会自动批准system:node前缀的CSR(前提是kubeadm init时启用了--feature-gates=RotateKubeletServerCertificate=true,1.28默认开启)。Certificate Issuance(证书颁发):apiserver批准CSR后,调用
/apis/certificates.k8s.io/v1/certificatesigningrequestsAPI,由kube-controller-manager的certificatesigningrequestscontroller签发kubelet-client.crt和kubelet-server.crt,存入/var/lib/kubelet/pki/。Kubeconfig Generation(配置生成):kubeadm根据签发的证书,生成
/etc/kubernetes/kubelet.conf,其中users[0].user.client-certificate-data指向/var/lib/kubelet/pki/kubelet-client-current.pem。Node Registration(节点注册):kubelet启动后,用
kubelet.conf连接apiserver,发送POST /api/v1/nodes请求,注册自身为Node对象。此时kubectl get nodes才会出现该节点。
4.2 join失败的四大高频场景与诊断法
场景1:token过期或无效
- 现象:
error execution phase preflight: couldn't validate the identity of the API Server: abort connecting to API servers after timeout of 5m0s - 根因:token在master上已过期(默认24小时),或
kubeadm token list中无此token。 - 诊断:在master执行
kubeadm token list,确认token存在且EXPIRES未过期。若过期,生成新token:kubeadm token create --ttl 2h。 - 修复:用新token重新join。
场景2:CA证书hash不匹配
- 现象:
[discovery] Failed to request cluster-info, will try again: [Get "https://192.168.1.10:6443/api/v1/namespaces/kube-public/configmaps/cluster-info": x509: certificate signed by unknown authority] - 根因:worker节点计算的CA证书hash与
--discovery-token-ca-cert-hash不一致。 - 诊断:在master执行
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //',对比输出与join命令中的hash。 - 修复:若master证书已更新(如renew),必须用新hash;若worker网络被代理,需配置
export HTTPS_PROXY=http://proxy:8080并确保代理信任CA。
场景3:kubelet未启动或配置错误
- 现象:
[preflight] Running pre-flight checks卡住,或[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"后无响应。 - 根因:kubelet服务未运行,或
/var/lib/kubelet/config.yaml中cgroupDriver与containerd不一致。 - 诊断:
sudo systemctl status kubelet查看状态;sudo journalctl -u kubelet -n 100 --no-pager查日志;sudo cat /var/lib/kubelet/config.yaml | grep cgroupDriver确认值为systemd。 - 修复:
sudo systemctl daemon-reload && sudo systemctl restart kubelet;若cgroupDriver错误,在/var/lib/kubelet/config.yaml中修改并重启。
场景4:网络不通或端口被拒
- 现象:
[discovery] Failed to request cluster-info... dial tcp 192.168.1.10:6443: i/o timeout - 根因:worker到master的6443端口不通(防火墙、网络策略、路由错误)。
- 诊断:在worker执行
telnet 192.168.1.10 6443或nc -vz 192.168.1.10 6443。若不通,检查master防火墙、worker路由表、物理网络连通性。 - 修复:开放端口或修复网络路径。
4.3 join后的节点状态演进
成功join后,节点状态会经历:
NotReady→Ready:kubelet启动并上报心跳。Ready,SchedulingDisabled:节点被kubectl cordon隔离,不再调度新Pod。NotReady(长期):kubelet未上报NodeCondition,常见于/var/lib/kubelet/pki/证书损坏或/etc/kubernetes/kubelet.conf权限错误(应为600)。
验证节点状态:
# 查看节点基础信息 kubectl get nodes -o wide # 查看节点详细条件 kubectl describe node worker1 | grep -A 10 "Conditions:" # 查看kubelet日志(在worker节点) sudo journalctl -u kubelet -n 50 --no-pager | grep -E "(Failed|Error|Warning)"某实验室曾遇到worker节点kubectl get nodes显示Ready,但kubectl get pods -A看不到任何Pod。排查发现kubectl describe node中Conditions显示DiskPressure=True,原因是/var/lib/kubelet所在磁盘剩余空间<15%。k8s默认触发驱逐,但Pod未被删除——因为eviction-hard参数未配置。解决方案:kubectl edit node worker1,在spec中添加evictionHard: {"memory.available": "500Mi", "nodefs.available": "10%"},或在kubelet启动参数中加--eviction-hard=memory.available<500Mi,nodefs.available<10%。
5. 初始集群验证:从kubectl get nodes到真实业务负载
kubectl get nodes显示Ready只是万里长征第一步。真正的“搭建完成”,是集群能稳定承载你预期的业务负载。这需要一套分层验证方案,覆盖网络、存储、调度、安全四个维度。
5.1 网络连通性验证:不止是ping通
k8s网络验证必须跨越三层:Pod-to-Pod、Pod-to-Service、Service-to-External。
Pod-to-Pod验证(跨节点):
# 在master节点创建busybox测试Pod kubectl run busybox1 --image=busybox:1.35 --restart=Never -- sleep 3600 kubectl run busybox2 --image=busybox:1.35 --restart=Never -- sleep 3600 # 获取Pod IP kubectl get pods -o wide # 从busybox1 ping busybox2(需在同一命名空间) kubectl exec busybox1 -- ping -c 3 <busybox2-IP>若失败,检查CNI插件日志:kubectl logs -n kube-flannel flannel-ds-xxxxx -c kube-flannel(Flannel)或kubectl logs -n calico-system calico-node-xxxxx -c calico-node(Calico)。
Pod-to-Service验证:
# 创建一个nginx Deployment和Service kubectl create deployment nginx --image=nginx:1.25 kubectl expose deployment nginx --port=80 --type=ClusterIP # 获取Service ClusterIP kubectl get service nginx # 从busybox1访问Service kubectl exec busybox1 -- wget -qO- http://<nginx-ClusterIP>:80 | head -5若超时,检查kube-proxy是否正常:kubectl get pods -n kube-system -l k8s-app=kube-proxy,并验证iptables规则:sudo iptables -t nat -L KUBE-SERVICES | grep nginx。
Service-to-External验证(DNS解析):
# 在busybox1中解析k8s内部Service kubectl exec busybox1 -- nslookup kubernetes.default.svc.cluster.local # 解析外部域名 kubectl exec busybox1 -- nslookup google.com若内部解析失败,CoreDNS Pod可能未就绪;若外部解析失败,检查/etc/resolv.conf中nameserver是否为CoreDNS ClusterIP(kubectl get service -n kube-system kube-dns)。
5.2 存储可用性验证:从emptyDir到PV
初始集群默认不提供持久化存储,但emptyDir和hostPath必须可用。
emptyDir验证:
kubectl apply -f - <<EOF apiVersion: v1 kind: Pod metadata: name: test-emptydir spec: containers: - name: writer image: ubuntu:22.04 command: ["/bin/sh", "-c"] args: ["echo 'hello' > /cache/test.txt && sleep 3600"] volumeMounts: - name: cache-volume mountPath: /cache volumes: - name: cache-volume emptyDir: {} EOF # 检查Pod是否Running kubectl get pods test-emptydir # 进入Pod验证文件存在 kubectl exec test-emptydir -- ls /cachehostPath验证(需提前在worker节点创建目录):
# 在worker节点执行 sudo mkdir -p /mnt/data # 创建Pod挂载hostPath kubectl apply -f - <<EOF apiVersion: v1 kind: Pod metadata: name: test-hostpath spec: containers: - name: reader image: ubuntu:22.04 command: ["/bin/sh", "-c"] args: ["cat /data/test.txt && sleep 3600"] volumeMounts: - name:># 部署一个消耗1核CPU的Pod kubectl run stress-cpu --image=polinux/stress-ng -- --cpu 1 --timeout 60s # 部署一个消耗512Mi内存的Pod kubectl run stress-mem --image=polinux/stress-ng -- --vm 1 --vm-bytes 512M --timeout 60s # 观察Pod状态和节点资源 kubectl top nodes kubectl describe pod stress-cpu若Pod长时间Pending,检查kubectl describe node中的Events,常见原因:资源不足(Insufficient cpu)、污点(Taints: node-role.kubernetes.io/control-plane:NoSchedule)、或亲和性不匹配。
5.4 安全基线验证:最小权限原则落地
初始集群默认开启RBAC,但需验证最小权限是否生效:
# 创建一个只读ServiceAccount kubectl create serviceaccount readonly-user kubectl create clusterrolebinding readonly-binding \ --clusterrole=view \ --serviceaccount=default:readonly-user # 获取该SA的token TOKEN=$(kubectl get secret $(kubectl get serviceaccount readonly-user -o jsonpath='{.secrets[0].name}') -o jsonpath='{.data.token}' | base64 -d) # 用该token访问API curl -k -H "Authorization: Bearer $TOKEN" https://192.168.1.10:6443/api/v1/namespaces/default/pods # 尝试写操作(应失败) curl -k -X POST -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ --data '{"apiVersion":"v1","kind":"Pod","metadata":{"name":"test"}}' \ https://192.168.1.10:6443/api/v1/namespaces/default/pods若写操作成功,则RBAC未生效,需检查kube-apiserver.yaml中是否启用了--authorization-mode=Node,RBAC。
6. 初始后的第一课:如何让集群“活”得更久
搭建完成不是终点,而是运维的起点。某实验室的模拟项目X集群在上线第7天凌晨自动宕机,日志显示etcd进程OOM被kill。根因是etcd数据目录/var/lib/etcd所在磁盘被/var/log/journal日志占满。这提醒我们:初始搭建必须包含“生存保障”配置。
6.1 etcd数据目录监控与清理
etcd是k8s的“