1. 架构设计与方案选型
1.1 为什么K8s前面必须有一层高可用负载均衡
很多刚接触Kubernetes的工程师会有一个直观疑问:kube-apiserver本身是多个节点组成的集群,为什么客户端(kubectl、kubelet、调度器)不能直接连多个apiserver?
答案很简单:kubeconfig里只能填一个地址。或者说,虽然你可以填多个server地址,但Kubernetes官方的client-go至今没有内置原生的多apiserver failover逻辑。实际生产中,控制平面节点要么用Nginx四层转发,要么用云厂商的SLB,要么自建负载均衡层,把多个apiserver统一收口成一个入口。
但自建这一层的时候,很多人会犯一个致命错误:只部署一台Nginx或者HAProxy,然后它就变成了整个集群的单点。apiserver做成了高可用,入口反而是单点,这等于把鸡蛋全部放进了一个篮子里。我就见过不止一个团队,k8s集群三个master节点、三个etcd节点,前面却只有一个nginx在转发6443,节点宕机后整个集群对外完全不可用。
所以这里聊的方案是:haproxy + pacemaker组合,搭建一层具备故障自愈能力的负载均衡入口。haproxy负责真正的四层流量分发,pacemaker负责守护haproxy进程和虚拟IP(VIP),当某个节点宕机、haproxy进程崩溃或者网络异常时,VIP会自动漂移到存活节点,kubectl连接的地址始终不变。
这个组合的优势在于:组件全部开源、不依赖云厂商、不引入额外的商业License、运维逻辑清晰。相比keepalived方案,pacemaker能做更细粒度的资源监控和约束控制;相比云SLB,它不限制在特定云平台上,私有化和混合云环境都能用。
1.2 haproxy + pacemaker 和主流备选方案的取舍
先说说为什么选haproxy而不是Nginx。
K8s的apiserver流量对负载均衡层的要求非常明确:L4 TCP转发、低延迟、高吞吐、连接数大。haproxy本身就是做四层起家的,配置模型对TCP代理场景非常友好,健康检查支持TCP端口探测、HTTP探测、脚本探测等多达十余种方式。而Nginx虽然也支持stream模块转发TCP,但在高并发长连接场景下,haproxy的事件驱动模型和内存管理机制表现更稳定,而且在haproxy里配置多个backend、做流量权重调整、查看实时连接状态,都是开箱即用的功能。
再说说pacemaker和keepalived的区别。
keepalived的核心能力是VRRP虚IP漂移,它的配置模型围绕IP漂移展开,最多再做点简单的脚本检测。pacemaker是个完整的集群资源管理器(CRM),它不仅能管VIP,还能管服务进程、文件系统挂载、IP地址之外的第三方资源代理。pacemaker对资源状态的判断更严谨,它会通过corosync组播持续探查节点存活状态,配合fencing机制避免脑裂。
实际选型时,我用一个标准来判断:我的负载均衡层需要管几个东西?
- 只需要一个VIP漂移?keepalived就够。
- 需要管VIP + haproxy进程 + 健康检查 + 资源启动顺序 + 同节点约束?pacemaker明显更顺手。
在生产环境里,haproxy进程意外退出、节点网卡闪断、corosync通信异常,这些故障场景keepalived处理起来都很吃力,而pacemaker可以把每一个资源都纳入监控,任何一项异常都会触发切换动作,这是它最大的价值。
2. 环境规划与基础组件安装
2.1 节点角色与网络规划
先规划一套最小高可用环境,后面所有配置都按这套环境展开。
这套环境我通常会放在"高可用前置层"和K8s集群之间,可以是独立的三台机器,也可以和master节点复用(生产环境建议独立部署,避免资源争抢)。
| 角色 | 主机名 | IP地址 | 说明 |
|---|---|---|---|
| LB-1 | lb01 | 192.168.1.21 | 主用负载均衡节点 |
| LB-2 | lb02 | 192.168.1.22 | 备用负载均衡节点 |
| LB-3 | lb03 | 192.168.1.23 | 备用负载均衡节点 |
| VIP | - | 192.168.1.20 | 虚拟IP,对外统一入口 |
| Master-1 | master01 | 192.168.1.11 | kube-apiserver |
| Master-2 | master02 | 192.168.1.12 | kube-apiserver |
| Master-3 | master03 | 192.168.1.13 | kube-apiserver |
这里有个网络细节要注意:VIP必须和集群节点位于同一网段,否则基于ARP的IP漂移机制无法生效。如果一定要跨网段使用VIP,那就得依赖路由器做ARP代理或使用BGP方案,复杂度就上去了。生产环境大多数场景下,LB节点和VIP放在同一个二层网络里就够用。
操作系统选择上,我用的是CentOS 7.9或者Rocky Linux 8.x这类RHEL系发行版。如果你用Ubuntu,软件包名和systemd服务名会有一点差异,但整体配置逻辑完全一致。三台LB节点的内核版本、系统版本最好完全一致,不然corosync在协议协商上偶尔会出一些奇怪的问题。
2.2 基础环境配置与组件安装
环境准备阶段有几件事必须提前处理好,否则后面集群起不来或者莫名其妙抖动:
第一件事是主机名解析。三台LB节点必须在 /etc/hosts 里互相写全hostname解析,不能依赖DNS。corosync在启动阶段会做节点名称和IP的对应解析,如果解析失败,节点会一直处于offline状态。
192.168.1.21 lb01 192.168.1.22 lb02 192.168.1.23 lb03第二件事是防火墙和SELinux。corosync要使用UDP 5404和5405端口做组播通信,haproxy要监听6443端口,监控工具可能需要访问2224端口(pcsd)。如果不想在生产环境排查一个下午的"节点为什么总是ring错误",建议提前把这些端口放行,或者直接关闭防火墙。SELinux如果开启了,haproxy绑定非标准端口和pacemaker执行资源脚本都会受到限制,我建议在实验环境直接setenforce 0,生产环境按安全基线放行具体策略。
第三件事是时间同步。corosync对节点间时间偏差比较敏感,偏差过大会导致心跳超时和选举异常。用chrony或者ntpd把三台节点同步到同一时间源。
组件安装直接使用yum仓库:
# RHEL/CentOS系 yum install -y haproxy pacemaker pcs fence-agents-all resource-agents corosync # Ubuntu/Debian系 # apt install -y haproxy pacemaker pcs fence-agents resource-agents corosync这里我把 pcs 单独列出来,因为它是pacemaker的命令行管理工具,早期版本用crm命令管理,新版本(RHEL 7+)默认用pcs。下面所有配置操作我都用pcs完成。
3. haproxy核心配置与参数细节
3.1 四层转发配置与健康检查解析
haproxy的功能定位非常纯粹:把访问VIP:6443的TCP流量转发到后端的三个kube-apiserver。所以配置文件不需要七层http模式,不需要ssl卸载,不需要acl路由,全是backend server的IP:Port列表。
我的核心配置如下:
global log /dev/log local0 log /dev/log local1 notice chroot /var/lib/haproxy stats socket /var/lib/haproxy/haproxy.sock mode 600 level admin user haproxy group haproxy daemon nbproc 1 defaults log global mode tcp option tcplog option dontlognull retries 3 timeout connect 5000ms timeout client 50000ms timeout server 50000ms listen k8s-api bind 0.0.0.0:6443 mode tcp balance roundrobin option tcp-check server master01 192.168.1.11:6443 check inter 3s fall 2 rise 2 server master02 192.168.1.12:6443 check inter 3s fall 2 rise 2 server master03 192.168.1.13:6443 check inter 3s fall 2 rise 2逐个参数说下我的考虑:
mode tcp:K8s的apiserver默认是TLS加密通信,在这种场景下haproxy只能用四层模式,直接透传TCP流量,不做TLS终止也做不了,因为证书握手需要完整TLS上下文。
option tcp-check:这是四层健康检查的关键。haproxy会和后端apiserver建立TCP连接,连接成功就认为节点健康。apiserver的6443端口有TLS监听,但不影响TCP三次握手完成。这里我刻意没有用更复杂的tcp-check send/expect字符串,因为apiserver的TLS握手响应内容因证书配置不同有差异,用简单的connect检查最稳。
check inter 3s fall 2 rise 2:每3秒检查一次,连续失败2次标记为down,连续成功2次标记为up。生产环境这个参数需要根据实际网络质量微调。如果内网环境比较稳定,可以把fall调成3,避免偶发抖动导致后端被摘除;如果机器负载经常很高,检查间隔可以延长到5秒。
timeout client/server 50000ms:在实际生产里,apiserver的请求偶尔会持续较长时间,例如大量Pod删除时watch请求长时间保持。50秒超时不能设置得太短,否则长连接被haproxy强行断开,客户端会体验到"avaliable"变成"unavailable"的诡异闪断。如果你发现kubectl经常报connection reset,先看看haproxy的超时时间是不是不够。
3.2 配置校验与平滑重载实践
haproxy配置文件的语法校验用一个命令搞定:
haproxy -c -f /etc/haproxy/haproxy.cfg这个命令只做语法解析,不做实际启动,非常适合改完配置后先校验再重载。校验通过后用systemctl重载服务:
systemctl reload haproxy注意这里用的是reload而不是restart。haproxy支持平滑重载:旧进程继续处理已有连接,新进程接管新连接,连接不会中断。但这要求haproxy以daemon模式运行并且正确配置了socket,否则reload可能会失败。
实测下来,reload过程中kubectl那边的长连接几乎感知不到变化。如果是restart,TCP连接会全部重置,客户端需要重连,体验就会差很多。所以配置变更走reload是底线操作。
4. pacemaker集群资源编排实战
4.1 corosync集群创建
pacemaker依赖corosync做底层通信和成员管理,二者配合的集群搭建逻辑是这样的:corosync解决"哪些节点是活的"这个问题,pacemaker在这个基础上决策"资源应该跑在哪"。
先启动pcsd并设置hacluster用户密码:
systemctl enable --now pcsd passwd hacluster # 设置统一密码,三台节点保持相同然后在一台节点上执行集群认证:
pcs host auth lb01 lb02 lb03 -u hacluster -p 你的密码接着创建集群。走到这一步有个重要选项:transport。远古版本用组播,现代环境更推荐单播。组播在部分云网络环境会被禁用,单播更稳。
pcs cluster setup k8s-lb lb01 lb02 lb03 transport udp这里我选择了UDP单播传输。如果你所在环境网络质量一般,也可以用transport knet,这是corosync 3.x之后新的网络内核,支持加密和自动链路切换。实测中knet在丢包环境中更稳,但配置稍微复杂一丢丢。
启动集群:
pcs cluster start --all pcs cluster enable --all启动后查看状态:
pcs status正常情况下三台节点都应该是Online状态。如果某个节点一直显示Offline,第一件事就是检查corosync日志/var/log/cluster/corosync.log,重点看有没有Link send error、Ring 0相关报错。
4.2 高可用资源定义与约束
资源编排是pacemaker的核心魅力所在。我们一共要创建三类资源:
- VIP资源(IPaddr2)
- haproxy服务资源(systemd: haproxy)
- 对这两个资源的约束关系
创建VIP资源:
pcs resource create lb-vip ocf:heartbeat:IPaddr2 \ ip=192.168.1.20 cidr_netmask=24 nic=eth0 \ op monitor interval=5sIPaddr2是pacemaker自带的资源代理,负责IP地址的增删和ARP广播。op monitor interval=5s表示每5秒检测一次VIP是否正常。这里有个细节:nic参数建议显式指定,如果默认网卡名在部分发行版里是ens192之类的,不指定会导致VIP绑定到错误的接口上。
创建haproxy服务资源:
pcs resource create lb-haproxy systemd:haproxy \ op monitor interval=5ssystemd:haproxy是pacemaker的systemd资源代理,它做的事情就是跟随集群状态启动/停止haproxy服务,并且周期性确认haproxy进程是否存活。如果haproxy进程崩溃,monitor会在5秒内发现异常并在本节点重启haproxy。
这里要停下来解释一个很多人踩过的坑:为什么不直接systemd enable haproxy让它开机自启?
因为如果haproxy在系统启动时被systemd拉起了,而pacemaker以为这个资源不在集群控制范围内,启动顺序和资源状态就会错乱,出现"VIP在A节点、haproxy在B节点"的尴尬局面。正确做法是把haproxy的systemd自启禁止掉,完全交给pacemaker来管理:
systemctl disable haproxy --now4.3 VIP和haproxy的联动
资源创建完了还不行,必须加约束,否则pacemaker不知道这两个资源该怎么配合。我用两条约束搞定:
第一条是排列约束(colocation),要求VIP和haproxy必须运行在同一台节点上:
pcs constraint colocation add lb-vip with lb-haproxy INFINITY第二条是顺序约束(order),要求haproxy先启动,VIP后启动:
pcs constraint order start lb-haproxy then start lb-vip顺序约束为什么是haproxy先启动而不是反过来?因为haproxy监听的地址是0.0.0.0:6443,它不需要VIP处于up状态也能正常监听。反过来,如果VIP先绑定了,haproxy还没起来,中间会有一段流量黑洞时间,kubectl连过来会直接拒绝连接。先启动haproxy再绑定VIP,流量入口真正对外提供服务时,后端已经是就绪状态了。
约束配置完成后,整体资源状态可以用pcs status查看。理想状态是:三台节点上,有一台同时运行着lb-vip和lb-haproxy,另外两台节点不运行任何资源,但成员状态是Online。当主节点宕机时,这两台备用节点中的一个会接管VIP并拉起haproxy服务。
5. 故障切换验证与流量路径复盘
5.1 手动故障演练步骤
资源编排完成以后,最重要的不是看pcs status是不是全绿色,而是做故障演练,确认切换动作真的符合预期。
我每周例行的故障演练流程是这样的:
场景一:优雅切主
pcs resource relocate lb-haproxy lb02这条命令会把这个资源从当前节点"请走"到指定节点。执行后观察资源状态:
pcs status预期结果:lb-vip和lb-haproxy同时出现在lb02上,lb01上的VIP被释放。整个过程中,用kubectl连续执行命令,应该不会感知到任何中断。
场景二:模拟宕机
在lb01上执行systemctl stop corosync,模拟节点异常退出。
预期结果:集群在心跳超时后(默认约10-30秒),lb02或lb03接管资源。这个场景最有价值,因为它能暴露出很多潜在问题:比如STONITH配置不当导致节点无法被fence、corosync通信异常导致分裂、资源代理脚本在cgroup环境下启动失败等。
场景三:进程级故障
pkill -9 haproxy预期结果:pacemaker在5秒内检测到haproxy进程消失,自动重新拉起。VIP不受影响,因为haproxy还在原来的节点上重新启动。
5.2 切换过程时序与调优
pacemaker切换的过程其实是一个"选举 + 资源迁移"的组合动作,大致时序如下:
- corosync检测到节点A心跳丢失,更新成员状态
- pacemaker的DC节点(Designated Controller)评估资源状态
- DC发现资源依赖的节点失效,触发资源迁移决策
- 在存活节点上执行资源启动(先haproxy,后VIP)
- 如果配置了fencing,先隔离故障节点再启动资源
这里最容易影响RTO(恢复时间目标)的是第1步和第2步。corosync的token超时时间默认是3000ms,也就是说节点故障后最多约3秒时间确认死亡。如果网络抖动频繁,容易误判。生产环境我会把token调到5000ms左右,pcs cluster setup时加参数:
pcs cluster setup k8s-lb lb01 lb02 lb03 transport udp token=5000不要把token调得太激进。我之前在一个跨机房的场景里把token调成1000ms,结果每半天误切换一次,负载均衡节点频繁漂移,反而比单点故障影响更大。
6. 常见问题与避坑指南
6.1 配置与排障高频问题
把我在实际维护中遇到的问题整理成一张速查表,不一定全,但覆盖了90%的高发场景:
| 问题现象 | 排查思路 | 解决建议 |
|---|---|---|
| 节点状态Offline | 检查corosync日志、UDP 5404/5405端口、防火墙 | 确认/etc/hosts解析一致,协议栈版本一致 |
| VIP绑不到指定网卡 | 检查nic参数 | 显式指定网卡名,如eth0/ens192 |
| haproxy起不来 | journalctl看systemd日志 | 检查SELinux策略、配置文件语法、6443端口占用 |
| kubectl间歇性断连 | 检查haproxy超时配置 | 调整timeout client/server为50s以上 |
| 资源漂移后VIP仍在原节点 | 检查是否残留地址未清空 | 手动ip addr del清楚后再让pacemaker接管 |
| 节点网络分区导致集群分裂 | 确认corosync是否配了多链路 | 生产环境建议双网卡冗余 |
有一个场景值得单独提醒:多路径存储和VIP的冲突。如果你的环境里用了multipath,它可能会在系统启动时检查VIP地址是否存在,导致VIP绑定时出现"地址已存在"的报错。我遇到过两次,解决方法是给multipath加黑名单或者调整VIP的启动资源顺序,让IPaddr2先执行清理。
6.2 运维心法总结
最后分享几个日常维护中验证过的心得,这些不在官方文档的突出位置,但非常影响实际体验:
第一,永远不要让haproxy和VIP资源散落在不同章节管理。pacemaker的约束一旦建好,就不要手动在节点上执行systemctl start haproxy之类操作。一旦手动拉起过haproxy,pacemarker的资源状态缓存可能就乱了,表现为资源一直显示stop但实际进程在跑,后续漂移会出妖蛾子。
第二,pcs status不等于一切正常。pcs status只能反映pacemaker视角的资源状态,haproxy配置写错了但进程能启动、后端server全部标记down,这些它都看不出来。我的习惯是配合haproxy的socket命令定期检查后端节点健康状态:
echo "show stat" | socat stdio /var/lib/haproxy/haproxy.sock如果master01的status是DOWN,说明haproxy已经探到apiserver有异常,虽然VIP还是漂在主节点,但实际流量被摘除了一部分。
第三,版本升级一定先在一台节点上试。pacemaker的大版本升级常常会动资源代理的写法,比如RHEL 7的pcs命令到RHEL 8/9就有不少差异。升级之前先在测试环境跑一遍pcs resource config导出配置,和升级后的语法做对比。生产环境我见过因为升级导致ocf:heartbeat:IPaddr2参数解析失败的案例,影响面是整个集群断连。
第四,监控就要监控到链路。负载均衡层的监控不能只看进程在不在,要看VIP到后端的整条链路通不通。我建议在外部监控点定期探测VIP的6443端口,同时用haproxy的统计接口拿到每个backeend的队列深度。当某个apiserver的请求排队超过阈值时,即使haproxy还在正常工作,也要触发告警,因为后端性能已经出问题了。
第五,清空集群要走pcs cluster stop --all再逐个节点清理。很多运维图省事直接reboot节点,如果corosync的ring信息没清理干净,重启后可能会出现"旧节点身份残留",导致新节点加入失败。用pcs cluster stop --all带上--force参数可以让节点彻底退出集群,再重新加入时干净很多。