1. 先把话说透:DR 模式到底省在哪,代价又是什么
很多人的 LVS 集群是"配通即止",能 ping 通、能出页面,就当交付了。真正上量之后才发现——吞吐上不去、RS(真实服务器)会莫名抢走 VIP、某个节点重启后整个集群抖半天。这些问题在 DR 模式下格外集中,因为 DR 的优雅和它的脆弱,来自同一个设计:它靠改 MAC 地址来转发,靠抑制 ARP 来维持秩序。只要有一台机器的内核参数没对齐,整套机制就会散架。
这一篇是 DR 模式部署的第二部分,不去重复第一部分的拓扑画法和命令照抄,而是把那些"配上了但不理解"的东西拎出来讲清楚:VIP 为什么必须绑在 loopback 上而不是 eth0 上,arp_ignore和arp_announce那两个数字到底该怎么推,为什么开了ip_forward反而可能给你添麻烦,以及rp_filter这个跟 LVS 八竿子打不着的安全参数,是怎么把你的回包悄悄丢掉的。
这篇内容适合三类人:一是刚接手一套 LVS 环境、需要读懂别人留下的配置的运维;二是准备把业务从四层反向代理迁到 LVS 的架构同学;三是已经在用 DR 模式、但被"负载不均""访问超时""节点抢 VIP"折磨过,想搞清楚底层逻辑的老手。文章里的命令和参数都可以直接抄,但我会把每一个参数背后的判断过程写出来,因为照抄能跑通,理解才能排障。
1.1 三种模式的本质差异,决定你该选哪个
LVS 的三种转发模式,本质上是在回答同一个问题:客户端的请求包到了调度器,怎么让 RS 收到它,并且让 RS 的回包能原路回到客户端?
NAT 模式最直白:调度器把目的 IP 从 VIP 改成 RS 的 IP,回包再从 RS 回到调度器做源地址转换。代价是调度器成为双向流量的瓶颈,出口带宽和 CPU 都压在它身上,RS 的默认网关必须指向调度器,跨网段部署时扩展性很差。
TUN 模式玩的是 IP 隧道:调度器把原始 IP 包整个封装进一个新的 IP 包里发给 RS,RS 拆封后看到"客户端的包原封不动",于是能直接把回包发出去。优点是调度器和 RS 可以跨网段,缺点是 RS 必须支持隧道解封装,很多设备不支持,而且多了一层封装开销,MTU 也要重新算。
DR 模式最"物理":调度器不碰 IP 头,只把数据帧的目的 MAC 地址改成某台 RS 的 MAC,然后直接扔到同一个二层网络里。RS 收到这个帧,发现目的 IP 是自己的 VIP,于是接受这个连接;回包时,RS 发现源 IP 是 VIP,而这个 VIP 就长在自己的 loopback 上,等于"自己发自己",于是直接把回包从 eth0 送出去,不再经过调度器。
差异用一句话概括:NAT 是两条腿走路(请求和响应都经过调度器),TUN 是包了个信封,DR 是只做定向投递。
这也解释了 DR 模式的硬约束——调度器和所有 RS 必须在同一个广播域(同一个 VLAN、同一个交换机下)。跨网段?不行。RS 上面跑的是云主机、宿主机做了 MAC 层隔离?也不行。这是很多人从机房环境平移到云环境里踩的第一个坑:云平台的二层网络通常是软件模拟的,MAC 层的自由改写不一定被允许,甚至会被安全组或网络策略直接拦截。
1.2 DR 的报文路径与那个绕不开的 ARP 矛盾
把报文路径拆开看,是一次请求要经过三个清晰的阶段:
- 客户端发起请求,目的 IP 是 VIP。因为 VIP 配在调度器的物理网卡上,并且对外暴露了 ARP 应答,客户端的 ARP 请求拿到的是调度器的 MAC,于是帧的目的 MAC 是调度器的网卡 MAC。
- 调度器收到帧,内核的 IPVS 模块在内核态就完成了"改目的 MAC"这个动作,然后把帧重新丢回网卡发出去。注意:这一步 IP 头完全没变,源 IP 还是客户端 IP,目的 IP 还是 VIP。
- 帧到达 RS。RS 的 eth0 收到一个目的 IP 是 VIP 的包,而此时 VIP 正绑定在 RS 的 loopback 上,所以内核认为"这是发给我的",交给本地服务处理。服务处理完直接构造回包,源 IP 是 VIP,目的 IP 是客户端 IP,走 eth0 出去。
到这里,矛盾就出现了:回包从 RS 的 eth0 出去,但源 IP 是 VIP。如果 VIP 在网段里被别人 ARP 出来了,交换机就会把这个回包引到 VIP 的持有者那里——而 VIP 本该只属于调度器。更糟的是,如果某台 RS 的 eth0 上挂了 VIP,它会对"VIP 是谁"这个 ARP 广播做出应答,于是交换机上的 MAC 表被污染,本该发给调度器的请求被打到某台 RS 上,请求进去以后没有 IPVS 规则,直接被丢弃或者被 RS 的本地服务接掉。
所以 DR 模式的核心纪律只有一条:VIP 可以被多台机器"认领"进来收包,但对外应答 ARP 的、以及让交换机知道它在这儿的,只能有一个——调度器。
RS 上要做的两件事,本质都是为了这条纪律服务:
- VIP 绑到 loopback 的 /32 地址上,让它"存在但不可见"——存在是为了让内核认下这个目的 IP,/32 掩码是为了不产生额外的路由和广播行为。
- 调整
arp_ignore/arp_announce,让 RS 只对"目标地址就是自己物理网卡地址"的 ARP 请求做出应答,绝不对 VIP 的 ARP 请求做出反应。
这套机制不涉及任何三层的地址转换,调度器只做二层重写,所以处理能力可以做到很高,单机几十万并发连接是常见水平。但它的代价也很明确:任何一个 RS 的内核参数漏配,都会变成一颗哑弹,只在流量真正打上来的时候才炸。测试时单机访问一切正常,因为你的测试流量可能刚好被调度到了那台配对的机器上。
2. 地址规划与内核参数:DR 模式的地基
配置 DR 之前,先把地址和参数在纸上写清楚。这一步做扎实,后面 80% 的诡异问题都不会出现。
2.1 拓扑与地址分配:把表先填好
假设一套典型结构:一台调度器(Director),三台 RS,都接在同一台交换机同一 VLAN 内。
| 角色 | 主机名代称 | 物理网卡地址 | 绑定的 VIP | 说明 |
|---|---|---|---|---|
| 调度器 | director-01 | 192.168.10.10/24 | 192.168.10.100/32(配在 eth0 上) | 对外唯一 ARP 应答者 |
| 真实服务器 A | rs-a | 192.168.10.11/24 | 192.168.10.100/32(配在 lo 上) | 抑制 ARP |
| 真实服务器 B | rs-b | 192.168.10.12/24 | 192.168.10.100/32(配在 lo 上) | 抑制 ARP |
| 真实服务器 C | rs-c | 192.168.10.13/24 | 192.168.10.100/32(配在 lo 上) | 抑制 ARP |
| 网关 | — | 192.168.10.1 | — | 三台 RS 的默认网关 |
几点说明,都是踩过之后才明白的:
VIP 的掩码。调度器上 VIP 挂在 eth0 上,掩码用 /24 或者 /32 都可以,取决于你是否希望这个 VIP 参与本网段的路由。如果 VIP 和调度器物理地址在同一网段,且不需要向外通告,用 /32 更干净。RS 上的 VIP 一定用 /32,因为它是 loopback 上的一个"本地地址",用大掩码会让内核认为整个网段都在 lo 上,路由表会乱掉。
调度器是否需要第二个 VIP 或多个 VIP。如果业务要用 80 和 443 两个端口,一个 VIP 加两条规则就够,不需要两个 VIP。只有当你需要按域名区分后端集群时,才考虑多 VIP 绑定到同一块网卡上。
默认网关。三台 RS 的网关必须指向真实路由设备,不能指向调度器。这是 DR 和 NAT 最容易被习惯性搞混的地方——写惯了 NAT 的人会下意识把 RS 的网关改到调度器上,结果回包走了调度器,调度器上没有对应的连接跟踪,包被丢弃,页面上表现为"请求发出去没回应"。
服务端口。假设对外提供 80 端口。那么三台 RS 上都必须有服务监听在 80。至于监听地址是 0.0.0.0 还是 VIP,两种都能工作,但含义不同:监听 0.0.0.0 意味着该端口对物理地址也开放,直连 RS 的 IP 也能访问到;监听 VIP 则更收敛,只能通过 VIP 进来。生产上我倾向于监听 0.0.0.0 但同时用主机防火墙把物理地址的入站挡掉,这样调试时还能本地直连,风险也可控。
2.2 VIP 为什么必须绑在 loopback 上
这是新手最容易做错的一步:把 RS 上的 VIP 直接ip addr add到 eth0 上。表面看也能用,实际上是把整个集群的正确性押在了运气上。
绑在 eth0 上会发生什么?内核认为这个 VIP 是本机的一个"接口地址",于是:
- 主机会对"谁是 VIP"的 ARP 请求做出应答,因为
arp_announce规则允许它为本地地址广播自己的 MAC。 - 交换机的 MAC 地址表会被多台机器同时争夺,谁最后应答谁赢,于是 VIP 的流量被随机引导到某一台 RS 上,调度器彻底失效。
- 更隐蔽的后果:某些型号的交换机会频繁刷新 MAC 表,导致在同一时刻,一部分客户端的流量走调度器,一部分直接打到 RS,表现成"部分用户访问正常,部分用户报错,过一会儿自己又好了"。
绑在 lo 上呢?loopback 是一个纯本地的虚拟接口,不属于任何物理二层域。内核把这个 VIP 当成"本机拥有的地址"来接收数据包,但因为它不在物理网卡上,默认不会为它做 ARP 应答(配合arp_ignore=1之后更是如此)。于是:
- 目的 IP 是 VIP 的包到了 eth0,内核一看"哦,这个地址在 lo 上,是我的",接收并交给上层。
- 没有任何 ARP 应答发出去,交换机只知道调度器持有 VIP。
- 回包从 eth0 出去,源 IP 是 VIP,二层是 RS 自己的 MAC,符合"回包不经过调度器"的路径要求。
一个很实用的记忆法:调度器上的 VIP 是"给外面看的",RS 上的 VIP 是"给自己看的"。给外面看的必须出现在物理接口上,给自己看的必须藏在 lo 上,这样它只参与本地接收判断,不参与网络宣告。
有个细节值得提醒:ip addr add 192.168.10.100/32 dev lo之后,用ip addr show lo能看到这个地址,但用ip route get 192.168.10.100查一下,返回的出口应该是 lo。如果你看到它走了 eth0,说明掩码写错了或者路由表里有多余条目,需要清理。
2.3 arp_ignore 与 arp_announce 的取值推演
这两个参数是 DR 模式的灵魂,但网上大量文章只给结论不给推理,导致换一个场景就不知道该怎么调。我们把它拆开。
arp_ignore控制的是收到 ARP 请求时,什么条件下才应答。取值 0 到 8,常用的三个:
| 取值 | 行为 | 在 DR 场景中的适用性 |
|---|---|---|
| 0 | 只要本机有任意一个接口拥有目标 IP,就应答 | 危险,会为 lo 上的 VIP 应答,绝不能用 |
| 1 | 只有当 ARP 请求的目标 IP 就是收到请求的那块网卡的地址时,才应答 | 标准做法 |
| 2 | 更严格,还要求源 IP 与目标 IP 在同一子网 | 通常没必要,可能影响网关通信 |
取 1 的含义很直接:我在 eth0 上收到"谁是 192.168.10.100"的询问,而我 eth0 的地址是 192.168.10.11,不是 .100,所以我不吭声。而 VIP 虽然在 lo 上存在,但因为不是 eth0 的地址,也不会触发应答。这正是我们要的效果。
arp_announce控制的是本机主动发 ARP 请求或应答时,源 IP 用哪个。取值也是 0 到 2:
| 取值 | 行为 | 在 DR 场景中的适用性 |
|---|---|---|
| 0 | 用任意接口上的任意地址 | 可能用 VIP 作为源地址发 ARP,污染网络 |
| 1 | 尽量避开不属于目标子网的源地址 | 折中方案 |
| 2 | 严格使用与目标 IP 在同一子网的那个接口地址 | 标准做法 |
取 2 的意义在于:RS 向网关发 ARP 或者对外发任何 ARP 请求时,源 IP 一定是 eth0 的物理地址,绝不会把 VIP 暴露出去。如果不配这一条,RS 在某些情况下(比如路由缓存刷新、或者 VIP 所在的 /32 被认为可达)会拿 VIP 作为源地址发广播,交换机学到"VIP 在这个 MAC 后面",集群就乱了。
配置方式上,all和default两个路径都要写,因为内核在查找参数时的优先级是"接口 > all > default",只写一个容易在某些接口上不生效:
cat >> /etc/sysctl.conf <<'EOF' net.ipv4.conf.all.arp_ignore = 1 net.ipv4.conf.all.arp_announce = 2 net.ipv4.conf.default.arp_ignore = 1 net.ipv4.conf.default.arp_announce = 2 net.ipv4.conf.lo.arp_ignore = 1 net.ipv4.conf.lo.arp_announce = 2 net.ipv4.conf.eth0.arp_ignore = 1 net.ipv4.conf.eth0.arp_announce = 2 EOF sysctl -p注意:
sysctl -p之后再新建的网卡(比如后续加了 bonding 或者 VLAN 子接口)会继承default的设置,所以default那两行不能省。
一个实测心得:配完之后不要只用sysctl -a看数值,那只能证明参数写进去了。真正的验证方法是,在另一台同网段机器上执行arping -I eth0 192.168.10.100,看有几台机器应答。正确的结果应该只有一台——调度器。如果有两台以上应答,说明有 RS 的参数没生效,这时候再回到那台机器上查,通常是因为ip addr add加的时候用了dev eth0,或者sysctl -p之后又手动改过参数没重载。
2.4 被忽略的 rp_filter 与 ip_forward
这两个参数严格说不是 LVS 的配置,但它们能让你排查一整天。
rp_filter(反向路径过滤)是内核的一个安全特性:收到一个包时,反过来查路由表,看看"如果我要发包给这个源 IP,会不会从收到这个包的接口出去"。如果不会,就认为这个包是伪造的,直接丢弃。
在 DR 模式下,RS 收到的包长这样:从 eth0 进来,源 IP 是客户端(比如 203.0.113.50),目的 IP 是 VIP。内核查路由表,"我要发到 203.0.113.50,出口是哪?"——是网关,也就是 eth0。看起来对得上,一切正常。
但问题出在回包阶段。RS 回包的源 IP 是 VIP,从 eth0 出去。有些配置下(特别是多网卡或者 VIP 绑在 lo 上且路由表里有明细路由时),内核在转发检查时可能认为"VIP 是本机地址,不该从 eth0 出去",于是把包拦下来。表现就是:RS 上服务日志显示连接已被接受、请求已处理,但客户端收不到任何响应。
处理方式很简单,把严格模式改成宽松模式或者关闭:
sysctl -w net.ipv4.conf.all.rp_filter=0 sysctl -w net.ipv4.conf.default.rp_filter=0 sysctl -w net.ipv4.conf.eth0.rp_filter=0 sysctl -w net.ipv4.conf.lo.rp_filter=0提示:如果主机是某发行版的默认安装,
all.rp_filter和eth0.rp_filter的取值可能不同,实际生效的是两者的较大值。所以四个路径都要设成 0,别只改all。
至于net.ipv4.ip_forward——DR 模式不需要开。这是个反直觉的点。因为 DR 模式里调度器做的事情是"改 MAC 再发出去",看起来像转发,实际上这是 IPVS 在内核里完成的,走的是自己的钩子函数,跟内核的 IP 转发路径是两条线。开了ip_forward=1不会让 DR 更"能转发",反而可能让调度器在收到一些目的地址不是自己的包时,尝试去路由转发,引入不必要的复杂性。
真正需要ip_forward=1的是 NAT 模式和 TUN 模式。DR 模式下,如果调度器同时还是某个内网的路由器,那是另一回事,两者应该分开评估。
3. Director 与 RS 的完整落地配置
前面是地基,这一段开始砌墙。配置顺序建议是:先在 RS 上把 VIP 和内核参数搞定,再在调度器上加规则。反过来的话,规则一加,流量就打进来,RS 还没准备好,抓包会看到一堆 SYN 重传,容易误判成网络问题。
3.1 内核模块与 ipvsadm 的安装
IPVS 是内核里的功能,取决于内核编译选项。主流的发行版内核基本都带ip_vs模块,但不一定默认加载。先确认:
lsmod | grep ip_vs modprobe ip_vs modprobe ip_vs_rr cat /proc/net/ip_vsip_vs_rr是轮询调度算法模块,如果你后面要用加权最少连接(wlc)、源地址哈希(sh)等,对应的模块是ip_vs_wlc、ip_vs_sh,一般会在ipvsadm添加规则时自动按需加载。但如果内核裁剪过模块,就得手动modprobe。
cat /proc/net/ip_vs能看到一个头部信息,包含当前版本、连接表大小等。这个文件是后面调优时会反复看的地方。
安装管理工具:
# 以 RPM 系为例 yum install -y ipvsadm # DEB 系 apt install -y ipvsadm安装完不要急着加规则,先确认ipvsadm -Ln输出为空表头即可,说明内核接口通了。
3.2 调度器端:VIP、转发规则与调度算法选择
调度器上第一件事是把 VIP 挂到物理网卡:
ip addr add 192.168.10.100/32 dev eth0 ip addr show eth0 | grep 192.168.10.100用 /32 而不是 /24,是因为这个 VIP 不需要参与子网路由计算,它只是一个对外标识。如果你希望它和物理地址一样能 ping 通整个网段,用 /24 也行,但要确认路由表里没有产生多余的网段路由。
接着添加虚拟服务和真实服务器:
# 添加虚拟服务:VIP:80,调度算法用加权轮询 ipvsadm -A -t 192.168.10.100:80 -s wrr # 添加三台 RS,-g 表示 DR 模式(gatewaying),-w 是权重 ipvsadm -a -t 192.168.10.100:80 -r 192.168.10.11:80 -g -w 5 ipvsadm -a -t 192.168.10.100:80 -r 192.168.10.12:80 -g -w 3 ipvsadm -a -t 192.168.10.100:80 -r 192.168.10.13:80 -g -w 2 # 查看结果 ipvsadm -Ln三个参数要说明白:
-g就是 DR 模式,全称 gatewaying,老文档里会写成"直接路由"。对应的-m是 NAT,-i是 TUN。这个字母记混是很常见的失误,配错了以后表现是"规则存在但流量路径不对",用ipvsadm -Lnc看连接状态能发现端倪。
-w是权重,只在加权类算法下生效。权重不是随便填的,它应该反映机器的实际处理能力。我的做法是:先用同样的配置跑一轮压测,记录每台机器在 CPU 打到 70% 时的 QPS,然后按 QPS 比例设权重。上面 5:3:2 的例子对应的是三种不同规格的机器,如果三台配置完全一致,就全填 1,用wrr等价于rr。
-s是调度算法,选型逻辑大致是这样:
| 算法 | 适用场景 | 不适用场景 |
|---|---|---|
| rr(轮询) | 后端处理能力一致、请求耗时均匀 | 请求耗时有长尾 |
| wrr(加权轮询) | 后端规格不同,需要按能力分配 | 长连接业务 |
| lc(最少连接) | 请求耗时差异大 | 短连接高频,连接数抖动剧烈 |
| wlc(加权最少连接) | 规格不同 + 耗时差异大,默认首选 | 极端长尾 |
| sh(源地址哈希) | 需要天然会话保持,且后端有本地缓存 | 客户端集中在少数 NAT 出口后 |
| sed / nq | 特定场景,如希望请求均匀分散到不同节点 | 一般业务用不上 |
lc和wlc是很多场景下的稳妥选择,因为它按"当前活动连接数"来分,天然适应耗时不均的请求。但要注意,如果后端有慢查询导致连接堆积,lc会把新请求全部导向"看起来空闲"的机器,而那些机器可能正处在慢查询的临界点上,容易引发雪崩。这时候反而是wrr更可控。
sh(源地址哈希)要慎用。它把同一个源 IP 的请求固定到同一台 RS,看起来像"会话保持",但在客户端大量集中在少数出口(比如企业统一出口、运营商 NAT)的情况下,会导致严重的倾斜——一个出口 IP 的所有用户全打到一台机器上。这类问题的特征是"整体 QPS 不高,但某一台 RS 的 CPU 打满",很容易被误判成程序 bug。
3.3 RS 端:服务监听与 lo 上的 VIP
RS 上的配置顺序,我习惯按这个来:
第一步,绑定 VIP 到 lo:
ip addr add 192.168.10.100/32 dev lo第二步,写内核参数(前面 2.3 那段),执行sysctl -p。
第三步,确认服务监听。以常见的 Web 服务为例:
ss -lntp | grep ':80'应该能看到监听在0.0.0.0:80或*:80。如果是127.0.0.1:80,那外部流量进不来,必须改配置。
第四步,做本地验证。这一步很关键,很多人跳过:
curl -s -o /dev/null -w '%{http_code}\n' http://192.168.10.100/在 RS 本机上用 VIP 访问自己,能返回 200 就说明"VIP 在 lo 上被内核认下来了,服务也在正常监听"。这一步不通,后面的所有配置都是白搭。
第五步,顺手确认一下 RS 的默认网关:
ip route show default必须是真实网关,不是调度器。这一条我在前面强调过,但现场还是经常遇到——因为很多人的第一套 LVS 是照着 NAT 的教程配的,网关那一行忘了改回来。
还有一个容易被忽略的点:RS 上的防火墙。如果启用了主机防火墙,要注意放行的不只是80端口,还有 VIP 这个地址的入站。有些防火墙规则是按目的地址写的,只放行了192.168.10.11没放行192.168.10.100,结果就是"直连 IP 能通,走 VIP 不通"。这个现象和 DR 配置错误的表象一模一样,排查时很容易走偏。
3.4 三层验证法:从规则表到抓包
配完之后不要急着用浏览器试,按三层顺序验证,出问题能立刻定位到哪一层。
第一层:规则表对不对。
ipvsadm -Ln --stats输出里能看到每个虚拟服务下挂的 RS、权重、当前的连接数和累计的包数、字节数。如果包数一直是 0,说明流量根本没到 IPVS 这一层,问题在调度器的网络或者 VIP 绑定上。
第二层:连接表有没有建起来。
ipvsadm -Lnc这个命令显示当前的连接跟踪条目,能看到源 IP、目的 IP、状态(ESTABLISHED / SYN_RECV 等)。如果能看到SYN_RECV状态一直挂着不变成ESTABLISHED,几乎可以确定是 RS 的回包没有回到客户端——要么 RS 的 ARP 没抑制住导致回包路径错乱,要么 RS 的网关配错,要么rp_filter把包丢了。
第三层:抓包看报文路径。
# 调度器上,抓 VIP 相关的包 tcpdump -i eth0 -nn 'host 192.168.10.100 and port 80' -c 20 # RS 上,同时抓 tcpdump -i eth0 -nn 'host 192.168.10.100 and port 80' -c 20正常的抓包结果应该是这样的:客户端发 SYN 到 VIP,调度器收到后把 SYN 转发出去(你在调度器的抓包里看到入和出各一份,且出方向的 MAC 是 RS 的),RS 收到 SYN 后回 SYN-ACK,这个 SYN-ACK 直接从 RS 的 eth0 发出去,源 IP 是 VIP、目的 IP 是客户端,之后是 ACK 和 HTTP 请求。
如果你在 RS 上看到 SYN 进来了,但没有 SYN-ACK 出去,问题在 RS 本地(监听地址、防火墙、rp_filter)。如果 RS 上看到 SYN-ACK 出去了,但客户端没收到,问题在 RS 的网关或者交换机上的 MAC 表。这两类问题在表象上都是"访问超时",但排查方向完全不同。
4. 让配置活下来:持久化与 keepalived 接管
用ipvsadm手敲的规则,重启一次就没了。这是新手最容易在"部署成功"之后翻车的地方——机器一重启,集群无声无息地停摆。
4.1 ipvsadm 规则为什么活不过重启
因为 IPVS 的规则表存在内核内存里,没有任何守护进程负责在开机时重建它。ipvsadm只是一个操作内核表的命令行工具,它本身不是服务,不具备"运行在后台维护状态"的能力。
基础的持久化方案是用ipvsadm-save和ipvsadm-restore配合开机脚本:
# 保存当前规则 ipvsadm-save -n > /etc/sysconfig/ipvsadm # 开机恢复(RPM 系) systemctl enable ipvsadm systemctl start ipvsadm # 手动恢复验证 ipvsadm-restore < /etc/sysconfig/ipvsadm ipvsadm -Ln-n参数表示保存时用数字形式输出 IP,不做反向解析。不加的话保存出来的文件里可能是主机名,恢复时如果 DNS 有问题就解析不了。这个小细节在生产环境里救过我一次——有次机房 DNS 抖动,重启后规则恢复失败,日志里全是解析超时,一看文件里存的是主机名。
这套方案解决了"规则丢失",但解决不了"RS 挂了怎么办"。规则还在,流量还是照分过去,客户端就报错。要做到自动摘除故障节点,就得上 keepalived。
4.2 keepalived 的 DR 模式配置模板
keepalived 做两件事:一是管理 VIP 的漂移(高可用),二是管理 IPVS 规则和 RS 的健康检查(这才是它的高价值部分)。
一份可以直接改的配置骨架:
vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 150 advert_int 1 authentication { auth_type PASS auth_pass LvsDemo2024 } virtual_ipaddress { 192.168.10.100/32 dev eth0 } } virtual_server 192.168.10.100 80 { delay_loop 6 lb_algo wrr lb_kind DR persistence_timeout 0 protocol TCP real_server 192.168.10.11 80 { weight 5 TCP_CHECK { connect_timeout 3 retry 3 delay_before_retry 3 connect_port 80 } } real_server 192.168.10.12 80 { weight 3 TCP_CHECK { connect_timeout 3 retry 3 delay_before_retry 3 connect_port 80 } } real_server 192.168.10.13 80 { weight 2 TCP_CHECK { connect_timeout 3 retry 3 delay_before_retry 3 connect_port 80 } } }几个参数背后的取舍:
lb_kind DR对应前面的-g,写错了 keepalived 会用别的模式起规则,表象是"规则加上了但不通"。
delay_loop 6是健康检查的轮询间隔,单位秒。这个值决定了从 RS 挂掉到被摘除的最长时间。它是delay_loop加上connect_timeout × retry的量级,也就是 6 + 3×3 = 15 秒左右。如果你的业务对故障切换极敏感,把这个值调小,比如delay_loop 3、retry 2、connect_timeout 2,能压到 7 秒左右。但别调得太激进,否则网络抖动时会造成误摘除,反复上下线比不下线还糟。
persistence_timeout是会话保持时间。设成 0 表示不做保持,同一个客户端的连续请求可能打到不同的 RS。对于无状态服务这是最优的;对于有登录态的服务,需要考虑是否要设成 300 之类。但设了之后要注意:保持期间,同一源 IP 的所有请求都压在同一台机器上,会破坏负载均衡。我一般的做法是,能在应用层解决会话共享(比如把 session 放到统一的缓存里)就不在四层做保持,四层的保持是个补丁,不是方案。
TCP_CHECK是最简单的健康检查,只做 TCP 握手。它判断不了"服务进程在但逻辑已死"的情况。如果后端是个容易假死的服务,可以升级成HTTP_GET带状态码判断:
HTTP_GET { url { path /healthz status_code 200 } connect_timeout 3 retry 3 }提示:
HTTP_GET会让 keepalived 定期对每台 RS 发起真实的 HTTP 请求。如果你的访问日志是按 IP 统计的,会看到大量来自调度器的健康检查记录,记得在日志配置里过滤掉,否则日志量和统计都会被污染。
4.3 健康检查参数怎么定:一次算清楚
这套参数没有标准答案,但可以按"最大可容忍故障时间"倒推。
假设业务要求:单台 RS 挂掉后,最多 10 秒内必须停止向它分发流量。那么:
delay_loop + connect_timeout × retry ≤ 10取connect_timeout = 2、retry = 3,则delay_loop ≤ 4。
再考虑反面:假设网络存在偶发抖动,持续 1 秒左右。如果connect_timeout = 1、retry = 1,那么一次抖动就会导致摘除,误判率很高。所以retry至少要 3,connect_timeout至少 2(一次 TCP 握手在局域网内通常远小于 100ms,2 秒是很宽松的上限)。
两者权衡下来,delay_loop 4、connect_timeout 2、retry 3是一组比较平衡的值:最坏 10 秒摘除,同时能容忍 2 秒以内的抖动 3 次以内不误判。
还有一点:delay_before_retry 3是重试之间的间隔。它不参与上面的最坏时间计算(因为一旦第一次失败就开始倒计时,重试间隔只影响总耗时的上限),但设太小会导致重试扎堆,瞬间对已经过载的 RS 造成额外压力。3 秒是个合理的值。
4.4 双机主备与脑裂:DR 模式的高可用怎么做才不出事
单调度器是个明确的单点。标准做法是两台调度器做 VRRP 主备,同一时刻只有一台持有 VIP 并对外应答 ARP。
配置上,两台机器的vrrp_instance里,virtual_router_id必须一致,priority一高一低(比如 150 和 100),state一台 MASTER 一台 BACKUP。authentication的密码两边一致,这是防止别人误加入这个 VRRP 组的最小防护。
这里有个和 DR 模式本身强相关的问题值得展开:两台调度器都在同一 VLAN,VIP 在 VRRP 切换的瞬间会发生什么?
答案是:切换时,新的 MASTER 会发送免费 ARP,通告"VIP 现在在我这儿"。交换机的 MAC 表随之更新,新流量导向新的调度器。但已经建立的长连接不会迁移——它们的报文本来直接由 RS 回给客户端,回包路径不受影响,但客户端后续的请求包会打到新调度器上,而新调度器上没有这些连接的状态(如果没开连接同步的话),会被当成新连接处理。表现为:用户正在上传一个大文件,切换后上传中断。
解决方式是开启lvs_sync_daemon_interface,让两台调度器同步连接状态:
vrrp_instance VI_1 { # ... 其他配置 lvs_sync_daemon_interface eth0 garp_master_delay 5 }并且在全局配置里设置syncid,两台机器一致:
global_defs { lvs_sync_daemon_interface eth0 }连接同步能缓解长连接中断,但它本身也会产生网络开销,而且不是 100% 可靠(同步有延迟)。所以真正的建议是:不要把切换当成无损操作,业务侧要有重试机制。
至于脑裂——两台机器都认为自己该持有 VIP,同时对外应答 ARP。原因通常是 VRRP 心跳断了(网线、交换机端口、防火墙拦了 VRRP 协议号 112),两台机器互相收不到对方的心跳,都切成 MASTER。后果是 VIP 在两个 MAC 之间反复漂移,流量乱序,业务大面积异常。
排查手段不复杂:在两台机器上同时执行ip addr show eth0 | grep 192.168.10.100,如果都有,就是脑裂了。临时止损是手动停掉一台的 keepalived。
预防手段有几个层次:
- 网络层:确认交换机没有对 VRRP 组播做限制,两台机器在同一 VLAN。
- 配置层:
advert_int别设得太小(1 秒是常规值,设成 0.1 秒对网络抖动更敏感)。 - 监控层:对"两台调度器同时持有 VIP"这件事做告警,这是最直接有效的一道防线。别指望人工发现,脑裂往往发生在凌晨。
5. 排障实录:那些配置正确却依然不通的情况
这一段是我这些年攒下来的问题清单,按现象归类,可以直接当速查表用。
5.1 访问不通的六种成因速查
| 现象 | 最可能的原因 | 验证方法 | 处理方式 |
|---|---|---|---|
| 浏览器一直转圈,最终超时 | RS 的rp_filter丢弃回包 | RS 上dmesg看是否有 martian source 记录 | 四个路径的rp_filter全部设为 0 |
| 部分用户正常、部分报错,且随时间变化 | RS 未抑制 ARP,VIP 被抢 | 另一台机器arping -I eth0 VIP看应答数量 | 补arp_ignore=1、arp_announce=2,重新ip addr add dev lo |
| 规则表里连接数一直是 0 | 流量没到 IPVS,VIP 没配在物理网卡上 | 调度器ip addr show eth0 | 把 VIP 加到 eth0 |
| 连接能建立,请求发出去就断 | RS 默认网关指向了调度器 | RS 上ip route show default | 改回真实网关 |
| 直连 RS 的 IP 能通,走 VIP 不通 | RS 防火墙没放行 VIP 地址 | iptables -L -n -v看是否有 DROP 计数增长 | 放行 VIP 的入站或直接关掉主机防火墙测试 |
| 间歇性 502 / 连接重置 | 后端服务处理慢,被健康检查误摘 | 对比 keepalived 日志与业务日志时间点 | 放宽connect_timeout、retry |
表格里的第二条值得多说一句。ARP 抢 VIP 这个问题最阴险的地方在于它是概率性的。谁的免费 ARP 后发出来,交换机就认为 VIP 在谁那儿。而免费 ARP 的触发时机可能是机器重启、网卡 up/down、甚至内核的邻居表老化刷新。所以你可能会遇到"集群跑了三个月没事,某天凌晨重启了一台 RS,第二天开始有零星报错"的情况。这类问题不要试图从业务日志里找规律,直接去网络层验证 ARP 应答。
5.2 负载不均与会话保持的取舍
负载不均的表象是ipvsadm -Ln --stats里三台 RS 的连接数差了一个数量级。原因通常有三类:
第一类:算法本身导致的。用了lc但后端有长连接(比如 WebSocket、数据库连接池),长连接会一直占着"活动连接"的计数,导致lc认为这台机器很忙,把新连接都推给别人,最后变成"一台机器养着一堆长连接,其他机器闲着"。这种场景下lc是错的,应该用wrr或者基于连接数以外指标的方案。
第二类:会话保持带来的。设了persistence_timeout 300,某几个大客户端的请求全部压在少数节点上。特征是"连接数分布不均,但活跃源 IP 数量很少的几个段特别集中"。
第三类:客户端网络结构导致的。用了sh源地址哈希,但客户端大量走同一个出口 IP。这类问题的特征非常明显:某一台 RS 的流量占比异常高,且分布的源 IP 数量极少。
三种情况的处理方向完全不同,所以先看源 IP 分布,再决定改什么。命令是:
ipvsadm -Lnc | awk '{print $4}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -20看当前连接里源 IP 的集中度。如果前几个 IP 占了大部分连接,那问题在会话保持或源地址哈希,跟调度算法无关。
5.3 高并发下的几个调优旋钮
默认参数是给通用场景准备的,流量上来之后需要动几个地方。
连接哈希表大小。内核 4.19 之后可以通过net.ipv4.vs.conn_tab_bits调整,取值 8 到 20,默认 12(即 4096 个桶)。桶太少会导致哈希冲突链变长,查找变慢,表现为高并发下新连接建立延迟升高。
估算方法是按峰值并发连接数的一半来定桶数:
目标桶数 ≈ 峰值并发连接数 / 2假设峰值 50 万并发,目标桶数 25 万,取 2 的幂就是 262144,即conn_tab_bits = 18。
sysctl -w net.ipv4.vs.conn_tab_bits=18这个参数只能在加载ip_vs模块之前设置,模块加载后再改无效,必须卸载重载。所以正确做法是写进/etc/sysctl.conf或者模块加载配置里,重启生效。修改前先确认模块能安全卸载——线上环境直接rmmod会中断所有现有连接。
# 检查当前值 cat /sys/module/ip_vs/parameters/conn_tab_bits连接超时。IPVS 有三类超时:TCP 已建立连接、TCP SYN 阶段、TCP FIN 阶段。默认通常是 900 秒、120 秒、120 秒。对于短连接业务,900 秒的已建立超时会导致连接表里堆积大量已经结束的条目,占用内存。可以适当调低:
ipvsadm --set 300 60 30三个数字依次是 TCP、TCPFIN、UDP 的超时秒数。调之前想清楚业务的最长请求耗时,别设得比它还短,否则长请求会被中途掐断。
网卡与中断。调度器只做二层重写,CPU 开销主要在多队列网卡的中断分发上。确认网卡开了多队列,ethtool -l eth0看 Combined 队列数是否和 CPU 核数匹配。如果只有 1 个队列,多核机器上会退化成单核处理,这个瓶颈在高 PPS 场景下非常明显,但很容易被忽略,因为 CPU 整体使用率看起来并不高(只有一两个核打满)。
MTU。DR 模式不改 IP 头,所以不存在封装导致的 MTU 缩小问题,这是它相对于 TUN 的一个优势。但如果链路上有其他设备(比如某些负载设备或者隧道),还是建议在 RS 和服务端确认一下 MTU 一致。不一致的表现是"小请求正常,大文件传输出错或卡住"。
5.4 一个真实场景的复盘
说一个我印象比较深的案例,涉及的是"配置全对,但集群就是不工作"。
现象是:调度器规则正常,三台 RS 参数都按要求配了,arping验证只有调度器应答,直连 RS 访问正常,但通过 VIP 访问全部超时。抓包显示 SYN 到了 RS,RS 也回了 SYN-ACK,但客户端收不到。
排查花了两个小时,最后发现是交换机上开了一项"源地址保护"(有些设备叫 IP 源防护、IP Source Guard),它的作用是防止终端伪造 IP。这项功能看到 RS 发出的包,源 IP 是 VIP(192.168.10.100),但这台机器的端口对应的合法 IP 是 192.168.10.11,判定为伪造,直接丢弃。
这个问题的性质是:LVS DR 模式的回包,从交换机的视角看,就是"源 IP 和端口位置不匹配"的包。它在三层是正确的,但和二层安全策略天然冲突。
处理方式有几种:一是在交换机上针对这三台 RS 的连接端口关闭该功能(需要网络侧配合);二是把 VIP 加到端口允许的源地址列表里;三是换一种转发模式。这类问题的排查难度在于,它不属于任何一台服务器的配置问题,你在服务器上怎么查都是对的,只有跟网络侧一起看交换机日志才能定位。
所以有一条经验值得写在最前面:部署 DR 模式之前,先跟负责交换机的同事确认一件事——这台交换机上有没有对源 IP 做校验的安全功能。有的话,提前加白名单,能省掉一整天。
6. 我个人在实际操作中的几点体会
配 LVS DR 这件事,命令本身不难,难在"知道自己在改什么"。我见过太多配置是从上一套环境复制过来的,arp_ignore、rp_filter那些参数原封不动,谁也说不清当初为什么这么设。等到出了事,只能靠翻文档猜。
我的建议是,每次部署完,花十分钟做三件事,把结果记在交付文档里:
第一,在另一台机器上arping一次 VIP,把"只有一台应答"这个结果截图存档。这是证明 ARP 抑制成功的唯一硬证据,别人接手时看一眼就懂。
第二,在调度器和一台 RS 上同时抓 20 个包,确认回包路径是"RS 直接出、不经过调度器"。这一条如果错了,性能优势等于没有,但很多人从来没验证过。
第三,把峰值并发连接数和conn_tab_bits的对应关系写进文档,说明为什么是这个值。下次扩容时,接手的人知道该不该调。
还有个小技巧分享:如果你不确定某个参数改动的效果,可以先在一台 RS 上改,然后只对那台 RS 开一个独立的虚拟服务(用一个临时 VIP 加一条规则指向它),用单独的客户端验证。这样不影响主业务的流量,验证完删规则即可。比起直接改全线配置再观察,这种方式回滚成本低得多,心理压力也小。
最后提一句关于"第(二)篇"这个定位。DR 模式的内容拆成两篇是合理的,第一篇讲怎么搭起来,第二篇讲怎么让它真的扛得住。但真正决定这套集群能不能长期少出问题的,往往不在 LVS 本身,而在边缘的那些东西——交换机策略、防火墙规则、内核安全参数、健康检查阈值。这些地方不出问题时毫无存在感,出了问题又极难归因。所以我的做法是,把它们当成"已知的必然风险"列成检查清单,每次扩容、每次重启、每次换机房,都过一遍。清单不长,但每一次都能拦住点什么。