本文面向已有Kubernetes实践经验的中高级工程师,旨在深度剖析其控制平面的核心——API Server的高可用与负载均衡机制。我们将从经典的Keepalived+Haproxy方案入手,不仅解释其工作模式,更会下探到底层的VRRP协议、Gratuitous ARP、TCP连接状态等,并结合详尽的配置实例与工程陷阱分析。最终,我们会将视野拓宽至云原生环境,探讨其架构演进路径与不同方案间的核心权衡,为构建生产级高可用的Kubernetes集群提供坚实的理论与实践基础。
现象与问题背景
在Kubernetes的宏伟蓝图中,API Server扮演着无可替代的“大脑”角色。它是整个集群的数据总线和操作入口,所有组件——无论是用户侧的kubectl、CI/CD流水线,还是集群内部的kubelet、controller-manager、scheduler——都通过它来查询和变更集群状态。这些状态最终被持久化到etcd中。这种中心化的设计带来了模型的一致性与简洁性,但也引入了一个致命的单点故障(Single Point of Failure, SPOF)风险。
一个单节点的API Server,一旦因硬件故障、网络中断或进程崩溃而宕机,整个Kubernetes集群的控制平面将瞬间失能。虽然已在运行的Pod和服务(数据平面)可以继续提供服务,但集群将进入一种“脑死亡”状态:无法部署新的应用、无法进行扩缩容、无法查询任何资源状态、所有自动化运维和控制器全部停摆。对于追求业务连续性的生产环境而言,这种状态是不可接受的。因此,实现API Server的高可用(High Availability, HA)与负载均衡(Load Balancing, LB)便成为所有生产级Kubernetes集群的必修课。
核心需求可以归结为两点:
- 高可用性:至少部署两个(通常是三个或更多奇数个)API Server实例。当其中一个实例失效时,流量必须能够自动、快速地切换到健康的实例上,对客户端透明。
- 负载均衡:将来自众多客户端的请求均匀地分发到所有健康的API Server实例上,避免单一实例过载,提高整体吞吐量和资源利用率。
这引出了一系列架构问题:如何为多个API Server实例提供一个统一的、稳定的访问入口?这个入口本身如何做到高可用?如何有效地探测后端API Server的真实健康状况?这些问题,正是本文将要层层剥开的核心。
关键原理拆解
在进入具体实现之前,我们必须回到计算机科学的基础原理,理解支撑高可用负载均衡的几个核心概念。这部分我将以一位教授的视角,为你阐述其背后的网络与系统原理。
1. 虚拟IP(VIP)与VRRP协议
要让多个服务器实例对外呈现为单一入口,最经典的手段是使用一个“虚拟IP地址”(Virtual IP Address, VIP)。这个IP地址不固定绑定在任何一台物理服务器的网卡上,而是作为一个浮动地址,由一个高可用集群动态地“持有”。在任何时刻,只有集群中的一个节点(主节点,MASTER)会持有并响应这个VIP的流量。
VRRP (Virtual Router Redundancy Protocol) 是实现这一机制的IETF标准协议(RFC 5798)。它工作在OSI模型的第三层(网络层)。其核心思想如下:
- 角色:一个VRRP组内有多个节点,一个为
MASTER,其余为BACKUP。 - 抢占与选举:所有
BACKUP节点都在静默监听这个通告。如果在一定时间内(默认为3倍通告间隔)没有收到来自MASTER的报文,它们会认为MASTER已失效。此时,优先级最高的BACKUP节点会“抢占”成为新的MASTER。 - 地址接管:新
MASTER会立即将VIP配置到自己的网络接口上,并对外发送一个关键的报文——Gratuitous ARP。
* 心跳通信:MASTER节点会周期性地(默认为1秒)向一个特定的组播地址(224.0.0.18)发送VRRP通告报文(Advertisement),宣告自己的存活和优先级。
2. Gratuitous ARP:地址宣告的魔法
Gratuitous ARP(免费ARP)是理解VIP切换的关键。正常的ARP请求/响应是“我问你答”模式,用于查询某个IP地址对应的MAC地址。而Gratuitous ARP是一种主动的、未经请求的ARP响应。
当新的MASTER节点接管VIP后,它会向局域网内广播一个ARP包,包的内容是:“大家好,IP地址为[VIP]的设备,现在对应的MAC地址是[我的MAC地址]”。
这个行为会触发局域网内所有交换机和主机的ARP缓存更新。交换机会更新其MAC地址表,将指向VIP的流量转发到新的MASTER节点的物理端口。其他主机也会更新自己的ARP缓存表。这个过程几乎是瞬时的,从而完成了流量的快速切换。这是纯粹的二层(数据链路层)和三层(网络层)操作,对上层应用完全透明。
3. 负载均衡的层次:L4 vs. L7
有了高可用的VIP入口,我们还需要将流量分发到后端的多个API Server。这便是负载均衡器的工作,它主要分为两个层次:
- L4 负载均衡(传输层):工作在TCP/UDP层。它根据IP地址和端口号来转发数据包,而不关心包内的应用层数据。常见的实现如LVS(Linux Virtual Server,工作在内核态,性能极高)和Haproxy的TCP模式。它通常通过修改数据包的目标IP地址(DNAT)来实现转发。优点是性能开销小,速度快。缺点是无法感知应用层的状态,例如一个API Server进程虽然活着但内部逻辑卡死,L4负载均衡器可能无法察觉。
- L7 负载均衡(应用层):工作在HTTP/HTTPS等应用层。它会完整地解析应用层协议。例如,一个L7负载均衡器会与客户端建立TCP连接,解析HTTP请求,然后再与后端某个服务器建立新的TCP连接,并转发HTTP请求。优点是智能化,可以根据URL路径、HTTP头等信息做精细的路由决策,并且可以执行更精确的应用级健康检查(如请求API Server的
/healthz端点)。缺点是需要终结和重新建立TCP连接,CPU和内存开销更大。
对于Kubernetes API Server,它使用HTTPS协议,因此L7负载均衡(如Haproxy、Nginx)在功能上更为契合,尤其是其强大的健康检查能力。虽然性能略低于L4,但在控制平面的场景下,其可靠性收益远大于性能损耗。
系统架构总览
基于上述原理,一个经典的自建(On-Premise)Kubernetes集群高可用架构呼之欲出。我们可以用文字来描绘这幅常见的架构图:
- Master节点层(3台或更多):每一台Master节点都独立运行着
kube-apiserver,kube-controller-manager,kube-scheduler和etcd实例(etcd集群通常也部署在这些节点上)。 - 负载均衡层(2台):这是两台专用的服务器或虚拟机,它们是高可用的核心。每一台都运行着Keepalived和Haproxy。
- 虚拟IP(VIP):一个在Master节点所在网段内未被使用的IP地址,作为整个集群API的统一入口。
- 流量路径:
- 所有客户端(
kubectl,kubelet等)都配置为访问VIP的6443端口。 - 在任意时刻,两台负载均衡节点中只有一台(MASTER)的Keepalived会持有这个VIP。
- 流量到达持有VIP的节点后,首先被其上的Haproxy进程接收。
- Haproxy根据其负载均衡策略(如轮询)和健康检查结果,选择一个后端健康的Master节点上的API Server。
- Haproxy将请求转发给选中的API Server。
- 所有客户端(
- 故障切换:
- 如果正在工作的负载均衡节点(MASTER)宕机,或其上的Keepalived/Haproxy进程异常,Keepalived心跳会中断。
- 另一台负载均衡节点(BACKUP)在超时后,提升自己为新的MASTER,接管VIP,并发送Gratuitous ARP。
- 网络设备更新ARP缓存,后续所有发往VIP的流量都将无缝切换到这台新的工作节点上。整个切换过程通常在数秒内完成。
这个架构通过Keepalived解决了负载均衡器本身的单点问题,通过Haproxy解决了后端API Server的负载均衡和健康探测问题,形成了一个健壮、可靠的闭环。
核心模块设计与实现
现在,让我们切换到极客工程师的视角,直接看代码和配置。理论再好,落不了地也是白搭。下面的配置是生产环境中的典型实践,藏着很多细节和坑点。
Keepalived 配置
假设我们有两台LB节点,IP分别为 `192.168.1.101` (LB1) 和 `192.168.1.102` (LB2)。VIP设为 `192.168.1.100`。
LB1 (MASTER) 的 `/etc/keepalived/keepalived.conf`:
global_defs {
router_id LVS_DEVEL_1
}
# 用于监控Haproxy进程状态的脚本
vrrp_script chk_haproxy {
script "/usr/bin/killall -0 haproxy" # 检查haproxy进程是否存在
interval 2 # 每2秒检查一次
weight 2 # 如果脚本成功,优先级+2
}
vrrp_instance VI_1 {
state MASTER # 初始状态为MASTER
interface ens192 # VIP绑定的网卡
virtual_router_id 51 # VRRP组ID,同一组内必须相同
priority 101 # 优先级,MASTER要高于BACKUP
advert_int 1 # 心跳间隔,单位秒
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.1.100/24 # 定义VIP
}
track_script {
chk_haproxy
}
}
LB2 (BACKUP) 的 `/etc/keepalived/keepalived.conf`:
global_defs {
router_id LVS_DEVEL_2
}
vrrp_script chk_haproxy {
script "/usr/bin/killall -0 haproxy"
interval 2
weight 2
}
vrrp_instance VI_1 {
state BACKUP
interface ens192
virtual_router_id 51
priority 100 # 优先级较低
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.1.100/24
}
track_script {
chk_haproxy
}
}
极客坑点分析:
virtual_router_id:同一个VLAN内必须唯一,否则会造成VRRP组混乱。priority:是选举的核心。LB1是101,LB2是100。当LB1在线时,它永远是MASTER。track_script是关键:只靠Keepalived进程本身存活来决定主备是不够的。如果Haproxy进程挂了,Keepalived还活着,那么VIP依然留在这台已经无法转发流量的节点上,这就成了“假”高可用。vrrp_script定义了一个监控脚本,track_script将其与VRRP实例绑定。当chk_haproxy脚本执行失败(即`haproxy`进程不存在),当前节点的优先级会降低(默认行为,或者按`weight`调整)。对于MASTER节点,优先级降低后会低于BACKUP的优先级,从而触发主备切换。这是保证服务真实可用的核心机制。- 抢占(Preemption):默认情况下,Keepalived是开启抢占模式的。这意味着一旦原来的高优先级MASTER恢复,它会立刻抢回VIP。在某些场景下,为了避免网络抖动可能引起的频繁切换,可以添加
nopreempt配置。
Haproxy 配置
Haproxy的配置位于`/etc/haproxy/haproxy.cfg`,负责将来自VIP的流量转发给后端的API Server(假设地址为 `192.168.1.201`, `192.168.1.202`, `192.168.1.203`)。
global
log 127.0.0.1 local2
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn 4000
user haproxy
group haproxy
daemon
defaults
mode tcp
log global
option tcplog
option dontlognull
option redispatch
retries 3
timeout queue 1m
timeout connect 10s
timeout client 1m
timeout server 1m
timeout check 10s
maxconn 3000
frontend kubernetes-apiserver
bind 192.168.1.100:6443 # 绑定在VIP的6443端口
mode tcp
option tcplog
default_backend kubernetes-apiserver-backend
backend kubernetes-apiserver-backend
mode tcp
balance roundrobin # 负载均衡算法:轮询
server master1 192.168.1.201:6443 check port 6443
server master2 192.168.1.202:6443 check port 6443
server master3 192.168.1.203:6443 check port 6443
极客坑点分析:
mode tcpvs.mode http:这里我们用了TCP模式。为什么?因为API Server使用mTLS进行客户端证书认证,这是在TCP连接建立后的TLS握手阶段完成的。如果Haproxy工作在HTTP模式,它需要终结TLS,这就意味着Haproxy需要持有API Server的证书和私钥,并处理所有客户端证书的认证逻辑,配置会变得异常复杂。TCP模式直接透传TLS握手和加密数据,保持了端到端的加密,配置简单且高效。- 健康检查:`check port 6443` 是一个基础的TCP端口检查。它能保证API Server的端口是监听的,但无法保证服务内部是健康的。一个更优的(但更复杂的)方案是使用支持TLS的健康检查,去请求API Server的
/healthz非认证端点。但这需要Haproxy编译时支持SSL/TLS,并且配置会更复杂。对于多数场景,TCP端口检查结合API Server本身的重启机制(如果内部服务出问题它会崩溃并由kubelet拉起),已经足够健壮。 bind地址:必须绑定在VIP上,而不是`0.0.0.0`。否则,当该节点为BACKUP时,Haproxy也会尝试监听这个端口,可能导致端口冲突或非预期行为。虽然Linux内核参数net.ipv4.ip_nonlocal_bind=1可以允许绑定在不存在的IP上,但明确绑定在VIP上是更清晰和安全的选择。balance roundrobin:轮询是最简单的算法。对于无状态的API Server请求来说,这通常是合适的。其他选项如leastconn(最少连接)在长连接场景下可能更优,但API Server的请求通常是短生命周期的REST API调用,轮询足够了。
性能优化与高可用设计(Trade-off分析)
没有完美的架构,只有不断权衡的艺术。这个看似经典的方案,在细节处充满了Trade-off。
1. 故障切换时间 vs. 网络抖动
Keepalived的故障切换时间主要由advert_int和后台的选举逻辑决定,通常在3-5秒之间。这个时间对于控制平面来说完全可以接受。但如果将advert_int设置得过小(比如0.1秒)来追求更快的切换,在网络轻微抖动时,可能会导致BACKUP节点误判MASTER失效,从而引起不必要的、频繁的主备切换(脑裂/Brain-Split的前兆),反而降低了系统的稳定性。这是一个典型的灵敏度与稳定性的权衡。
2. Haproxy vs. LVS/IPVS
我们选择了Haproxy。如果追求极致性能,完全可以使用集成在Linux内核中的IPVS(LVS的后继者)。IPVS通过netfilter钩子直接在内核空间进行数据包的转发和地址转换,其吞吐量远超用户态的Haproxy。Kubernetes的Service本身就是基于IPVS(或iptables)实现的。
Trade-off:
- 性能: IPVS (L4, 内核态) > Haproxy (L4/L7, 用户态)。对于需要处理海量并发连接的数据平面网关,IPVS是首选。但对于控制平面的API Server,其QPS通常在几百到几千的量级,Haproxy的性能绰绰有余。
- 灵活性与可观测性: Haproxy胜出。Haproxy有丰富的统计页面,详细的日志,支持复杂的ACL规则和应用层健康检查。IPVS则像一个黑盒,排错和监控相对困难。
- 运维复杂度: Haproxy的配置和理解门槛更低。IPVS的配置和调试需要更深的Linux网络知识。
结论:对于API Server这个场景,Haproxy的易用性和功能丰富性带来的好处,超过了IPVS的极致性能优势。
3. 健康检查的深度
上面Haproxy配置中使用了简单的TCP端口检查。这是一种“浅”检查。更“深”的检查是什么?
- 应用层检查: 定期请求API Server的
/healthz或/livez端点。这能确认API Server不仅端口在监听,而且内部的HTTP服务循环和关键依赖(如etcd连接)是正常的。 - 实现挑战: API Server默认开启HTTPS,健康检查也需要通过TLS。Haproxy的
option httpchk需要额外配置SSL/TLS参数,或者将API Server的健康检查端点配置为HTTP(不推荐,会增加配置复杂度)。
Trade-off: 简单的TCP检查开销极小,实现简单,但可能漏掉应用层面的“僵尸进程”。应用层检查更可靠,但配置更复杂,且对负载均衡器和API Server都有额外的性能开销。对于关键生产系统,投入精力实现基于HTTPS的应用层健康检查是值得的。
架构演进与落地路径
技术方案不是一成不变的,它会随着环境和需求的变化而演进。
阶段一:单Master节点
所有开发、测试环境的起点。一个Master节点,所有组件all-in-one。部署简单,成本低,但无任何高可用保障。适合非生产环境。
阶段二:自建HA(Keepalived + Haproxy)
本文详述的方案。这是在私有化部署、物理机房、或者对网络有完全控制权的IaaS云上构建生产级Kubernetes集群的黄金标准。工具如kubeadm可以很好地支持生成这种集群的配置。它技术成熟,社区支持广泛,不受任何云厂商绑定。
阶段三:云原生HA(云厂商负载均衡器)
当你在公有云(如AWS, GCP, Azure)上部署Kubernetes时,完全可以抛弃Keepalived和Haproxy,转而使用云厂商提供的托管负载均衡器服务(如AWS的NLB/ELB, GCP的Cloud Load Balancer)。
架构变为:
- 创建一个云厂商的L4或L7负载均衡器。
- 将所有的Master节点(EC2实例)的API Server端口注册为该负载均衡器的后端目标组。
- 云厂商负责负载均衡器本身的高可用(这通常是跨可用区AZ的),以及对后端目标实例的健康检查和流量分发。
- 客户端(包括集群内的kubelet)直接访问该负载均衡器提供的公网或内网DNS地址。
Trade-off分析:
- 运维成本: 云方案极低。无需自己维护Keepalived/Haproxy,无需关心VIP和ARP。一切都是通过API或控制台点击完成。
- 成本: 云负载均衡器是收费服务,可能会比自建方案成本高,尤其是在流量巨大的情况下。
- 集成与特性: 与云生态深度集成,例如可以方便地与云的证书管理、WAF、监控系统联动。
* 厂商锁定: 这是最大的缺点。你的集群部署和网络配置与特定云厂商深度绑定,迁移到其他云或自建环境的成本会非常高。
一种“反模式”:DNS负载均衡
有人会问,能否通过DNS实现负载均衡?即为一个域名(如k8s-api.mycorp.com)配置多个A记录,分别指向不同的Master节点IP。
答案是:可以,但非常不推荐用于API Server。
DNS负载均衡本质上是客户端侧的负载均衡。但它的故障切换能力极差。当一个Master节点宕机后,由于DNS缓存的存在(在客户端、在各级DNS服务器),客户端在TTL过期前会持续尝试连接那个已经失效的IP,导致长时间的服务中断。这对于需要毫秒级响应和快速失败的集群内部组件(如controller-manager)是致命的。因此,DNS负载均衡只适用于对延迟和故障切换不敏感的人工操作场景,绝不能作为集群自动化的核心入口。
综上,从自建的Keepalived+Haproxy到云原生的托管LB,我们看到的是一个从“自己动手,完全掌控”到“拥抱托管,简化运维”的演进路径。选择哪条路,取决于你的业务场景、团队技术栈以及对成本和厂商锁定的考量。但无论选择哪种方案,其背后解决高可用和负载均衡问题的核心原理——冗余、心跳、健康检查、流量分发——都是相通的。
延伸阅读与相关资源
-
想系统性规划股票、期货、外汇或数字币等多资产的交易系统建设,可以参考我们的
交易系统整体解决方案。 -
如果你正在评估撮合引擎、风控系统、清结算、账户体系等模块的落地方式,可以浏览
产品与服务
中关于交易系统搭建与定制开发的介绍。 -
需要针对现有架构做评估、重构或从零规划,可以通过
联系我们
和架构顾问沟通细节,获取定制化的技术方案建议。