如果你维护过一套用二进制方式部署的 Kubernetes 集群大概率会遇到这样一个阶段集群只有一个 master 节点apiserver、controller-manager、scheduler、etcd 全挤在一台机器上。平时测试没问题可一旦这台机器重启、网络抖动、或者需要做系统升级整个控制面就断了业务 pod 哪怕还活着你也没办法通过 kubectl 管理集群。我最近就把这样一套单节点集群扩展成了多 master 高可用架构核心动作是在原有基础上新增 master2同时引入两台 LB 节点用 nginx 做四层负载均衡用 keepalived 提供 VIP 漂移。整个过程踩了不少坑这篇就完整梳理一遍从架构设计、证书调整、etcd 扩容到 LB 配置、全局 kubeconfig 切换全部用实际命令和配置说话。适合已经能独立部署单节点 Kubernetes、想继续把手里的集群改造成高可用的人。1. 从单节点到高可用整体架构与设计思路1.1 先确认现有集群的状态改架构之前先别急着下载二进制文件。你手里的“单节点基础”具体是什么形态决定了后面所有操作方式。我这次面对的集群是全部控制面组件在同一台机器上etcd 也是单实例证书是通过 openssl 手动签发的一套内部 CA部署路径标准在 /etc/kubernetes 下。先做一轮盘点命令不多但每一条都要看结果kubectl get nodes -o wide kubectl get cs ss -lntp | grep -E 6443|2379 cat /etc/kubernetes/apiserver 2/dev/null || systemctl cat kube-apiserver重点关注几个信息当前 apiserver 的监听地址和端口默认是 0.0.0.0:6443etd 监听地址和 peer 端口确认是 127.0.0.1:2379 还是内网 IP:2380当前各组件 kubeconfig 里的 server 字段指向的是哪个 IP证书目录的结构有没有 ca.crt、ca.keyapiserver 证书里包含了哪些 IP我这次的情况是 kubeconfig 指向 master1 的内网 IP 10.0.0.11etcd 单实例只监听本机所以后面很多地方要改。1.2 目标架构与 IP 规划架构最终定成这样角色主机名IP部署组件原有 mastermaster110.0.0.11apiserver、controller-manager、scheduler、etcd、nginx、keepalived新增 mastermaster210.0.0.12apiserver、controller-manager、scheduler、etcd负载均衡器1lb110.0.0.13nginx、keepalived负载均衡器2lb210.0.0.14nginx、keepalived业务节点原有node110.0.0.15kubelet、kube-proxy、containerdVIP-10.0.0.100由 keepalived 漂移为什么 master1 上也放了 nginx 和 keepalived我的习惯是让 master1 同时具备 LB 功能这样即使两台独立 LB 都挂了VIP 还能落在 master1 上控制面不至于完全失去入口。当然这不是必须的如果你希望角色干净一点可以把 master1 上的 nginx 和 keepalived 去掉只保留两台专用 LB。还需要注意一个端口细节nginx 的四层代理监听的是 6443但 master 节点本身也有 apiserver 在监听 6443同一台机器两个进程不能绑同一个端口。所以我让 master1 上的 nginx 监听 16443LB1 和 LB2 上的 nginx 监听 6443VIP 的 kubeconfig 访问端口统一用 6443。如果你严格把 LB 和 master 分开就不用纠结这个直接都监听 6443。1.3 为什么是 nginx 四层转发 keepalived连接 apiserver 的流量本质上是长连接TLS 握手后会有大量持续的数据交换。这种场景适合四层转发也就是 nginx 的 stream 模块在 L4 做 TCP 透传不需要解析 HTTP 内容性能损耗极小。如果非要用七层反向代理nginx 的 http 模块也能做但需要额外处理 websocket、长连接、TLS 终结等问题复杂度和风险都更高。keepalived 负责 VIP 的高可用。它的原理是基于 VRRP 协议在多个节点之间选主VIP 由主节点持有主节点故障后备份节点自动接管。VIP 跟 DNS 或者客户端写死的 IP 相比有两个直接好处一是客户端只需要记住一个地址二是故障切换对客户端透明kubeconfig 里的 server 一直是同一个 VIP。在实际部署中很多教程会让你用 haproxy 代替 nginx。我选 nginx 是因为集群里本身可能已经有其他 nginx 实例运维更熟悉配置也直观stream 配置块只有几行就能搞定。两个方案都能用没必要在这种简单场景里引入额外组件。2. 二进制部署需要提前准备的组件与证书体系2.1 新增 master2 要准备哪些二进制文件Kubernetes 二进制部署最大的特点就是所有组件都是独立进程不依赖容器运行时去拉镜像。新增 master2 需要准备的组件和原有 master1 完全对应kube-apiserverkube-controller-managerkube-scheduleretcdkubelet如果你打算让 master2 也运行业务 podkube-proxykubectl版本必须和 master1 保持一致。用下面的命令先确认真实版本kube-apiserver --version etcd --version kubelet --version下载二进制时要注意架构amd64/arm64下载完放到 /usr/local/bin 并加执行权限。版本不一致比较常见的一个问题是 apiserver 和 etcd 之间兼容性比如新版 apiserver 对旧版 etcd 的某些 API 不兼容启动后直接报错。确认版本一致之后再往下走。2.2 证书体系怎么扩展SAN 是重点二进制部署的集群证书体系通常是自己用 openssl 或 cfssl 搭的。新增 master2 后apiserver 提供给客户端访问的地址变多了包括master1 IP 10.0.0.11master2 IP 10.0.0.12VIP IP 10.0.0.100各主机名kubernetes.default.svc 这类集群内部域名之前单节点部署时apiserver 证书里很可能只有 master1 的 IP 和主机名。如果你不加修改直接用这套证书启动 master2外部客户端用 https://10.0.0.100:6443 访问 apiserver 时TLS 握手会失败报错类似 x509: certificate is valid for 10.0.0.11, not 10.0.0.100。这个问题的根因就是证书 SAN 里没有新的 IP。所以必须重新签发 apiserver 证书而且要保证 master1 和 master2 共用同一份 CA这样 kubelet、kube-proxy 等客户端在验证 apiserver 时才会信任它。具体做法是在 master1 上重新生成 apiserver 的 CSR把 SAN 扩展完整。如果你用的是 cfsslJSON 配置大致是这样的{ CN: kubernetes, hosts: [ 10.0.0.11, 10.0.0.12, 10.0.0.100, 127.0.0.1, 192.168.0.1, kubernetes, kubernetes.default, kubernetes.default.svc, kubernetes.default.svc.cluster.local ], key: { algo: rsa, size: 2048 } }如果你用 openssl就修改 openssl.cnf 的 [alt_names] 段把所有 IP 和域名都追加进去subjectAltName IP:10.0.0.11, IP:10.0.0.12, IP:10.0.0.100, IP:127.0.0.1, IP:192.168.0.1, DNS:kubernetes.default.svc签发完成后把新的 apiserver.crt 和 apiserver.key 同时分发到 master1 和 master2 对应目录并重启两边的 apiserver。这里我吃过一次亏签发新证书时只加了 master2 的 IP忘了 VIP。结果 LB 配好了从 LB 访问 6443 直接报证书不匹配排查了半个小时才反应过来。SAN 列表里的 VIP 一定不能漏这是整个架构能跑通的必要条件。2.3 etcd 从单实例到多实例二进制部署的集群里etcd 通常也是单实例。我们的目标架构里有 master1 和 master2 两个控制面节点所以 etcd 至少需要变成两个成员的集群。有两种选择master1 和 master2 各跑一个 etcd形成双成员集群再加一个独立节点或复用 worker 节点形成三成员集群双成员 etcd 在原理上不如三成员稳因为当其中一个成员挂掉剩余一个只有一半的投票权无法选出 leader整个集群不可写。但在资源和节点受限的实验室环境下双成员也够用毕竟另一台 master 还活着。我这次选的是三成员方案新增了一个单独的 etcd 节点 10.0.0.16只跑 etcd避免和 kubelet 的资源竞争。不管选哪种最核心的一步是把原本单节点的 etcd 改造成集群。初始部署时 etcd 启动命令里配置的是 --initial-clustermaster1https://10.0.0.11:2380 --initial-cluster-statenew。现在要新增成员必须改成--initial-clustermaster1https://10.0.0.11:2380,master2https://10.0.0.12:2380,etcd3https://10.0.0.16:2380 \ --initial-cluster-stateexisting注意最后这个 existing它的意思是 etcd 进程启动时不再尝试自己初始化一个新集群而是作为已有集群中的一员去接入。如果这里保持为 new新启动的 etcd 会尝试再次初始化集群大概率会跟现有 etcd 互相冲突轻则成员列表混乱重则数据目录直接损坏。操作顺序也很重要。先在 master1 上修改 etcd 启动参数并重启让 master1 以 existing 模式加入原有的单节点集群。然后在 master2 和 etcd3 上同样修改配置并启动最后用 etcdctl 确认集群状态etcdctl --endpointshttps://10.0.0.11:2379 \ --cacert/etc/kubernetes/pki/etcd/ca.crt \ --cert/etc/kubernetes/pki/etcd/server.crt \ --key/etc/kubernetes/pki/etcd/server.key \ member list如果输出里能看到三个成员并且都是 started这一步就算完成了。2.4 新节点需要拷贝哪些证书master2 上要放四类证书文件/etc/kubernetes/pki/ca.crt 和 ca.key集群的根 CA/etc/kubernetes/pki/apiserver.crt 和 apiserver.key重新签发后的新证书/etc/kubernetes/pki/etcd/ca.crtetcd 通讯用 CA/etc/kubernetes/pki/etcd/server.crt 和 server.keyetcd 服务端证书需要在 SAN 里包含 master2 的 IP如果用 kubeadm 初始化的集群证书在 /etc/kubernetes/pki 下已经分好了。手动部署的集群需要自己确认。最简单的方式是直接在 master1 上把这些文件用 scp 或内网共享目录拷贝到 master2然后逐一核对校验值确保证书没有在传输过程中损坏。3. 两台 LBnginx 四层负载均衡与 keepalived 高可用实操3.1 nginx stream 四层代理配置nginx 默认编译一般不会带 stream 模块先检查一下nginx -V 21 | grep stream如果没有需要重新编译安装一个带 --with-stream 的版本或者直接用发行版自带的 nginx 包通常默认都带。我用的系统是 CentOS 系的yum 安装的 nginx 自带 stream 模块省了这一步。nginx 的关键配置写在 /etc/nginx/nginx.conf 顶层不能在 http 块里写因为四层转发不是 HTTP 协议。配置如下stream { log_format proxy $remote_addr [$time_local] $protocol $status $bytes_sent $bytes_received $session_time $upstream_addr; access_log /var/log/nginx/k8s-apiserver.log proxy; upstream k8s_apiserver { server 10.0.0.11:6443 max_fails3 fail_timeout30s; server 10.0.0.12:6443 max_fails3 fail_timeout30s; } server { listen 6443; proxy_pass k8s_apiserver; proxy_connect_timeout 5s; proxy_timeout 300s; } }注意几个参数max_fails 和 fail_timeout 控制后端健康检查。如果 apiserver 连续 3 次失败nginx 会将其标记为不可用并等待 30 秒后再尝试。这里我调过多次实测 max_fails3 fail_timeout30s 是比较合适的太灵敏容易误杀正在启动的 apiserver太迟钝又会让故障恢复变慢。proxy_timeout 300s 是关键。kubectl 执行某些操作时会长时间占用连接尤其是 exec 和 logs如果 proxy_timeout 太短连接会被 nginx 强行断开表现是 kubectl 操作做到一半突然卡死。日志里记录了 upstream_addr这是排查问题的重要信息。apiserver 请求来自哪个后端、每个连接耗时多少在日志里一眼就能看出来。如果你的 LB 节点和 master 节点分开部署listen 6443 没有问题。如果 LB 和 master 在同一台机器listen 要改成 16443 或者其他不冲突端口。配置完执行 nginx -t 检查语法没问题后 systemctl restart nginx。用 curl 验证curl -k https://10.0.0.13:6443/version能返回 JSON 格式的版本信息说明四层转发已经通了。3.2 keepalived VIP 配置keepalived 的配置文件在 /etc/keepalived/keepalived.conf。两台 LB 的配置几乎一样区别只在优先级和角色。LB1 上的配置! Configuration File for keepalived global_defs { router_id LVS_DEVEL } vrrp_script chk_nginx { script /etc/keepalived/check_nginx.sh interval 2 weight -20 fall 3 rise 2 } vrrp_instance VI_1 { state MASTER interface ens33 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { 10.0.0.100/24 dev ens33 } track_script { chk_nginx } }LB2 上的配置大同小异state 改成 BACKUPpriority 改成 90其他保持一致。这里说实话有一个容易踩的点很多人以为把两台都配成 BACKUP 更好避免抢占。但 VRRP 协议不支持双 BACKUP 架构如果两台都是 BACKUP没有主节点VIP 永远无法绑定。所以必须有一台是 MASTER。track_script 里指向的 check_nginx.sh 是自定义的健康检查脚本内容如下#!/bin/bash if ss -lnt | grep -q :6443; then exit 0 else exit 1 fi脚本的作用是探测 nginx 的四层端口是否在监听。如果 nginx 挂了keepalived 会降低本节点的权重VIP 自动漂移到另一台 LB。注意脚本一定要加执行权限chmod x /etc/keepalived/check_nginx.sh另外提一句如果系统防火墙开启了需要放行 VRRP 协议。iptables 或 firewalld 默认会拦截 VRRP 组播报文导致两台 LB 互相感知不到对方出现双主现象。放行方式firewall-cmd --permanent --add-rich-rulerule protocol valuevrrp accept firewall-cmd --reload这一步很容易被忽略但遗漏之后的问题非常隐蔽因为看起来 keepalived 服务都正常VIP 却有可能在某一时刻同时出现在两台机器上。对于 apiserver 这类状态敏感的服务来说双主会带来不可预测的写冲突。手动测试时可以用 ip addr show 检查 VIP 实际落在哪台机器上。设置完成后两台 LB 上分别执行 systemctl enable --now keepalived然后用 ip addr show 确认 VIP 绑定的位置。正常情况下 vip 会绑定在 MASTER 节点也就是 LB1 上。把 LB1 上的 keepalived 停掉几秒后 VIP 应该漂移到 LB2。3.3 LB 整体联动验证LB 配置完成之后不要急着继续搭 master2先把这一层验证扎实。我习惯做三组测试第一组用 VIP 访问 apiservercurl -k https://10.0.0.100:6443/version第二组停掉 master1 的 apiserver再访问 VIPsystemctl stop kube-apiserver curl -k https://10.0.0.100:6443/version此时流量应该全部转发到 master2。注意 master2 此时可能还没起来所以需要在 master2 部署完之后再做这组测试。如果你在只有 master1 单节点的情况下先行验证 LB停掉 apiserver 之后 curl 超时是正常的。第三组看 nginx 日志确认负载均衡效果tail -f /var/log/nginx/k8s-apiserver.log请求会交替出现在 master1 和 master2 的上游地址中。如果所有请求都指向同一个节点检查两台 apiserver 是否都正常监听 6443。4. master2 节点完整加入流程与全局连接切换4.1 在 master2 上部署 etcd 并加入集群先确认 master1 上 etcd 的 systemd unit 内容和证书目录结构保证 master2 的配置风格一致。然后按顺序操作 master2 的 etcd创建 /etc/etcd 配置目录和 /var/lib/etcd 数据目录将 etcd 的 CA 证书、服务端证书、客户端证书拷贝到 /etc/kubernetes/pki/etcd编写 systemd unit关键参数如下[Unit] Descriptionetcd Afternetwork.target [Service] Userroot Typenotify ExecStart/usr/local/bin/etcd \ --namemaster2 \ --data-dir/var/lib/etcd \ --listen-client-urlshttps://10.0.0.12:2379,https://127.0.0.1:2379 \ --advertise-client-urlshttps://10.0.0.12:2379 \ --listen-peer-urlshttps://10.0.0.12:2380 \ --initial-advertise-peer-urlshttps://10.0.0.12:2380 \ --initial-clustermaster1https://10.0.0.11:2380,master2https://10.0.0.12:2380,etcd3https://10.0.0.16:2380 \ --initial-cluster-stateexisting \ --initial-cluster-tokenetcd-cluster \ --cert-file/etc/kubernetes/pki/etcd/server.crt \ --key-file/etc/kubernetes/pki/etcd/server.key \ --peer-cert-file/etc/kubernetes/pki/etcd/peer.crt \ --peer-key-file/etc/kubernetes/pki/etcd/peer.key \ --trusted-ca-file/etc/kubernetes/pki/etcd/ca.crt \ --peer-trusted-ca-file/etc/kubernetes/pki/etcd/ca.crt [Install] WantedBymulti-user.target启动顺序上有个小讲究先在 master1 上把 etcd 的启动参数改成包含全部成员的 existing 模式并重启再启动 master2 和 etcd3。如果顺序反了新节点会一直尝试连接 master1 的 2380而 master1 又没有以集群模式对外提供 peer 服务加入会一直超时。启动完成后回到 master1 上执行 member list确认三个 etcd 成员都已就绪。任何节点出现 unhealthy 状态都不要继续往下走先把 etcd 集群修好。etcd 是控制面的地基地基不稳后面全是问题。4.2 在 master2 上启动 apiserver、controller-manager、schedulermaster2 上的三个控制面组件启动参数基本照搬 master1但有几个地方必须修改。apiserver 的关键参数如下ExecStart/usr/local/bin/kube-apiserver \ --advertise-address10.0.0.12 \ --bind-address0.0.0.0 \ --secure-port6443 \ --service-cluster-ip-range192.168.0.0/16 \ --service-node-port-range30000-32767 \ --etcd-servershttps://10.0.0.11:2379,https://10.0.0.12:2379,https://10.0.0.16:2379 \ --etcd-cafile/etc/kubernetes/pki/etcd/ca.crt \ --etcd-certfile/etc/kubernetes/pki/etcd/client.crt \ --etcd-keyfile/etc/kubernetes/pki/etcd/client.key \ --client-ca-file/etc/kubernetes/pki/ca.crt \ --kubelet-client-certificate/etc/kubernetes/pki/apiserver-kubelet-client.crt \ --kubelet-client-key/etc/kubernetes/pki/apiserver-kubelet-client.key \ --service-account-key-file/etc/kubernetes/pki/sa.pub \ --service-account-signing-key-file/etc/kubernetes/pki/sa.key \ --service-account-issuerhttps://kubernetes.default.svc.cluster.local \ --authorization-modeNode,RBAC \ --tls-cert-file/etc/kubernetes/pki/apiserver.crt \ --tls-private-key-file/etc/kubernetes/pki/apiserver.key \ --allow-privilegedtrueapiserver 的 --service-cluster-ip-range 必须和 master1 完全一致。如果这里配成不同的网段两个 apiserver 对 Service ClusterIP 的理解就不一样集群会出现各种幽灵 IP。controller-manager 和 scheduler 的配置简单很多关键在于启动参数里有一个 --kubeconfig 或者 --master 指向的位置。二进制部署时这里不能再写死 master1 的 IP要改成 VIP--kubeconfig/etc/kubernetes/controller-manager-kubeconfig.yaml --leader-electtruescheduler 同理--kubeconfig/etc/kubernetes/scheduler-kubeconfig.yaml --leader-electtrue这里的 kubeconfig 里 server 字段必须写成 https://10.0.0.100:6443。因为两台 controller-manager 要竞争同一个 leader 锁如果 master2 上的 controller-manager 还在连接 10.0.0.11 或 10.0.0.12 的某个地址而 master1 恢复时又不断电两边就像在两条平行线上各自选举集群里会同时出现两个 active 的 controller-manager。配置文件里apiVersion: v1 kind: Config clusters: - cluster: server: https://10.0.0.100:6443 certificate-authority: /etc/kubernetes/pki/ca.crt name: kubernetes contexts: - context: cluster: kubernetes user: controller-manager name: system:controller-manager current-context: system:controller-manager users: - name: controller-manager user: client-certificate: /etc/kubernetes/pki/controller-manager.crt client-key: /etc/kubernetes/pki/controller-manager.keyscheduler kubeconfig 结构一样只是证书换成 scheduler.crt 和 scheduler.key。4.3 原有节点与新增节点的 kubeconfig 统一切换到 VIP这是整个改造里最需要细心的一步。master2 上的三个控制面组件可以通过 VIP 访问 apiserver但如果 master1 上还是用原来的 IP 连接自己而 master1 恰好挂掉那 master1 上的 kubelet、kube-proxy、controller-manager 就全部失去与 API server 的连接高可用还是纸上谈兵。所以全局的 kubeconfig 都要切换。需要改的文件包括/etc/kubernetes/controller-manager-kubeconfig.yaml/etc/kubernetes/scheduler-kubeconfig.yaml/etc/kubernetes/kubelet.conf/etc/kubernetes/kube-proxy.kubeconfig如果 kube-proxy 独立使用配置文件~/.kube/config管理员 kubectl 配置切换方法很简单把所有 server 字段从 10.0.0.11 改成 10.0.0.100sed -i s#https://10.0.0.11:6443#https://10.0.0.100:6443#g /etc/kubernetes/*.yaml /etc/kubernetes/*.conf然后逐一重启对应服务systemctl restart kube-apiserver kube-controller-manager kube-scheduler kubelet kube-proxy注意顺序因为 kubelet 和管理面组件自身需要通过 apiserver 通信而 apiserver 重启后需要几秒才能重新监听所以整个切换过程中出现短暂连接失败是正常的服务会自动重试。如果重启完两分钟后节点状态还是 NotReady再通过 systemctl status 查看具体报错。worker 节点上的 kubelet.conf 和 kube-proxy.kubeconfig 也要做同样的修改。如果你的 worker 不直接访问 apiserver而只通过 kubelet 访问其实 kubelet.conf 里的 server 字段改了就行。还有一个容易忽略的点apiserver 自身的 --advertise-address 不能改成 VIP。advertise-address 告诉 kubelet 等组件用什么地址来回连 apiserver。如果你把 master1 的 advertise-address 写成 VIP而当前 VIP 在 LB2 上那么 kubelet 对 master1 的探活和日志回传都会跑到 LB2 上去网络路径绕了一圈不说LB2 的 nginx 还不一定能转发 kubelet 的 10250 端口流量。所以 advertise-address 保持本机 IP不要动。4.4 验证集群高可用所有服务都起来之后先看整体状态kubectl get nodes -o wide kubectl get cs kubectl get leases -n kube-systemget cs 的输出可能不再显示各个组件的健康状态这跟新版 Kubernetes 移除了 master 组件健康检查接口有关不必担心。可以通过下面的方式手动检查各组件日志是否报错。关键验证是故障演练。我实际做过的测试有三个停掉 master1 上的 apiserver执行 kubectl get nodes。正常情况是命令不中断由 master2 继续服务。注意如果 kubectl 客户端里有对特定证书的缓存连接重建需要几秒耐心等待即可。停掉 master1 上的 controller-manager 和 scheduler观察集群是否有新的 Deployment 能被正常调度。正常情况下 master2 上的 controller-manager 会通过 leader 选举自动接管调度和控制器功能不受影响。停掉 LB1 上的 keepalivedVIP 漂移到 LB2再次执行 kubectl get nodes依然正常。这三组测试全部通过说明高可用架构真正生效了。只做第一组不测 controller-manager 和 scheduler很容易出现一种假象apiserver 是好的但整个控制面的协调工作停摆了业务 pod 依然无法正常被调度。5. 高可用部署中的常见坑与定位技巧5.1 VIP 连不上、证书不匹配、端口不通这类问题在刚部署完的阶段出现频率非常高建议先排除几个最常见的点。症状可能原因排查命令curl https://VIP:6443 超时nginx 未监听、keepalived 未启动、防火墙拦截ss -lntp | grep 6443ip addr showcurl 返回证书错误apiserver 证书 SAN 缺少 VIP 或 master2 IPopenssl x509 -in apiserver.crt -noout -textkubectl get nodes 报 connect: connection refusedkubeconfig 的 server 还是旧 IPkubectl config view --minify两台 LB 同时持有 VIPVRRP 报文被防火墙拦截journalctl -u keepalived | tail -30证书问题尤其容易误判。我第一次在 master2 上启动 apiserver 时日志一直报 x509: certificate is valid for 10.0.0.11, not 10.0.0.12。排查了半天才发现 apiserver 的证书还是老版本根本原因是我重新签发证书后没有同步到 master2。这类问题用 openssl 看证书 SAN 非常快。5.2 etcd 反复加入失败、报错 request timed out新增 etcd 节点时最常见的报错是etcdserver: request timed out这个报错通常有两类原因。一是 peer 端口 2380 不通通过 telnet 验证telnet 10.0.0.12 2380如果端口不通检查 etcd 的 listen-peer-urls 和防火墙。二是 --initial-cluster-state 配置不一致新节点状态是 existing而原有节点还是 new两边无法对齐。另外一个隐蔽的问题是 etcd 的 --data-dir 里如果有之前残留的旧数据etcd 启动时可能会尝试用旧集群配置初始化导致 member 信息不一致。处理办法是备份数据目录后清空让 etcd 以全新成员身份加入。etcd 集群加入过程我强烈建议一步一验证etcdctl --endpointshttps://10.0.0.11:2379 \ --cacert/etc/kubernetes/pki/etcd/ca.crt \ --cert/etc/kubernetes/pki/etcd/server.crt \ --key/etc/kubernetes/pki/etcd/server.key \ endpoint health --cluster输出全部 healthy 再继续下一台不要一次性把三台都启动完才检查那样出了问题很难定位是哪一台加入失败。5.3 apiserver 健康检查与 leader 选举细节高可用架构下控制面组件能否平稳切换很大程度取决于 leader 选举配置是否正确。controller-manager 和 scheduler 必须保持 --leader-electtrue。默认值确实是 true但有些脚本在部署时为了简化会加上 --leader-electfalse这在单节点时工作正常多节点时就是灾难。两个 controller-manager 同时运行会造成 Deployment、Service 等资源的重复 reconcile虽然多数场景下不会产生严重的资源损坏但行为会变得不可预测。查看当前 leader 是谁kubectl get leases -n kube-system kubectl get endpoints kube-controller-manager -n kube-system -o yaml输出里会显示持有者名称正常情况下只能有一个 holderIdentity。另外controller-manager 和 scheduler 的 kubeconfig 里 server 指向 VIP 之后要保持调度组件和 apiserver 之间的网络路径短且稳定。如果 VIP 漂移到远端的 LB而两个 master 距离较远leader 续约的网络延迟会增大极端情况下可能出现频繁的 leader 切换。这种情况下可以把 LB 改成在 master1 和 master2 本机各放一套形成分层结构。5.4 版本不一致引发的兼容性问题二进制部署不像 kubeadm 那样有版本强校验换版本容易出问题。比如 apiserver 4.x 版本去连接 3.x 版本的 etcdKubernetes 官方规定了 apiserver 与 etcd 的版本兼容矩阵跨度过大时 apiserver 可以直接拒绝启动。我在实际部署中遇到过一次master1 是某 k8s 版本由于下载源问题给 master2 下载到了另一个 patch 版本启动时 apiserver 直接退出。解决办法很简单从 master1 上把对应二进制文件拷到 master2或者检查下载版本的 checksum 后再上传。补丁版本不一致通常不影响正常运行但版本跨度大时最好保持完全一致省得给自己埋雷。6. 压测与日常维护注意事项6.1 高可用集群建议做一次压力测试部署完成不等于高可用生效。我建议至少跑一轮基础压测再上线核心目的是验证 nginx 四层转发的连接稳定性以及 keepalived 切换时对长连接的影响。简单的压测方式是用 ab 或者 wrk 从外部访问 VIP 的 apiserver 端口wrk -t4 -c100 -d30s https://10.0.0.100:6443/version这个场景主要看连接是否建立但不建议对 apiserver 做过于激烈的 QPS 压测直接把 CPU 打满会影响控制面稳定性。更合理的做法是模拟实际业务操作比如并发创建、删除 Deployment、访问 Service 等。观察 nginx 日志里的连接耗时重点看有没有大量 5 秒以上的连接。如果发现 nginx 日志中有很多 upstream 连接重置的报错先看后端 apiserver 是否因负载过高而主动断连。一个常见的坑是 nginx 默认的 proxy_timeout 太短长连接在 60 秒被 nginx 断开而 kubectl exec 这类操作往往超过这个时间。把 proxy_timeout 调到 300 秒甚至 600 秒能解决大部分问题。6.2 VIP 切换时的注意点keepalived 的 VIP 切换理论上在几秒内完成但要注意 Kubernetes 客户端可能已经缓存了旧的 TCP 连接。kubectl 是每次命令新建连接影响不大。但如果有一些长驻进程通过 client-go 持续访问 apiserver比如 controller-manager、scheduler 自身的连接它们会通过重试重新握手不需要额外干预。真正需要注意的是不要在 VIP 切换的瞬间去执行真的需要写入集群的操作比如 kubectl apply。虽然 Kubernetes 有重试机制但极短时间内可能遇到连接拒绝或超时表现为操作失败。如果必须在这个窗口期操作重试一次即可。6.3 证书过期时间要提前排期二进制部署的集群证书有效期通常是自己签定的常见的是一年或三年。证书过期后整个集群不可用而且有些证书过期了不一定立刻显眼地报错。比如 apiserver 的客户端证书过期kubectl 可能报 x509: certificate has expired or is not yet valid这个错误很容易识别。但 kubelet 的证书过期节点状态会变成 NotReady且日志里不一定会直接提示证书过期。建议集群部署完成后立刻做一个证书到期时间清单openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -enddate openssl x509 -in /etc/kubernetes/pki/kubelet.crt -noout -enddate有条件的话把过期提醒写进监控系统。二进制部署没有 kubeadm 的自动轮换机制证书管理必须靠人工。6.4 备份策略要有版本意识高可用不代表数据不会丢。etcd 集群三节点只是避免了单点故障如果人为误删了一个 namespace 或者执行了野生的 delete pod --all数据还是会被正常同步删除。我习惯每天凌晨做一次 etcd 快照ETCDCTL_API3 etcdctl --endpointshttps://10.0.0.100:2379 \ --cacert/etc/kubernetes/pki/etcd/ca.crt \ --cert/etc/kubernetes/pki/etcd/server.crt \ --key/etc/kubernetes/pki/etcd/server.key \ snapshot save /data/backup/etcd-snapshot-$(date %F).db恢复时用 etcdctl snapshot restore 恢复到一台新节点再通过 etcd 集群重建的方式接入其他成员。这个过程建议单独做一次演练不要等到真出事才第一次尝试恢复。6.5 日志与监控要补齐二进制部署的集群日志都有 systemd journal 管理。通过 journalctl 查看每个组件的状态journalctl -u kube-apiserver -f journalctl -u etcd -f建议把日志输出到文件并接入采集系统方便故障时回溯。apiserver 的审计日志、nginx 的访问日志、keepalived 的状态切换日志这些都是排查高可用问题的一手材料。7. 我实际操作中总结的几点经验整套流程走下来最大的感受是二进制部署高可用集群这件事难度不在某个单独的技术点上而在所有组件之间的连接关系梳理。apiserver 要连 etcdcontroller-manager 要连 apiserverkubelet 要连 apiserverLB 要连 apiserver每一环都要指向正确的地址每一份证书都要覆盖正确的 IP 和域名。证书 SAN 里加 VIP 这个细节如果从一开始就规划好后面能省很多排查时间。建议在最初部署单节点集群时就预留好 VIP 和 master2 的 IP哪怕当时还没有这两台机器。证书签一次用一年如果忘了加后面再重新签发要同时重启两个 master 的 apiserver所有使用旧证书的客户端也要重新加载涉及面很广。另一个体会是keepalived 的 VIP 切换测试不要只做一次。VRRP 协议在持续运行中可能因为网络抖动、防火墙策略变更而出现异常。我现在的做法是每月手动切换一次 VIP把 LB1 的 keepalived 临时停掉观察切换再恢复全程只需要几十秒但能确保整个高可用链路长期处于可用状态。最后想说的是二进制部署虽然比 kubeadm 繁琐得多但正因为所有组件都是手动启动、手动配证书、手动接连接你对集群内部的工作方式会比用一键部署的人清楚得多。这套知识在排查生产环境问题时特别值钱哪怕之后你切回 kubeadm 管理集群遇到类似问题也能快速定位。
阅读完成 · 觉得有帮助?