自建K8s高可用入口:HAProxy+Pacemaker落地实践 Kubernetes集群好不容易搭好了Master高可用、etcd堆叠、WorkNode都就绪了结果发现一个尴尬的问题客户端、kubelet、kubectl这些流量入口到底走哪里直接把其中一个Master的IP写死等于把高可用废了一半用NodePort暴露节点一挂端口就断。这个时候很多自建K8s环境的运维都会走到同一条路上在集群前面再架一层独立的负载均衡入口而HAProxyPacemaker这套组合就是我现在最常用、也最愿意推荐的自建方案。这篇文章不讲虚的直接以两台LB节点扛VIP HAProxy做四层转发 Pacemaker管资源漂移为骨架把从环境规划、配置细节、资源编排到故障演练、生产避坑的完整链路捋一遍。适合正在自建生产级K8s、或者刚入坑K8s网络方案、对高可用负载均衡层搭建还没形成完整思路的朋友。如果你打算用云厂商的SLB那这篇你可以跳过了——但如果你是机房自建、离线环境、或想彻底搞懂K8s前置流量入口的原理这篇值得先收藏。1. 为什么K8s集群门前还要再立一道独立负载均衡墙1.1 先把流量入口的三种常规方案盘一遍先说清楚一个常见的认知盲区K8s集群外部流量要进来不是只有Service这一层的事。我们把外部流量到达Pod这条路拆开看通常有四种方式方案暴露方式典型问题NodePort每个节点开30000-32767端口客户端必须知道节点IP节点挂了一个端口就废LoadBalancer依赖云厂商LB插件自建机房没有现成的LB可用IngressIngress Controller本身还是Pod它自己也得先暴露出来还是有同样的问题外部LB NodePort/Ingress前面挂独立负载均衡如果LB本身是单点整个集群入口就断在自建机房里Ingress Controller通常是最常用的七层入口但所有Ingress ControllerNginx、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 AgentRAPacemaker不直接理解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系的包管理略有差异但原理一致。角色主机名IPLB节点1k8s-lb-01192.168.10.100LB节点2k8s-lb-02192.168.10.101Master节点1k8s-master-01192.168.10.11Master节点2k8s-master-02192.168.10.12Master节点3k8s-master-03192.168.10.13VIP-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 # 不配置chrootPacemaker的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 roundrobinK8s API Server的请求大多是短连接kubectl命令、长Keep-Alive组件与API Server的长轮询混合的roundrobin在四层场景下能很好的均匀分布。option tcp-check check每3秒探测一次后端6443端口连续3次失败标记为DOWN连续2次成功标记为UP。端口探测简单可靠。为什么不用httpchkK8s API Server的6443是HTTPS端口如果要做HTTP级别的健康检查需要额外配置TLS检查而四层负载场景下API Server如果进程死掉端口自然就断了真正可怕的是端口还在但API响应异常的极端情况我在生产里遇到过所以在进阶方案里可以给HAProxy加上check-ssl和httpchk请求探活但作为基础入口tcp-check已经够用。配置写好后先手动验证一下语法暂时不要急着注册到Pacemakerhaproxy -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 configuredCurrent DC: k8s-lb-01的输出说明成员关系已经建立。测试环境里这时先关掉两个默认的烦人设置——STONITH和quorum策略后面第5章会专门解释为什么这么处理sudo pcs property set stonith-enabledfalse sudo pcs property set no-quorum-policyignore3.4 创建资源组VIP和HAProxy绑定在一起这是Pacemaker最关键的一步。我推荐直接用资源组把VIP和HAProxy绑起来这样就不用手写colocation和order约束了sudo pcs resource create k8s-vip ocf:heartbeat:IPaddr2 ip192.168.10.199 cidr_netmask24 op monitor interval5s \ --group k8s-lb-group sudo pcs resource create k8s-haproxy systemd:haproxy op monitor interval5s \ --group k8s-lb-group两条命令背后的逻辑第一个资源k8s-vip类型是ocf:heartbeat:IPaddr2这是Pacemaker管理虚拟IP的标准资源代理传入VIP地址和掩码。op monitor interval5s表示每5秒检查一次VIP是否还在本节点网卡上。第二个资源k8s-haproxy类型是systemd:haproxyPacemaker直接把HAProxy当作一个systemd服务来管——检查、启动、停止都通过systemctl。两个资源放进同一个组k8s-lb-group后组内资源按定义顺序启动先加VIP再起HAProxy。因为后端HAProxy绑定的是0.0.0.0:6443VIP什么时候在并不影响进程启动但顺序更规范避免服务起来了、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-apiserversudo 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-enabledfalse这是测试环境的常规做法但你必须清楚这意味着什么当两个节点之间网络中断、但各自都认为自己活着时没有STONITH的集群可能出现资源共享——两台机器同时持有VIP、同时启动HAProxy。对K8s入口来说VIP冲突会导致流量混乱、API Server被同时报多个地址非常危险。生产环境必须要有Fencing能力。最简单的理解就是当集群无法确定一个节点是否存活时直接把那个节点强制断电比如通过BMC/IPMI远程电源管理确保它不可能再持有任何资源。有些环境没有专门的Fencing硬件可以临时用fence_heuristics_ping或共享存储的SCSI锁但都不如BMC方案彻底。如果你真的只能在两台物理机上裸奔、没有任何电源管理手段那至少要在pcs property set no-quorum-policyfreeze的基础上做妥协而不是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票而总票数是2corosync会认为没有quorum法定人数不足集群拒绝继续提供服务。这就是为什么测试环境里我让大家设置no-quorum-policyignore——宁可不装、也不让集群在故障时因为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-policyfreeze配合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集群内部的服务发现、路由决策。所以认清边界、用对场景这方案就能一直稳下去。