Kubernetes集群好不容易搭好了,Master高可用、etcd堆叠、WorkNode都就绪了,结果发现一个尴尬的问题:客户端、kubelet、kubectl这些流量入口到底走哪里?直接把其中一个Master的IP写死,等于把高可用废了一半;用NodePort暴露,节点一挂端口就断。这个时候,很多自建K8s环境的运维都会走到同一条路上:在集群前面再架一层独立的负载均衡入口,而HAProxy+Pacemaker这套组合,就是我现在最常用、也最愿意推荐的自建方案。
这篇文章不讲虚的,直接以"两台LB节点扛VIP + HAProxy做四层转发 + Pacemaker管资源漂移"为骨架,把从环境规划、配置细节、资源编排到故障演练、生产避坑的完整链路捋一遍。适合正在自建生产级K8s、或者刚入坑K8s网络方案、对高可用负载均衡层搭建还没形成完整思路的朋友。如果你打算用云厂商的SLB,那这篇你可以跳过了——但如果你是机房自建、离线环境、或想彻底搞懂K8s前置流量入口的原理,这篇值得先收藏。
1. 为什么K8s集群门前还要再立一道"独立负载均衡墙"
1.1 先把流量入口的三种常规方案盘一遍
先说清楚一个常见的认知盲区:K8s集群外部流量要进来,不是只有Service这一层的事。我们把"外部流量到达Pod"这条路拆开看,通常有四种方式:
| 方案 | 暴露方式 | 典型问题 |
|---|---|---|
| NodePort | 每个节点开30000-32767端口 | 客户端必须知道节点IP,节点挂了一个端口就废 |
| LoadBalancer | 依赖云厂商LB插件 | 自建机房没有现成的LB可用 |
| Ingress | Ingress Controller本身还是Pod | 它自己也得先暴露出来,还是有同样的问题 |
| 外部LB + NodePort/Ingress | 前面挂独立负载均衡 | 如果LB本身是单点,整个集群入口就断 |
在自建机房里,Ingress Controller通常是最常用的七层入口,但所有Ingress Controller(Nginx、Traefik这些)本质都是Pod,它们自己需要暴露给集群外部的流量。很多文档会告诉你"用NodePort暴露Ingress Controller",但这只是把问题从"Pod暴露"变成了"端口暴露",节点的单点故障依然存在。换句话说,只要集群前面没有一个独立的、自带高可用的流量分发层,K8s集群内部再高可用,对外也是一个"看起来挺稳、其实入口随时会断"的状态。
1.2 为什么这个负载均衡墙必须独立于K8s本身
这里有一个很容易踩的思维陷阱:既然K8s集群里有那么多Controller,为什么不直接让一个Controller来干这活?
关键在于一个设计原则:负载均衡层不能依赖它所要保护的系统本身。如果前面的LB用的是K8s集群内的某个Pod或Service,那当K8s集群出现节点故障、网络分区、etcd不稳定时,LB自己也会跟着挂掉,这就是典型的"用自己保护自己"。独立于集群之外的LB层,只要两台物理机或者两个轻量VM不一起宕机,它就能在集群半死不活的时候继续把流量分发到还活着的Master上。
这就是为什么K8s官方文档在配置高可用集群时,反复提到需要"外部负载均衡器",而且这个外部LB通常要指向Master节点API Server的6443端口。既然是"外部",那自建环境下,就得靠我们自己搭。
1.3 负载均衡器选型:HAProxy为什么比Nginx更顺手
既然要在前面架一道墙,用什么架?Nginx和HAProxy是最常见的两个答案。Nginx本身也能做四层负载(stream模块),但我在实际项目中经历几次之后就放弃了用Nginx做四层入口的做法,原因很直接:
第一,HAProxy本身就是为负载均衡而生的,它的配置模型非常清晰——frontend定义入口、backend定义后端服务器池、listen是两者的合并体。这种模型在处理大量后端节点、频繁调整后端状态时非常顺手。Nginx的stream模块配置也没有问题,但它的强项是HTTP七层处理,四层场景下透明代理、健康检查、会话保持这些能力,HAProxy明显更成熟。
第二,HAProxy对健康检查的支持更细。四层模式的负载均衡,看起来简单,就是转发TCP流,但生产环境里你会发现,后端Master节点"端口还开着"和"API Server真的能正常服务"是两回事。HAProxy既支持纯TCP端口探测,也支持通过tcp-check发送HTTP请求检查/healthz,更精细地判断后端是否健康。这一点在K8s API Server场景下尤其重要。
第三,性能上,HAProxy对长连接、高并发的处理非常稳,短连接转发、TLS透传这些场景都经过大量生产验证。Nginx当然也能扛,但在同一个项目里,我不太愿意让一台机器同时承担"K8s API入口"和"业务七层Ingress"这两种职责,前者用HAProxy,后者用Nginx,分工明确,排查问题也方便。
2. HAProxy与Pacemaker的分工逻辑:一个管流量,一个管漂移
2.1 单台HAProxy本身就是新的单点
很多人在第一步就停下来了:在K8s Master前面放一台HAProxy,把API Server地址告诉所有节点和客户端,问题似乎解决了。是的,流量有入口了,如果这台HAProxy挂了怎么办?
这就引出了一个新的单点。所以必须给HAProxy做高可用,让前端有一个VIP(虚拟IP),这个VIP会绑在两个节点中的一个上,哪个节点上的HAProxy活着,VIP就在哪。当这个节点宕机,VIP自动漂移到另一个节点。这不是"可选项",这是自建LB层的基本操作。
2.2 Keepalived很简单,为什么还要用Pacemaker
给VIP做漂移,大部分人第一反应是Keepalived——轻量、简单、配置一个VRRP实例就完事。说实话,如果只是"让VIP能在两台机器之间飘",Keepalived完全够用。我之所以最终选择Pacemaker,是因为它解决的问题比VIP漂移多得多:
第一,Pacemaker是真正的集群资源管理器(CRM),它能管理的不只是IP,还有服务进程本身。用Keepalived时,哪怕你不写脚本,它默认也只会判断节点是否存活;要检查HAProxy进程是否活着,你得自己写脚本检测然后触发切换。而Pacemaker可以注册两个资源:一个VIP资源(ocf:heartbeat:IPaddr2),一个HAProxy服务资源(systemd:haproxy),再把这两个资源绑定成一个组。资源管理器会自己监控服务状态:HAProxy进程异常退出,它自动拉起;拉不起来,它自动切换节点,顺便把VIP也带过去。
第二,Pacemaker有完善的约束机制。你可以明确告诉集群:"VIP必须先起来,HAProxy后起来""这两个资源必须在同一个节点上"。Keepalived要实现"IP和服务一起漂移",全靠自写脚本,写多了就知道维护成本有多高。Pacemaker把这些约束原生支持了,用起来反而直觉。
第三,集群扩展能力。这套Pacemaker除了给K8s做LB,以后如果还想在上面托管PostgreSQL、Redis、NFS服务,都是直接加资源就行。Keepalived基本就是"管VIP,其他靠自己"。
2.3 Pacemaker的核心概念:VIP、资源代理、约束、quorum
如果你没用过Pacemaker,先花两分钟扫一下这几个概念,后面命令就不会看得一头雾水:
- VIP(虚拟IP):对外提供固定服务的IP。客户端只和VIP通信,实际绑在哪个节点不重要。
- 资源代理(Resource Agent,RA):Pacemaker不直接理解"IP地址"或"HAProxy进程",它通过资源代理脚本来控制。
ocf:heartbeat:IPaddr2管IP地址的添加/删除,systemd:haproxy管systemd服务的启动/停止/状态检查。 - 资源组(Resource Group):把多个资源捆在一起。组内资源按配置顺序依次启动,并自动绑定在同一节点上,一旦漂移就整体漂移。
- 约束(Constraint):包括location(资源倾向跑在哪个节点)、colocation(资源之间是否要同节点)、order(资源之间的启停顺序)。用资源组可以简化掉后两者的显式配置。
- quorum(法定人数):集群节点超过半数是集群正常工作的前提。两节点集群少一个节点就只剩50%,这是Pacemaker最尴尬的点,后面避坑章节会细说。
这里顺便说明一个很多人误解的点:Pacemaker本身不是一个服务,它是一组组件的统称,包括pacemakerd、corosync、fencing等模块。corosync负责节点间的心跳、通信和投票,pacemaker负责决策"谁该在哪个节点上跑什么资源"。两者一起装,一起配。
3. 从空机器到双节点高可用入口:安装、配置、资源编排全过程
3.1 环境规划:这台LB层要放哪些角色
下面以一套典型自建K8s高可用集群为例:三台Master节点、若干Worker节点、两台专用的LB节点。所有节点操作系统我这里用的是Rocky Linux系,CentOS 7/8的步骤几乎一样,Debian系的包管理略有差异但原理一致。
| 角色 | 主机名 | IP |
|---|---|---|
| LB节点1 | k8s-lb-01 | 192.168.10.100 |
| LB节点2 | k8s-lb-02 | 192.168.10.101 |
| Master节点1 | k8s-master-01 | 192.168.10.11 |
| Master节点2 | k8s-master-02 | 192.168.10.12 |
| Master节点3 | k8s-master-03 | 192.168.10.13 |
| VIP | - | 192.168.10.199 |
这里有个你可以直接抄的规划:VIP我习惯不和任何业务网段的DHCP池混用,固定选一个空闲地址,避免IP被自动分配导致冲突。两个LB节点最低配置2核4G就够,它们在K8s集群里的角色很纯粹——只转发流量,不跑业务Pod、不装kubelet。
3.2 HAProxy安装与配置细节
两台LB节点都要装HAProxy。装好之后,不要急着起服务,先把配置写完整:
sudo dnf install -y haproxyHAProxy的配置核心就是frontend + backend。下面是一份可以直接套用、经过生产验证的配置:
global maxconn 4096 log 127.0.0.1 local0 info # 不配置chroot,Pacemaker的systemd资源代理在启停时更顺畅 defaults mode tcp timeout connect 5s timeout client 120s timeout server 120s frontend k8s_api_frontend bind *:6443 default_backend k8s_api_backend backend k8s_api_backend balance roundrobin option tcp-check default-server inter 3s fall 3 rise 2 server master-01 192.168.10.11:6443 check server master-02 192.168.10.12:6443 check server master-03 192.168.10.13:6443 check重点解释几个细节:
- mode tcp:我们是四层转发,不要把Haproxy放在七层去解密K8s API的TLS证书,直接透传TCP流就够了。
- balance roundrobin:K8s API Server的请求大多是短连接(kubectl命令)、长Keep-Alive(组件与API Server的长轮询)混合的,roundrobin在四层场景下能很好的均匀分布。
- option tcp-check + check:每3秒探测一次后端6443端口,连续3次失败标记为DOWN,连续2次成功标记为UP。端口探测简单可靠。
- 为什么不用httpchk?K8s API Server的6443是HTTPS端口,如果要做HTTP级别的健康检查,需要额外配置TLS检查,而四层负载场景下,API Server如果进程死掉端口自然就断了;真正可怕的是"端口还在但API响应异常"的极端情况,我在生产里遇到过,所以在进阶方案里可以给HAProxy加上
check-ssl和httpchk请求探活,但作为基础入口,tcp-check已经够用。
配置写好后,先手动验证一下语法,暂时不要急着注册到Pacemaker:
haproxy -c -f /etc/haproxy/haproxy.cfg3.3 安装Pacemaker并初始化双节点集群
两台LB节点都要执行:
sudo dnf install -y pacemaker corosync pcs三件套的分工:pcs是管理工具,通过它执行集群创建、资源创建命令;pacemaker自己是决策引擎;corosync负责成员关系和心跳通信。安装完成后,先设一个hacluster用户的密码:
sudo passwd hacluster然后任意一台LB节点上执行认证和集群创建:
sudo pcs host auth k8s-lb-01 k8s-lb-02 -u hacluster sudo pcs cluster setup k8s-lb-cluster k8s-lb-01 k8s-lb-02 sudo pcs cluster start --all sudo pcs cluster enable --allpcs cluster setup会自动生成corosync.conf并同步到两个节点。装好之后先看一眼集群状态:
sudo pcs status如果看到类似"2 nodes configured""Current DC: k8s-lb-01"的输出,说明成员关系已经建立。测试环境里这时先关掉两个默认的烦人设置——STONITH和quorum策略,后面第5章会专门解释为什么这么处理:
sudo pcs property set stonith-enabled=false sudo pcs property set no-quorum-policy=ignore3.4 创建资源组:VIP和HAProxy绑定在一起
这是Pacemaker最关键的一步。我推荐直接用资源组把VIP和HAProxy绑起来,这样就不用手写colocation和order约束了:
sudo pcs resource create k8s-vip ocf:heartbeat:IPaddr2 ip=192.168.10.199 cidr_netmask=24 op monitor interval=5s \ --group k8s-lb-group sudo pcs resource create k8s-haproxy systemd:haproxy op monitor interval=5s \ --group k8s-lb-group两条命令背后的逻辑:
- 第一个资源
k8s-vip:类型是ocf:heartbeat:IPaddr2,这是Pacemaker管理虚拟IP的标准资源代理,传入VIP地址和掩码。op monitor interval=5s表示每5秒检查一次VIP是否还在本节点网卡上。 - 第二个资源
k8s-haproxy:类型是systemd:haproxy,Pacemaker直接把HAProxy当作一个systemd服务来管——检查、启动、停止都通过systemctl。 - 两个资源放进同一个组
k8s-lb-group后,组内资源按定义顺序启动:先加VIP,再起HAProxy。因为后端HAProxy绑定的是0.0.0.0:6443,VIP什么时候在并不影响进程启动,但顺序更规范,避免"服务起来了、IP还没配上"的极小窗口期。
创建完成后再看pcs status,正常情况应该是:
Resource Group: k8s-lb-group k8s-vip (ocf::heartbeat:IPaddr2): Started k8s-lb-01 k8s-haproxy (systemd:haproxy): Started k8s-lb-01资源都起来了,在任意一台能访问192.168.10.199的机器上用ip addr确认VIP,用curl -k https://192.168.10.199:6443/healthz看API Server的返回。返回ok就说明这条链路已经通了。
这里有一个非常实用的技巧:Pacemaker创建的VIP,默认会附带一个绑定的IP漂移规则。它会自动处理ARP通告,新节点收到VIP后会主动发免费ARP,让交换机更新MAC表——这是Keepalived和Pacemaker都能做到的,但如果有抓包经验你会发现Pacemaker的IPaddr2实现得更干净,不会出现旧IP残留的问题。
4. 故障演练实录:VIP漂移、后端摘除与恢复的真实表现
4.1 先验证负载均衡本身:流量是否均匀分发
资源创建完毕、入口通了之后,先别急着进入"下一项工作",一定要做一次负载均衡效果的验证。做法很简单:
在客户端机器上循环调用API Server的匿名健康检查接口:
for i in $(seq 1 30); do curl -sk https://192.168.10.199:6443/healthz echo " round $i" done因为balance roundrobin是按连接数轮询的,多次短连接请求应该会均匀分布到三台Master。想看得更直观,可以在三台Master上分别用tcpdump -i eth0 port 6443抓包,或者在HAProxy上开stats页面看每个后端的Counter计数。我这里直接建议在生产环境打开HAProxy的socket统计和可选stats页面,因为后面排障时会极其有用:
listen stats bind *:8404 mode http stats enable stats uri /stats stats auth admin:your_password打开页面后,你会看到k8s_api_backend下三个server的Sessions和Bytes计数,如果三个数字比较接近,说明分发逻辑正常。
4.2 演练一:停掉一台LB节点,VIP到底怎么漂
这一步是整个方案的核心验证。在LB1上执行sudo poweroff模拟宕机,然后在LB2上看集群状态:
sudo pcs status正常情况下,你会看到类似下文的输出:
Node k8s-lb-01: OFFLINE Resource Group: k8s-lb-group k8s-vip (ocf::heartbeat:IPaddr2): Started k8s-lb-02 k8s-haproxy (systemd:haproxy): Started k8s-lb-02VIP和HAProxy整体漂到了LB2。再从客户端访问VIP,/healthz依然返回ok。这里要特别提醒一个预期管理问题:已经建立的长连接会断,需要客户端重连——TCP连接和VIP漂移没有神奇的"无缝迁移"能力,但因为K8s各组件对API Server的连接重试机制非常健壮,短连接场景下业务感知基本为零。
如果想看"服务进程挂了但节点还活着"的场景,可以不打网关,直接在LB1上执行:
sudo systemctl stop haproxyPacemaker会在几秒内发现HAProxy资源运行异常,尝试拉起;如果拉起失败,就整组漂移到LB2。这个测试比直接宕机更接近日常故障,建议反复演练几次。
4.3 演练二:一台Master的API Server挂了,Haproxy会怎么处理
LB层本身的高可用验证完了,还要验证一下LB对后端的健康检查逻辑。在Master1上停掉kube-apiserver:
sudo systemctl stop kube-apiserver然后到HAProxy的stats页面刷新,会看到master-01的状态由UP逐渐变成DOWN,原因是HAProxy探测TCP 6443端口失败达到3次(fall 3),于是把该后端摘除。这时候VIP入口依然正常响应——流量只被分发到剩下两台Master。
之后重启Master1的kube-apiserver,再观察stats页面,master-01的状态会在连续两次探测成功(rise 2)后自动回到UP。整个过程无需人工介入,也不用改写HAProxy配置。这就是"LB层自治"的意义:K8s集群的某个Master在维护、在升级、在重启,集群入口不需要关停。
5. 那些"能跑但很危险"的细节:STONITH、quorum、进程托管坑
5.1 STONITH绝不是可以随便关闭的东西
前面第3章里,我让大家执行了stonith-enabled=false,这是测试环境的常规做法,但你必须清楚这意味着什么:当两个节点之间网络中断、但各自都认为自己活着时,没有STONITH的集群可能出现"资源共享"——两台机器同时持有VIP、同时启动HAProxy。对K8s入口来说,VIP冲突会导致流量混乱、API Server被同时报多个地址,非常危险。
生产环境必须要有Fencing能力。最简单的理解就是:当集群无法确定一个节点是否存活时,直接把那个节点强制断电(比如通过BMC/IPMI远程电源管理),确保它不可能再持有任何资源。有些环境没有专门的Fencing硬件,可以临时用fence_heuristics_ping或共享存储的SCSI锁,但都不如BMC方案彻底。如果你真的只能在两台物理机上裸奔、没有任何电源管理手段,那至少要在pcs property set no-quorum-policy=freeze的基础上做妥协,而不是ignore——因为"freeze"至少会在不确定情况下冻结资源操作。
5.2 Pacemaker与systemctl的"夺权"问题
这个是使用Pacemaker管理服务类资源时最容易踩的坑:资源一旦被Pacemaker接管,绝对不能再用systemctl去停它。
我见过不止一次:某运维兄弟发现HAProxy配置文件写错了,直接systemctl stop haproxy想改配置,结果几秒钟后服务又被自动拉起,他一脸震惊地问"我明明停掉了啊?"——不,Pacemaker的监控周期是5秒,它发现自己托管的资源不在运行状态,会立刻尝试恢复。要修改配置并重载,正确做法是:
sudo pcs resource disable k8s-haproxy # 修改配置 sudo pcs resource enable k8s-haproxy或者只是重载配置不中断服务:
haproxy -c -f /etc/haproxy/haproxy.cfg && sudo haproxy -sf $(cat /run/haproxy.pid) -f /etc/haproxy/haproxy.cfg理解这个机制你就会明白:Pacemaker不是单纯的"进程守护者",它是资源管理者。你之前习惯的systemctl操作方式,在这个层级已经失效了。
5.3 双节点集群的quorum是死穴,至少要有三个投票者
回到第3章,我没有让大家忽略quorum问题,现在单独拿出来讲。
两节点集群天生尴尬:如果节点1挂了,幸存节点只有1票,而总票数是2,corosync会认为没有quorum(法定人数不足),集群拒绝继续提供服务。这就是为什么测试环境里我让大家设置no-quorum-policy=ignore——宁可不装、也不让集群在故障时因为quorum而停止服务。但生产环境这个策略肯定不行。
最稳妥的解法是引入第三个投票者:一台配置很低的机器,只跑corosync参与表决,不承载任何资源。这样总票数变成3,任意一台LB宕机后,另一边加上仲裁节点仍有2票,quorum通过。这就是Pacemaker官方推荐的"仲裁节点"方案(也叫qdevice),配置起来并不复杂:
sudo pcs device add qdevice qnetd在单独的小机器上装corosync-qnetd,然后让两个LB节点通过pcs接入仲裁设备。这样既保持了LB层只有两个承载节点,又解决了双节点quorum硬伤。
5.4 HAProxy的日志、连接数与超时参数
HAProxy默认不往文件日志,只有syslog。不配置日志会带来一个后果:故障时排查困难,后端状态变化、流量异常全部无迹可寻。
配置文件里已经写了log 127.0.0.1 local0 info,接下来要在系统的rsyslog里加一条转发规则:
cat > /etc/rsyslog.d/haproxy.conf <<EOF local0.* /var/log/haproxy.log & stop EOF systemctl restart rsyslog连接数方面,maxconn 4096只是一个基线。如果你的K8s集群很大,所有Worker节点都通过VIP连接API Server,再加上外部工具调用,并发可能远超这个数。建议先观察一两个星期的连接峰值,再上调。四层的timeout client和timeout server我习惯给120s——太高会让失效的连接长时间占着HAProxy的连接槽位,太低又频繁切断K8s组件的长连接。实测下来120s是相对均衡的值。
5.5 VIP规划的几个小坑
最后分享几个规划层面的经验:
第一,VIP不要选在DHCP自动分发的范围内,手动固定一个地址。第二,VIP要和后端Master处于同一个二层网络,这样漂移时免费ARP才能正常通告,跨三层需要额外配置ARP代理,麻烦且不推荐。第三,如果交换机有端口绑定(Port Security),记得提前把VIP的MAC地址从地址表里排除,否则漂移后新节点通告免费ARP时会被交换机拒绝。
6. 一些更加稳妥的延伸做法和我的最终取舍
这套HAProxy + Pacemaker的方案,我在好几个自建K8s集群上跑过一年多,稳定性的确靠谱。但如果你要问我"最后还有什么建议",我会说几点:
能加仲裁节点,一定要加。哪怕第三台机器只有1C1G,只跑corosync-qnetd,都能让双节点集群从"靠运气"变成"按规则办事"。STONITH如果实在没有BMC,至少把no-quorum-policy=freeze配合quorum设备用起来。
K8s组件指向VIP时,我建议把/etc/kubernetes/kubelet.conf、/etc/kubernetes/controller-manager.conf这些配置文件里的server: https://192.168.10.199:6443统一写好。所有Master、Worker节点的kubelet都走VIP,这样即使一台LB挂了,所有客户端组件也会自动通过漂移后的VIP连接到另一台LB,整个过程对K8s集群内部完全透明。
如果你觉得Pacemaker太重、体积太吓人,只想用Keepalived管VIP、脚本管服务,也不是不能跑——但前提是你能接受脚本失控时的抓瞎。我在一个轻量环境里就吃过Keepalived脚本的亏:脚本自己挂死了,VIP没漂,服务也断了,等到业务方反馈才排查到。Pacemaker的资源和监控是心跳级的,这种"哑巴故障"概率会低很多。
曾经有人问我:生产环境为什么不用云厂商的LB?因为我这边很多项目就是机房自建,没有云LB可用;即使是能用云LB的地方,也要考虑云LB本身的地域故障域、成本、以及和线下网络的打通问题。HAProxy + Pacemaker这套组合,是在"没有外部依赖"的前提下,能拿到的最高可用性。它的边界也明确:只负责流量入口,不负责K8s集群内部的服务发现、路由决策。所以认清边界、用对场景,这方案就能一直稳下去。