news 2026/8/31 4:32:05

二进制部署高可用K8s集群:一键脚本设计与排障全复盘

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
二进制部署高可用K8s集群:一键脚本设计与排障全复盘

简介:这是一套面向云原生初学者与运维工程师的二进制高可用Kubernetes集群一键部署工具,专为深入理解k8s控制平面组件原理而设计,解决手动部署etcd、kube-apiserver、scheduler等组件流程繁琐、易出错的痛点。资源共13个文件,包含4个核心Shell脚本(如install_HA_k8s.sh、install_etcd.sh)、2个二进制压缩包(etcd与kubernetes-server)、2个CNI网络配置(calico.yaml、coredns.yaml)、3个CFSSL证书工具(cfssl_linux-amd64等)及readme.txt说明文档,整体包大小398.89MB,覆盖证书生成、高可用主节点初始化、工作节点加入、VIP漂移与网络插件安装全流程。已有1574人学习下载,用户可直接执行脚本完成从环境准备到集群验证的完整闭环,配套清晰步骤注释与典型排错提示,特别适合动手实践、面试复盘或企业轻量级生产环境快速搭建。 回想第一次在客户现场部署高可用K8s集群的场景,我到现在还记得当时的狼狈:三台Master、两台Node,加上独立etcd集群和VIP,整套下来每台机器都要敲上百条命令。仅证书签发这一步,就因为SAN漏掉了VIP,导致apiserver地址对不上,又重签了一遍。凌晨两点半,我坐在机房里泡着浓茶想,这套流程明明可以用脚本固化下来,为什么还要让后来的人继续重复踩坑?

后来我花了大约一周时间,把整套二进制高可用K8s集群的部署流程拆解、重写、反复测试,最终沉淀成一套一键部署脚本。本文就是这套脚本从架构设计、实现逻辑到落地排障的完整复盘。内容涵盖高可用架构的底层机制、脚本分层设计思路、证书与网络插件的关键细节,以及集群上线后最常见的故障排查链路。适合有一定Linux基础、想在离线内网或生产环境自建K8s集群的运维和交付同学参考。

1. 为什么是二进制:被kubeadm和RKE2都“坑”过之后的选择

1.1 三种部署方式的真实差异

开始写脚本之前,我先明确了一个问题:为什么不用现成的kubeadm,也没有直接用RKE2,而是选二进制?

很多人觉得kubeadm是官方推荐,RKE2是Rancher的轻量级发行版,这两个方案已经足够成熟,自己再折腾二进制有点重复造轮子。但我在实际交付过程中,两种方式都遇到过比较麻烦的场景。

kubeadm最大的问题在于依赖镜像仓库。初始化集群时要拉取kube-apiserver、kube-controller-manager、kube-scheduler、coredns、pause等一组镜像,升级时还要拉新版本镜像。生产环境里很多机房是隔离网络,就算有内网镜像仓库,也需要先把所有镜像搬运进去。如果赶上交付现场临时发现某个镜像校验值不对,或者仓库同步延迟,整个时间表都会被拖垮。

RKE2的思路是把K8s组件打包成单个二进制,内置containerd和k3s风格的目录结构,对运维来说确实省心。但它有个隐性成本:版本跟随Rancher的发布节奏走。企业内部如果有安全合规要求,需要固定某个K8s小版本、自己维护补丁,或者要跟内部监控、日志、安全Agent做深度适配时,RKE2的封装反而成了阻碍——很多东西不开放让你改。

二进制部署的本质,是把所有组件从官方Release页下载下来,自己生成证书、自己写systemd配置、自己组装集群。它不适合所有人,但适合以下场景:

部署方式镜像依赖版本可控性故障排查友好度对网络环境要求
kubeadm强依赖镜像仓库中,受发行版约束中,组件被容器封装需要镜像源
RKE2/RKE相对少低,跟随上游发行版节奏中,封装较多需要官方二进制包
二进制手动部署高,完全自主可控高,所有日志直接可见只要有tar包即可

1.2 二进制部署的本质:把“黑盒”变“白盒”

我后来跟朋友聊天时打过一个比方:kubeadm像是在帮你组装一台品牌机,双击安装就能开机,但你想看看内存插槽走线、电源模组怎么设计,它不让你拆;二进制部署更像是你从京东买齐了CPU、主板、电源、机箱,自己动手装一台。装的过程更费劲,但装完之后,每根线是谁接的、每个零件什么规格,你心里一清二楚。

这个“白盒”属性在排障时价值极大。K8s集群出问题时,大约有七成故障出在“组件之间的衔接层”——kubelet和容器运行时对不上、apiserver连不上etcd、证书过期、CNI网络没起来。如果是kubeadm部署,你要先钻进容器里看日志、看配置;如果是二进制部署,所有组件进程直接挂在systemd下,配置就在/etc/kubernetes目录里,日志直接打在journalctl里,哪里断了改哪里,整个过程非常直接。

1.3 什么样的环境适合二进制方案

从我实际经手的项目看,二进制方案在下面几类环境中出镜率最高:

  • 独立交付、信创替代、政企内网项目:网络隔离严重,镜像仓库不一定可用,但服务器上能上传tar包。
  • 多集群统一版本管理的平台团队:需要把几十套集群钉在同一个K8s版本上,kubeadm自动升级会引入版本漂移。
  • 深度定制场景:比如需要替换默认调度器、自定义kubelet启动参数、对接企业内部CA体系。
  • 学习K8s原理的技术团队:手动部署一遍,对apiserver、etcd、controller-manager、scheduler之间关系的理解,比只看文档深刻得多。

我写这套脚本的定位很明确:不追求覆盖所有花式功能,而是把一个三Master两Node的高可用集群,做到“给一份IP清单,一条命令跑完,半小时内交付可用集群”。

2. 高可用架构的底层逻辑:VIP、负载均衡和选主机制

2.1 部署拓扑:三Master双Worker的最小高可用形态

在动手写脚本之前,我先把目标架构画了出来。没有用mermaid,直接看文字描述也足够直观。

集群最少需要五台机器:

  • 三台Master节点,跑kube-apiserver、kube-controller-manager、kube-scheduler、etcd;
  • 两台Worker节点,跑kubelet、kube-proxy和业务负载。

为什么Master要三台?因为etcd要用Raft协议保证数据一致性,Raft要求多数派才能写入。三节点集群允许挂掉一台,剩下两台仍是多数派;如果只有两台,挂掉一台就只剩一台,不满足多数派条件,整个集群的写入会全部卡住。

三台Master同时承担etcd角色,省下了单独部署etcd集群的机器成本。对于生产环境规模不大、几百个Pod以内的集群,这种“叠放”架构足够稳定。如果业务量极大,etcd和apiserver之间会产生资源竞争,那时候再拆成独立etcd集群也不迟。

2.2 etcd由Raft协议决定的三节点奇数规则

规划etcd时有一个点必须想明白:etcd选主和数据写入的规则,决定了集群节点数必须是奇数

Raft协议里,一个写请求要提交成功,必须得到超过半数的节点确认。三节点集群允许挂一台,五节点集群允许挂两台。如果部署成四节点,允许挂一台,但四台机器只比三台多承担了三分之一的存储成本,却没有提升任何可用性——所以四节点etcd是运维里最常见的浪费型设计。

我在脚本里同时处理了etcd的两个核心运维参数,这两个参数也建议你在自己的集群里提前配好:

--quota-backend-bytes=8589934592 --auto-compaction-mode=periodic --auto-compaction-retention=72h

quota-backend-bytes是etcd存储的配额上限,默认2G很容易写满,我直接给到8G。auto-compaction-retention=72h表示每72小时自动压缩一次历史数据,防止etcd的数据文件无限膨胀。这两个参数不配,集群跑上几个月后很容易出现“数据目录超大但看不到什么日志”的诡异故障。

2.3 关键决策:kube-apiserver访问etcd到底该走哪条路径

很多人第一次搭高可用K8s集群时会在这里卡很久:kube-apiserver连接etcd时,地址列表里到底填什么?

  • 方案A:填三个Master节点的物理IP,比如https://192.168.10.11:2379,https://192.168.10.12:2379,https://192.168.10.13:2379
  • 方案B:填VIP,比如https://192.168.10.100:2379

我实际测试下来,方案A更稳。因为kube-apiserver和etcd同机部署时,直连本机etcd不但延迟最低,而且即使VIP发生漂移,apiserver和etcd之间的连接也不会断。

方案B的问题在于,HAProxy如果你只把2379端口也纳入VIP转发,那么VIP抖一下,所有apiserver到etcd的连接都要重连。虽然K8s能自动重试,但生产现场任何一次不必要的抖动都可能引发雪崩。所以我的脚本里,etcd服务直接暴露在三台Master的物理IP上,VIP只转发6443端口。

2.4 规划清单:IP、主机名、CIDR、端口

写脚本前还有一份清单必须确定,否则后面改起来非常痛苦:

配置项示例值说明
VIP192.168.10.100kube-apiserver的负载入口
Master节点IP192.168.10.11/12/13三台,跑etcd和K8s控制面组件
Worker节点IP192.168.10.21/22业务负载节点
Service CIDR10.96.0.0/12K8s Service虚拟IP段
Pod CIDR10.244.0.0/16Pod容器IP段,CNI默认用这个段
节点名称k8s-master-01/02/03, k8s-node-01/02必须全局唯一,不能重名
DNS服务器无特殊要求推荐内网DNS,全节点能互通

这里有一个经验要重点说:主机名、IP和CIDR一定要在跑脚本前确定好,并且写进环境配置文件,不要部署到一半再改。主机名重复、IP错位、Service CIDR和Pod CIDR冲突,这三类问题在K8s排障里占比极高。脚本里我做了前置校验,发现IP不连通、主机名重名、端口被占用就直接报错退出,这也是“一键部署”能在各种现场稳定复现的关键。

3. 一键部署脚本的分层设计:从入口到落地的完整链路

3.1 入口脚本:交互式参数采集与配置生成

我把整个部署流程拆成了两层。第一层是一份全局环境配置文件,我习惯命名为cluster.env;第二层是一串带编号的模块脚本,从0108按顺序执行。

入口脚本本身不干重活,只做三件事:读取配置、校验环境、按顺序调用子模块。这种做法最大的好处是:如果在某个模块执行失败,你可以单独重新跑那一个模块,不用从头再来一遍。

#!/bin/bash # deploy-k8s.sh set -o errexit set -o pipefail source ./cluster.env echo "[INFO] 开始部署 Kubernetes ${K8S_VERSION} 集群..." ./scripts/00-check-env.sh ./scripts/01-init-system.sh ./scripts/02-gen-certs.sh ./scripts/03-install-etcd.sh ./scripts/04-install-master.sh ./scripts/05-install-node.sh ./scripts/06-install-cni.sh ./scripts/07-verify-cluster.sh

入口脚本里的set -o errexit非常关键,意思是任何一条命令失败就让脚本整体退出。K8s部署是链条式依赖,前面失败后面继续跑只会产生一堆半成品配置,重启后更难排查。我见过很多朋友的部署脚本不加这个参数,结果某个证书生成失败,后面的安装流程照样跑了一整轮,最后整个集群状态混乱到只能重装系统。

3.2 幂等化的奥秘:怎么做到脚本反复执行不出乱子

“幂等”这个词听起来抽象,但理解起来很简单:同一个脚本,在已经部署过的机器上再跑一遍,不会破坏已有环境,不会重复生成一堆垃圾配置

K8s部署里最容易出现重复执行问题的是证书生成和systemd配置。如果脚本每次执行都重新生成CA证书,那么旧证书全部失效,集群里所有组件之间的信任关系直接崩掉。所以我的证书模块开头会先检查目标路径是否存在:

if [ -f "/etc/kubernetes/pki/ca.crt" ]; then echo "[WARN] 已检测到CA证书,跳过证书生成。" else ./gen-certs.sh fi

systemd unit文件也一样。一个服务已经在运行,你又往/usr/lib/systemd/system/下覆盖了一份新的unit文件,再执行systemctl daemon-reload,正在运行的服务状态可能变成activating或直接重启,对线上集群来说这是不可接受的。所以脚本里每个服务模块都做了同样的判断:配置文件存在且服务状态为active (running),则跳过。

这套幂等逻辑让我在后续排障时代价极低:现场出了问题,丢一个模块重跑,而不是整台机器重装。

3.3 证书模块:所有网络通信的安全底座

证书是K8s二进制部署里最绕不开、也最容易出错的部分,我单独花了一整节讲它。

K8s集群内部通信全部走TLS加密,整个证书体系分三套:

  • etcd证书:etcd节点之间的peer通信、etcd对外服务的client通信;
  • Kubernetes组件证书:kube-apiserver对外提供服务的server证书,以及controller-manager、scheduler、kubelet、kube-proxy等组件的client证书;
  • kubeconfig证书:kubectl、kubelet等客户端访问apiserver时使用的用户证书。

三套证书共用同一个CA,还是各自独立的CA?我选择的是etcd单独一套CA,K8s单独一套CA。好处是职责隔离——如果某个etcd节点被攻破或者证书误发,K8s控制面证书不受影响;反过来也一样。

生成K8s apiserver证书时,有一个隐藏的大坑就是SAN(Subject Alternative Name)。apiserver的证书必须包含所有可能的访问地址,包括:

  • 三个Master节点的物理IP;
  • VIP地址;
  • Service CIDR里的第一个IP,通常是10.96.0.1,这是kube-apiserver在集群内部的Service地址;
  • 本机回环地址127.0.0.1
  • 域名,比如kubernetes.default.svc.cluster.localkubernetes.default.svckubernetes.defaultkubernetes

我最开始手动部署时漏掉了VIP,导致kubectl通过VIP访问apiserver时报证书校验失败,那个错误信息还特别不直观——x509: certificate is valid for 10.96.0.1, not 192.168.10.100。看到这个你才能反应过来是SAN没配全。所以脚本里我把SAN列表定义成了数组,并且强制把VIPMASTER_IPSSERVICE_CIDR首地址都加进去:

SAN_LIST=( "127.0.0.1" "10.96.0.1" "${VIP}" "${MASTER_IPS[@]}" "kubernetes" "kubernetes.default" "kubernetes.default.svc" "kubernetes.default.svc.cluster.local" )

3.4 组件安装与systemd托管:让二进制程序变成可靠服务

证书生成完之后,就是安装组件。Master节点的组件从官方Release页下载对应的tar包,解压后把二进制文件放到/usr/local/bin/目录下,再为每个组件写一个systemd unit文件。

以kube-apiserver为例,它的systemd单元文件核心内容大概长这样:

[Unit] Description=Kubernetes API Server Documentation=https://github.com/kubernetes/kubernetes After=network.target [Service] ExecStart=/usr/local/bin/kube-apiserver \ --bind-address=0.0.0.0 \ --secure-port=6443 \ --etcd-servers=https://192.168.10.11:2379,https://192.168.10.12:2379,https://192.168.10.13:2379 \ --service-cluster-ip-range=10.96.0.0/12 \ --service-node-port-range=30000-32767 \ --client-ca-file=/etc/kubernetes/pki/ca.crt \ --tls-cert-file=/etc/kubernetes/pki/apiserver.crt \ --tls-private-key-file=/etc/kubernetes/pki/apiserver.key \ --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt \ --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key \ --allow-privileged=true \ --service-account-key-file=/etc/kubernetes/pki/sa.pub \ --kubelet-preferred-address-types=InternalIP,Hostname,ExternalIP Restart=on-failure RestartSec=10 LimitNOFILE=65535 [Install] WantedBy=multi-user.target

有一个微小的启动参数值得注意:--kubelet-preferred-address-types=InternalIP,Hostname,ExternalIP。如果不指定这个参数,新版K8s默认会优先用Hostname去连接kubelet,而在没有内网DNS的环境里,Hostname解析不出来,apiserver访问kubelet就会超时,表现为kubectl logskubectl exec经常卡住。我见过太多集群没配这个参数,排查半天最后发现是DNS解析问题。

controller-manager和scheduler的systemd配置相对简单,核心是加--leader-elect=true。这两个组件不支持多实例同时工作,必须靠leader选举机制保证同一时刻只有一个实例在真正干活。三台Master上各跑一个实例,宕机一台,其他实例立刻顶上,这就是Master节点组件高可用的实现原理。

Worker节点上的kubelet和kube-proxy也用systemd托管。kubelet的配置里有一个参数跟容器运行时直接相关,我拿到下一节专门讲——因为这个地方错一步,整个集群的Pod都起不来。

4. 三个最容易翻车的环节:证书SAN、网络插件和运行时对接

4.1 证书SAN漏配导致apiserver访问异常

前面说了SAN漏配的坑,这里再展开一个真实排障过程。

现象:集群部署完成后,在任意一台Master上执行kubectl get nodes,报错Unable to connect to the server: x509: certificate is valid for 10.96.0.1, not 192.168.10.100

这个报错信息的意思是:你拿着证书去访问192.168.10.100,但证书里写明的合法地址只有10.96.0.1。如果你之前手动生成证书时忘了加VIP,那么通过VIP访问就永远不可能成功。

处理方式也很直接:重新生成apiserver证书,把VIP加进SAN,然后用新证书重启kube-apiserver组件。

cfssl gencert -ca=ca.crt -ca-key=ca.key \ -profile=kubernetes \ -hostname=127.0.0.1,10.96.0.1,192.168.10.11,192.168.10.12,192.168.10.13,192.168.10.100,kubernetes,kubernetes.default \ kubernetes-csr.json | cfssljson -bare apiserver

这条命令里,-hostname参数就是决定证书“允许哪些地址访问”的关键。我在脚本里把它参数化,每次生成前自动拼接,彻底杜绝手写漏项。

4.2 容器运行时cgroup驱动要和kubelet保持一致

K8s部署的第二个高频翻车点,是kubelet和容器运行时之间的cgroup驱动不一致。

cgroup是Linux内核用来限制进程资源使用量的机制。K8s要控制Pod的CPU、内存使用上限,kubelet必须用cgroup去约束容器。containerd和kubelet都有两套驱动可以选择:systemdcgroupfs只要kubelet和容器运行时用的驱动不一致,kubelet就会报错,节点状态直接NotReady。

我在脚本里统一处理成systemd驱动,这是目前所有主流发行版推荐的方案。

kubelet侧在配置文件中指定:

apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration cgroupDriver: systemd

containerd侧在/etc/containerd/config.toml中指定:

[plugins."io.containerd.grpc.v1.cri"] systemd_cgroup = true

如果这两个参数不一致,kubelet启动时会报一个非常明确但很容易被忽略的错误:

failed to run Kubelet: failed to validate kubelet flags: cgroup-driver does not match the runtime cgroup-driver

看见这个报错,先别慌,对照一下两边驱动配置,改成一致即可。

另外,使用containerd作为容器运行时,还有一个镜像源问题。K8s每个Pod启动前都要先拉一个pause镜像,这个镜像默认地址是registry.k8s.io/pause:3.9。在完全离线的环境里,必须提前把pause镜像导入到每个节点的containerd里,并且把sandbox_image改成内网仓库地址。我的脚本里专门有一个模块做镜像预加载,部署前先ctr images import,确保节点就绪后有镜像可用。

4.3 CNI网络选型和IPVS内核模块加载

集群节点状态变成Ready之后,最激动人心的时刻是创建第一个Pod。但很多二进制部署的集群,Pod创建了却一直ContainerCreating,卡在沙箱创建或网络设置阶段。这时候八成是CNI网络插件没有正确部署。

CNI是K8s容器网络的接口标准,常见的实现有Flannel、Calico、Cilium。我的脚本默认用Flannel,原因是它最简单、最稳定、能满足绝大多数场景的需求——Pod互通、Service后端的负载均衡,Flannel的VXLAN模式都能覆盖。

Flannel部署上去之后,有一个重要的前置条件:kube-proxy如果要开启IPVS模式,内核必须加载相应的IPVS模块。IPVS是Linux内核里比iptables更高效的负载均衡方案,K8s访问Service流量时默认会走这里。

脚本里我写了一段前置检查,确保以下内核模块全部存在:

ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack_ipv4

如果模块缺失,用modprobe加载:

modprobe ip_vs modprobe ip_vs_rr modprobe ip_vs_wrr modprobe ip_vs_sh modprobe nf_conntrack_ipv4

注意,nf_conntrack_ipv4在新版内核里已经改名成nf_conntrack了,脚本里要做一个版本判断。这部分细节网上教程很少提到,但部署时遇到modprobe: FATAL: Module nf_conntrack_ipv4 not found的概率并不低。

另外还要确认ip_forward开启:

sysctl -w net.ipv4.ip_forward=1

这个参数不开启,容器内部的流量转发会失败,Pod能创建但网络完全不通。

4.4 部署脚本执行后的验证指标

脚本最后一定要有验证环节,不能跑完就算完。我的07-verify-cluster.sh模块会自动执行以下几项检查:

  • kubectl get nodes,确认所有节点状态为Ready
  • kubectl get pods -n kube-system,确认coredns、kube-proxy、flannel等系统组件全部Running
  • 创建一个测试Pod,等待它完全启动,再删除;
  • kubectl get svc -A,确认Service网络分配正常。

如果检查失败,脚本会输出对应组件的日志路径,并提示用户用journalctl -u kubelet -fkubectl describe进一步排查。

5. 集群上线之后的排障经验:从NodeNotReady到服务访问不通

5.1 NodeNotReady的完整排查链路

集群部署完并不代表万事大吉,NodeNotReady是运维群里出现频率最高的问题。我总结出一条固定排查链路,每次遇到都能快速定位。

第一步,在Master上执行kubectl describe node <节点名>,看Conditions里的信息。但这里有一句大实话:describe输出的内容经常不够具体,只能告诉你节点处于什么状态,没法告诉你根因。

第二步,登录到出问题的节点上,执行systemctl status kubelet -l。这一步能确定kubelet到底有没有在运行。如果kubelet根本没起来,再看日志:

journalctl -u kubelet -f --no-pager

kubelet日志里出现频率最高的几个根因,我整理成了一张速查表:

日志关键字根因处理方式
cgroup-driver does not matchkubelet与容器运行时cgroup驱动不一致统一改成systemd并重启
Container runtime network not readyCNI网络插件未部署或异常检查flannel/calico Pod状态
Error getting nodekubeconfig证书或RBAC权限问题检查kubelet.kubeconfig证书有效性
Failed to get system container stats节点资源异常或cgroup管理失控检查磁盘和inode使用率
PLEG is not healthy容器运行时响应超时排查containerd状态,必要时重启

第三步,确认容器运行时状态:

systemctl status containerd crictl ps

kubelet和containerd之间存在一个“容器运行时接口”,如果containerd挂掉或响应缓慢,kubelet会把节点标记为NotReady。这里有个容易忽略的点:containerd异常通常是因为磁盘满了,所以排查时先df -hdf -i看一眼,百分之六十的运行时假死都是磁盘写满导致的。

5.2 镜像拉取失败和DNS解析异常的常见根因

节点状态恢复Ready之后,第二步通常是部署业务应用。这里最常见的两个问题是ImagePullBackOff和DNS解析异常。

ImagePullBackOff的排查思路很直白:执行kubectl describe pod <pod名>,看Events里拉镜像时具体报什么错。常见原因无非三种:

  • 镜像地址写错,或者tag不存在;
  • 私有仓库需要认证,但Pod没有配置imagePullSecrets
  • 节点无法访问镜像仓库,离线环境或网络策略拦截。

离线环境下我用脚本部署时,会先在所有节点上预加载业务镜像。具体做法是把镜像打成tar包,然后每个节点执行ctr -n k8s.io images import xxx.tar。注意,containerd的命名空间默认是k8s.io,如果用ctr images import不带-n参数,镜像会被导入到默认命名空间,K8s根本看不到。

DNS解析异常的表现是:Pod能启动,但Service域名访问不通,比如curl http://my-service.default.svc一直超时。

排查链路是:

  1. 先确认CoreDNS Pod是否Running:kubectl get pods -n kube-system | grep coredns
  2. 再确认CoreDNS日志里有没有报错:kubectl logs -n kube-system -l k8s-app=kube-dns
  3. 下一步检查kube-proxy是否正常工作:kubectl logs -n kube-system -l k8s-app=kube-proxy
  4. 最后看节点上IPVS规则是否生成:ipvsadm -L -n,如果没有任何规则,说明kube-proxy的IPVS模式初始化失败。

DNS解析异常还有一个很容易被忽略的坑:CoreDNS的副本数如果超过一个,且它们之间网络互通正常、但上游DNS配置不一致,会导致部分Pod解析正常、部分Pod解析超时。我建议把CoreDNS副本数固定为2,并检查CoreDNS ConfigMap里的forward配置是否指向了正确的上游DNS。

5.3 高可用组件选主异常时的特征与处理

高可用集群里,controller-manager和scheduler通过leader选举机制实现多活,但它们选主异常的隐蔽性很强。节点都显示Ready,Pod也都在跑,但某些功能时好时坏。

比如kubectl delete pod之后,Pod被删了却迟迟没有新Pod创建,或者控制器长期不响应期望状态。这时候要看controller-manager的日志:

journalctl -u kube-controller-manager -f

如果出现大量leaderelection lostfailed to acquire lease,说明选主机制出了问题。常见原因有两个:

  • 三个Master节点时钟不同步。leader选举依赖租约过期时间,时钟偏差太大会导致租约频繁过期、反复选主。
  • etcd写入延迟太高。controller-manager选主要往etcd写一个Lease记录,如果etcd响应慢,租约续不上,就会不断丢主。

处理方案也很明确:先在所有节点上配置chrony或ntp时间同步;然后检查etcd健康状态:

etcdctl --endpoints=https://192.168.10.11:2379,https://192.168.10.12:2379,https://192.168.10.13:2379 \ --cacert=/etc/etcd/pki/ca.crt \ --cert=/etc/etcd/pki/server.crt \ --key=/etc/etcd/pki/server.key \ endpoint health --cluster

看到三端全部返回healthy,再观察选主日志是否恢复。这一套组合拳打下来,大部分选主异常都能解决。

6. 把脚本沉淀成运维资产:日常管理与后续扩展

6.1 节点的增删改:脚本如何支持纳管新节点

集群交付后第一个常见需求就是扩容加节点。我脚本里单独写了一个add-node.sh,它做的事情可以概括为:

  1. 解压K8s Node组件二进制包;
  2. 生成kubelet、kube-proxy需要的kubeconfig文件;
  3. 从已有Master节点拷贝CA证书和配置;
  4. 启动kubelet和kube-proxy;
  5. 在Master上执行kubectl label node添加角色标签。

新节点的证书不用重新生成。K8s的证书体系里,kubelet的证书有两种方式:一种是手动签发长期证书,另一种是通过kubelet TLS Bootstrapping机制自动申请。我脚本里默认采用bootstrap方式:

kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubeconfig \ --kubeconfig=/etc/kubernetes/kubelet.kubeconfig

这样一来,新节点只需要一个bootstrap token,kubelet启动后会自动跟apiserver申请证书,并把申请到的证书写入kubelet.kubeconfig。后续就算证书到期,也能自动续期,省去了每过一年就得手动重签证书的麻烦。

6.2 从集群到业务:部署LNMP、Spring Boot、Redis的衔接

集群搭好之后,下一步就是往里面运行业务。搜热词里能看出,很多人关心K8s部署LNMP、Spring Boot项目、Redis集群。这里我给一个通用的思维框架:K8s部署业务的核心不是“把应用塞进去”,而是把应用拆解成部署、服务、配置三块。

  • 无状态应用用Deployment管理,Pod挂了自动拉起;
  • 有状态应用用StatefulSet管理,比如Redis集群、Kafka集群,每个Pod有固定网络标识和存储;
  • 配置和敏感信息用ConfigMap和Secret管理,不要写死在镜像里。

以部署一个Spring Boot项目为例,最简化的yaml文件至少包含:

apiVersion: apps/v1 kind: Deployment metadata: name: springboot-app spec: replicas: 3 selector: matchLabels: app: springboot-app template: metadata: labels: app: springboot-app spec: containers: - name: app image: registry.internal/springboot-app:1.0.0 ports: - containerPort: 8080 --- apiVersion: v1 kind: Service metadata: name: springboot-app spec: selector: app: springboot-app ports: - port: 80 targetPort: 8080

这套脚本交付的集群自带Service负载均衡、健康检查和自动恢复能力。后面再往集群里接Ingress、Prometheus监控、日志采集,都是在现有底座上长新枝的事。

6.3 升级策略与备份容灾

二进制部署有个一直被人诟病的点:升级麻烦。其实一旦脚本化之后,升级过程反而比kubeadm更可控。

我的做法是:先在一台测试节点上升级kubelet二进制,观察日志和Pod稳定性,然后逐台滚动替换。控制面组件升级则按“先备节点、后主节点”的顺序,每台替换后要等它重新加入集群、选主成功,再操作下一台。

备份方面,K8s集群最重要的备份对象不是各组件配置,而是etcd数据。etcd里保存了集群的全部状态——所有Pod、Service、Deployment、ConfigMap、Secret。我建议每天做一次快照,保留最近7天:

etcdctl snapshot save /backup/etcd-snapshot-$(date +%F).db \ --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/etcd/pki/ca.crt \ --cert=/etc/etcd/pki/server.crt \ --key=/etc/etcd/pki/server.key

恢复时使用etcdctl snapshot restore,然后把恢复出来的数据目录替换到目标节点上。整个恢复流程我在测试环境演练过很多次,如果数据备份完整,从宕机到恢复,半小时内能拉起一个可用集群。

我在使用中发现,二进制部署这套方案最值钱的地方,不是“不依赖kubeadm”这个形式,而是它逼着你把集群的每一个组件都理解透了。等你在生产环境遇到过几次NodeNotReady、证书过期、etcd容量告警之后,就会明白:所有能用脚本覆盖的复杂流程,背后都藏着一套可以复用的底层认知。

最后分享一个小技巧:脚本维护时,尽量把版本号、IP、CIDR这类变量收敛到最顶层的cluster.env文件里,不要散落在各个模块脚本中。这样每次版本升级或者换一套环境重新交付,只需要改一份配置文件,剩下的事情交给脚本去跑。这套脚本帮我完成过多次现场交付,从机器准备到集群可用,最快一次大约二十五分钟。希望能帮你少踩一些我当年踩过的坑。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/31 4:32:02

【MuJoCo从入门到精通】第3章 MJCF 基础语法

开篇:本文所有截图都经过了作者验证!部分内容由大模型生成,作者经过整理和实验验证,排除了大模型生成中的错误和代码版本,环境错误,幻觉等所有坑点,所有踩过的坑希望能助力您学习MuJoCo 提升效率。可放心学习! 在上一章中,我们通过单摆模型学习了 ​​mjModel​​ 和…

作者头像 李华
网站建设 2026/8/31 4:30:31

Python自动化测试脚本:如何利用Python编写高效的自动化测试脚本

用于自动化执行测试用例来编写的程序, 是自动化测试脚本, 它能够助力我们迅速、高效地达成软件测试工作, 提升测试质量以及效率。若要开启自动化测试脚本的开发之旅, 首要之事便是得掌握语言的基础要点。它属于一种易于学习且功能较强的编程语言, 在诸多领域都有广泛运用, 像软…

作者头像 李华
网站建设 2026/8/31 4:25:55

电子学原理与应用:从LED不亮到电路工程思维的养成

第一次接触电子学的人&#xff0c;经常会陷入一种奇怪的状态&#xff1a;书上的欧姆定律、基尔霍夫定律、三极管放大原理都看懂了&#xff0c;考试也能过&#xff0c;可一到实验台上&#xff0c;照着教程搭一个最简单的LED电路&#xff0c;却可能怎么都不亮。这时你才发现&…

作者头像 李华
网站建设 2026/8/31 4:22:53

Rust框架选型避坑指南:性能优势与实际场景验证

每次看到“史上最强框架”“最牛逼框架”这类标题&#xff0c;我都习惯先打两个问号&#xff1a;什么场景下的最强&#xff1f;谁在什么条件下验证过&#xff1f;最近关于 Rust 框架的讨论非常多&#xff0c;有些说法甚至直接拿 Rust 去对比整个生态里的其他选择。这里先给一个…

作者头像 李华
网站建设 2026/8/31 4:20:09

从Fork到状态机:AI Agent工作流中的分叉与人工审批

如果只看“有人 fork 了一个项目”这行消息&#xff0c;很容易把它理解成“仓库多了一份拷贝”。但放在开源协作和 AI Agent 工具链这两个语境里&#xff0c;它远比表面上复杂。最近 HumanLayer 发布 effect-machine 分叉项目的消息&#xff0c;之所以能同时引起 Effect 生态和…

作者头像 李华
网站建设 2026/8/31 4:19:55

Unsloth实战:本地GPU微调大模型与QLoRA显存优化指南

在本地 GPU 上微调大模型&#xff0c;很多人卡在第一步&#xff1a;模型能加载&#xff0c;但一训练就显存溢出&#xff0c;或者速度慢到没法迭代。Unsloth 这个开源项目就是专门解决这个问题的&#xff0c;它把 LoRA、QLoRA 微调流程做了大量底层优化&#xff0c;让消费级显卡…

作者头像 李华