
最近刚完成一套 k8s 集群搭建和以往只关心“kubeadm init 能不能过”不同这次我把大部分精力放在了搭完之后的验证与结果分析上。整套环境是 3 个控制平面节点加 2 个工作节点用 kubeadm 部署版本锁定在 Kubernetes 1.28 系列容器运行时统一 containerd网络组件选了 Calico。集群初始化成功后我没有急着往上扔业务而是先做了一整轮健康检查、网络拔测、资源基线和故障演练。这篇文章就是这一轮“k8s 集群搭建结果”的完整记录。不管你是第一次搭 k8s 集群还是已经搭到一半卡在节点 NotReady 上这份结果都能提供一个对照样本。我会把最终节点拓扑、组件版本、验证命令、性能数据还有过程里踩过的坑全部整理出来。先说结论这套集群最终所有节点 Ready全部系统组件 RunningDNS 解析正常Service 对外访问正常跨节点 Pod 通信正常控制面高可用演练中没有出现长时间中断。看起来是“标准结果”但每一项都是经过实测的后文会展开讲。1. 搭建结果总览这套集群的最终形态1.1 节点拓扑与基础环境集群最终是 3 台控制平面节点加 2 台工作节点主机名和角色划分如下角色主机名内网IP规格操作系统容器运行时control-planek8s-master01192.168.1.1014C8GUbuntu 22.04containerd 1.7.13control-planek8s-master02192.168.1.1024C8GUbuntu 22.04containerd 1.7.13control-planek8s-master03192.168.1.1034C8GUbuntu 22.04containerd 1.7.13workerk8s-worker01192.168.1.1048C16GUbuntu 22.04containerd 1.7.13workerk8s-worker02192.168.1.1058C16GUbuntu 22.04containerd 1.7.13为什么选 32 而不是 12控制面节点在生产环境至少要 3 台才能容忍任意一台 master 宕机etcd 的多数派选举也需要至少 3 个成员。两个 worker 是留给业务调度的基础容量能覆盖“一台 worker 宕机另一台还能扛住部分业务”的场景。这套规模做测试环境绰绰有余但真跑生产业务会偏紧后续至少应该扩容到 3 个 worker。所有节点统一使用 Ubuntu 22.04内核 5.15系统初始化时关闭了 swap并配置了net.bridge.bridge-nf-call-iptables等内核参数。这一步不做kubelet 启动后很容易报failed to run Kubelet: failed to validate kubelet flags之类的问题。容器运行时选了 containerdcgroup 驱动和 kubelet 保持一致都设置为systemd避免后续节点状态异常。1.2 组件版本与关键网络配置集群里各核心组件的最终版本如下组件版本说明kubeadmv1.28.6集群初始化与节点加入kubeletv1.28.6节点代理kubectlv1.28.6命令行客户端etcd3.5.9控制面内置3 节点集群CoreDNSv1.10.1集群 DNSCalicov3.27.2CNI 网络插件metrics-serverv0.6.4资源指标采集kube-vipv0.8.0控制面 VIP 高可用版本选型原则是“不用最新只用已经出过几个 patch 的稳定线”。1.28.6 这个版本社区反馈比较平稳新版本出来我通常会再等两三个小版本再考虑升级。高可用方案这次用了 kube-vip它和传统的 keepalived haproxy 方案相比少维护一层负载均衡组件直接用 static pod 的方式在控制面节点上提供 VIP。不管用哪种方案关键点是 kubeadm 配置里的controlPlaneEndpoint必须是 VIP 地址而不是某一台 master 的 IP。如果这里写成单节点 IP后面任何一台 master 宕机kube-apiserver 的访问都可能直接中断。网络方案选择了 Calico工作模式用 IPIPPod CIDR 是 10.244.0.0/16Service CIDR 是 10.96.0.0/12。kube-proxy 使用 ipvs 模式相比 iptables 模式在大规模 Service 下性能更好也支持更丰富的调度算法。这里有个前置条件节点需要加载ip_vs相关内核模块不然 kube-proxy 起来了 Service 也不通后面我会在隐患排查部分展开写。2. 结果验证如何证明集群真的可用2.1 控制面与节点健康检查集群搭建完成后第一步永远是执行kubectl get nodes。这是所有人最紧张的时刻也是判断集群是否“活”过来的第一道关口。$ kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME k8s-master01 Ready control-plane 3h v1.28.6 192.168.1.101 Ubuntu 22.04.3 LTS 5.15.0-91-generic containerd://1.7.13 k8s-master02 Ready control-plane 3h v1.28.6 192.168.1.102 Ubuntu 22.04.3 LTS 5.15.0-91-generic containerd://1.7.13 k8s-master03 Ready control-plane 3h v1.28.6 192.168.1.103 Ubuntu 22.04.3 LTS 5.15.0-91-generic containerd://1.7.13 k8s-worker01 Ready none 2h v1.28.6 192.168.1.104 Ubuntu 22.04.3 LTS 5.15.0-91-generic containerd://1.7.13 k8s-worker02 Ready none 2h v1.28.6 192.168.1.105 Ubuntu 22.04.3 LTS 5.15.0-91-generic containerd://1.7.13节点全部 Ready 只是第一步。系统组件是不是都正常需要再看一眼kube-system命名空间$ kubectl get pods -n kube-system -o wide NAME READY STATUS RESTARTS AGE calico-kube-controllers-xxxxx 1/1 Running 0 3h calico-node-abcde 1/1 Running 0 3h calico-node-xxxxx 1/1 Running 0 3h coredns-xxxxxxxxxx-yyyyy 1/1 Running 0 3h coredns-xxxxxxxxxx-zzzzz 1/1 Running 0 3h etcd-k8s-master01 1/1 Running 0 3h etcd-k8s-master02 1/1 Running 0 3h etcd-k8s-master03 1/1 Running 0 3h kube-apiserver-k8s-master01 1/1 Running 0 3h kube-apiserver-k8s-master02 1/1 Running 0 3h kube-apiserver-k8s-master03 1/1 Running 0 3h kube-controller-manager-k8s-master01 1/1 Running 0 3h kube-controller-manager-k8s-master02 1/1 Running 0 3h kube-controller-manager-k8s-master03 1/1 Running 0 3h kube-scheduler-k8s-master01 1/1 Running 0 3h kube-scheduler-k8s-master02 1/1 Running 0 3h kube-scheduler-k8s-master03 1/1 Running 0 3h kube-proxy-xxxxx 1/1 Running 0 3h kube-vip-k8s-master01 1/1 Running 0 3h kube-vip-k8s-master02 1/1 Running 0 3h kube-vip-k8s-master03 1/1 Running 0 3h metrics-server-xxxxxxxxxx-yyyyy 1/1 Running 0 3h我还会单独检查一遍 etcd 健康状态。etcd 存的是整个集群的元数据它出了问题节点状态、Pod 状态、证书全部不可信。在控制面节点上直接进入 etcd 容器执行健康检查$ kubectl -n kube-system exec etcd-k8s-master01 -- etcdctl \ --endpointshttps://127.0.0.1:2379 \ --cacert/etc/kubernetes/pki/etcd/ca.crt \ --cert/etc/kubernetes/pki/etcd/server.crt \ --key/etc/kubernetes/pki/etcd/server.key \ endpoint health https://127.0.0.1:2379 is healthy: successfully committed proposal: took 4.534924ms三台 master 的 etcd 都返回 healthy这才算控制面底座是稳的。只看etcd-k8s-master01不够每个节点都要看一遍因为 raft 集群里只要有一个成员异常整个 etcd 集群的容错能力就会下降。做完静态检查后我还做了一次主动故障演练。在测试环境里我把 k8s-master01 直接关机同时在一台外部机器上持续请求https://192.168.1.100:6443/healthzVIP 是 192.168.1.100。实际结果是请求在 VIP 漂移过程中出现了极短时间的重试但很快就恢复了没有出现持续几十秒的不可用。这说明 kube-vip 和 etcd 的多数派机制配合正常。2.2 DNS、Service 与跨节点通信实测节点 Ready 和组件 Running 只能说明进程活着不能说明网络链路是通的。我最常用的验证方法是直接创建一个临时的 busybox Pod在 Pod 里做 DNS 解析$ kubectl run dns-test --imagebusybox:1.28 --rm -it --restartNever -- nslookup kubernetes.default.svc.cluster.local Server: 10.96.0.10 Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local Name: kubernetes.default.svc.cluster.local Address 1: 10.96.0.1如果这条命令卡住或者返回server cant find第一嫌疑一定是 CoreDNS 而不是业务 Pod。CoreDNS 在集群里的地位相当于水管的阀门阀门坏了后面再多的水龙头都白搭。DNS 通了以后再验证 Service。我用一个最简单的 nginx Deployment 做测试$ kubectl create deployment web --imagenginx:1.24 --replicas3 $ kubectl expose deployment web --port80 --target-port80 --typeNodePort $ kubectl get svc web NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE web NodePort 10.96.193.145 none 80:30080/TCP 1m然后从两台 worker 节点分别访问$ curl http://192.168.1.104:30080 $ curl http://192.168.1.105:30080两次都能正常返回 nginx 默认页面。这说明 NodePort 端口映射、kube-proxy 转发、后端 Pod 调度和容器内服务监听都是通的。到这里集群的基础网络链路基本可以放心。跨节点 Pod 通信我也做了测试。在 worker01 上跑一个 busybox记录它的 Pod IP在 worker02 上再跑一个 busybox用 ping 和 wget 去访问对方。这里想提醒一下ping 通不代表业务端口通ICMP 和 TCP 走的路径不一定完全一致所以必须用真实端口做一次 TCP 请求才算数。我实际验证了从 worker02 的 Pod 访问 worker01 上的 nginx Pod IPHTTP 请求正常返回跨节点容器网络是通的。2.3 调度与伸缩行为验证集群能不能正常调度直接体现在 Pod 在节点上的分布。kubectl get pods -o wide看到三个 web 副本分布在我的两个 worker 节点上说明调度器没有把 Pod 全部堆到同一台机器。我又做了一次扩缩容验证$ kubectl scale deployment web --replicas1 $ kubectl scale deployment web --replicas5扩容后新 Pod 能快速创建并进入 Running没有被卡在 Pending 或 ContainerCreating。这说明 kube-scheduler、kubelet 的节点准入、镜像拉取这几个环节都正常。有一个容易误判的点控制面节点默认带node-role.kubernetes.io/control-plane:NoSchedule污点业务 Pod 不会被调度上去。很多人看到 master 上没有业务 Pod以为是故障其实是预期行为。如果确实需要在控制面节点跑业务可以在 Deployment 里显式加容忍但生产环境不建议这么做。3. 性能基线集群能不能承载业务3.1 节点资源占用基线集群“能用”和“好用”是两回事。我习惯在搭建完成后记录一份资源占用基线方便后续判断业务上来以后哪些资源是系统组件消耗的哪些是业务消耗的。装好 metrics-server 后用kubectl top nodes看基础占用$ kubectl top nodes NAME CPU(cores) CPU(%) MEMORY(bytes) MEMORY(%) k8s-master01 356m 8% 1734Mi 22% k8s-master02 321m 8% 1688Mi 21% k8s-master03 338m 8% 1702Mi 22% k8s-worker01 187m 2% 912Mi 6% k8s-worker02 195m 2% 938Mi 6%这个数据是集群刚建成、还没有业务 Pod 时的基准。控制面节点 CPU 普遍在 300m 左右主要由 kube-apiserver、etcd、Calico 组件贡献内存稳定在 1.7G 上下。worker 节点占用干净很多只有 kube-proxy、Calico 和 containerd 的常驻开销。这份基线最直接的价值以后业务一跑如果 worker 节点内存直接飙到 80%你就能判断大致是业务消耗而不是系统组件异常。很多排障的起点都是“先对比基线再看异常增加的部分”。3.2 API Server 可用性与网络吞吐控制面可用性不是“感觉好用”最好有量化数据。我写了一个简单的循环脚本持续请求 VIP 上的 API Server 健康端点while true; do code$(curl -s -o /dev/null -w %{http_code} https://192.168.1.100:6443/healthz) echo $(date %H:%M:%S) $code sleep 1 done在内网环境下跑 5 分钟所有请求都返回 200单次请求平均耗时在 6ms 左右。这是一个非常基础的健康度指标。如果你发现大量请求超过 100ms就要考虑控制面节点性能瓶颈、etcd 慢查询或者网络抖动。跨节点网络吞吐我也顺手测了一下。在两台 worker 节点上分别安装了 iperf3节点间跑 TCP 单流测试结果大约在 1.9Gbps 左右。考虑到 Calico IPIP 模式有额外封装开销这个数字在千兆内网环境中是合理的。如果你的跨节点网络明显比裸机带宽低一个量级优先查网卡 offload 设置、防火墙规则和 CNI 的封装模式。3.3 简单容量估算基于上面的基线我对这套集群的承载能力做了一个粗略估算。以 8C16G 的 worker 节点为例系统组件占用大约 0.2C 和 1G 内存再预留 20% 给系统兜底实际可给业务使用的大概是 6C 和 11G 内存左右。如果单个业务实例的 request 设置为 0.5C 和 512M 内存单台 worker 大约能稳定运行 10 到 12 个实例加上调度时的资源碎片和突发流量建议保守一点。两个 worker 节点加起来在不扩容的情况下支撑 20 到 25 个中小型 Pod 实例是比较舒服的区间。这里还要强调一个概念调度器是看 request 而不是看实际用量的。把 request 都配成 0节点会显得很空但一旦业务真实负载上来内存会直接被打爆。容量估算必须基于业务侧的 request 配置来做否则就是自欺欺人。4. 常见问题与排查实录踩过的坑比命令值钱4.1 kubeadm init 卡在镜像拉取搭建过程中最让人烦躁的问题就是执行kubeadm init后长时间卡在[wait-control-plane]或镜像下载阶段。原因基本都是 kubelet 和 kubeadm 默认使用的镜像仓库地址在当前网络环境下不可用或访问超时导致 etcd、apiserver 等关键镜像拉不下来。我的处理方案是配置可用的镜像仓库替代默认地址提前把所有需要的镜像拉到本地再执行初始化。先查看当前版本需要哪些镜像$ kubeadm config images list然后指定镜像仓库拉取$ kubeadm config images pull --image-repositoryregistry.example.com/kubernetes如果你的环境更封闭比如内网离线环境可以把镜像在能联网的机器上 pull 下来用docker save或ctr images export打包再拷贝到离线节点上导入。离线导入比在线拉取稳定得多尤其遇到网络波动的时候反复失败会让你怀疑人生。4.2 节点 NotReadyCNI 配置未初始化集群初始化后执行kubectl get nodes发现 worker 节点一直是NotReady排查 kubelet 日志时看到这样的报错network plugin is not ready: cni config uninitialized这个问题几乎每次都会遇到。原因很简单集群没有安装 CNI 插件节点上的 CNI 配置目录是空的kubelet 自然无法把节点标记为 Ready。解决办法就是安装 Calico$ kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.2/manifests/calico.yaml这里最需要注意的是 Calico 的 Pod CIDR 必须和kubeadm init时的--pod-network-cidr保持一致。我这次用的是 10.244.0.0/16如果不一致Calico 设置的 IP 池和 kube-controller-manager 分配的 Pod CIDR 会对不上后面所有 Pod 都会卡在 ContainerCreating。除了报错信息还可以用kubectl describe node node-name看 Conditions 和 Events。Node 的条件信息比日志更直观很多异常都能在 Events 里直接看到根本原因。4.3 CoreDNS Pending 与 CrashLoopBackOffCoreDNS 一直 Pending 是另一个高频问题。Pending 的本质是 Pod 没有调度到合适的节点。常见原因有三个节点不满足调度条件、资源不足、或者存在未容忍的污点。直接用kubectl describe pod查看调度事件基本就能定位。还有一种情况是 CoreDNS 反复重启状态不是 Pending 而是 CrashLoopBackOff。我遇到过是因为 CoreDNS 的 ConfigMap 被改坏导致容器启动时无法解析核心配置文件。处理方式比较直接把 ConfigMap 恢复成默认配置再删掉对应 Pod 让它重新创建。这里要特别提醒CoreDNS 是集群内部所有 Service 名称解析的入口它一旦抖动业务 Pod 之间互相访问就会出现间歇性失败。排障时不要只盯着业务 Pod先看 kube-system 里的 CoreDNS 健康状态。4.4 ipvs 模式下 ClusterIP 不通有一段时间我遇到过 Service 的 ClusterIP 怎么都访问不通但 NodePort 却能通。这种诡异现象的根源通常不在 Service 本身而在 kube-proxy 的运行模式。如果 kube-proxy 配置了 ipvs 模式但节点内核没有加载ip_vs相关模块kube-proxy 虽然能启动但实际上没有把转发规则写进内核ClusterIP 自然不通。解决方法是提前在所有节点加载模块cat EOF | sudo tee /etc/modules-load.d/k8s.conf ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack EOF然后重启systemd-modules-load服务再重启 kube-proxy 的 Pod。这个问题的隐蔽之处在于没有任何直接的错误日志kube-proxy 容器是 Running 状态但实际上内核模块缺失。所以我在初始化集群时会把模块加载这一步写进初始化脚本而不是等到 Service 不通了再回头查。4.5 etcd 成员不健康时钟漂移控制面三个 etcd 节点里有一个成员偶尔报unhealthy过一会又恢复。查了很长时间最后发现是那台 master 节点的系统时间比另外两台快了几百毫秒。etcd 的 raft 协议对时间非常敏感时钟漂移会导致心跳乱掉选举异常。这不是玩笑三台 master 的时间不一致整个控制面都可能出现间歇性不可用。解决办法就是统一时间同步$ sudo timedatectl set-ntp true同时建议把时间同步做成开机自启并在节点初始化清单里加入时间校对的检查项。很多所谓“高可用集群突然抽风”的问题最后都能追溯到时钟漂移。5. 验收清单与后续规划5.1 交付验收清单结合这次搭建的实际经验我整理了一份集群结果验收清单。以后每搭一套新集群我都会照着清单过一遍避免漏项验证项命令或操作预期结果节点状态kubectl get nodes所有节点 Ready控制面组件kubectl get pods -n kube-system全部 Running/Completedetcd 健康etcdctl endpoint health全部 healthyDNS 解析nslookup kubernetes.default.svc.cluster.local返回正确 ClusterIPService 访问curl NodeIP:NodePort返回业务页面调度伸缩kubectl scale deployment --replicas5副本正常扩缩资源监控kubectl top nodes有 CPU/内存数据返回高可用演练停掉一台 master观察 VIP 和 apiserver 健康无长时间中断这套清单做完基本可以确认集群不仅能初始化也能承载业务。它比“看 READY 列全是Ready”靠谱得多因为后者只是静态状态前者才是动态能力。5.2 后续规划与维护建议搭建结果稳定后后续维护还有几件大事要跟进。首先是监控告警我计划在 kube-system 里部署 Prometheus 和对应的告警规则覆盖节点 CPU、内存、磁盘以及控制面组件的存活状态。没有监控的集群就像没有仪表盘的驾驶舱跑高速的时候心里完全没底。其次是日志采集。业务 Pod 日志默认只存在节点本地Pod 一删日志就没了。后续计划接一套集中式日志方案让应用日志统一落到外部存储。再次是备份策略etcd 的定期快照和 kubeadm 配置文件的备份必须安排上否则一旦控制面损坏恢复成本会非常高。最后是版本升级计划不要长期停在某个版本上跟踪社区 patch 更新选择适当时机升级。这次搭建最大的体会是k8s 集群搭建结果不是某一时刻的状态而是一套可持续复验的过程。把验证命令沉淀成脚本把常见故障整理成清单后面维护会轻松很多。如果非要说一个最值得记住的细节我会选这句话验收时不要只盯着kubectl get nodes的 Ready 列至少要让一个真实业务 Pod 跨节点跑起来然后手动把其中一台节点停机再恢复看到系统自己把 Pod 调度到别处。那一刻你对 k8s 的信心才算是真正建立起来了。