ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

二进制高可用K8s集群一键部署:从证书到VIP的完整实践

二进制高可用K8s集群一键部署:从证书到VIP的完整实践 简介二进制高可用k8s集群一键部署脚本专为希望深入理解Kubernetes工作原理的开发者与运维人员设计基于阿良的二进制部署文档将原本繁琐的手动下载组件、配置证书、初始化主节点等步骤整合为一键执行大幅降低高可用集群的搭建门槛。压缩包共13个文件约398.89MB核心内容包括install_HA_k8s.sh、install_k8s.sh、install_etcd.sh、install_vip.sh等4个Shell脚本以及etcd、docker、kubernetes-server等二进制安装包另有calico.yaml、coredns.yaml网络插件配置和readme.txt部署说明结构清晰便于对照学习。目前已有1581人学习下载。通过该脚本读者既能快速获得一套可运行的高可用k8s集群又能在阅读脚本与配置文件的过程中掌握二进制部署的完整链路包括etcd集群、apiserver负载均衡、CNI网络插件等关键环节是一份兼顾实战效率与原理理解的优质云原生学习工具。1. 二进制高可用K8s集群一键部署脚本为什么我劝你别直接复制网上的部署笔记二进制高可用 K8s 集群一键部署脚本这个标题里每个词都是筛选条件不是 kubeadm而是二进制不是单节点而是高可用不是手工敲命令而是一键脚本。反直觉的结论是这三者的组合往往意味着上千行 bash而不是三条命令但这上千行换来的是集群彻底透明、可离线交付、可重复执行。真实场景里测试环境用 kubeadm 拉起来很快一上生产就出现证书过期、etcd 脑裂、kubelet 版本对不上排查时压根看不到组件是被怎么拉起的。这套脚本方案把 etcd、apiserver、controller-manager、scheduler、kubelet 的控制权收回自己手里把 VIP 漂移和证书签发串成一条可重复执行的链路。适合正在搭生产或类生产集群的运维和 SRE也适合被 kubeadm 坑过又想弄懂控制面原理的人。2. 选型论证二进制部署、VIP高可用与一键脚本的边界2.1 二进制部署和kubeadm的本质差异从编排到透明kubeadm 的本质是把 kube-apiserver、kube-controller-manager、kube-scheduler 全部装进静态 Pod交给 kubelet 拉起证书签发、etcd 初始化、service account 生成都封装在一个强约束流程里。优点是快三行命令就能拉起一个集群缺点也在这里——对细节不可见。apiserver 的启动参数被 kubeadm 用默认值兜底证书轮换依赖 kubelet 自动续期一旦要深度定制审计策略、准入控制或自定义证书体系就得绕过它的约束链反而比从零搭更费劲。我自己遇到的典型场景是kubeadm 装的集群里 coredns 疯狂 CrashLoopBackOff日志里看不到任何 apiserver 的介入痕迹只能去翻静态 Pod 目录下的 manifest发现镜像 tag 和仓库地址都是 kubeadm 写死的默认值。二进制部署里这种问题一眼就能定位因为每个镜像、每个参数都写在自己维护的 unit 文件里。二进制部署是用官方编译好的二进制文件kube-apiserver、kube-controller-manager、kube-scheduler、kubelet、kube-proxy、etcd直接落到节点上用 systemd unit 文件管理进程证书用 openssl 自己签。这里没有黑匣子每个参数写在 unit 文件的 ExecStart 里进程启动失败时日志直接抛到 journalctl 里完整报错栈都看得到。代价是责任全在自己手上从 CA 到 node 加入每一步都要确认排错时不能像 kubeadm 那样一键 reset 重来。顺带提一个经常被忽略的差异kubeadm 生成的 etcd 证书和 apiserver 证书默认有效期只有一年它依赖 kubelet 的自动续期来兜底而 etcd 的 peer 证书并不在自动续期范围内。很多 kubeadm 集群跑满一年后第一个报错就是 etcd 的 TLS 握手失败这在二进制部署里可以通过脚本统一签发十年期证书把“证书轮换”这个动作从运维黑匣子变成自己维护的清单。我实际用下来的体感是如果你的集群要长期维护、三年后还得有人能接得住二进制方案前期那点投入非常值得如果只是快速验证一个业务想法kubeadm 更合适别让部署方式本身成为瓶颈。2.2 高可用选型keepalivedHAProxy为什么是生产里最稳的组合高可用核心回答一个问题控制面有多台 apiserverkubelet 和 kubectl 怎么知道该把请求发给谁。可行的方案有三个外部负载均衡器云厂商 SLB 或自建 LB、keepalived 做 VIP、把 apiserver 暴露到 nodePort 后面。我基本只推荐 keepalivedHAProxy 的组合因为组件职责单一故障域隔离得干净。方案实现成本故障恢复机制适用场景云厂商 SLB低SLB 摘除后端秒级生效云上首选但跨可用区要买多个实例keepalivedHAProxy中VRRP 心跳四层转发3-5 秒切完自建机房、离线环境首选nodePort 暴露低无自动故障转移临时演示不适合生产keepalived 负责 VIP 漂移HAProxy 负责把 6443 流量转发到三台 apiserver两者可以装在 master 节点上也可以单独用两台负载机。生产里我倾向独立负载机因为这台机器的负载非常可控不会和 master 的 apiserver 抢 CPU 和内存。HAProxy 侧的核心配置只有一小段却决定了整个负载层的可靠性frontend k8s-apiserver bind 0.0.0.0:6443 default_backend k8s-masters backend k8s-masters option httpchk GET /healthz server master1 192.168.10.11:6443 check inter 3s fall 3 rise 2 server master2 192.168.10.12:6443 check inter 3s fall 3 rise 2 server master3 192.168.10.13:6443 check inter 3s fall 3 rise 2解释一下option httpchk GET /healthz 让 HAProxy 用 HTTP 请求探测 apiserver 真实健康状态而不是只判断端口能 connectinter 3s 是每三秒探测一次fall 3 是连续三次失败才把节点摘掉rise 2 是恢复两次后重新放进来。这套参数的含义是apiserver 抖动一次不会导致流量被误摘真挂了最多九秒内完成隔离。提示VIP 从异常到完成切换理想状态是 3-5 秒内超过 10 秒就应该检查 VRRP 的优先级配置是否把权重调小了。2.3 一键脚本的边界什么能自动化什么必须人工确认“一键部署”这四个字最容易让人误以为一条命令就能确定所有事情。见过太多血泪教训脚本一跑所有组件都起来但没人知道证书签发到了哪个目录、默认密码写在哪、配置文件是从哪个模板渲染出来的。我现在设计一键脚本的原则确定性操作全部交给脚本判断性操作全部留给配置文件。自动化范围包括环境预检、目录创建、证书生成、二进制分发、unit 文件写入、kubeconfig 生成与分发人工负责的部分包括主机名规划、IP 与 VIP 确定、版本号选择、CNI 插件选型。这些参数写在一个 config.env 里脚本只读参数、执行操作不做交互问答和动态探测——一旦脚本开始“聪明”地猜测拓扑部署行为的可预测性就没了排障时你根本不知道它走到了哪一步。# config.env 片段 MASTER1_IP192.168.10.11 MASTER2_IP192.168.10.12 MASTER3_IP192.168.10.13 VIP_ADDR192.168.10.10 KUBE_VERSIONv1.30.5 ETCD_VERSIONv3.5.15 CNI_VERSIONv1.5.1 SERVICE_CIDR10.96.0.0/16 POD_CIDR10.244.0.0/16参数说明每个变量名都带明确前缀MASTER*_IP 告诉脚本哪些节点是控制面SERVICE_CIDR 和 POD_CIDR 决定 Service 和 Pod 的网段必须和公司网络规划一致一旦部署完不能轻易改否则 kube-proxy 的 iptables 规则全要重刷。判断脚本写得好不好有一个简单标准一个只懂 K8s 概念、不熟悉你环境的人拿 config.env 的注释能不能把集群拉起来。能说明边界设对了需要给你发三次微信确认参数边界就太宽了。可能有人会问那为什么不用 Ansible 或者 Terraform 把事情做得更“完整”我的回答是脚本在这里只是载体真正要锁住的是部署顺序和参数集合。Ansible 当然可以写但多一层抽象就多一层排查成本二进制部署的高可用集群一旦出问题最直接的排查路径是登录节点看 journalctl、看 unit 文件、看 etcd 成员列表。纯 bash 脚本把每一步压缩成可见文本反而好维护。我见过不少团队用 Ansible role 封装 K8s 部署最后维护成本全花在调试 Jinja 模板和变量优先级上没人敢再动那个 playbook。3. 落地前的地基主机规划、版本对齐与环境预检3.1 主机与网络规划三主节点起步端口放行清单要落表一个高可用集群最少需要 3 个 master 节点1 个单节点不叫高可用。工作节点数量取决于业务但为了验证脚本最少再加 2 个工作节点。最小拓扑我放在脚本注释里作为 README 的一部分角色数量硬件参考关键配置master34C8GSSD 系统盘关闭 swap时间同步hostname 唯一worker2按业务规划与 master 同网段共享 /etc/hosts负载层可选独立22C4G装 keepalivedHAProxy最好与 master 分机规划时的注意点master 节点之间主机名不能重复/etc/hosts 在所有节点统一写好三台 master 的 IP否则 etcd 的 peer 通信会猜错 client URL。VIP虚拟 IP选一个和节点同网段的地址但这个 IP 不能出现在 DHCP 自动分配池里否则漂移时会撞地址。端口方面etcd 需要 2379client和 2380peerkube-apiserver 需要 6443kubelet 需要 10250kube-proxy 需要 10256如果需要 metrics。如果开启了 NodePort 业务端口默认的 30000-32767 也要一并放行。这套端口清单我在脚本里作为预检项检查能省掉后面一大半网络玄学问题。3.2 版本对齐kubernetes、etcd、CNI 的兼容性匹配表二进制部署最怕的就是版本号强行兼容。kube-apiserver 的版本和 kubelet 能接受的版本差太远节点会一直 NotReadyetcd 推荐版本和 K8s 版本不匹配会导致 watch 请求超时或者启动告警风暴。常见做法是直接参考官方 CHANGELOG 里的“kube-apiserver 支持 kubelet 的版本范围”然后固定三个版本号放进环境变量KUBE_VERSION控制面组件、ETCD_VERSION、CNI_VERSION。版本kube-apiserveretcd说明K8s 1.28 系列1.28.x3.5.x1.28 对 etcd 3.5 的 watch 行为有改进K8s 1.29 系列1.29.x3.5.x推荐 etcd 3.5.10 修复已知慢查询K8s 1.30 系列1.30.x3.5.x注意 1.30 移除了一些旧 API注意etcd 3.4 在 1.28 集群上不是官方推荐版本主要是 lease 管理的问题。在脚本里我会写一个 version_check.sh在部署前把三个版本号打印出来并做一次字符串比较防止从旧笔记里复制出 1.22 和 etcd 3.5 这种不匹配组合#!/usr/bin/env bash # version_check.sh KUBE_VERSION${KUBE_VERSION:-v1.30.5} ETCD_VERSION${ETCD_VERSION:-v3.5.15} echo Kubernetes: ${KUBE_VERSION} echo etcd: ${ETCD_VERSION} case ${KUBE_VERSION} in v1.2[89]*) [[ ${ETCD_VERSION} v3.5* ]] || echo ERROR: etcd must be v3.5.x for k8s 1.28 ;; esac这里只做前缀匹配不做完整语义化版本比较因为 Kubernetes 官方版本号是 vX.Y.Z 三段式前缀匹配已经能覆盖 99% 的兼容判断。真正需要完整比较的场景是 kubelet 与 kube-apiserver 的次要版本差那个更适合交给 kubectl version --short 在集群起来后再校验。3.3 环境预检磁盘、内核参数、时钟同步硬性检查环境预检是一键脚本的“第一道闸门”。我一般会检查四类硬条件swap 必须关闭、内核模块br_netfilter、overlay必须加载、/etc/hosts 必须包含所有规划节点、chronyd 或 ntpd 必须是活跃状态。时间不同步在 etcd 集群里最危险超过 500ms 的时钟偏差会让选举行为变得随机表现就是刚才还正常的集群突然 leader 频繁切换。预检脚本逻辑用 bash 写就好重点是把错误码和提示分开#!/usr/bin/env bash check_env() { local errors() # swap 必须关闭否则 kubelet 会启动失败或 OOM if grep -qs swap /proc/swaps; then errors(swap is enabled, run swapoff -a first) fi # overlay 和 br_netfilter 是容器网络的基础 for mod in overlay br_netfilter; do if ! lsmod | grep -q $mod; then errors(kernel module $mod not loaded) fi done # 时间偏差过大etcd 会出现诡异选举问题 if command -v timedatectl /dev/null 21; then local offset offset$(timedatectl show -p NTPSynchronized --value) if [ $offset ! yes ]; then errors(NTP not synchronized) fi fi if [ ${#errors[]} -gt 0 ]; then printf ERROR: %s\n ${errors[]} return 1 fi echo environment check passed } check_env这段脚本的核心思路是把环境问题收集到一个数组里全部检查完一起输出。errors 数组避免一个错误就直接退出方便用户第一次部署时把问题一次性修完NTPSynchronized 检查用的是 timedatectl 的导出属性比解析 chronyc 的输出稳定得多。注意这里只检查不修复修复动作需要人工确认因为 swapoff -a 在有些系统上会让已有进程的物理内存释放行为发生变化。3.4 离线包管理与校验不依赖公网的一键前提二进制部署的另一个痛点是安装源。在线环境可以直接从官网下载 tar 包但生产内网环境根本连不出去。我一般把脚本设计成“支持离线目录”在部署机上准备一个 packages/ 目录里面按组件分子目录例如 packages/kubernetes-server-linux-amd64.tar.gz、packages/etcd-v3.5.15-linux-amd64.tar.gz、packages/cni-plugins-linux-amd64-v1.5.1.tgz。脚本启动时检查这些文件是否存在不存在就提示从可用的镜像源或内网源下载而不去执行 curl。这样部署过程可复现不依赖公网可用性。脚本里对包的校验也写成 sha256sum 比对把校验值放进 config.env# config.env 追加 KUBE_SHA2569c8e6c... # 实际值由下载时计算 ETCD_SHA2569a8f2e... CNI_SHA2567b6d1a...# 校验逻辑 verify_package() { local file$1 local expected$2 local actual actual$(sha256sum $file | awk {print $1}) if [ $actual ! $expected ]; then echo ERROR: checksum mismatch for $file exit 1 fi } verify_package packages/etcd-v3.5.15-linux-amd64.tar.gz $ETCD_SHA256逻辑说明下载后先用 sha256sum 计算实际值再和 config.env 里预置的期望值比对。这里有个细节——校验值必须从可靠的渠道获取不能下载文件的同一个页面直接抄否则校验就失去了意义。我一般习惯在部署机上先用sha256sum packages/*.tar.gz生成一次校验清单再把这个清单作为脚本的一部分提交避免每次部署都重新计算。4. 核心组装从CA证书到kubelet全链路的代码实现4.1 先签CA再启etcd证书和集群的顺序不能乱etcd 集群的启动顺序是二进制部署里最先要立的规矩。先有 CA再有 etcd 证书再有 etcd 集群。证书签发我一般用 openssl 而不是 cfssl虽然 cfssl 更方便生成批量证书但 openssl 的配置文件透明能直接看到每个证书的 SAN 展开。签发 etcd 证书的常见做法是写一个 openssl 配置把三台 master 的 IP 和 hostname 写进 SAN# openssl.cnf 片段 [ req ] default_bits 2048 prompt no distinguished_name dn req_extensions req_ext [ dn ] CN etcd [ req_ext ] subjectAltName alt_names [ alt_names ] DNS.1 etcd-1 DNS.2 etcd-2 DNS.3 etcd-3 IP.1 192.168.10.11 IP.2 192.168.10.12 IP.3 192.168.10.13 IP.4 127.0.0.1签发命令openssl req -newkey rsa:2048 -nodes \ -keyout etcd-server.key -out etcd-server.csr \ -config openssl.cnf openssl x509 -req -in etcd-server.csr \ -CA ca.crt -CAkey ca.key -CAcreateserial \ -days 3650 -extensions req_ext -extfile openssl.cnf \ -out etcd-server.crt参数说明-extensions req_ext 和 -extfile 同时出现是让 etcd 证书带上 SAN 的关键少了这一步etcd 节点间用 IP 互访时 TLS 校验失败日志会报 x509: cannot validate certificate for IP。证书有效期我习惯给 10 年etcd 的证书不比 apiserver——有轮换机制的一定要做轮换没有机制的别给自己埋雷。签发完成后把 ca.crt、etcd-server.crt、etcd-server.key 分发到三个 master 的 /etc/etcd/ssl/ 目录。etcd 启动用 systemd unit 文件重点是 --initial-cluster 参数要列出三个节点的 name、IP 和 2380 端口这是 etcd 组集群时识别彼此的“通讯录”。如果这个参数在三个节点上不一致members 会一直显示为 unstarted。4.2 控制面组件编排与VIP流量路径参数含义逐个说清三个控制面组件kube-apiserver、kube-controller-manager、kube-scheduler的 systemd unit 都是同类模板区别在于启动参数。apiserver 的参数最多我把关键的几个拎出来# /etc/systemd/system/kube-apiserver.service 里的关键段 ExecStart/usr/local/bin/kube-apiserver \ --etcd-servershttps://192.168.10.11:2379,https://192.168.10.12:2379,https://192.168.10.13:2379 \ --bind-address0.0.0.0 \ --secure-port6443 \ --advertise-address192.168.10.11 \ --service-cluster-ip-range10.96.0.0/16 \ --service-node-port-range30000-32767 \ --kubelet-preferred-address-typesInternalIP \ --cert-dir/etc/kubernetes/ssl \ --client-ca-file/etc/kubernetes/ssl/ca.crt \ --tls-cert-file/etc/kubernetes/ssl/apiserver.crt \ --tls-private-key-file/etc/kubernetes/ssl/apiserver.key \ --service-account-signing-key-file/etc/kubernetes/ssl/sa.key \ --service-account-key-file/etc/kubernetes/ssl/sa.pub \ --kubelet-client-certificate/etc/kubernetes/ssl/apiserver-kubelet-client.crt \ --kubelet-client-key/etc/kubernetes/ssl/apiserver-kubelet-client.key \ --enable-aggregator-routingtrue这里容易忽略的是 --kubelet-client-certificate 和 --kubelet-client-keyapiserver 去访问 kubelet 的 10250 端口必须携带客户端证书否则 kubectl logs 会报 unauthorized。还有 --service-account-signing-key-file 和 --service-account-key-file 必须是同一对密钥否则 SA token 签发验证不通过Pod 里访问 API 会一直 401。controller-manager 的三个重点参数是 --cluster-cidr、--service-cluster-ip-range、--allocate-node-cidrs这三个参数让 controller-manager 知道它应该给节点分配哪一段 Pod CIDR。scheduler 参数最少默认配置就够用。流量路径总结如下外部的 kubectl 访问 VIP:6443keepalived 把 VIP 落在某台 master 或专门的负载节点上HAProxy 监听 6443 后把请求转发到三台 apiserver 的真实端口。kubelet 和 kube-proxy 访问的也是 VIP:6443这样任何一台 apiserver 挂了HAProxy 探活失败会让后端被摘除流量不会丢。4.3 kubeconfig分发一个角色一把钥匙kubeconfig 是集群的“门禁卡”。admin、controller-manager、scheduler、kubelet 各有各的用户和权限不能共用一把钥匙。我一般在一台机器上生成好所有角色的 kubeconfig再统一分发# 以 kubelet 为例 kubectl config set-cluster kubernetes \ --certificate-authority/etc/kubernetes/ssl/ca.crt \ --serverhttps://192.168.10.10:6443 \ --kubeconfig/etc/kubernetes/kubelet.conf kubectl config set-credentials system:node:node1 \ --client-certificate/etc/kubernetes/ssl/kubelet-node1.crt \ --client-key/etc/kubernetes/ssl/kubelet-node1.key \ --kubeconfig/etc/kubernetes/kubelet.conf kubectl config set-context default \ --clusterkubernetes \ --usersystem:node:node1 \ --kubeconfig/etc/kubernetes/kubelet.conf kubectl config use-context default --kubeconfig/etc/kubernetes/kubelet.conf这里的细节server 地址写的是 VIP192.168.10.10不是节点自己的 IP。如果写节点 IP而该 master 宕机时 kubelet 一条路走不通就废了写 VIP 才能利用前面 HAProxy 的转发能力。证书方面每个节点的 kubelet 证书必须带自己的 hostname一旦混用kubelet 无法向 apiserver 证明自己是这个 node证书认证直接过不去。工作节点分发完 kubelet 和 kube-proxy 的 kubeconfig再写入 kubelet 的 systemd unit节点角色就完成了。4.4 入口脚本组织函数式分层替代命令行堆叠一键脚本最大的风险是排错困难。我采用的模式是把所有操作拆成函数precheck_node、deploy_ca、deploy_etcd、deploy_master_components、deploy_worker_components、post_check_cluster。每一个函数对应一个日志函数记录当前执行到第几步、成功还是失败。入口脚本只做指挥#!/usr/bin/env bash set -euo pipefail source /opt/k8s-deploy/config.env source /opt/k8s-deploy/lib/log.sh source /opt/k8s-deploy/lib/checks.sh LOG_FILE/var/log/k8s-deploy.log log_info start k8s deploy, version: ${KUBE_VERSION} precheck_node if [ $(hostname) ${MASTER1_HOST} ] || [ $(hostname) ${MASTER2_HOST} ] || [ $(hostname) ${MASTER3_HOST} ]; then deploy_ca deploy_etcd deploy_master_components else deploy_worker_components fi post_check_cluster log_info deploy finished, VIP: ${VIP_ADDR}逻辑说明脚本根据当前节点的 hostname 决定走 master 分支还是 worker 分支。master 分支里的 deploy_ca 只在第一个 master 上真正执行另外两个 master 通过 scp 的方式同步 CA 和证书避免三个节点各自签出不同 CA。worker 分支只拉取证书和 kubeconfig不碰 CA。每次执行前先跑 precheck_node环境不满足直接退出不允许“带病部署”。参数说明set -euo pipefail 是必须的其中 -e 遇到错误就退出-u 变量未定义就报错pipefail 保证管道任一段失败整体失败。这三个组合起来脚本不会在某个子命令失败后继续把后面的命令都执行排障时日志里第一个 error 就是根因所在不用一路翻到末尾。注意deploy_ca 里的 scp 分发必须配置好 ssh 免密或者把脚本做成从一台中控机远程执行否则三台 master 之间来回输密码会打断自动化流程。5. 避坑二进制部署最容易翻车的5个细节5.1 现象kubectl get nodes 一直 NotReady容器没崩但节点就是不 Ready现象部署完成kubectl get nodes 看到 master 和 worker 都在 NotReadykube-system 里有几个 Pod 在 CrashLoopBackOff但 kubelet 的进程是活的日志也没有致命的 startup 错误。原因最常被背锅的其实是 CNI 网络插件。二进制部署不会自动装 CNIPod 的 pause 容器起得来但网络插件没有就绪kubelet 直接把节点标记为 NotReady。也可能是 pause 镜像拉不下来内网环境尤其常见报错会藏在 kubelet 日志的Failed to pull image registry.k8s.io/pause:3.9里。解决部署完控制面先装 CNI常见做法是用 Calico 的 manifest先确认镜像源可访问或已导入到本地 registry。检查顺序是 kubectl get pods -n kube-system 看 coredns 与 calico 是否在 CrashLoopBackOff再看具体 Pod 的 logs分清是镜像拉取失败还是 CNI 配置找不到。如果是后者多半是 /etc/cni/net.d/ 目录下没有可用的配置重新拷贝一份 calico 生成的 conf 文件就好。5.2 现象etcd 集群启动后频繁 leader 切换proposal 大量失败现象etcd 三个节点都显示 member 正常但日志里频繁出现 leader 切换监控面板上 proposal 失败计数持续上涨集群整体响应变慢。原因这是时间偏差和磁盘性能两个因素混在一起。时间偏差超过阈值选举心跳就对不上磁盘 fsync 延迟过高etcd 提交 WAL 的耗时远超心跳间隔。前者常见于没跑时间同步后者在机械盘或高负载母机上尤其明显尤其是虚拟化环境下宿主机 CPU 争抢导致 IO 抖动。解决先看 timedatectl 确认 NTP 服务正常再看 etcd 数据目录所在磁盘的 type 是不是 ssd。临时救急可以把心跳间隔调大--heartbeat-interval500--election-timeout5000但这不是根因只延长了问题暴露的时间。根因还得落在时间同步和磁盘性能上。我踩过的一次是内网环境 NTP 服务本身没配置上游所有节点都同步到同一台不准确的母钟看起来“同步”了实际全偏了。5.3 现象VIP 能 ping 通但 kubelet 访问 apiserver 超时现象keepalived 起来了VIP 能从外部 ping 通但 kubectl 访问 VIP:6443 时通时不通kubelet 也间歇性报 Failed to connect to apiserver。原因keepalived 的 VIP 探活配置只检查了本地进程没检查 HAProxy 后端是否真的健康。VIP 漂移过来了但 HAProxy 的后端 apiserver 探活失败流量进了 HAProxy 却被转发到一个挂掉的 apiserver表现就是 connect 成但请求超时。这是典型的半死不活状态比全挂还难查。解决HAProxy 的 backend 段必须配置 option httpchk GET /healthz让探测真正打到 apiserver 的健康端点而不是只做四层 TCP 握手。记住一个检查顺序先 curl 每台 apiserver 的 /healthz 确认后端真实状态再 curl VIP 看转发是否生效最后再看 keepalived 的日志确认漂移状态。这个顺序能快速定位是负载层的问题还是后端组件的问题。5.4 现象重启节点后 kubelet 静默失败systemctl status 显示运行但节点没出现现象节点重启后 systemctl status kubelet 显示 active (running)但 kubectl get nodes 里没有这个节点journalctl 也没有明显 error。原因kubelet 的 systemd unit 里的 hostname 解析混乱。如果 /etc/hosts 里本机主机名和 /etc/hostname 不一致kubelet 启动后向 apiserver 注册的 node name 和证书里的 CN 对不上认证失败导致节点一直处于未注册状态。systemd 不会报错因为 kubelet 进程还活着。解决在所有节点统一 hostname要么改 /etc/hostname 和 /etc/hosts 里本机 IP 对应的行要么在 kubelet 的 KUBELET_EXTRA_ARGS 里加 --hostname-override 强制指定。生产环境我推荐统一 hostname而不是靠 override 给自己挖坑因为 override 只解决 kubelet 的注册名后面证书 SAN、监控抓取、日志关联全都依赖一致的 hostname。5.5 现象证书过期前一个月开始出现偶发 401kubectl 时好时坏现象集群运行一段时间后kubectl 执行命令有时正常有时报certificate has expired or is not yet valid重启 apiserver 后暂时恢复但过几天又出现。原因kubeconfig 里的 client-certificate 过期时间接近apiserver 对每个请求做证书校验失败的比例上升有时缓存命中还能通没用缓存的请求直接 401。二进制部署的证书没有自动轮换过期前必须手动签发替换这也再次印证了 2.1 里说的别完全依赖 kubeadm 的自动续期。解决在脚本里加一个 check_cert_expiry 函数遍历所有证书目录用 openssl x509 -enddate 判断剩余天数小于 30 天就告警#!/usr/bin/env bash check_cert_expiry() { for cert in /etc/kubernetes/ssl/*.crt /etc/etcd/ssl/*.crt; do local end end$(openssl x509 -enddate -noout -in $cert | cut -d -f2) local end_ts end_ts$(date -d $end %s) local now_ts now_ts$(date %s) local remain_days remain_days$(( (end_ts - now_ts) / 86400 )) if [ $remain_days -lt 30 ]; then echo WARN: $cert expires in ${remain_days} days fi done }日常巡检建议周期跑这个检查而不是等到故障发生。签发新证书后注意 kubeconfig 里的证书是内嵌的必须重新生成 kubeconfig 并分发只替换 .crt 文件不会自动生效。6. 验证高可用和脚本可重复性故障演练当作发布流程的一环6.1 停掉apiserver看集群怎么自愈验证脚本的价值不能停留在“装上能跑”还要能证明它坏了能自愈。我最常用的演练步骤登录 MASTER1systemctl stop kube-apiserver然后从另一台机器跑 kubectl get nodes --request-timeout5s正常情况应该在 3 秒左右重新连上 VIP 并成功返回。再查 etcd 的 leader确认 etcd 集群也完成了新的选举。更彻底的是把整台机器关机测试看看 VIP 是否在 HAProxy 健康检查失败后漂移到另一台以及 etcd 集群能否在没有该节点的情况下保持多数派。每次演练后把恢复耗时记录到脚本注释里时间一长就能看出哪些组件开始老化。健康检查脚本是每次验证的入口#!/usr/bin/env bash # 检查三个 apiserver 的健康端点 for ip in 192.168.10.11 192.168.10.12 192.168.10.13; do curl -sk --max-time 3 https://${ip}:6443/healthz \ -o /dev/null -w %{http_code}\n || echo ${ip} down done # 检查 VIP curl -sk --max-time 3 https://192.168.10.10:6443/healthz || echo VIP down这里的验证点是即使某台 apiserver 挂了VIP 的 /healthz 依然应该返回 200。如果 VIP 返回失败说明 HAProxy 的后端摘除逻辑有问题需要回到 5.3 检查 httpchk 配置。6.2 让脚本“跑两遍”也不翻车一键脚本最大的隐性要求是幂等。同一个配置在同一个集群上跑第二遍绝不能出现第二个 etcd 集群或者覆盖掉已有证书。实现手段要在脚本层约束证书文件存在时跳过生成etcd 数据目录存在时跳过初始化kubeconfig 存在时跳过分发写入。同时在开头记录集群 ID 到 /etc/kubernetes/cluster-id 文件如果文件和当前环境的 VIP、网段不一致直接中止执行并提示人工确认是误操作还是真的重建。我的习惯是每次部署结束后在脚本日志里打印“本集群第几次初始化”通过一个计数文件累计。半年后回头看能确认哪些环境被反复清理重建过哪些环境的脚本真正做到了“跑一次就行”。经验教训二进制部署方案最大的收益是可控性但可控性不等于能救一切。遇到 keepalived 和 HAProxy 参数写错导致的诡异问题最有效的排查手段永远是 journalctl -u keepalived 和 journalctl -u haproxy 逐条看日志而不是换参数盲试。希望帮到你。本文还有配套的精品资源点击获取
返回列表