简介这是一套面向云原生初学者与运维工程师的二进制高可用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-bytes8589934592 --auto-compaction-modeperiodic --auto-compaction-retention72hquota-backend-bytes是etcd存储的配额上限默认2G很容易写满我直接给到8G。auto-compaction-retention72h表示每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第二层是一串带编号的模块脚本从01到08按顺序执行。入口脚本本身不干重活只做三件事读取配置、校验环境、按顺序调用子模块。这种做法最大的好处是如果在某个模块执行失败你可以单独重新跑那一个模块不用从头再来一遍。#!/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 fisystemd 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单独一套CAK8s单独一套CA。好处是职责隔离——如果某个etcd节点被攻破或者证书误发K8s控制面证书不受影响反过来也一样。生成K8s apiserver证书时有一个隐藏的大坑就是SANSubject Alternative Name。apiserver的证书必须包含所有可能的访问地址包括三个Master节点的物理IPVIP地址Service CIDR里的第一个IP通常是10.96.0.1这是kube-apiserver在集群内部的Service地址本机回环地址127.0.0.1域名比如kubernetes.default.svc.cluster.local、kubernetes.default.svc、kubernetes.default、kubernetes。我最开始手动部署时漏掉了VIP导致kubectl通过VIP访问apiserver时报证书校验失败那个错误信息还特别不直观——x509: certificate is valid for 10.96.0.1, not 192.168.10.100。看到这个你才能反应过来是SAN没配全。所以脚本里我把SAN列表定义成了数组并且强制把VIP、MASTER_IPS、SERVICE_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] DescriptionKubernetes API Server Documentationhttps://github.com/kubernetes/kubernetes Afternetwork.target [Service] ExecStart/usr/local/bin/kube-apiserver \ --bind-address0.0.0.0 \ --secure-port6443 \ --etcd-servershttps://192.168.10.11:2379,https://192.168.10.12:2379,https://192.168.10.13:2379 \ --service-cluster-ip-range10.96.0.0/12 \ --service-node-port-range30000-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-privilegedtrue \ --service-account-key-file/etc/kubernetes/pki/sa.pub \ --kubelet-preferred-address-typesInternalIP,Hostname,ExternalIP Restarton-failure RestartSec10 LimitNOFILE65535 [Install] WantedBymulti-user.target有一个微小的启动参数值得注意--kubelet-preferred-address-typesInternalIP,Hostname,ExternalIP。如果不指定这个参数新版K8s默认会优先用Hostname去连接kubelet而在没有内网DNS的环境里Hostname解析不出来apiserver访问kubelet就会超时表现为kubectl logs、kubectl exec经常卡住。我见过太多集群没配这个参数排查半天最后发现是DNS解析问题。controller-manager和scheduler的systemd配置相对简单核心是加--leader-electtrue。这两个组件不支持多实例同时工作必须靠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 -caca.crt -ca-keyca.key \ -profilekubernetes \ -hostname127.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都有两套驱动可以选择systemd和cgroupfs。只要kubelet和容器运行时用的驱动不一致kubelet就会报错节点状态直接NotReady。我在脚本里统一处理成systemd驱动这是目前所有主流发行版推荐的方案。kubelet侧在配置文件中指定apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration cgroupDriver: systemdcontainerd侧在/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_forward1这个参数不开启容器内部的流量转发会失败Pod能创建但网络完全不通。4.4 部署脚本执行后的验证指标脚本最后一定要有验证环节不能跑完就算完。我的07-verify-cluster.sh模块会自动执行以下几项检查kubectl get nodes确认所有节点状态为Readykubectl get pods -n kube-system确认coredns、kube-proxy、flannel等系统组件全部Running创建一个测试Pod等待它完全启动再删除kubectl get svc -A确认Service网络分配正常。如果检查失败脚本会输出对应组件的日志路径并提示用户用journalctl -u kubelet -f或kubectl describe进一步排查。5. 集群上线之后的排障经验从NodeNotReady到服务访问不通5.1 NodeNotReady的完整排查链路集群部署完并不代表万事大吉NodeNotReady是运维群里出现频率最高的问题。我总结出一条固定排查链路每次遇到都能快速定位。第一步在Master上执行kubectl describe node 节点名看Conditions里的信息。但这里有一句大实话describe输出的内容经常不够具体只能告诉你节点处于什么状态没法告诉你根因。第二步登录到出问题的节点上执行systemctl status kubelet -l。这一步能确定kubelet到底有没有在运行。如果kubelet根本没起来再看日志journalctl -u kubelet -f --no-pagerkubelet日志里出现频率最高的几个根因我整理成了一张速查表日志关键字根因处理方式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 pskubelet和containerd之间存在一个“容器运行时接口”如果containerd挂掉或响应缓慢kubelet会把节点标记为NotReady。这里有个容易忽略的点containerd异常通常是因为磁盘满了所以排查时先df -h和df -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一直超时。排查链路是先确认CoreDNS Pod是否Runningkubectl get pods -n kube-system | grep coredns再确认CoreDNS日志里有没有报错kubectl logs -n kube-system -l k8s-appkube-dns下一步检查kube-proxy是否正常工作kubectl logs -n kube-system -l k8s-appkube-proxy最后看节点上IPVS规则是否生成ipvsadm -L -n如果没有任何规则说明kube-proxy的IPVS模式初始化失败。DNS解析异常还有一个很容易被忽略的坑CoreDNS的副本数如果超过一个且它们之间网络互通正常、但上游DNS配置不一致会导致部分Pod解析正常、部分Pod解析超时。我建议把CoreDNS副本数固定为2并检查CoreDNS ConfigMap里的forward配置是否指向了正确的上游DNS。5.3 高可用组件选主异常时的特征与处理高可用集群里controller-manager和scheduler通过leader选举机制实现多活但它们选主异常的隐蔽性很强。节点都显示ReadyPod也都在跑但某些功能时好时坏。比如kubectl delete pod之后Pod被删了却迟迟没有新Pod创建或者控制器长期不响应期望状态。这时候要看controller-manager的日志journalctl -u kube-controller-manager -f如果出现大量leaderelection lost或failed to acquire lease说明选主机制出了问题。常见原因有两个三个Master节点时钟不同步。leader选举依赖租约过期时间时钟偏差太大会导致租约频繁过期、反复选主。etcd写入延迟太高。controller-manager选主要往etcd写一个Lease记录如果etcd响应慢租约续不上就会不断丢主。处理方案也很明确先在所有节点上配置chrony或ntp时间同步然后检查etcd健康状态etcdctl --endpointshttps://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它做的事情可以概括为解压K8s Node组件二进制包生成kubelet、kube-proxy需要的kubeconfig文件从已有Master节点拷贝CA证书和配置启动kubelet和kube-proxy在Master上执行kubectl label node添加角色标签。新节点的证书不用重新生成。K8s的证书体系里kubelet的证书有两种方式一种是手动签发长期证书另一种是通过kubelet TLS Bootstrapping机制自动申请。我脚本里默认采用bootstrap方式kubelet --bootstrap-kubeconfig/etc/kubernetes/bootstrap-kubeconfig \ --kubeconfig/etc/kubernetes/kubelet.kubeconfig这样一来新节点只需要一个bootstrap tokenkubelet启动后会自动跟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 \ --endpointshttps://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文件里不要散落在各个模块脚本中。这样每次版本升级或者换一套环境重新交付只需要改一份配置文件剩下的事情交给脚本去跑。这套脚本帮我完成过多次现场交付从机器准备到集群可用最快一次大约二十五分钟。希望能帮你少踩一些我当年踩过的坑。本文还有配套的精品资源点击获取