本文面向已经对 Kubernetes 有一定实践经验的中高级工程师。我们将深入探讨 Kubernetes 控制平面的核心——API Server 的高可用与负载均衡机制。我们将超越“如何配置”的层面,从网络协议、操作系统内核行为和分布式系统设计原则出发,剖析以 Keepalived 和 Haproxy 为代表的经典高可用方案,并分析其在真实生产环境中的技术权衡与架构演进路径。
现象与问题背景
在任何一个 Kubernetes 集群中,API Server 都扮演着“大脑”和“门户”的角色。它是整个控制平面的唯一入口,所有组件(kubelet, controller-manager, scheduler)以及用户(kubectl)都通过它来查询和变更集群状态。这些状态最终被持久化到 etcd 中。这种中心化的设计简化了架构,但也引入了显而易见的单点故障(Single Point of Failure, SPOF)风险。
一个单节点的 API Server 在生产环境中是不可接受的。一旦该节点宕机或进程异常,整个集群将陷入“脑死亡”状态:无法调度新的 Pod,无法进行应用更新,无法响应扩缩容,甚至无法通过 `kubectl` 查看集群状态。虽然已在运行的工作负载会继续执行,但集群的管理和自愈能力完全丧失。因此,部署多个 API Server 实例,实现高可用,是生产化部署的必然要求。
然而,部署多个实例(例如,在三个 Master 节点上各运行一个 API Server)会立刻引出下一个问题:客户端(包括集群内部组件)应该连接哪一个实例?它们如何发现健康的服务实例?当某个实例失效时,连接如何能自动、快速地切换到其他健康实例上?这本质上就是一个经典的服务发现与负载均衡问题。我们需要一个统一的、稳定的访问入口,它能屏蔽后端多个 API Server 实例的复杂性,并提供故障转移能力。这个入口,通常是一个虚拟 IP 地址(Virtual IP, VIP)。
关键原理拆解
在进入具体实现之前,我们必须回归到计算机科学的基础原理,理解支撑这套高可用方案的基石。这并非炫技,而是为了让你在遇到问题时,能从根源上进行诊断。
学术派视角:从网络协议栈看 VIP 的实现
- L2 层通信与 ARP 协议: 在一个局域网(二层网络)中,设备间的通信依赖于 MAC 地址。IP 地址(三层)到 MAC 地址(二层)的转换由 ARP(Address Resolution Protocol)协议完成。当一台主机 A 要与 IP 地址为 `192.168.1.10` 的主机 B 通信时,它会广播一个 ARP 请求:“谁是 `192.168.1.10`?请告诉我你的 MAC 地址。” 主机 B 收到后,会单播一个 ARP 响应:“我是 `192.168.1.10`,我的 MAC 地址是 `AA:BB:CC:DD:EE:FF`。” 主机 A 随后将这个映射关系缓存到自己的 ARP 表中。
- VRRP 与 Gratuitous ARP: Keepalived 使用的核心协议是 VRRP(Virtual Router Redundancy Protocol)。一组运行 VRRP 的服务器(一个 MASTER,多个 BACKUP)共享一个虚拟 IP(VIP)。MASTER 节点会周期性地发送心跳包,宣告自己的存活。如果 BACKUP 节点在规定时间内未收到心跳,它们会进行选举,优先级最高的成为新的 MASTER。关键的一步来了:新的 MASTER 会立即向网络中广播一个特殊的 ARP 包,称为免费 ARP (Gratuitous ARP)。这个包的作用是声明:“现在,持有 VIP `192.168.1.100` 的是我,我的 MAC 地址是 `NEW_MAC_ADDRESS`。” 交换机和网络中的其他设备收到这个广播后,会强制更新自己的 ARP 缓存表,将 VIP 与新的 MAC 地址关联起来。这样,后续发往 VIP 的所有网络流量就被重定向到了新的 MASTER 节点。这整个过程对上层应用是完全透明的,它们只知道自己一直在和 VIP 通信。
学术派视角:从 TCP/IP 协议栈看四层代理
- 连接的建立与终止: Haproxy 在 TCP 模式(四层)下工作时,扮演一个全双工代理的角色。当客户端向 Haproxy 监听的 VIP 和端口发起 TCP 连接时(SYN),Haproxy 会与客户端完成 TCP 三次握手。然后,Haproxy 会根据其负载均衡策略(如 round-robin)选择一个后端的真实 API Server,并代表客户端向该服务器发起一个新的 TCP 连接。
- 用户态与内核态的边界: 这个过程涉及两个独立的 TCP 连接:`Client <-> Haproxy` 和 `Haproxy <-> API Server`。数据包从客户端网卡进入内核,经过协议栈处理后,由内核唤醒 Haproxy 用户态进程。Haproxy 进程读取数据,进行负载均衡决策,然后将数据写入到通往后端服务器的另一个 socket。数据再次从用户态陷入内核态,经由协议栈和网卡发送出去。这个过程虽然高效,但存在两次完整的协议栈处理和用户态/内核态切换的开销。对于 API Server 这种控制平面流量而言,这点开销完全可以接受。相比之下,L7 代理(如 HTTP 模式)还需要解析应用层数据,开销更大。
- 健康检查的本质: 负载均衡器必须能剔除不健康的后端实例。在四层,最直接的健康检查就是尝试与后端服务器的端口建立一个 TCP 连接。如果三次握手成功,就认为后端是健康的(`option tcp-check`)。这可以检测到进程崩溃或端口不监听的情况,但无法感知应用层面的僵死状态(例如,进程在但无法响应请求)。
系统架构总览
基于以上原理,我们来描绘一个经典的、在私有化部署中广泛应用的 Kubernetes 高可用架构。假设我们有三台 Master 节点,IP 分别为 `192.168.1.11`, `192.168.1.12`, `192.168.1.13`。
组件部署:
- 每台 Master 节点上都部署了 `kube-apiserver`, `kube-controller-manager`, `kube-scheduler`。
- 每台 Master 节点上都部署了 `keepalived` 进程和 `haproxy` 进程。
- 所有 `keepalived` 进程构成一个 VRRP 组,共同管理一个 VIP,例如 `192.168.1.100`。
- 所有 `haproxy` 进程都配置为监听本地回环地址或 VIP,并将流量代理到所有三个 `kube-apiserver` 实例(`192.168.1.11:6443`, `192.168.1.12:6443`, `192.168.1.13:6443`)。
稳态时的数据流:
- 假设 `master-1`(`192.168.1.11`)上的 `keepalived` 实例是 MASTER 状态,它持有着 VIP `192.168.1.100`。
- 所有客户端(`kubectl`、其他组件)都配置为连接 `https://192.168.1.100:6443`。
- 网络流量到达 `master-1` 的网卡。
- `master-1` 上的 `haproxy` 进程接收到流量,因为它正监听着 `192.168.1.100:6443`。
- `haproxy` 根据轮询策略,将 TCP 连接转发给后端的三个 API Server 之一,可能是 `master-1` 自己,也可能是 `master-2` 或 `master-3`。
故障时的切换流程:
- `master-1` 节点整机宕机。
- `master-2` 和 `master-3` 上的 `keepalived` 实例因收不到 VRRP 心跳而触发选举。
- 假设 `master-2` 的优先级更高,它转变为 MASTER 状态。
- `master-2` 的 `keepalived` 进程立即通过 Gratuitous ARP 在网络中声明 VIP `192.168.1.100` 现在归它所有。
- 网络交换机更新 ARP 表,将发往 VIP 的流量重定向到 `master-2`。
- `master-2` 上的 `haproxy` 开始接收流量。由于它的后端列表中已经排除了宕机的 `master-1`(通过健康检查),它只会将流量转发给健康的 `master-2` 和 `master-3` 上的 API Server。
- 对客户端而言,只经历了短暂的 TCP 连接中断,重试后即可恢复,整个故障转移完成。
核心模块设计与实现
理论结合实践,让我们看看关键的配置文件。这里没有魔法,全是工程细节。
极客工程师视角:Keepalived 配置 (`/etc/keepalived/keepalived.conf`)
这是决定 VIP 漂移行为的核心。三台 Master 节点的配置大同小异,主要区别在于 `state` 和 `priority`。
global_defs {
# 高可用集群中的唯一标识
router_id K8S_MASTER
}
# VRRP 实例定义
vrrp_instance VI_1 {
# master-1 上为 MASTER, 其他为 BACKUP
state MASTER
# 绑定 VIP 的物理网卡
interface eth0
# 虚拟路由 ID,同一组必须相同
virtual_router_id 51
# 优先级,MASTER > BACKUP。master-1: 102, master-2: 101, master-3: 100
priority 102
# 心跳间隔,单位秒
advert_int 1
# 认证信息,防止网络中有其他 VRRP 组冲突
authentication {
auth_type PASS
auth_pass 1111
}
# 定义虚拟 IP
virtual_ipaddress {
192.168.1.100/24
}
# (可选但推荐) 当高优先级的节点恢复后,不抢占 VIP,避免网络抖动
# nopreempt
}
坑点分析:
- `virtual_router_id`:必须在同一个二层网络中唯一,否则会造成混乱。
- `priority`:必须为每个节点设置不同的优先级,这是选举的基础。
- `nopreempt`:在 `BACKUP` 节点的配置中建议加上。如果没有它,当原来的 `MASTER` 节点恢复后,会因为优先级更高而立即抢占 VIP,导致一次不必要的流量切换,造成网络抖动。`nopreempt` 意味着“只要当前的 MASTER 还活着,即使我的优先级比你高,我也不抢”。
极客工程师视角:Haproxy 配置 (`/etc/haproxy/haproxy.cfg`)
这是四层负载均衡的核心。所有节点的配置可以完全一样。
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
retries 3
timeout connect 5s
timeout client 1m
timeout server 1m
frontend kube-apiserver
bind 192.168.1.100:6443
mode tcp
default_backend kube-apiserver-backend
backend kube-apiserver-backend
mode tcp
balance roundrobin
# 启用 TCP 级别的健康检查
option tcp-check
server master1 192.168.1.11:6443 check fall 3 rise 2
server master2 192.168.1.12:6443 check fall 3 rise 2
server master3 192.168.1.13:6443 check fall 3 rise 2
坑点分析:
- `bind 192.168.1.100:6443`:Haproxy 监听的是 VIP。但 VIP 是动态漂移的,一个进程如何能监听一个可能还不存在于本机的 IP 地址?这里需要设置内核参数 `net.ipv4.ip_nonlocal_bind = 1`,允许进程绑定不属于本地的 IP 地址。这是一个非常关键且容易被忽略的配置点。
- `mode tcp` vs `mode http`:API Server 之间的通信是基于 HTTPS 的 gRPC。使用 `mode tcp` 进行四层转发性能最高,也最简单。如果使用 `mode http`,Haproxy 需要进行 TLS 终止和七层解析,配置复杂且没有必要,反而会成为性能瓶颈。
- `balance roundrobin`:轮询是最简单的负载均衡算法,对于无状态的 API Server 来说完全适用。
- `check fall 3 rise 2`:表示连续 3 次健康检查失败,就将该后端标记为不可用;之后,如果连续 2 次检查成功,再重新恢复。这是一种防抖动机制,避免因瞬时网络问题导致后端服务被频繁摘除和加入。
性能优化与高可用设计
这套架构看似完美,但在大规模或对延迟极度敏感的场景下,仍有诸多权衡(Trade-off)。
对抗与权衡:
- 故障恢复时间(RTO):整个故障转移的时间 = Keepalived 的心跳检测超时 + 选举时间 + Gratuitous ARP 生效时间。通常在 3-5 秒左右。这个时间对于控制平面是可接受的。但如果你的业务是高频交易,这是无法容忍的。缩短 `advert_int` 可以降低检测时间,但会增加网络中的 VRRP 广播流量。
- Haproxy 瓶颈与 SPOF:虽然我们有多个 Haproxy 实例,但在任意时刻,只有一个实例(位于 Keepalived MASTER 节点上)在工作。它是否会成为性能瓶颈?Haproxy 在 TCP 模式下性能极高,通常能处理数十万并发连接,瓶颈几乎总是在后端的 API Server 或 etcd。但它确实是一个逻辑上的单点,如果 Haproxy 进程自身异常(而 Keepalived 进程正常),VIP 不会漂移,服务依然会中断。为此,可以配置 Keepalived 的 `vrrp_script` 来监控 Haproxy 进程的健康,如果进程不存在就主动降低自身优先级,触发 VIP 切换。
- 源 IP 地址丢失:由于 Haproxy 是一个代理,API Server 的审计日志中看到的客户端源 IP 将是 Haproxy 所在节点的 IP,而不是真实客户端的 IP。这对于安全审计是致命的。解决方案是启用 PROXY Protocol。在 Haproxy 的 `server` 配置行末尾加上 `send-proxy-v2`,同时 API Server 也需要通过启动参数 `–enable-proxy-protocol=true` 来开启支持。PROXY Protocol 会在 TCP 连接的初始数据中插入一个包含了真实源 IP 的头部。这是一个协议层面的约定,需要两端都支持。
– 健康检查的深度:`tcp-check` 只能检查端口是否可达。如果 API Server 进程虽然存活,但内部逻辑卡死,无法服务请求(例如,与 etcd 的连接断开),`tcp-check` 无法发现。更精确的方式是使用七层健康检查,例如请求 API Server 的 `/healthz` 端点。但这需要在 Haproxy 中配置 `option httpchk GET /healthz`,并且处理 TLS 证书问题,复杂度显著增加。通常,我们会接受 `tcp-check` 的局限性,依赖 Kubernetes Pod 本身的存活探针(liveness probe)来重启异常的 API Server 进程。
架构演进与落地路径
没有一种架构是万能的。`Keepalived + Haproxy` 是在私有化数据中心环境中久经考验的“标准答案”,但它不是唯一的,也不是终极的。架构师的职责是根据场景选择最合适的方案。
第一阶段:DNS 轮询(不推荐用于生产)
- 做法:为多个 Master 节点的 IP 配置同一个 DNS A 记录。
- 优点:无需额外组件,配置最简单。
- 缺点:故障转移能力极差。客户端和中间网络设备会缓存 DNS 解析结果。当一台 Master 宕机后,仍有大量请求被发送到无效 IP,直到缓存过期。这在生产环境中是灾难性的。
第二阶段:Keepalived + Haproxy(私有化部署标配)
- 做法:本文详细阐述的方案。
- 优点:可靠性高,故障转移自动化,技术成熟,社区资源丰富。
- 缺点:依赖二层网络广播(Gratuitous ARP),在某些严格网络策略的云环境或 SDN 环境中可能受限。存在逻辑上的单点负载均衡器。
第三阶段:云厂商托管方案(公有云首选)
- 做法:使用云厂商提供的负载均衡器服务,如 AWS 的 NLB/ELB,阿里云的 SLB。将所有 Master 节点作为后端服务器加入到负载均衡器中。
- 优点:负载均衡器自身具备极高的可用性,无需自行维护。与云生态深度集成,配置简单,运维成本低。
- 缺点:厂商锁定,产生额外费用。
第四阶段:BGP + ECMP(大规模数据中心终极方案)
- 做法:使用 MetalLB 等项目,让 Kubernetes 集群中的节点通过 BGP 协议向物理路由器宣告(Announce)一个或多个 VIP。物理路由器开启 ECMP(Equal-Cost Multi-Path)功能,将发往该 VIP 的流量哈希到所有宣告了该路由的节点上。
- 优点:实现了真正的多活负载均衡,没有单点瓶颈。流量路径更优,性能更高。
- 缺点:对网络基础设施有要求(需要支持 BGP),需要网络团队深度配合,配置和排错复杂度远高于前几种方案。这是面向大规模、高性能场景的屠龙之技。
总而言之,为 Kubernetes API Server 构建高可用负载均衡体系,是一个从基本原理到工程实践的系统性工程。理解 ARP、VRRP 和 TCP 代理的底层机制,才能在面对复杂的配置和诡异的网络问题时游刃有余。而`Keepalived + Haproxy` 方案,正是这座连接理论与实践、通向生产级稳定的坚实桥梁。
延伸阅读与相关资源
-
想系统性规划股票、期货、外汇或数字币等多资产的交易系统建设,可以参考我们的
交易系统整体解决方案。 -
如果你正在评估撮合引擎、风控系统、清结算、账户体系等模块的落地方式,可以浏览
产品与服务
中关于交易系统搭建与定制开发的介绍。 -
需要针对现有架构做评估、重构或从零规划,可以通过
联系我们
和架构顾问沟通细节,获取定制化的技术方案建议。