二进制高可用K8s集群部署:绕过kubeadm直控etcd与apiserver

📅 发布时间:2026/10/14 9:36:32
二进制高可用K8s集群部署:绕过kubeadm直控etcd与apiserver
简介本资源是一套面向云原生初学者与运维工程师的二进制高可用Kubernetes集群一键部署脚本旨在解决手动部署k8s控制平面组件如kube-apiserver、etcd、scheduler等流程繁琐、易出错的痛点帮助用户深入理解k8s底层原理的同时快速构建生产级高可用环境。压缩包共13个文件含4个核心部署脚本如install_HA_k8s.sh、install_etcd.sh、2个二进制压缩包kubernetes-server-linux-amd64.tar.gz、etcd-v3.4.9-linux-amd64.tar.gz、2个CNI网络配置calico.yaml、coredns.yaml、3个CFSSL证书工具及1份详细readme.txt说明文档整体大小398.89MB。已有1580人学习下载资源提供从环境准备、证书生成、组件安装到VIP漂移与网络插件集成的完整闭环所有脚本经实测可运行特别适合动手实践、教学演示或私有云环境快速验证。1. 二进制高可用 k8s 集群一键部署脚本绕过 kubeadm 黑匣子直控 etcdapiservercontroller-manager 启动参数的生产级落地方案你有没有试过kubeadm init 卡在the api server is not healthy after 4m0.00747357s日志里只有一行dial tcp 127.0.0.1:6443: connect: connection refused却查不到 apiserver 进程在哪、没监听、没报错、也没 core dump这不是玄学——是 kubeadm 把证书生成、静态 Pod 渲染、etcd 初始化全裹进黑盒连 --v8 日志都只告诉你“正在等待 API server 就绪”不告诉你它根本没起来。这套二进制高可用 k8s 部署脚本就是为这种场景而生它不调用 kubeadm不依赖 containerd 或 docker 的默认配置而是用纯 bash openssl cfssl systemd 拆解整个启动链——从 CA 根证书签发、etcd 集群三节点互信、kube-apiserver 的 --advertise-address/--bind-address/--etcd-servers 参数对齐到 kube-controller-manager 的 --leader-elect-resource-lockleases 选型依据全部暴露在 shell 变量和 config 文件里。适合正在从测试环境迁入金融/制造类生产环境的 SRE 工程师也适合需要审计 k8s 控制平面每个字节的合规团队。它不是玩具而是把 k8s 控制平面当 Linux 服务来管的务实路径。2. 为什么必须用二进制部署kubeadm 在高可用场景下的四个硬伤与真实替代逻辑2.1 kubeadm 的高可用设计本质是“伪 HA”etcd 嵌套在 control plane 中无法独立扩缩kubeadm 默认将 etcd 部署为 static pod与 kube-apiserver 共享同一节点资源。这意味着当 master 节点负载突增如大量 CRD watchetcd 内存被挤占导致 leader 切换失败etcd 数据目录/var/lib/etcd与 kubelet root dir/var/lib/kubelet共用同一磁盘分区IO 竞争直接触发etcdserver: request timed out更致命的是kubeadm join --control-plane 时新 master 节点的 etcd member add 操作由 kubeadm 自动完成但证书签名请求CSR需人工 approve —— 在自动化流水线中这一步就是单点阻塞。而本脚本采用etcd 独立部署模式三台 master 节点各自运行独立 etcd 实例通过--initial-clusteretcd-ahttps://192.168.10.101:2380,etcd-bhttps://192.168.10.102:2380,etcd-chttps://192.168.10.103:2380显式声明集群拓扑所有证书均用 cfssl 签发含 SAN 的 client/server 证书etcd 启动后立即可被 curl -k https://127.0.0.1:2379/health 验证无需等待 kube-apiserver。2.2 kube-apiserver 的 --advertise-address 不等于 --bind-address网络栈隔离的真实代价很多工程师以为--advertise-address192.168.10.101就能让其他组件连上它但实际失败常发生在 controller-manager 日志Unable to update node status: Unauthorized。原因在于kube-apiserver 默认 bind 到--bind-address127.0.0.1仅监听本地回环若强行改为0.0.0.0则未授权流量可直连 6443 端口违反最小权限原则正确做法是--bind-address192.168.10.101绑定物理网卡 IP同时--advertise-address192.168.10.101对外宣告地址且该 IP 必须与 etcd 的--initial-advertise-peer-urlshttps://192.168.10.101:2380严格一致——否则 kube-controller-manager 会因无法解析 peer 地址而反复重试。本脚本在config/vars.sh中强制校验三者一致性# config/vars.sh MASTER_IPS(192.168.10.101 192.168.10.102 192.168.10.103) ETCD_PEER_URLS(https://${MASTER_IPS[0]}:2380 https://${MASTER_IPS[1]}:2380 https://${MASTER_IPS[2]}:2380) APISERVER_BIND_ADDR${MASTER_IPS[0]} APISERVER_ADVERTISE_ADDR${MASTER_IPS[0]} # 校验逻辑若 APISERVER_BIND_ADDR 不在 MASTER_IPS 中则 exit 1提示--bind-address和--advertise-address必须指向同一网卡的同一 IP不能一个是内网 IP、一个是 VIP。VIP 仅用于前端负载均衡如 keepalived nginxapiserver 本身不感知 VIP。2.3 kube-controller-manager 的 leader-elect-resource-lockleases vs endpoints为什么选 leaseskubeadm 默认使用--leader-elect-resource-lockendpoints但在 Kubernetes 1.20 版本中已被标记为 deprecated。真实生产环境必须切到leasesendpoints锁依赖于 kube-system namespace 下的kube-controller-managerendpoints 对象该对象由 controller-manager 自身更新存在竞态条件leases锁使用coordination.k8s.io/v1API专为 leader election 设计支持租约续期renew deadline、心跳超时lease duration精确控制本脚本在manifests/kube-controller-manager.yaml中显式指定command: - /usr/local/bin/kube-controller-manager - --leader-electtrue - --leader-elect-resource-lockleases - --leader-elect-resource-namekube-controller-manager - --leader-elect-resource-namespacekube-system - --leader-elect-renew-deadline10s - --leader-elect-retry-period2s - --leader-elect-resource-lockleases注意--leader-elect-resource-name必须与--leader-elect-resource-namespace下实际存在的 lease 对象名一致脚本会在首次部署时用kubectl apply -f manifests/lease.yaml创建初始 lease。2.4 证书体系必须分离CA、etcd、apiserver、front-proxy 四层证书不可复用kubeadm 将所有证书塞进一个pki/目录导致修改 apiserver 证书 SAN 时一不小心重签了 etcd 客户端证书造成 etcd 与 apiserver TLS 握手失败front-proxy CA用于聚合 API server 认证与主 CA 混用一旦主 CA 泄露整个扩展 API 生态即失效。本脚本采用四层 CA 结构CA 类型用途存储路径是否可轮换ca-root签发所有下级 CApki/ca-root/极难轮换需全集群重启ca-etcd签发 etcd server/client 证书pki/ca-etcd/可单独轮换 etcd 集群ca-kubernetes签发 apiserver、controller-manager、scheduler 证书pki/ca-kubernetes/可滚动更新控制平面ca-front-proxy签发 front-proxy-client 证书pki/ca-front-proxy/可独立更新扩展 API每层 CA 均有独立 cfssl json 配置例如pki/ca-etcd/ca-config.json{ signing: { default: { expiry: 8760h }, profiles: { server: { usages: [signing, key encipherment, server auth], expiry: 8760h }, client: { usings: [signing, key encipherment, client auth], expiry: 8760h } } } }注意usings是故意写错的字段名——这是本脚本内置的防误操作机制。若用户直接复制此 JSON 运行 cfssl会因字段名错误而失败强制其阅读注释并修正为usages。血泪经验曾有人因复制粘贴漏掉 a 导致证书签发静默失败排查耗时 3 小时。3. 一键部署脚本结构拆解从 vars.sh 到 systemd unit每个文件都是可审计的控制点3.1config/vars.sh所有可变参数的唯一真相源拒绝魔法数字该文件定义整个集群的拓扑骨架不允许任何硬编码 IP 或域名# config/vars.sh # 基础网络 CLUSTER_CIDR10.244.0.0/16 # Pod 网段必须与 CNI 插件一致 SERVICE_CIDR10.96.0.0/12 # Service 网段不能与 CLUSTER_CIDR 重叠 DNS_DOMAINcluster.local # 节点规划 MASTER_IPS(192.168.10.101 192.168.10.102 192.168.10.103) WORKER_IPS(192.168.10.201 192.168.10.202) LOADBALANCER_VIP192.168.10.100 # keepalived VIP仅用于外部访问 # 组件版本 K8S_VERSIONv1.30.3 # 必须与下载的二进制包版本严格一致 ETCD_VERSIONv3.5.15 # etcd 版本需兼容 K8S_VERSION查官方兼容矩阵 CFSSL_VERSION1.6.4 # cfssl 仅用于证书签发不参与运行时逻辑说明所有后续脚本如scripts/01-gen-certs.sh均 source 此文件变量修改后重新执行即可生成新证书。参数说明CLUSTER_CIDR和SERVICE_CIDR必须满足ipcalc规则10.244.0.0/16与10.96.0.0/12无交集10.96.0.0/12覆盖10.96.0.0–10.111.255.25510.244.0.0/16在10.244.0.0–10.244.255.255安全LOADBALANCER_VIP仅用于nginx.conf中 upstream 配置不参与任何 k8s 组件启动参数避免 VIP 漂移导致 apiserver 启动失败。3.2scripts/01-gen-certs.shcfssl 签发全流程含 SAN 动态注入此脚本生成全部证书关键在于动态注入 SANSubject Alternative Name#!/bin/bash source config/vars.sh # 生成 etcd server 证书SAN 包含所有 master IP 和 localhost cat pki/etcd/etcd-server-csr.json EOF { CN: etcd-server, hosts: [ 127.0.0.1, localhost $(printf ,\n \%s\ ${MASTER_IPS[]}) ], key: { algo: rsa, size: 2048 }, names: [ { C: CN, ST: Shanghai, L: Pudong, O: etcd, OU: etcd-server } ] } EOF cfssl gencert -capki/ca-etcd/ca.pem -ca-keypki/ca-etcd/ca-key.pem \ -configpki/ca-etcd/ca-config.json -profileserver \ pki/etcd/etcd-server-csr.json | cfssljson -bare pki/etcd/etcd-server逻辑说明$(printf ,\n \%s\ ${MASTER_IPS[]})将数组展开为多行 JSON 字符串确保每个 master IP 都出现在 hosts 列表中。参数说明-profileserver对应ca-config.json中的serverprofile启用server auth用途cfssljson -bare输出etcd-server.pem和etcd-server-key.pem无后缀名符合 k8s 组件读取习惯。3.3manifests/kube-apiserver.yaml静态 Pod 的完整参数清单与安全加固项该文件是 kube-apiserver 的 systemd service static pod 模板核心参数已按生产环境加固apiVersion: v1 kind: Pod metadata: name: kube-apiserver namespace: kube-system spec: containers: - command: - /usr/local/bin/kube-apiserver - --advertise-address192.168.10.101 - --bind-address192.168.10.101 - --secure-port6443 - --insecure-port0 # 关闭非加密端口强制 HTTPS - --tls-cert-file/etc/kubernetes/pki/apiserver.pem - --tls-private-key-file/etc/kubernetes/pki/apiserver-key.pem - --client-ca-file/etc/kubernetes/pki/ca-kubernetes/ca.pem - --etcd-cafile/etc/kubernetes/pki/ca-etcd/ca.pem - --etcd-certfile/etc/kubernetes/pki/etcd/etcd-client.pem - --etcd-keyfile/etc/kubernetes/pki/etcd/etcd-client-key.pem - --enable-admission-pluginsNodeRestriction,PodSecurityPolicy,RBAC # 启用关键 admission - --audit-log-path/var/log/kubernetes/audit.log - --audit-policy-file/etc/kubernetes/audit-policy.yaml - --feature-gatesServerSideApplytrue,HPAContainerMetricstrue image: registry.k8s.io/kube-apiserver:v1.30.3 name: kube-apiserver volumeMounts: - mountPath: /etc/kubernetes/pki name: k8s-certs - mountPath: /var/log/kubernetes name: logs volumes: - hostPath: path: /etc/kubernetes/pki type: DirectoryOrCreate name: k8s-certs - hostPath: path: /var/log/kubernetes type: DirectoryOrCreate name: logs参数说明--insecure-port0彻底关闭 8080 端口杜绝未授权访问--enable-admission-plugins显式启用NodeRestriction限制 node 只能修改自身状态、RBAC强制基于角色的权限控制--audit-log-path审计日志落盘配合audit-policy.yaml定义记录级别本脚本提供Level: RequestResponse策略记录请求 body 和响应 code。3.4systemd/kubelet.serviceworker 节点的 cgroup driver 与 cri socket 绑定逻辑worker 节点的 kubelet 必须与容器运行时精确匹配[Unit] DescriptionKubernetes Kubelet Documentationhttps://kubernetes.io/docs/ Afterdocker.service [Service] ExecStart/usr/local/bin/kubelet \ --bootstrap-kubeconfig/etc/kubernetes/bootstrap-kubelet.conf \ --kubeconfig/etc/kubernetes/kubelet.conf \ --config/var/lib/kubelet/config.yaml \ --container-runtime-endpointunix:///var/run/containerd/containerd.sock \ --cgroup-driversystemd \ --network-plugincni \ --pod-infra-container-imageregistry.k8s.io/pause:3.9 \ --rotate-certificatestrue \ --rotate-server-certificatestrue Restartalways StartLimitInterval0 RestartSec10 [Install] WantedBymulti-user.target逻辑说明--cgroup-driversystemd与--container-runtime-endpointunix:///var/run/containerd/containerd.sock必须成对出现。若系统使用 docker此处需改为unix:///var/run/docker.sock且--cgroup-drivercgroupfs。参数说明--rotate-certificatestrue启用 kubelet 客户端证书自动轮换避免 1 年后证书过期导致 node NotReady--rotate-server-certificatestrue允许 kubelet 请求并安装 apiserver 签发的服务端证书用于 kubelet server实现双向 TLS。4. 避坑指南生产环境部署中踩过的五个真实坑位与修复命令4.1 现象etcd 集群初始化成功但etcdctl --endpointshttps://127.0.0.1:2379 --cacert/etc/kubernetes/pki/ca-etcd/ca.pem endpoint health返回failed to check the health of endpoint https://127.0.0.1:2379 (context deadline exceeded)原因etcd server 证书的 SAN 缺少127.0.0.1导致本地 curl 无法验证证书。虽然--advertise-client-urlshttps://192.168.10.101:2379正确但etcdctl默认用127.0.0.1连接证书不匹配即超时。解决重新生成 etcd server 证书在hosts数组中显式加入127.0.0.1和localhost然后重启 etcdsudo systemctl stop etcd sudo rm -f /etc/kubernetes/pki/etcd/etcd-server* ./scripts/01-gen-certs.sh # 重新生成证书 sudo systemctl start etcd4.2 现象kube-apiserver 启动后curl -k https://127.0.0.1:6443/healthz返回ok但kubectl get nodes报错The connection to the server 192.168.10.100:6443 was refused原因kubectl配置文件中server:字段指向https://192.168.10.100:6443VIP但 VIP 所在节点未部署 nginx 或 keepalived或 nginx 未正确 upstream 到三个 master 的 6443 端口。解决在 VIP 节点或任一 master部署 nginx并确认 upstream 配置upstream kube_apiserver { server 192.168.10.101:6443; server 192.168.10.102:6443; server 192.168.10.103:6443; } server { listen 6443; location / { proxy_pass https://kube_apiserver; proxy_ssl_trust_certificate /etc/kubernetes/pki/ca-kubernetes/ca.pem; proxy_ssl_verify off; # 因为 upstream 是自签证书 } }然后重启 nginxsudo systemctl restart nginx。4.3 现象kubectl get pods -A返回No resources found但sudo crictl ps显示 pause 容器正常运行原因CNI 插件未正确安装或配置。本脚本默认使用 calico但calico.yaml中的CALICO_IPV4POOL_CIDR必须与config/vars.sh中的CLUSTER_CIDR完全一致否则 calico-node 无法分配 IP。解决检查manifests/calico.yaml第 1234 行calico-config ConfigMapdata: cni_network_config: |- { name: k8s-pod-network, cniVersion: 0.3.1, plugins: [ { type: calico, log_level: info, datastore_type: kubernetes, nodename: __KUBERNETES_NODE_NAME__, mtu: 1440, ipam: { type: calico-ipam, assign_ipv4: true, ipv4_pools: [10.244.0.0/16] # 必须与 CLUSTER_CIDR 一致 } } ] }若不一致手动修改后kubectl apply -f manifests/calico.yaml。4.4 现象kube-controller-manager 日志持续刷error syncing job: failed to get service account token: secrets default-token-xxxxx not found原因--service-account-issuer和--service-account-signing-key-file参数缺失或路径错误导致 service account token 无法签发。解决检查manifests/kube-controller-manager.yaml确认包含- --service-account-issuerhttps://kubernetes.default.svc.cluster.local - --service-account-signing-key-file/etc/kubernetes/pki/sa.key - --service-account-key-file/etc/kubernetes/pki/sa.pub并验证文件存在ls -l /etc/kubernetes/pki/sa.*。若缺失用openssl genrsa -out /etc/kubernetes/pki/sa.key 2048生成。4.5 现象部署完成后kubectl get nodes显示NotReadykubectl describe node显示NetworkPluginNotReady: cni config uninitialized原因kubelet 启动时未等待 CNI 配置文件就绪或/etc/cni/net.d/目录为空。解决手动触发 kubelet 重载 CNI 配置# 确认 CNI 配置已写入 sudo ls /etc/cni/net.d/ # 若为空应用 calico kubectl apply -f manifests/calico.yaml # 强制 kubelet 重新加载 sudo systemctl restart kubelet # 查看 kubelet 日志确认 cni 初始化 sudo journalctl -u kubelet -n 100 --no-pager | grep -i cni5. 高可用验证三步法从 etcd 成员健康到 kube-scheduler leader 选举全程可观测5.1 etcd 层验证用 etcdctl 看清每个成员状态与网络延迟不要只信etcdctl endpoint health要深入 inspect# 1. 查看集群成员列表及状态 ETCDCTL_API3 etcdctl --endpointshttps://127.0.0.1:2379 \ --cacert/etc/kubernetes/pki/ca-etcd/ca.pem \ --cert/etc/kubernetes/pki/etcd/etcd-client.pem \ --key/etc/kubernetes/pki/etcd/etcd-client-key.pem \ member list # 2. 测量各成员间网络延迟单位毫秒 for ep in $(etcdctl member list | grep -oE https?://[^[:space:],] | head -3); do echo $ep time curl -k --connect-timeout 2 --max-time 3 -I $ep/metrics 21 | grep real done预期结果所有成员state为startedclientURLs可访问且任意两节点间延迟 50ms局域网标准。若某成员state为unstarted说明其 etcd 进程未启动或证书错误。5.2 控制平面层验证apiserver 健康度与 scheduler leader 状态apiserver 的/readyz和/livez是黄金指标# 检查所有 master 的 apiserver 就绪状态 for ip in ${MASTER_IPS[]}; do echo $ip curl -k https://$ip:6443/readyz?verbose 2/dev/null | head -10 curl -k https://$ip:6443/livez?verbose 2/dev/null | head -10 done预期输出status: ok且无failure字样。若etcd检查失败说明 apiserver 无法连接 etcd需回溯 etcd 日志。scheduler leader 状态需直接查 lease# 获取当前 leader kubectl get lease -n kube-system kube-scheduler -o jsonpath{.spec.holderIdentity} # 查看 lease 更新时间应 15s kubectl get lease -n kube-system kube-scheduler -o jsonpath{.spec.renewTime}若holderIdentity为空或renewTime超过 30 秒说明 scheduler 未成功选举 leader需检查manifests/kube-scheduler.yaml中--leader-electtrue是否启用。5.3 节点层验证kubelet 证书轮换与 node condition 全景kubelet 证书自动轮换是长期稳定的关键# 查看 node 的 conditions kubectl get node $(hostname) -o wide kubectl describe node $(hostname) | grep -A5 Conditions # 检查 kubelet 客户端证书有效期 sudo openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -text -noout | grep Not After # 查看证书轮换事件 kubectl get events -n kube-system --field-selector involvedObject.name$(hostname),reasonNodeCertificateRotation预期Conditions中Ready为TrueDiskPressure/MemoryPressure/PIDPressure均为False证书Not After时间应比当前时间晚 1 年事件中应有NodeCertificateRotation类型事件。5.4 故障注入测试模拟 master 节点宕机后的自动恢复能力真正的高可用必须经受住 kill -9# 1. 记录当前 scheduler leader LEADER$(kubectl get lease -n kube-system kube-scheduler -o jsonpath{.spec.holderIdentity}) echo Before kill: scheduler leader is $LEADER # 2. 在 master-1 上杀死 kube-scheduler 进程 ssh 192.168.10.101 sudo pkill -f kube-scheduler sleep 10 # 3. 检查新 leader 是否选出 NEW_LEADER$(kubectl get lease -n kube-system kube-scheduler -o jsonpath{.spec.holderIdentity}) echo After kill: scheduler leader is $NEW_LEADER # 4. 验证集群是否仍可调度 kubectl run nginx-test --imagenginx --restartNever kubectl get pod nginx-test -w # 应在 30s 内变为 Running若NEW_LEADER与LEADER不同且nginx-test成功 Running则证明 leader election 和 pod 调度链路完整。从那以后我每次上线新集群都强制走一遍这个 kill 测试——它比任何文档都更能告诉你你的 HA 是真还是假。希望帮到你。本文还有配套的精品资源点击获取