生产级K8S集群自动化:从搭建到运维的系统化实践
发布时间:2026/10/15 3:25:03 锦皓数字建站

1. 为什么非要把K8S搭建“系统化”不手动搭行不行1.1 生产环境多集群手动维护的痛点先说个场景。一次新业务上线需要临时扩容一套K8S集群。我打开shell按照老流程开始敲命令初始化系统参数、装容器运行时、kubeadm init、配置网络插件一路敲下去大概花了两个小时。集群起来了看起来一切正常。但到了第三天某个节点出现报错排查半天发现是初始化时系统参数漏配了一项导致流量转发异常。这不是偶发现象。手动搭建K8S的真正问题不在于步骤本身有多难而在于每一步都依赖人对环境的即时判断。生产环境不是一张白纸底层的操作系统版本可能不完全一致内核参数有些节点被改过网卡命名规则不同甚至某些机器的时区都没对齐。同一个脚本在这台机器上跑得顺畅换一台机器就会冒出各种小问题。人不可能把所有差异都记在脑子里一旦集群数量超过三套维护成本就开始失控。我在接手这套生产环境的时候手上同时在维护的K8S集群共有四套两套业务集群、一套中间件集群、一套CI/CD集群。它们的版本不完全相同插件配置也有些差别。每次做什么改动我都要先回忆这套集群的历史状态再决定命令要怎么写。这种工作方式风险极高因为人的记忆是会出错的。于是在某次事故之后我决定把搭建和管理这套集群的流程全部沉淀成代码和配置形成一套自动化的控制系统也就是后来被我们内部称为K8S Control System的东西。1.2 为什么我选择自研控制系统而不是现成工具当时我也调研过一些开源的集群管理平台比如某UI管理工具、某些可视化面板。它们确实能降低操作门槛但用下来有几个痛点始终绕不开。第一这类工具一般面向“单集群管理”场景多集群切换要么支持很弱要么需要额外部署一套中心化组件。而我这里需要的恰恰是多集群的统一编排能力。第二大多数工具只覆盖了K8S自身的生命周期管理对于底层节点初始化、系统参数配置、容器运行时安装这些“地基”工作基本不管。但这些环节恰恰是生产环境故障率最高的地方。第三也是最关键的工具本身引入的复杂度会掩盖问题。一旦集群出故障你得先搞清楚是K8S的问题还是管理工具的问题排障链路变长。我想要的是一套能覆盖“从裸机到集群就绪再到日常运维”全过程的方案而且必须满足三个原则可重复同一套配置在任何一批新机器上跑出来的结果一致。可审计任何一次变更都留痕知道谁改过、改了哪台机器、改了什么东西。可回滚操作出问题的时候能快速回到上一个稳定状态。当时我决定自己写核心是沉淀一套脚本集和配置模板用代码管理集群状态。后来这套东西慢慢从一个临时脚本演变成了一个结构化的控制系统名字就叫K8S Control System。它不依赖任何外部平台核心只有一个控制节点加一批约定好的配置文件纯命令行即可驱动。这个思路在后面成了我们运维工作的主心骨。2. K8S Control System的整体架构三个平面各管什么2.1 管理平面集群清单是唯一事实来源整套系统第一个要解决的问题是怎么描述一个集群的“期望状态”。我采用的方式是用一组声明式的配置文件来定义集群放在一个独立的Git仓库里。每个集群一个目录里面放着该集群专属的配置节点列表、角色划分、版本号、网络插件参数、标签和污点、系统参数覆盖项等。这些配置是整个控制系统的唯一事实来源。任何时候你想知道某个集群应该是什么样不需要去连服务器执行命令打开对应的配置文件就能看懂全局。这种做法跟我以前维护服务器的方式完全不同。以前是服务器上有什么就是什么状态靠人去记现在是清单里怎么写服务器最终就必须变成什么样。不一致的部分就是控制系统需要去修复的部分。# 某集群的节点清单示例简化 cluster_name: production-biz k8s_version: 1.28.7 cni: calico control_plane_endpoint: 10.10.0.100:6443 nodes: - hostname: k8s-master-01 role: control-plane ip: 10.10.0.11 etcd: true - hostname: k8s-master-02 role: control-plane ip: 10.10.0.12 etcd: true - hostname: k8s-master-03 role: control-plane ip: 10.10.0.13 etcd: true - hostname: k8s-node-01 role: worker ip: 10.10.1.11 labels: node-type: app taints: []这里有个很容易踩的坑配置文件的语法一定要简单不要在上面做二次编程。我曾经尝试过用模板引擎把节点列表和配置生成逻辑全部抽象出来结果调试配置本身的时间比手动搭建还长。后来我学乖了配置就是YAML逻辑就是Shell脚本两者分开。控制脚本负责“怎么执行”配置文件负责“目标是什么”互不干扰。2.2 执行平面可重入脚本与控制节点执行平面是实际干活的模块。我没有选择把整套系统做成一个常驻服务而是用一组可重复执行的脚本组成。控制节点只需要一台普通的Linux机器具备到所有目标节点的SSH访问权限即可。每次操作由人工触发脚本读配置、连机器、执行命令、收集结果。为什么不用常驻Agent的模式因为多一个常驻组件生产环境就多一个故障点。控制节点本身如果挂了不应该影响任何现有集群的运行——它只负责变更不负责承载流量。这种“变更时才在线”的模式让控制系统的自身故障对我的业务集群零影响。脚本的可重入性是这套系统能落地的关键。所谓可重入简单说就是同一份脚本跑多少遍结果都是一样的不会因为上次执行一半就导致下次执行出错。举个例子初始化节点的脚本里有一段配置内核参数的逻辑。它不会简单地把参数追加到sysctl.conf而是先检查目标参数是否已经存在存在就替换不存在才追加。之后再执行sysctl -p重新加载。这样哪怕上一次脚本在写入一半时崩溃第二次执行也能基于当前状态继续而不是报错或覆盖。另外一个经验是每个脚本都应该有“幂等验证点”。也就是执行完关键步骤之后立刻检查这一步的结果是否符合预期。比如配置完容器运行时后马上执行一条命令验证是否能正常调用kubeadm init结束后立刻检查进程和端口。这种机制保证脚本不会在错误状态上继续往下走。2.3 状态平面收集事实与对账有了期望状态和执行能力还需要一个东西把它们连接起来当前状态。状态平面的工作就是收集每台目标节点的真实情况然后和期望状态做对比这个动作我们内部叫“对账”。对账机制做得很朴素在控制节点上运行一组只读的采集脚本把每台机器的系统参数、软件版本、运行状态汇总到一个JSON文件里再和集群清单做比对。比对结果分三种已对齐、待变更、异常。自动化流程只负责处理“待变更”的部分“异常”的则进入告警通道人工介入。这套机制的价值在运维场景下会越来越明显。比如新买了一批机器要扩容我先执行对账脚本它会告诉我这批机器的内核参数缺什么、容器运行时没装、时间同步未配置。然后我不需要去关心这些细节直接执行集群扩容流程脚本会按顺序补齐所有前置条件最后把节点加入集群。等对账机制跑了一段时间之后我又给它加了一个功能把所有关键操作写入操作日志。每一次脚本执行、每一个目标节点的变更记录都追加在一个结构化的日志文件里。出了问题只需要查日志就能定位到操作时间和具体变更内容。这个东西在排障时给了我极大的帮助后面排障部分会详细说。3. 自动搭建一条生产集群的完整流程3.1 第一步基础设施准备与节点初始化自动搭建流程的第一步是从一批裸机开始准备基础设施。这个阶段的目标是让每台机器达到运行K8S的最低标准。首先是操作系统层面的初始化。这里有一套固定的参数必须提前配好否则后面网络插件和kubelet会出各种诡异的问题。关键的几项包括关闭swapK8S要求kubelet所在节点必须关闭swap不然kubelet无法启动或会报错。加载内核模块主要是overlay和br_netfilter这两个模块分别负责容器镜像层存储和网络流量转发。配置内核参数包括net.bridge.bridge-nf-call-iptables、net.ipv4.ip_forward等。其中ip_forward如果不开启Pod之间的跨节点通信会直接失败。时间同步配置集群内所有节点时间偏差超过一定阈值会导致证书校验失败。我们统一装好chrony并指向内部NTP服务器。hosts文件追加如果内网有DNS这步可以跳过但我们这套环境的DNS在一些机器上配置得不全所以我选择把集群内所有节点的IP和主机名写入hosts避免各种组件之间解析不到对方。然后是容器运行时的安装。目前生产环境主流还是containerd我这边用的也是它。安装完成后有一个配置必须改就是SystemdCgroup参数要设成true。否则kubelet和容器运行时对待cgroup的管理方式不一致会出现资源统计错乱问题。这一步如果没配好后面Pod运行就会出现间歇性内存统计异常非常难排查。# 节点初始化脚本片段简化 swapoff -a sed -i /swap/s/^/#/ /etc/fstab cat /etc/modules-load.d/k8s.conf EOF overlay br_netfilter EOF modprobe overlay modprobe br_netfilter cat /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 sysctl --system3.2 第二步控制面高可用初始化基础设施准备好之后就开始初始化控制面。生产集群的控制面必须高可用不能只在一个节点上跑API Server。我这里用的是三台控制平面节点加一个VIP的方案外部流量统一访问VIPVIP背后是三个API Server实例etcd数据也由这三个节点共同承载。控制面初始化我选择了kubeadm这个工具。它最大的好处是把K8S控制面组件的安装过程标准化了证书生成、组件静态Pod配置、引导token这些细节都帮你搞定。但这不代表什么都交给它就行初始化之前必须明确指定控制平面端点。# 控制面初始化命令 kubeadm init \ --control-plane-endpoint10.10.0.100:6443 \ --kubernetes-versionv1.28.7 \ --pod-network-cidr172.16.0.0/16 \ --service-cidr10.96.0.0/12 \ --apiserver-advertise-address10.10.0.11 \ --cri-socketunix:///run/containerd/containerd.sock \ --upload-certs这里有几个参数我要重点解释一下。--control-plane-endpoint是整个高可用架构的核心。kubeadm会把API Server的证书SAN、每个组件要访问的地址全部都指向这个端点。如果后面要更换VIP或者域名不仅仅要改负载均衡配置所有证书都要重新换一遍。--upload-certs参数也很关键它的作用是把控制面证书上传到集群中加密保存这样另外两台控制面节点加入时能自动拿证书不需要手工拷贝。生产环境第一次初始化时如果漏了这个参数后面加入控制面节点会非常痛苦要手动同步一堆敏感文件。初始化完成后第二、第三台控制面节点执行同样的加入命令只是IP地址换成对应的节点地址。这个顺序不能乱一定是第一台init成功之后后面两台才能join。等三台控制面节点就绪后才进入下一个阶段。3.3 第三步负载均衡与证书的分发逻辑VIP的实现方式不同环境有不同选择。有条件的会在交换机层面配VIP没有条件的就在集群里跑keepalived。我这里用的是keepalived每台控制面节点上装一个通过VRRP协议选举出当前持有VIP的节点。如果持有VIP的节点挂了另外两台节点会自动接替API Server的访问不会中断。这里有一个细节我吃亏过必须提醒一下。光有VIP还不够API Server和kubelet的流量默认走6443端口keepalived不会自动转发流量。好在kubeadm生成的控制面组件本身就是监听在所有网卡上的VIP只要落在某一台机器上这台机器的API Server就能直接响应。所以keepalived的配置文件里只需要定义虚拟IP不需要额外的端口转发规则。证书分发逻辑方面kubeadm采用了一套比较完整的证书体系。API Server对外通信使用apiserver.crt各组件之间通信使用各自的证书etcd有自己独立的CA。我个人不再对kubeadm生成的证书体系做额外改动只需确保一件事集群内部组件访问API Server一律走内网域名或VIP不要直连某一台节点的IP。这样即使节点故障访问也不会中断。# keepalived配置简要虚拟IP部分 vrrp_instance VI_k8s { state BACKUP interface ens192 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 123456 } virtual_ipaddress { 10.10.0.100 } }这里提一个很多新手会踩的坑keepalived的state和priority配置不一致时脑裂问题很难查。我建议所有节点都用BACKUP状态靠priority大小来选举主节点而不是把一台配成MASTER。这样即使主节点重启完再回来也不会因为状态抢占把流量带乱。3.4 第四步节点加入与工作负载就绪控制面就绪后接下来就是把worker节点加入集群。这一步在控制系统的编排下非常简单脚本自动从集群生成加入token然后对每台worker节点执行kubeadm join。加入成功后脚本会等待Node状态变为Ready再根据集群清单给节点打上标签和污点。# 生成加入token kubeadm token create --print-join-command # worker节点加入示例 kubeadm join 10.10.0.100:6443 \ --token xxxxx.yyyyy \ --discovery-token-ca-cert-hash sha256:xxxxxxxxxx这里token有一个天然的问题默认有效期是24小时。自动化流程如果隔了很久才加入新节点token早就过期了。所以我在控制系统里做了一个优化每次执行节点扩容前先检查token是否存在且有效如果无效就立即重新生成。这个逻辑写进脚本之后再也没在扩容时卡过token过期的问题。节点加入完成之后还要安装网络插件。我选择的是Calico它的最大优势是支持BGP模式直接路由性能损耗低。安装过程同样走声明式把Calico的YAML模板保存到配置仓库里按集群实际Pod网段渲染之后apply。这一步在一个多集群场景里特别容易犯错——如果记错Pod网段Calico初始化会失败整个集群的Pod网络都起不来。控制系统把网段参数和集群配置绑定在一起渲染时自动替换基本堵死了这个坑。最后一步是验证工作负载就绪。控制系统会创建一个临时测试Pod检查它能否正常调度和访问网络确认没问题后把它删掉。到这里一条生产集群的自动搭建流程就算走完了。4. 管理不止是“搭得起来”日常运维也要自动化4.1 健康巡检把零散的检查变成固定指标集群搭建完成只是开始真正的考验在日常运维。K8S集群最大的特点就是内部组件多、关联复杂。API Server、etcd、kubelet、网络插件、DNS任何一个环节出问题表现出来都是业务Pod异常。而人不可能时刻盯着Dashboard看所以我把巡检做成了定时任务定期在控制节点上执行。巡检脚本的输出是一个固定的指标集合。我最关心的指标包括这些节点状态Ready节点数、NotReady节点数、各节点负载情况。Pod状态异常Pod数、CrashLoopBackOff数量、Pending数量。证书有效期所有关键证书剩余天数低于30天的直接告警。etcd健康状态通过etcdctl检查集群健康以及磁盘碎片率和DB大小。镜像仓库连通性确保节点能正常拉取镜像这在离线环境或镜像仓库故障时尤其重要。# etcd健康检查示例 ETCDCTL_API3 etcdctl health \ --cacert/etc/kubernetes/pki/etcd/ca.crt \ --cert/etc/kubernetes/pki/etcd/server.crt \ --key/etc/kubernetes/pki/etcd/server.key \ --endpoints10.10.0.11:2379,10.10.0.12:2379,10.10.0.13:2379巡检结果我不建议只做“正常/异常”的二元判断。更合理的做法是输出数值然后让告警规则去判断阈值。比如节点内存使用率我关心的不是它是否超过90%而是趋势是不是在持续上升。把这个数值保存下来配合时间维度去看能提前好几周发现潜在风险。还有一个经验巡检时一定要关注Pod的分布情况。如果一台节点同时承载了大量同一个服务的Pod这台节点出故障时影响范围会被放大。控制系统里加了调度分布检查发现某台节点上同一Deployment的副本数超过设定值时就会提示人工介入调整。4.2 证书轮换的自动化处理K8S集群的证书问题基本上每个维护超过一年的人都遇到过。默认证书有效期一年到期后各个组件之间认证失败表现形态各异API Server拒绝请求、kubelet报x509证书过期、甚至工作负载正常但kubectl完全没反应。控制系统对证书的管理做了一个固定流程每个月巡检时自动检查所有证书的剩余时间发现剩余时间小于45天就触发轮换流程。轮换本身用kubeadm完成命令其实很简单# 检查证书过期时间 kubeadm certs check-expiration # 更新所有证书 kubeadm certs renew all # 重启控制面组件 kubeadm certs renew all --config/etc/kubernetes/kubeadm-config.yamlrenew all之后控制面的静态Pod会自动重启加载新证书。但注意worker节点上的kubelet证书也需要一并处理否则控制面再次连接worker时还是会过期。我们的流程是控制面更新完毕之后再批量对所有worker节点执行kubelet证书更新及重启。这个顺序一定要固定先控制面后节点不然会出现一边有新证书一边还是旧证书的中间状态。证书这东西最大的风险是你不知道什么时候会出问题。所以哪怕你有自动轮换机制也一定要保留一张“最后防线”的告警证书剩余天数低于7天时不光在日志里记录还要直接发告警到值班群。我见过太多因为阈值时间设置太久而让人放松警惕、最后证书真的过期的事故。4.3 集群升级编排与回滚策略K8S社区版本迭代很快生产集群不可能一直停在旧版本上。但升级操作对生产环境来说风险极高尤其跨小版本升级各种配置兼容性问题都可能冒出来。控制系统的升级流程遵循一个严格顺序先升级控制面再升级worker节点控制面内部先升级主节点再逐台升级其他控制面节点。控制面的升级主要用kubeadm完成。流程是先更新kubeadm到目标版本再执行kubeadm upgrade plan看影响确认无误后执行kubeadm upgrade apply。这一步会更新API Server、Controller Manager、Scheduler的镜像以及相关证书。# 查看升级计划 kubeadm upgrade plan # 执行控制面升级 kubeadm upgrade apply v1.29.3worker节点升级则相对简单先驱逐这台节点上的Pod然后升级kubelet和kubeadm最后重启kubelet并取消驱逐。这里有个小细节驱逐Pod时必须加--ignore-daemonsets参数因为DaemonSet的Pod被驱逐后马上会在同节点上重建一直等待会卡死流程。# 驱逐节点上的工作负载 kubectl drain node-01 \ --ignore-daemonsets \ --delete-emptydir-data \ --force # 升级kubelet相关软件包 apt install -y kubeadm1.29.3-00 kubelet1.29.3-00 # 重启kubelet systemctl restart kubelet # 恢复节点调度 kubectl uncordon node-01升级前后各做一次完整巡检比对指标差异。一旦发现升级后某个核心指标异常控制系统的回滚策略是控制面可以直接用kubeadm upgrade apply回到上一版本但worker节点只能通过重新安装旧版本软件包再重启kubelet来回滚。所以我的建议是升级前一定要记录每台节点当前的软件包版本和配置内容不只是记在脚本日志里还要拍照留档。真到回滚的时候这些记录能救你一把。4.4 节点上下线与流量调度控制生产环境的日常运维里节点下线是高频操作。机器要维修、硬件要扩容、机房要断电都需要把某台节点的业务先迁走。控制系统把整个下线流程做成了脚本先标记不可调度再驱逐全部工作负载最后等待节点进入Ready状态但不可调用才算完成。驱逐的顺序很有讲究。如果一个节点上同时运行着有状态服务和普通Deployment一定要先对有状态服务做特殊处理。比如类似数据库或消息队列这种角色直接驱逐可能导致数据丢失。我在流程里加了“预留节点”机制对标签为stateful的Pod不做强制驱逐只做可维护性通知由应用负责人确认后再手动操作。自动化不能一刀切这是我在生产环境学到的很重要的一课。节点上线正好相反先确保节点通过前置检查然后取消不可调度标记观察Pod调度情况确认新调度上来的Pod运行正常再结束流程。上线和下线两项操作建议都安排在业务低峰期执行并且每条流程的执行人必须收到完整的操作回执不能脚本跑完就当作结束了。5. 生产环境排障实录那些自动化和人共同踩过的坑5.1 问题背景与排查方法分离先讲查到的方法再讲坑自动化系统解决的是“可重复操作”的问题但现场排障永远是运维工作里最考验经验的环节。这里记录几个我在这套控制系统运行过程中真实遇到过的问题。虽然具体环境可能跟你的不一样但排查思路和坑点有很高的通用性。5.2 常见问题速查表故障现象可能原因排查命令处理方式kubelet启动失败容器运行时未启动或cgroup配置不一致systemctl status kubelet/journalctl -u kubelet确认containerd运行正常检查SystemdCgroup是否为trueNode一直NotReady网络插件未就绪或Pod网段冲突kubectl get pods -n kube-system检查Calico是否Running确认Pod CIDR一致API Server证书过期证书未及时轮换kubeadm certs check-expiration执行renew all并重启控制面组件Pod长时间Pending资源不足或节点污点未匹配kubectl describe pod xxx查看调度事件确认节点标签和污点是否允许调度etcd空间不足历史数据积压未压缩ETCDCTL_API3 etcdctl endpoint status执行碎片整理检查自动压缩策略配置跨节点Pod无法通信内核转发未开启或安全组拦截sysctl net.ipv4.ip_forward确保转发开启检查防火墙规则和Calico路由表5.3 最值得记住的几种场景复盘第一个让我印象深刻的坑是证书新老交替过程中的“间歇性访问失败”。当时控制面证书轮换完成控制面组件没问题但部分worker节点还在用旧证书连接API Server。现象就是kubectl能连上、业务Pod正常但某些节点上报的状态时而正常时而过期。这种半新半旧的中间状态是定时炸弹唯一的解决办法就是流程固定先更新完所有控制面接着立即更新全部worker节点中间不要隔太久。第二个坑是keepalived的VIP切换造成API Server短暂不可用。现象是VIP从第一台控制面节点切到第二台时应用侧出现两到三次连接报错。原因在于VIP切换后新的API Server实例要重新建立所有客户端的连接而客户端没有做重连重试机制。这个问题的根因不在K8S而在访问API Server的客户端配置。后来我们在所有调用方那里加了连接池重试逻辑问题才彻底消失。这个教训再次印证了高可用不仅仅靠集群本身客户端也得能适应故障切换。第三个坑是磁盘空间告警。某天发现etcd的DB文件增长异常快但集群负载并没有明显变化。查了一圈发现是有个服务在疯狂创建又删除Pod产生了大量历史数据。这个问题的本质是etcd存储了所有事件记录如果某个业务狂刷事件磁盘就会被缓慢写满。我调整了etcd自动压缩周期同时对高频Pod变更的命名空间加了事件数量告警。现在这套机制帮我至少避免了两三次类似的磁盘告警。另一个很多人容易忽略的坑是K8S版本升级时CNI插件的兼容性。我升级某个集群从1.28到1.29之后Calico的Pod一直CrashLoopBackOff。排查发现是Calico版本太旧不支持新版本K8S的API。升级集群版本的同时必须同步升级CNI插件。控制系统在升级编排里加了版本兼容性检查升级前对比集群版本和CNI插件支持范围不满足的话直接中止流程并提示人工处理。从此之后再没遇到过CNI插件被升级搞挂的问题。6. 这套控制系统用了两年之后我的一点感受K8S Control System这个名字听起来有点唬人但拆开看其实就是一份配置仓库加一堆可重入脚本加一个巡检计划。它帮我解决的最大问题不是节省时间而是让整个集群的状态变得可预期、可描述、可修复。以前我维护集群靠的是脑内状态图和一条条手敲的命令现在靠的是配置清单和自动对账。心态完全不一样了。这两年我踩过不少坑也积累了一些心得。如果你想在自己的环境里做类似的自动化控制链路我的建议是四条第一配置文件和逻辑必须分离配置文件里只写目标状态不要混入执行逻辑第二所有脚本必须做到可重入不然自动化流程执行到一半失败时会陷入更复杂的修复流程第三巡检一定要输出数值而不是简单的是或否趋势判断能帮你提前发现潜在问题第四自动化能解决80%的固定操作问题但剩下20%的边界情况人的判断和现场排查经验依然无可替代。最后说个实用的小技巧不管你的自动化系统做得多么完善都请在每台节点上保留人工介入的手段。比如确保每台机器都能通过堡垒机登录、sudo权限没有误删、紧急维护时能直接手动执行kubectl。生产环境永远保留最后一道“人工逃生门”任何时候都别把路走死。这套控制系统再可靠也只是工具真正为业务兜底的永远是清醒的运维判断。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。