news 2026/10/19 3:17:03

二进制部署Kubernetes高可用:从单master到双master+双LB实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
二进制部署Kubernetes高可用:从单master到双master+双LB实践

做二进制方式部署 Kubernetes 的人,多少带点“倔”——不想被 kubeadm 那堆自动生成的证书和静态 Pod 挡住视线,就想亲手把 etcd、apiserver、controller-manager、scheduler 一个个进程拉起来,看它们到底是怎么协作的。我前阵子刚把一套“单 master + 单 worker”的二进制集群,扩张成“双 master + 双 LB”的高可用形态,中间踩了不少坑,也把几个关键原理彻底捋了一遍。

这篇就把从单节点演进到多节点高可用的完整过程讲清楚,重点是两块:一是在已有 master1 的基础上新增 master2,二是用两台 nginx + keepalived 做 API Server 的负载均衡和高可用。整个过程适合已经有一台二进制部署 master、想升级成控制面高可用结构的同学参考。你不需要从零开始,只要照着已有单节点补组件就行。

1. 扩容前先盘家底:单节点集群到底缺了哪几块高可用拼图

1.1 现有集群的组件清单与角色分布

动手之前,我习惯先列一份清单,把现有集群里每个节点跑什么组件写清楚。我当时的环境是:

节点名IP运行组件状态
k8s-master1192.168.31.10etcd1、kube-apiserver、kube-controller-manager、kube-scheduler、kubelet、kube-proxy已有
k8s-worker1192.168.31.12kubelet、kube-proxy已有
k8s-master2192.168.31.11待部署新增
k8s-lb1192.168.31.20nginx、keepalived新增
k8s-lb2192.168.31.21nginx、keepalived新增
VIP192.168.31.30统一 API Server 入口新增

这里面有几个角色划分要提前想清楚:

  • master1 上的 etcd 目前是单实例,里面存着整个集群的元数据,包括 Node、Pod、ConfigMap、RBAC 这些核心数据。
  • kube-apiserver 目前只有一台,所有 kubectl、kubelet、controller-manager、scheduler 都连它。
  • kube-controller-manager 和 kube-scheduler 也是单副本在跑,没有选主机制。
  • worker1 上的 kubelet 和 kube-proxy 的 kubeconfig 直接写死了 master1 的 IP 和 6443 端口。

单点集群最大的问题不是某台机器容易坏,而是控制面全部绑死在一台机器上。master1 一旦关机,别说调度 Pod,连 kubectl get nodes 都连不上去。

1.2 高可用不是“把组件复制一遍”那么简单

很多人以为新加 master2 就是把 master1 的文件复制过去,再跑一遍 systemd 服务就行。真这么干,会出现两个 apiserver 互相看不到对方、两个 controller-manager 同时抢同一个资源之类的怪问题。高可用改造的核心逻辑是这样的:

组件单节点状态多节点高可用方案
kube-apiserver单进程master1、master2 各起一个,前面挂 LB 统一入口
kube-controller-manager单进程两个节点都起,开启--leader-elect=true选主
kube-scheduler单进程两个节点都起,开启--leader-elect=true选主
etcd单实例master1、master2 组成 etcd 集群
API Server 入口master1 IPVIP 192.168.31.30 + nginx + keepalived

这里有个容易混淆的概念:keepalived 并不是负载均衡器,它只负责“VIP 漂移”。真正把流量分发给两台 master 的是 nginx 的四层转发。keepalived 做的事情是保证 VIP 这个入口永远存在于 LB1 或 LB2 上,谁优先级高、谁活着,VIP 就在谁身上。nginx 负责把打向 VIP:16443 的流量轮询转发到 master1:6443 和 master2:6443。

为什么用 16443 而不是 6443?因为 master 节点本身已经监听 6443,LB 再用 6443 会和后端端口混在一起看的时候容易乱。VIP 上开 16443,看一眼端口就能知道流量经过了 LB。这只是约定,不是强制,你完全可以用 8443。

还有一个事必须提前接受:如果把 etcd 只放在 master1 和 master2 上,那就是两节点的 etcd 集群。两节点的 quorum 是 2,意味着必须两个 etcd 同时在线才能写。所以后面我实测时会刻意区分“master1 只挂 apiserver”和“master1 整机断电”两种情况,前者能自动恢复,后者会卡在 etcd 仲裁上。生产环境我建议至少三控制面节点,别拿两节点 etcd 扛生产。

2. 证书与配置文件准备:给 master2 发“身份证”,把 VIP 写进 SAN

2.1 这套集群的证书体系要分清

二进制部署里,证书体系是最容易翻车的地方。很多人一上来就复制整个 pki 目录,结果 master2 上证书里的 IP 还是 master1 的,apiserver 一启动就报证书校验失败。

我先按角色把证书分成三组:

  • Kubernetes 主 CA 签的证书:apiserver 证书、admin 客户端证书、kubelet 客户端证书、controller-manager 和 scheduler 的客户端证书。
  • etcd 独立 CA 签的证书:etcd 节点间的 peer 证书、server 证书、client 证书。
  • service account 相关的 sa.key 和 sa.pub,这个不涉及 CA,但要保证所有控制面节点一致。

新增 master2 时,真正要“重新签”的只有两类:apiserver 的服务端证书,以及 etcd2 的 peer/server 证书。其余大部分是可以直接复制或只改 server 地址就能用的。

2.2 重新签发 apiserver 证书:SAN 里必须包含新节点和 VIP

apiserver 证书是集群里最特殊的服务端证书,因为客户端可能通过多种方式访问它:直接访问 master1 IP、直接访问 master2 IP、访问 VIP、Pod 内部访问 service 的 ClusterIP、访问 kubernetes.default.svc 这个 DNS 名。如果证书 SAN 缺了任何一项,对应路径就会报 x509 certificate is valid for ... not for ... 错误。

我习惯在 master1 上准备一个 SAN 配置文件,目录固定放在 /opt/kubernetes/pki:

[req] distinguished_name = dn req_extensions = ext [dn] [ext] subjectAltName = @alt_names [alt_names] IP.1 = 192.168.31.10 IP.2 = 192.168.31.11 IP.3 = 192.168.31.20 IP.4 = 192.168.31.21 IP.5 = 192.168.31.30 IP.6 = 10.96.0.1 DNS.1 = kubernetes DNS.2 = kubernetes.default DNS.3 = kubernetes.default.svc DNS.4 = localhost

IP.6 是 service 网段的第一个地址。我这边 service 网段是 10.96.0.0/12,所以第一个可用 IP 是 10.96.0.1。这个地址很容易被忘掉,忘了之后 Pod 里访问 Kubernetes service 会直接证书校验失败。

然后重新签发:

cd /opt/kubernetes/pki openssl genrsa -out apiserver-new.key 2048 openssl req -new -key apiserver-new.key -subj "/CN=kube-apiserver" -config openssl-apiserver.cnf -out apiserver-new.csr openssl x509 -req -in apiserver-new.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out apiserver-new.pem -days 3650 -extensions ext -extfile openssl-apiserver.cnf

生成以后,把 apiserver-new.pem 和 apiserver-new.key 分别替换到 master1 和 master2 的 /opt/kubernetes/pki 下。注意:ca.key 只在签发的这台机器上留一份,不要往 master2 传。master2 上只需要 ca.pem,不需要 ca.key。

2.3 controller-manager 和 scheduler 的 kubeconfig 怎么处理

这两个组件的客户端证书是集群级别的,CN 固定是 system:kube-controller-manager 和 system:kube-scheduler,并不绑定某台具体节点。所以最省事的做法是直接从 master1 复制 kubeconfig 到 master2,然后只改里面的 server 地址。

我这边把所有控制面组件统一指到了 VIP 上:

sed -i 's#https://192.168.31.10:6443#https://192.168.31.30:16443#' /opt/kubernetes/kubeconfig/kube-controller-manager.kubeconfig sed -i 's#https://192.168.31.10:6443#https://192.168.31.30:16443#' /opt/kubernetes/kubeconfig/kube-scheduler.kubeconfig

这样 controller-manager 和 scheduler 无论跑在哪个 master 上,都是通过同一个入口访问 apiserver,语义上比较干净。

如果你的单节点集群里,这两个组件当初没有开--leader-elect=true,那现在必须改掉。两个 master 都启动 controller-manager,如果不开 leader election,两个进程会同时去改 Deployment、同步副本数,直接造成资源竞争和状态混乱。

2.4 别忘了 sa.key 和 sa.pub

service account 的密钥对也是控制面组件之间协作的关键。kube-apiserver 用 sa.pub 校验 ServiceAccount Token,kube-controller-manager 用 sa.key 签发 Token。如果 master2 上只有 sa.pub 没有 sa.key,controller-manager 启动时 --service-account-private-key-file 会直接报找不到文件。

所以这一步的完整复制清单是:

scp /opt/kubernetes/pki/ca.pem 192.168.31.11:/opt/kubernetes/pki/ scp /opt/kubernetes/pki/sa.pub 192.168.31.11:/opt/kubernetes/pki/ scp /opt/kubernetes/pki/sa.key 192.168.31.11:/opt/kubernetes/pki/ scp /opt/kubernetes/pki/apiserver-new.pem 192.168.31.11:/opt/kubernetes/pki/apiserver.pem scp /opt/kubernetes/pki/apiserver-new.key 192.168.31.11:/opt/kubernetes/pki/apiserver.key

etcd 的证书我建议单独放在 /opt/kubernetes/pki/etcd 目录下,和 Kubernetes 主 CA 分开管理。万一哪天要吊销某个节点的证书,独立的 etcd CA 至少不会让整条信任链一起被动。

3. master2 环境和组件安装:etcd 集群化与控制面三件套就位

3.1 二进制文件和目录同步

master2 上先把基础目录建好,然后从 master1 同步二进制:

mkdir -p /opt/kubernetes/{bin,pki,pki/etcd,kubeconfig,log} rsync -avz 192.168.31.10:/opt/kubernetes/bin/ /opt/kubernetes/bin/

同步完检查一下版本,二进制部署最怕 master1 和 master2 之间版本不一致。比如 master1 是 v1.26.5,master2 却下成了 v1.27.1,两个 apiserver 都能起来,但 apiserver 和 etcd 之间的兼容性、kubelet 和 apiserver 的版本差都会埋雷。我的习惯是全部先看一遍--version,完全一致再往下走。

3.2 把 etcd2 作为新成员加入现有集群

etcd 不是重新起一个就行,必须作为新成员加入 master1 那个已有 etcd,否则两个 etcd 各存各的数据,apiserver 连哪个都不对。这一步先到 master1 上执行 member add:

export ETCDCTL_API=3 etcdctl --endpoints=https://192.168.31.10:2379 \ --cacert=/opt/kubernetes/pki/etcd/ca.pem \ --cert=/opt/kubernetes/pki/etcd/client.pem \ --key=/opt/kubernetes/pki/etcd/client-key.pem \ member add etcd2 --peer-urls=https://192.168.31.11:2380

命令执行后会返回一段提示信息,里面会明确告诉你启动 etcd2 时要用什么 initial-cluster。很多人在这一步把返回的ETCD_INITIAL_CLUSTER抄错,少写一个成员或者写错 IP,etcd2 起来后反复报 “not found in member list”。

返回的 initial-cluster 大概是这种格式:

etcd1=https://192.168.31.10:2380,etcd2=https://192.168.31.11:2380

把这个值原样写进 master2 的 etcd systemd 单元里。

3.3 master2 的 etcd systemd 配置

master2 上 etcd 的启动参数里,--name etcd2、--initial-cluster-state=existing是必须的。前者标识成员身份,后者告诉 etcd 我已经是一个集群的既有成员,不是要从头初始化。

[Unit] Description=Etcd Server After=network.target [Service] ExecStart=/opt/kubernetes/bin/etcd \ --name etcd2 \ --data-dir=/var/lib/etcd \ --initial-advertise-peer-urls https://192.168.31.11:2380 \ --listen-peer-urls https://192.168.31.11:2380 \ --advertise-client-urls https://192.168.31.11:2379 \ --listen-client-urls https://192.168.31.11:2379 \ --cert-file=/opt/kubernetes/pki/etcd/etcd2-server.pem \ --key-file=/opt/kubernetes/pki/etcd/etcd2-server-key.pem \ --peer-cert-file=/opt/kubernetes/pki/etcd/etcd2-peer.pem \ --peer-key-file=/opt/kubernetes/pki/etcd/etcd2-peer-key.pem \ --peer-client-cert-auth=true \ --peer-trusted-ca-file=/opt/kubernetes/pki/etcd/ca.pem \ --client-cert-auth=true \ --trusted-ca-file=/opt/kubernetes/pki/etcd/ca.pem \ --initial-cluster-state=existing \ --initial-cluster=etcd1=https://192.168.31.10:2380,etcd2=https://192.168.31.11:2380 Restart=on-failure LimitNOFILE=65536 [Install] WantedBy=multi-user.target

启动后回到 master1 验证集群状态:

etcdctl --endpoints=https://192.168.31.10:2379,https://192.168.31.11:2379 \ --cacert=/opt/kubernetes/pki/etcd/ca.pem \ --cert=/opt/kubernetes/pki/etcd/client.pem \ --key=/opt/kubernetes/pki/etcd/client-key.pem \ member list etcdctl ... endpoint health

member list 里能看到两个成员,endpoint health 显示两个 endpoint 都是 healthy,这一步才算过。

3.4 master2 上拉起 apiserver、controller-manager、scheduler

apiserver 的参数里,--advertise-address=192.168.31.11和--etcd-servers要写全。etcd-servers 不是只写本机的 2379,而是把两个 etcd 都写上,这样任意一个 etcd 挂了,apiserver 还能连另一个。

[Unit] Description=Kubernetes API Server After=network.target [Service] ExecStart=/opt/kubernetes/bin/kube-apiserver \ --bind-address=0.0.0.0 \ --secure-port=6443 \ --advertise-address=192.168.31.11 \ --etcd-servers=https://192.168.31.10:2379,https://192.168.31.11:2379 \ --etcd-cafile=/opt/kubernetes/pki/etcd/ca.pem \ --etcd-certfile=/opt/kubernetes/pki/etcd/client.pem \ --etcd-keyfile=/opt/kubernetes/pki/etcd/client-key.pem \ --client-ca-file=/opt/kubernetes/pki/ca.pem \ --tls-cert-file=/opt/kubernetes/pki/apiserver.pem \ --tls-private-key-file=/opt/kubernetes/pki/apiserver.key \ --service-cluster-ip-range=10.96.0.0/12 \ --service-node-port-range=30000-32767 \ --authorization-mode=Node,RBAC \ --allow-privileged=true \ --service-account-key-file=/opt/kubernetes/pki/sa.pub Restart=on-failure [Install] WantedBy=multi-user.target

controller-manager 和 scheduler 两个服务比较简单,核心就是 kubeconfig 路径和 leader-elect:

[Service] ExecStart=/opt/kubernetes/bin/kube-controller-manager \ --kubeconfig=/opt/kubernetes/kubeconfig/kube-controller-manager.kubeconfig \ --leader-elect=true \ --service-account-private-key-file=/opt/kubernetes/pki/sa.key [Service] ExecStart=/opt/kubernetes/bin/kube-scheduler \ --kubeconfig=/opt/kubernetes/kubeconfig/kube-scheduler.kubeconfig \ --leader-elect=true

全部起来后,master2 的 apiserver 直接 curl 自检:

curl -k --cacert /opt/kubernetes/pki/ca.pem https://192.168.31.11:6443/healthz

返回 ok 就说明这一节点控制面基本通了。

3.5 回 master1 改配置并重启

master1 不能一直用旧的 apiserver 证书和单 etcd 地址。把新签好的 apiserver.pem、apiserver.key 替换进去,同时把 systemd 单元里的 --etcd-servers 改成两个地址,然后重启 kube-apiserver。

我建议先在 master2 全部起来之后再动 master1,这样重启 master1 的 apiserver 期间,至少还有一个 apiserver 撑着,不会出现整段窗口没人响应。如果这时候 LB 还没配好,kubectl 暂时还是走 master1 的直连地址,会有一两秒钟的连接闪断,等 LB 配好后就彻底没有这个窗口了。

4. 两台 LB 上 nginx 和 keepalived 的完整配置:VIP 是灵魂,健康检查是保命符

4.1 先分清楚 nginx 和 keepalived 各自的职责

这个架构里,LB 节点上跑两个软件,很多人搞混它们的边界:

  • nginx 只做数据面的四层转发:监听 VIP:16443,把请求轮询转发给 master1:6443 和 master2:6443。
  • keepalived 只做控制面的 VIP 管理:两台 LB 上分别配置 VRRP,谁优先级高谁持有 VIP 192.168.31.30。keepalived 检测到 nginx 进程挂了,就降自己的优先级,让另一台把 VIP 抢过去。

nginx 挂了,VIP 漂移;后端 apiserver 挂了,nginx 会通过 fail_timeout 把它临时摘除。这两层健康检查的目标不同,一个是保入口,一个是保后端,缺一不可。

4.2 nginx 四层转发配置

nginx 的 stream 模块需要确认编译进去了:

nginx -V 2>&1 | grep stream

如果没有,就从官方仓库装一个带 --with-stream 的版本,或者用 openresty 这类自带 stream 的分发包。

配置文件放在 /etc/nginx/nginx.conf 的顶层,注意 stream 块不能写在 http 块里面,这是新手最常见的 nginx -t 报错原因:

stream { upstream kube_apiserver { server 192.168.31.10:6443 max_fails=3 fail_timeout=10s; server 192.168.31.11:6443 max_fails=3 fail_timeout=10s; } server { listen 16443; proxy_pass kube_apiserver; proxy_connect_timeout 5s; proxy_timeout 30s; } }

max_fails=3 和 fail_timeout=10s 是给后端 apiserver 做被动健康检查的:10 秒内失败 3 次,nginx 就把这个后端临时摘掉,10 秒后再重新探测。开源版 nginx 的 stream 模块没有主动健康检查,这个配置在 apiserver 拒绝连接或进程崩溃的场景下够用。如果 apiserver 进程假死但端口还在,这层就测不出来,只能靠系统层重启或者监控及时发现。

4.3 keepalived 配置:两台 LB 上只差几个字段

LB1 是 MASTER,优先级 100;LB2 是 BACKUP,优先级 90。除了 router_id、priority、unicast_src_ip 不一样,其余配置要保持一致,尤其是 virtual_router_id 和 auth_pass,两台不一样会导致 VRRP 各自为政,出现两个 VIP 同时存在的脑裂。

LB1 上的配置:

global_defs { router_id LB_1 } vrrp_script check_nginx { script "/etc/keepalived/check_nginx.sh" interval 2 weight -20 } vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 1111 } unicast_src_ip 192.168.31.20 unicast_peer { 192.168.31.21 } virtual_ipaddress { 192.168.31.30/24 dev eth0 } track_script { check_nginx } }

LB2 上把 router_id 改成 LB_2,state 改成 BACKUP,priority 改成 90,unicast_src_ip 改成 192.168.31.21,unicast_peer 改成 192.168.31.20,其他不动。

我特意用了 unicast 而不是默认的 multicast。很多云环境和办公网会屏蔽 VRRP 组播包,两边的 keepalived 互相收不到通告,结果两台同时认为自己是 MASTER,VIP 就飘成了两个。用 unicast 后,VRRP 报文走单播,依赖的只是两台 LB 之间的 TCP/IP 连通性,兼容性好很多。

健康检查脚本 /etc/keepalived/check_nginx.sh 内容很简单:

#!/bin/bash if pgrep -x nginx >/dev/null 2>&1; then exit 0 else exit 1 fi

加上可执行权限:

chmod +x /etc/keepalived/check_nginx.sh

脚本失败时 keepalived 会把自己这台节点的优先级降 20。MASTER 的 100 降到 80,低于 BACKUP 的 90,VIP 自然就被 BACKUP 抢走。这种方式比脚本里直接systemctl stop keepalived温和,nginx 恢复后优先级还能自动回来,VIP 不需要人为干预就能回切。

4.4 启动顺序和防火墙注意事项

我习惯先启动 nginx,再启动 keepalived:

systemctl start nginx systemctl start keepalived

其实 keepalived 先启动也不会有大问题,它抢到 VIP 后如果 nginx 还没起来,流量就会断,所以干脆顺序固定。注意两台 LB 都要放行 16443 端口,VRRP 如果走 unicast 则不需要额外放行组播协议。

启动后在 LB1 上确认 VIP 是否掌握:

ip addr show | grep 192.168.31.30

在任意一台机器上测试:

curl -k https://192.168.31.30:16443/healthz

返回 ok 就说明 nginx 转发到 apiserver 这条链路是通的。

5. 全链路验证与故障切换实测:把 master1 拔网线之后才知道的事

5.1 先统一入口,再开始验证

LB 配好后,把 admin kubeconfig 也切到 VIP,worker 节点上的 kubelet、kube-proxy 同样要切。kubelet 的 kubeconfig 通常写在 /etc/kubernetes/kubelet.kubeconfig 或 systemd 单元的 --kubeconfig 参数里,把 server 从 https://192.168.31.10:6443 改成 https://192.168.31.30:16443,然后重启 kubelet 和 kube-proxy。

使用 kubectl 的机器把 ~/.kube/config 也改成 VIP:

sed -i 's#https://192.168.31.10:6443#https://192.168.31.30:16443#' ~/.kube/config

全部切完之后执行一轮基础验证,我按下面这张表挨个过:

验证项命令预期结果
节点状态kubectl get nodesmaster1、master2、worker1 都是 Ready
etcd 集群etcdctl member list两个成员,healthy
LB 入口curl -k https://192.168.31.30:16443/healthzok
pod 运行kubectl get pods -A所有组件 Running
工作负载调度kubectl run demo --image=nginx:alpine能创建成功

5.2 场景一:master1 只挂 apiserver,集群不感知

我在 master1 上执行systemctl stop kube-apiserver,模拟控制面单点故障。这个时候大量请求会走向 VIP,nginx 会在连接失败后把请求转发到 master2 的 apiserver。

实测下来kubectl get nodes能正常返回,创建 Pod 也正常。原因有两层:nginx 摘除了 master1 这个后端;etcd1 和 etcd2 都活着,etcd 依然满足两节点的 quorum。只要 etcd 有仲裁,任何一台 apiserver 都能提供完整的读写服务。

这个场景是这台二 master 集群能兜住的主要故障类型。

5.3 场景二:LB1 挂掉,VIP 自动漂移到 LB2

我在 LB1 上执行systemctl stop keepalived,观察 VIP 变化。2 到 3 秒后,192.168.31.30 从 LB1 消失,出现在 LB2 上。这个动作里 nginx 并没有重启,keepalived 只是把入口换了一个宿主,流量的数据面依然是 nginx 转发到后端两台 apiserver。

反过来测试更值得做:在 LB2 持有 VIP 的情况下,到 LB1 启动 nginx 和 keepalived,观察 VIP 是否自动回切。默认配置下 MASTER 优先级恢复后会自动抢回 VIP,这个回切过程对客户端来说是无感的,TCP 连接建立走新入口后开始正常转发。

有一类坑值得提:如果两台 LB 上的 virtual_router_id 不一致,VIP 会同时出现在两台机器上,客户端请求会被随机分流,表现就是时通时不通。遇到这种问题先别急着看 nginx 日志,先确认两台 keepalived 的 virtual_router_id 和 auth_pass 完全一致。

5.4 场景三:master1 整机断电,二 master 结构的边界在哪

这是我最想强调的部分。如果 master1 整机关机,而不是只停 apiserver,那么 etcd1 也没了。当前 etcd 集群只有两个成员,quorum 需要两个成员都在,只剩 etcd2 时无法达成仲裁,etcd 会拒绝写请求。

此时从 LB 访问 apiserver 仍然能建立 TLS 连接,/healthz也可能返回 ok,因为 apiserver 进程还活着,但它内部访问 etcd 时写操作会超时。表现就是 kubectl get nodes 偶尔能出来,一执行 kubectl apply 或者删除 Pod 就卡住,最后报 etcd server: request timed out。

这不是部署失败,而是二控制面加二 etcd 的结构性约束。如果你只有两台机器,还有个妥协办法是把 etcd 也放到两台 LB 上,凑成四节点 etcd 集群,仲裁需要三台,这样任意一台控制面或 LB 单点故障都不影响 etcd。代价是 LB 节点上既要跑 nginx/keepalived,又要跑 etcd,角色混部,出问题时排查面会变大。生产环境更稳妥的选择还是三控制面节点,etcd 在三台或五台上分布,再做前置 LB。

5.5 验证后别忘了回切测试

故障演练不是演练完就算完,还要把服务恢复到正常状态。master1 恢复后,etcd1 会自动重新加入集群,kube-apiserver 重新注册到 nginx 后端,kubectl get nodes 会看到 master1 重新 Ready。

我的习惯是恢复后再做一轮同样的基础验证,确认没有遗留的异常。尤其是检查 etcd member list,防止 etcd2 起来时因为网络抖动被误判为异常节点。

6. 最后分享几个我踩过之后才记住的细节

第一个是版本一致性。两个 master、两个 LB 上的 kubectl、etcd、kubelet 版本尽量保持完全一致。别小看这个,etcd 版本不一致会导致 member 之间协议行为不一致,kubelet 版本过旧可能无法和新的 apiserver 正常协商。

第二个是 master 上的 kubelet 也要改配置指向 VIP。很多教程只改 worker 的 kubelet,忘了 master 本身也跑了 kubelet,结果 master 节点状态不稳定,Pod 调度过去之后 kubelet 连不上 apiserver,节点反复 NotReady。

第三个是 keepalived 的日志。如果 VIP 没有按预期漂移,先看 /var/log/messages 或者 journalctl -u keepalived。VRRP 状态切换、脚本检测失败、auth 错误都会在这里留下明确记录,比瞎猜配置快得多。

第四个是证书更新的连锁影响。替换 apiserver 证书后,所有和它通信的组件要重新加载信任关系。controller-manager、scheduler 这些进程通常要重启一次才能读新的证书文件。kubelet 也可能因为 apiserver 证书变化导致 TLS 校验报错,重启 kubelet 一般能解决。

这套架构从单 master 演进到双 master 加双 LB 之后,日常运维里最明显的感受是:重启一台 master 不再需要挑凌晨窗口了。LB 会自动把流量分走,Keepalived 会把入口保住,剩下的就是观察 etcd 是否还有 quorum。但我也得再强调一次,两节点 etcd 只能容忍一台 apiserver 挂掉,不能容忍一台机器整体断电。真正要免维护窗口,还是尽早往三控制面节点走。

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

用Python实现PCAN报文自动发送:从初始化到周期循环的完整方案

简介:该资源是一套基于Python3.7的PCAN自动发送CAN报文程序,面向汽车电子、嵌入式开发及CAN总线测试人员,用于解决使用PEAK PCAN-USB适配器时上位机周期性发送多帧CAN报文的自动化需求。压缩包共5个文件,包括2个Python脚本、2个py…

作者头像 李华
网站建设 2026/10/19 3:16:35

大模型工程化全流程开发指南:从需求评估到持续迭代

一提到“大模型开发”,很多人第一反应是“把训练脚本跑通”。但真正深入业务后你会发现,训练脚本只是万里长征第一步。过去一年我带完整参与了好几个大模型落地项目,从需求分析、数据整理、微调训练、推理部署到线上监控,整个链路…

作者头像 李华
网站建设 2026/10/19 3:16:18

边缘AI推理工程实战:约束平衡与模型量化部署全攻略

边缘AI推理这几年几乎成了行业标配:园区装一套视频分析盒子,工厂加一条质检线,商超放一台客流统计终端,甚至连充电桩都要跑个烟火识别模型。但真正动手做过的人都知道,把一个模型放进边缘设备并让它稳定跑上半年&#…

作者头像 李华
网站建设 2026/10/19 3:16:14

删除链表倒数第N个节点:双指针与虚拟头节点详解

1. 题目本质与适用场景“删除链表倒数第N个节点”这道题,基本是链表入门阶段绕不开的一道坎。它的题目描述很简短:给定一个单链表,删除倒数第N个节点,并返回链表的头节点。看起来只有一句话,但真正动起手来&#xff0c…

作者头像 李华
网站建设 2026/10/19 3:16:06

STM32L0x1低功耗ADC设计:时钟、校准与采样避坑指南

简介:低功耗 STM32L0x1 ADC 示例程序包,面向基于 ARM Cortex-M0 超低功耗 MCU 的模拟量采集场景,适合电池供电的物联网节点、传感器数据采集项目,也适合想掌握低功耗外设配置的嵌入式开发者。压缩包约 4.6MB,共 178 个…

作者头像 李华
网站建设 2026/10/19 3:16:02

从协议到生产:企业级MCP服务实战指南

最近大模型应用圈里最热的话题,莫过于 Model Context Protocol,也就是常说的 MCP。很多人把它当成又一个新框架来学,这没错,但如果你真想把 MCP 服务从本地 Demo 变成能扛住生产流量的企业级服务,里面要补的东西比想象…

作者头像 李华