这是Kubernetes高可用集群部署系列的第十篇。前面九篇,我们把etcd集群、负载均衡层、master节点、worker节点全部跑通,这一篇不再聊“怎么装”,而是聊“装完之后怎么验收”。我可以直接说结论:一个高可用集群即使部署时零报错,也不代表生产可用。我见过太多“kubectl get nodes 全 Ready,但一拔网线就全崩”的案例,问题基本都出在只验证了“装好了”,没验证“坏了能不能顶住”。所以这篇会带你把部署结果逐项验收,再把核心链路真的破坏一遍,最后补上日常维护最关键的那几件事。不论你是照着本系列从零搭的,还是刚接手一个别人搭好的Kubernetes集群,这一篇里的检查项和验证思路都能直接用。
1. 集群部署验收清单:把“部署完成”四个字做实
1.1 节点与核心组件复核不要只看 Ready
部署完成后的第一件事,不是急着跑业务,而是把集群的基础状态完整确认一遍。我习惯按下面这个顺序来,每一条都有明确目的,不搞“看起来没问题”这种自欺欺人的操作。
执行kubectl get nodes -o wide查看节点状态和版本信息,这是最基础的检查。我需要确认三件事:所有节点都处于 Ready、节点内核版本和容器运行时版本差异不大、每个节点的角色标识符合预期。这里最容易出现的问题是:某个节点虽然 Ready,但 kubelet 启动参数里的--node-labels没配好,角色标签缺失,导致后面调度策略不生效。用kubectl get nodes --show-labels复核一次标签,比后面发现问题再回头排查省事得多。
紧接着检查核心组件是否齐全且健康:
kubectl get pods -A -o wide重点关注 kube-system 命名空间里的几个固定角色:coredns、etcd、kube-apiserver、kube-controller-manager、kube-scheduler。这些组件在 master 节点上全是以 static pod 方式由 kubelet 直接拉起,如果某个 apiserver 的 pod 反复重启,问题通常不在 pod 本身,而在证书、etcd 连接或资源分配。我的经验是:不要只看 STATUS 是 Running,还要看RESTARTS列。静态 pod 出现单次重启可以容忍,比如证书临时抖动;但如果半小时内连续重启三次以上,就必须查 kubelet 日志和容器运行时日志,这不是正常现象。
还有一个很多人会漏的细节——kubectl cluster-info的输出。正常情况会显示 Kubernetes control plane 的地址和 CoreDNS 的地址。这里的地址如果是内网 VIP 或域名,说明 kubeconfig 配置正确;如果显示 127.0.0.1 或者某个外部 IP,说明 kubeconfig 里的 server 地址写死了,后续管理机一换网络环境就没法访问。这个地址尽量写成负载均衡的 VIP 或域名,而不是某个单点 master 的 IP。
1.2 证书有效期与 etcd 健康不能跳过
证书过期这个问题,隐蔽性极强,但破坏力极大。Kubernetes 各组件之间的通信大量依赖 TLS 证书,尤其是 kubelet 证书、apiserver 证书和 service account 相关证书。部署时如果没注意时间同步,或者证书签发时间异常,会出现“上午还能用,下午突然全部鉴权失败”的诡异现象。
用下面命令检查所有证书的有效期:
kubeadm certs check-expiration输出会列出每个证书的剩余时间,我重点看两个:admin.conf和kubelet证书。如果剩余时间少于 90 天,就应该规划续期,而不是等到过期当周再处理。很多团队的教训是:证书续期的操作并不难,难的是证书过期三个月后没人记得这件事,最终在凌晨被报警炸醒。
etcd 健康检查同样是必选项。etcd 是整个集群的存储底座,它的文件损坏或性能劣化会直接影响 apiserver。用客户端工具直接探测:
ETCDCTL_API=3 etcdctl --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key \ --endpoints=https://master01:2379,https://master02:2379,https://master03:2379 \ endpoint health三个节点都应返回 healthy。如果某个节点反复在 healthy 和 unhealthy 之间跳动,先检查节点时间同步(chrony/systemd-timesyncd),再看 etcd 日志有没有磁盘读写超时。在 haproxy 层把请求转给 apiserver 时,etcd 健康状态直接影响 apiserver 的/readyz,所以这一步检查越早,越省事。
2. 高可用能力专项验证:VIP切换与流量分发
2.1 keepalived 主备切换实测
高可用集群和普通集群的根本区别在于:单点故障时,服务不中断。这个能力不是搭好 keepalived 就自动具备的,必须做一次真实的故障注入。我通常选择在业务低峰期做这套验证,但仍要提前告知相关人,避免误报。
先看 VIP 当前落在哪台节点:
ip addr show | grep 10.0.0.100假设 VIP 在 lb01 上,手动停掉 lb01 上的 keepalived 服务:
systemctl stop keepalived正常情况下,lb02 会在 3 到 5 秒内接管 VIP。验证方法很简单,在 lb02 上执行ip addr show,能看到 VIP 已经绑定到 eth0;同时从客户端再次访问 apiserver 的 VIP 地址,请求仍然正常返回。keepalived 的切换速度取决于 VRRP 通告间隔,我这边配置的是advert_int 1,配合nopreempt模式,避免主节点恢复时反复切换造成抖动。
这里有个容易踩的坑:VRRP 协议使用组播或单播通信,如果服务器上有防火墙策略,需要放行 VRRP 协议(IP 协议号 112),否则两个 keepalived 节点会互相认为对方挂了,同时绑定 VIP,造成 IP 冲突。检查方式是在两个 LB 节点上分别执行tcpdump -i eth0 vrrp,如果没有任何 VRRP 报文,优先查防火墙和网卡组播配置,而不是查 keepalived 配置。
2.2 apiserver 负载均衡与健康检查
keepalived 只负责 VIP 漂移,真正的流量分发靠 haproxy。在 lb01 和 lb02 上都部署 haproxy,后端指向三个 master 节点的 6443 端口。
我的 haproxy 配置核心段如下:
frontend k8s-api bind *:6443 default_backend k8s-masters backend k8s-masters mode tcp balance roundrobin option tcp-check server master01 10.0.0.11:6443 check fall 3 rise 2 server master02 10.0.0.12:6443 check fall 3 rise 2 server master03 10.0.0.13:6443 check fall 3 rise 2这里的关键不只是“转发 TCP”,而是 haproxy 会定期对后端做健康检查。fall 3表示连续三次失败才标记节点不可用,rise 2表示连续两次成功就恢复。这两个参数决定了后端 apiserver 故障时的摘除和恢复速度,不要随意改成fall 1 rise 1,否则一次网络抖动就可能导致后端节点被反复上下线,apiserver 连接被频繁中断。
验证流量分发是否生效,可以在管理机上反复执行 kubectl 命令,比如kubectl get nodes连续执行十几次,然后在 haproxy 的 stats 页面观察各 master 的会话数分布。如果所有请求都打在同一台上,检查是不是 kubeconfig 里 server 地址写死在某个 master IP 上,或者 haproxy 没开启balance roundrobin。
这里还有个小技巧:把 haproxy 的 stats 页面打开,配合访问控制可以快速定位后端节点是否正常。配置如下:
listen stats bind *:8080 mode http stats enable stats uri /stats stats auth admin:yourpasswordstats 页面在排查“某个 apiserver 明明活着但请求失败”的场景时,能一眼看出端倪。不过 stats 端口千万不要暴露到公网。
3. 部署一套 nginx 业务,验证容器调度与故障自愈
3.1 Deployment 加 Service 完整下发
集群可用的最终标志,是业务容器能跑起来,并且对外提供服务。这里我以部署 nginx 为例,把 “Kubernetes 部署 nginx” 的完整流程走一遍,顺带把 Deployment、Service 的关键字段拆开讲透。
创建一个nginx-demo.yaml:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-demo namespace: default spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.26 ports: - containerPort: 80 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi readinessProbe: httpGet: path: / port: 80 initialDelaySeconds: 3 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: nginx-demo-svc spec: type: NodePort selector: app: nginx ports: - port: 80 targetPort: 80 nodePort: 32080执行kubectl apply -f nginx-demo.yaml,然后kubectl get pods -o wide观察 Pod 分布。如果三个 Pod 分别落在三个不同节点上,说明调度器工作正常;如果挤在同一个节点上,就需要检查节点标签或调度策略,但多数默认部署下,replicas: 3会尝试打散到不同节点。
Service 的selector必须和 Pod 的labels完全匹配。这里我用的是app: nginx,两者一一对应。nodePort: 32080是从 30000-32767 端口范围内选的一个固定值,生产环境建议显式指定,避免每次重建 Service 端口漂移。
在任意 worker 节点执行:
curl http://<node-ip>:32080如果能返回 nginx 欢迎页,说明容器网络、kube-proxy 转发、Service 后端选择都正常。这个验证非常重要,因为很多集群在控制面正常的情况下,节点上的 kube-proxy 或 CNI 插件有问题,导致 Service 访问失败。
3.2 故障演练:节点宕机与 Pod 漂移
高可用集群真正的大考,是节点故障时工作负载能否自动恢复。这里我不建议直接拔电源,先用更温和的手段:
kubectl cordon worker01 kubectl drain worker01 --ignore-daemonsets --delete-emptydir-datacordon让节点标记为不可调度,drain则把该节点上已有的 Pod 驱逐到其他节点。执行 drain 后,kubectl get pods -o wide会看到原来在 worker01 上的 Pod 在其他节点重新创建。如果某类 Pod 一直处于 Pending 状态,优先检查资源是否充足、是否有节点亲和性限制。
更接近真实故障的演练是把节点直接关机。节点宕机后,Kubernetes 不会立即重建 Pod,而是等待一段时间。这个时间受两个参数控制:kube-controller-manager 的--node-monitor-grace-period(默认 40 秒)决定节点状态从 NotReady 到标记为故障的时间;而 Pod 的默认驱逐容忍时间是 300 秒(5 分钟),即节点不可用超过 5 分钟后,该节点上的 Pod 才会被强制迁移。
实际操作中要理解这个机制,不要看到节点关机后 Pod 五分钟内没重建就觉得集群有问题。我通常在演练时开着 watch 观察状态变化:
watch -n 2 kubectl get pods -o wide第一次看第三节 Pending 状态,第五分钟开始会看到 Terminating 和重新创建的过程。这套机制验证通过,说明高可用集群的故障自愈链路是完整的。
有一件事必须提醒:如果业务是 StatefulSet,节点宕机后 Pod 重建可能因为数据卷的节点绑定而卡住,这不是控制面能解决的问题。所以故障演练一定要区分无状态应用和有状态应用,不能拿一条 nginx Deployment 的验证结果去推断数据库集群的容灾能力。
4. kubeadm preflight 与版本演进排查实录
4.1 preflight 到底在检查什么
我在部署 v1.26 版本集群时遇到过这样一段日志:
[init] using kubernetes version: v1.26.0 [preflight] running pre-flight checks后面紧跟着一串检查结果,大多数都通过了,但最后报出了一个 ERROR。这不是罕见情况,遇到过 preflight 检查失败的读者一定对那段红字印象深刻。preflight 不是走形式,它把集群初始化的关键前置条件一次性验完,主要有这么几类:
- 系统环境:是否使用 root 执行、操作系统版本、swap 是否关闭
- 端口占用:6443、2379-2380、10250、10259 等端口是否被占用
- 容器运行时:是否能够连接到 containerd 或 cri-dockerd 的 socket
- 内核参数:
net.bridge.bridge-nf-call-iptables是否开启 - 镜像可用性:kubeadm 需要的控制面镜像能否从配置的镜像仓库拉取
其中最容易出问题的就是 CRI 连接。Kubernetes 1.24 之后彻底移除了 dockershim,如果还沿用 Docker 作为运行时且没有装 cri-dockerd,preflight 会一直提示找不到 CRI socket。处理方式不是绕开检查,而是装好适配的 CRI 插件,并在 kubeadm 配置文件的criSocket字段里显式指定路径。
常见 preflight 报错和处理方式,我整理成了一张表:
| 报错信息 | 可能原因 | 处理方式 |
|---|---|---|
| [ERROR FileAvailable--etc-kubernetes-manifests] | 已有历史 kubeadm 痕迹 | 备份并清空 /etc/kubernetes/manifests |
| [ERROR Port-6443] | 端口被现有服务占用 | 停掉占用进程或换节点 |
| [ERROR Swap] | swap 未关闭 | swapoff -a并注释 /etc/fstab 中的 swap 行 |
| [ERROR CRI]: unable to check CRI | 容器运行时 socket 未配置或不可连 | 安装/配置 containerd,打通 /run/containerd/containerd.sock |
| [ERROR ImagePull] | 镜像仓库不可达或认证失败 | 提前拉镜像到本地,或配置imageRepository为内网镜像仓库 |
preflight 的宝贵之处在于:它把问题前置到 init 之前暴露,而不是等集群初始化到一半再失败。所以我们排查时不要用--ignore-preflight-errors=all来硬跳,除非你已经明确知道某个报错不会影响当前部署场景。
4.2 从 v1.26 到 v1.32:部署细节与升级注意
热词里出现 v1.26.0 不是偶然,很多生产集群至今仍跑在 v1.26 上。而本系列标题是 v1.32,这中间跨了多个大版本,部署细节有不少变化。如果你手里已经有一个旧版本集群,想平滑升级到 v1.32,有几个点必须提前留意。
容器运行时的 cgroup driver 要求更严。从 v1.28 开始,kubelet 对 cgroup driver 的检查越发严格,v1.32 部署时推荐直接使用 systemd driver。这意味着 containerd 的配置/etc/containerd/config.toml需要把SystemdCgroup设置为true,并且 kubelet 的启动参数--cgroup-driver=systemd要和它保持一致。这两个地方不一致,轻则节点反复 NotReady,重则容器内进程因为 cgroup 配置错误出现资源统计异常。
镜像仓库地址也在演进。v1.32 的 kubeadm 默认镜像仓库是registry.k8s.io,如果部署机器无法直接访问公网仓库,需要提前把所需的 control-plane 镜像拉到本地,或通过参数修改imageRepository为内网镜像。这里要特别小心,不要只看 kubeadm 默认值就一键部署,生产环境离开内网镜像仓库是走不远的。
API 资源的演进也值得关注。从 v1.26 到 v1.32,一部分 flowcontrol 和 policy API 从 v1beta1 升到 v1,旧的 CRD 如果使用已废弃的 apiVersion,在升级时需要同步修改。升级路径上,kubeadm 不支持跨越多个 minor 版本直接升级,必须逐版本升级,例如 v1.26 -> v1.27 -> ... -> v1.32。这也是我反复强调要规划版本窗口的原因,毕竟每跳一个小版本都涉及镜像更新、组件重启和 API 兼容性确认。
5. 生产接手后必须养成的几个习惯
5.1 一套顺手的健康巡检命令
集群不是搭完就一劳永逸的,日常巡检能帮你尽早发现隐患。我给自己定了一套固定巡检流程,每个工作日早上执行一次,耗时不到三分钟。
kubectl get nodes -o wide kubectl get pods -A | grep -v Running | grep -v Completed kubeadm certs check-expiration第一条看节点状态,第二条看异常 Pod,第三条看证书剩余时间。如果节点里有 NotReady,立刻journalctl -u kubelet -n 100看日志;如果 Pod 有 CrashLoopBackOff,先看kubectl describe pod里的 Events,再进容器查业务日志。这套组合拳能覆盖绝大部分日常问题。
我还会额外加一条 etcd 健康检查,频率不需要每天,但每两天看一次比较合理。etcd 的存储空间增长能反映集群元数据变化速度,如果某个命名空间下有海量 Pod 频繁创建删除,etcd 的磁盘占用会快速上升,提前发现可以避免存储打满的“血案”。
5.2 证书续期与 etcd 备份不能靠脑子记
证书续期这件事,最大的敌人是“忘了”。kubeadm 提供了续期命令:
kubeadm certs renew all执行完成后需要重启 kubelet 和 apiserver 等静态 Pod 组件,让新证书生效。这台机器上的/etc/kubernetes/admin.conf也要同步更新,否则管理端 kubeconfig 会失效。我在团队里推行的是每半年执行一次的续期计划,并且在日历上设置提前提醒,比等监控告警要温和得多。
etcd 备份同样要有固定节奏。数据是集群的“账本”,一旦损坏,重建集群比恢复备份代价高得多。备份命令很简单:
ETCDCTL_API=3 etcdctl --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key \ --endpoints=https://master01:2379 \ snapshot save /backup/etcd-snapshot-$(date +%Y%m%d).db恢复时用etcdctl snapshot restore将快照恢复到指定目录,然后重启 etcd。这里我最想强调的个人经验是:备份文件要异地存放,不能只放在 master 节点本地磁盘。否则整机故障时,备份和源数据一起丢失,就等于没有备份。至少推送到独立的存储服务器或对象存储,这点不花多少成本,却能在灾难演练时救你一命。
最后再讲一句心里话:从 v1.26 到 v1.32,Kubernetes 的部署细节一直在变化,但高可用集群的核心思想没有变——控制面要冗余、故障要自动转移、数据要能恢复。这套验收和演练思路,不管以后版本怎么迭代,都是适用的。