1. 部署前的整体设计与网络规划
上一篇已经把LVS集群里DR模式(Direct Routing,直接路由)的报文路径和地址关系拆得差不多了,这篇不再重复原理,直接进机房落地。但我还是要在动手前把整体设计过一遍,因为DR模式的部署失败,十有八九不是命令敲错,是网络规划一开始就埋了雷。我见过太多人拿一台虚拟机当调度器、另外两台虚拟机当后端,克隆完之后只改了 IP,结果一压测只有一台在响应,折腾一整天以为是 LVS 配置问题,实际上是后端网卡在抢答 VIP 的 ARP 请求。
直接路由这个模式最核心的一句话:请求报文改的是二层目的 MAC,三层目的 IP 从头到尾没变。调度器收到客户端发往 VIP 的包,在内核里改掉以太网头,把目的 MAC 换成某台后端真实服务器(下面我都叫 RS)的网卡 MAC,然后从同一个网卡原路扔出去;RS 收到的报文目的 IP 是 VIP,而它自己把 VIP 配在了 loopback 上,所以内核认为是发给自己,交给业务进程处理;回包的时候,RS 发现源 IP 是 VIP、其实是本机地址,于是直接用默认网关把响应送回客户端,响应报文根本不经过调度器。这就是 DR 模式吞吐高的根源,调度器只承担入向流量的一半带宽压力。
也正是因为"只改 MAC 不改 IP",DR 模式有一个硬性前提:调度器和所有 RS 必须处在同一个二层广播域内。跨网段、跨 VLAN 的机器玩不了 DR,硬要玩就只能上隧道模式(TUN)或者 NAT,那是另外的坑。所以第一步不是装软件,是打开拓扑图,确认所有机器的eth0在同一个交换机、同一个子网里。
1.1 三层地址关系与网关指向的生死线
我用一套四台机器的模拟环境来说明,规模不大但五脏俱全:两台调度器做 keepalived 主备,两台 RS 跑 Web 服务,一台客户端做验证。地址规划如下表,这张表你最好在动手之前先在自己的文档里抄一遍,后面所有配置都以它为准。
| 角色 | 主机名 | 物理 IP(eth0) | VIP | 默认网关指向 | VIP 绑在哪个接口 |
|---|---|---|---|---|---|
| 调度器主 | lb-master | 192.168.10.10/24 | 192.168.10.100 | 192.168.10.1 | eth0 |
| 调度器备 | lb-backup | 192.168.10.20/24 | 192.168.10.100 | 192.168.10.1 | eth0 |
| 后端 1 | rs-web-1 | 192.168.10.11/24 | 192.168.10.100 | 192.168.10.1 | lo(回环) |
| 后端 2 | rs-web-2 | 192.168.10.12/24 | 192.168.10.100 | 192.168.10.1 | lo(回环) |
| 客户端 | client | 192.168.10.50/24 | 无 | 192.168.10.1 | 无 |
这张表里最容易被新手写错、写错之后又最难查的,是RS 的默认网关那一列。很多人脑中有一个直觉:既然是 LVS 集群,后端服务器当然应该把网关指向调度器,让流量"原路返回"。这个直觉在 NAT 模式下是对的,在DR模式下是灾难性的。原因很简单:DR 模式下 RS 的回包源 IP 是 VIP,目的 IP 是客户端,它需要走三层路由出去。如果把网关指向调度器,回包会先到调度器,调度器再查路由转给网关,等于响应流量白白绕了一圈,调度器瞬间变成双向瓶颈,DR 模式最大的性能优势当场归零;更麻烦的是,如果调度器本身没有开转发、或者反向路径过滤没关,回包会被直接丢掉,表现为"TCP 三次握手完成、请求也到了、浏览器就是一直转圈"。
所以记住这条线:RS 的默认网关必须指向真实的路由器(或者三层交换机),不能指向调度器。这一条我在交付文档里都是用红字写的。
1.2 为什么 VIP 要"藏"在 loopback 上
指定 VIP 放在哪个接口,是 DR 模式第二个反直觉的地方。调度器上 VIP 配在eth0上很正常,它本来就是对外提供服务的地址。但 RS 上,VIP 必须配在lo回环接口上,而且要配成/32掩码。
为什么?因为我们希望 RS 在处理回包时"认为 VIP 是自己的地址",这样内核才会把源 IP 填成 VIP,客户端才认得出这是它访问的那个地址。但与此同时,我们极度不希望RS 在二层网络上回应"谁是 192.168.10.100"的 ARP 请求。如果 RS 老老实实地回了 ARP,那么交换机的 MAC 地址表里,VIP 就会被学到最后回应的那台 RS 的端口上,此后客户端发往 VIP 的报文会直接打到 RS,把调度器彻底绕开。表现出来就是:集群刚配好的那一刻访问正常,过几分钟(ARP 缓存过期刷新)之后,所有流量都粘在某一台 RS 上,另外几台完全空转,调度器上的ipvsadm -Ln --stats显示连接数一直是零。
把 VIP 藏在lo上配合内核参数,就是让内核"认这个 IP,但不广播这个 IP"。具体怎么调,下一节展开讲。这里先建立这个认知:DR 模式的整套部署,核心矛盾就是"VIP 要能被本机承认,但不能被局域网看见",调度器除外。
1.3 装机阶段就该做完的几件小事
在装软件之前,有几件基础工作如果漏掉,后面排查时会让你怀疑人生。第一件事是关掉或者正确配置防火墙。调度器上要放行 VRRP 协议(协议号 112)和服务的端口;RS 上要放行业务端口,并且确认lo接口进来的流量不被丢弃。我一般不在生产上直接systemctl stop firewalld,而是用富规则精确放行,因为集群环境经常有安全基线扫描,服务被停了会被打回来。
第二件事是确认所有机器的SELinux状态。有些发行版默认开启 enforcing 模式,HTTP 服务监听在非标准端口或者读取自定义目录时会静默失败,日志里只有一句含糊的权限拒绝。我习惯先getenforce看一眼,如果是 Enforcing,就临时setenforce 0验证是不是它的问题,确认后再写策略,而不是一上来就永久关闭。
第三件事,把所有机器的时间同步做掉。keepalived 的 VRRP 心跳对时间不敏感,但你后面查日志、对抓包时间戳的时候,两三台机器时间差几十分钟会让人抓狂。NTP 配好,这是基本功。
2. 核心难点:ARP 抑制与内核参数调优
这一节是DR模式部署里真正见功力的地方。前面说的那些"只有一台机器响应"的玄学问题,根子全在 ARP 上。Linux 内核提供了两个参数来对付这个问题:arp_ignore和arp_announce。这两个名字极其不直观,但拆开看其实很朴素——一个是"别人问我地址,我要不要答",一个是"我要发问,用哪个地址当落款"。
arp_ignore的默认值是 0,含义是:只要我的任何一块网卡上配了这个 IP,别人来问我就答。这在一个正常主机上是合理的,但在 DR 模式的 RS 上就是灾难——VIP 在lo上,别人从eth0来问,默认行为照样回。把它设成 1,含义变成:只有当收到 ARP 请求的那个接口上确实配置了这个 IP时才回应。这下好了,从eth0进来问 VIP,而eth0上没有 VIP(VIP 在 lo 上),内核就不回。这正是我们要的效果。
arp_announce默认值 0,含义是不做任何限制,用任何本地地址当源地址发 ARP。设成 2,含义是:发送 ARP 时优先选择与目标 IP 在同一子网、且配置在发送接口上的地址。这个参数在 RS 上同样重要,因为 RS 在某些场景下会主动对外发起连接(比如健康检查、回调第三方接口),如果它的 ARP 通告里用 VIP 当源地址,同样会造成局域网内的地址混乱。设成 2 可以把这个可能性堵死。
2.1 参数到底该写在 all、default 还是具体接口上
这个问题在很多教程里含糊不清,我踩过坑。Linux 的网络参数有三个作用域:all、default、以及具体接口(比如eth0、lo)。三者的继承关系在不同内核版本上处理细节不完全一样,default是新建接口的模板值,all是伪接口、语义上更接近"全局"。真正生效的逻辑往往是取具体接口的值,接口没单独设过才往上找。
我不想在这里赌内核实现,最稳妥的做法是三处都写,成本只多两行。这也是我这些年一直用的写法:
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 EOF sysctl -p执行完之后一定要核对,不要以为sysctl -p没报错就万事大吉:
sysctl net.ipv4.conf.all.arp_ignore net.ipv4.conf.lo.arp_announce输出应该是= 1和= 2。如果某一项还是 0,说明配置文件里后面有同名的行把它覆盖了(sysctl.conf是后读覆盖前读),去grep一下确认唯一性。
2.2 顺序错了就白干:先调参数,再绑 VIP
这是我见过最高频的翻车点,值得单独拎出来说。如果你在 RS 上先把192.168.10.100/32绑到lo上,然后才去改arp_ignore,那么在这中间的一段时间里,内核会认为 VIP 是一个新上线的地址,按照常规流程主动向外发送免费 ARP(Gratuitous ARP),向全网宣告"192.168.10.100 在我这里"。交换机和同网段其他机器会立刻更新缓存,等几秒后你把参数改好,那些缓存也不会立刻消失,得等老化。
正确的顺序只有一条:先关嘴,再露脸。也就是先下发arp_ignore/arp_announce并确认生效,再去绑 VIP。完整的 RS 初始化脚本我写成这样:
#!/bin/bash set -e VIP=192.168.10.100 # 1. 如果之前误绑过,先摘掉 ip addr del ${VIP}/32 dev lo 2>/dev/null || true # 2. 先调内核参数 sysctl -w net.ipv4.conf.all.arp_ignore=1 sysctl -w net.ipv4.conf.all.arp_announce=2 sysctl -w net.ipv4.conf.default.arp_ignore=1 sysctl -w net.ipv4.conf.default.arp_announce=2 sysctl -w net.ipv4.conf.lo.arp_ignore=1 sysctl -w net.ipv4.conf.lo.arp_announce=2 # 3. 再绑 VIP 到回环,掩码必须是 /32 ip addr add ${VIP}/32 dev lo label lo:0 # 4. 确认 ip addr show lo:0说明几个细节。掩码必须写/32,不能写/24。如果写成/24,内核会认为192.168.10.0/24这个网段都能从lo直连到达,从而污染路由表,导致 RS 访问同网段其他机器的时候报文出不去,从 lo 上兜圈子。这个错误极隐蔽,症状是"本机 ping 不通网关,但 ping 外网域名可以"之类莫名其妙的现象。
label lo:0是给这个地址起个别名标签,好处是ifconfig时代能看到lo:0这样的虚拟接口,运维交接时一目了然。用ip addr也能看到lo:0的标注,方便别人接手。
2.3 用 arping 把"抑制生效"这件事验死
参数改完不是结束,必须验证。我会在两个位置验。
第一处是在RS 本机上,从物理网卡去问 VIP:
arping -I eth0 -c 3 192.168.10.100注意-I eth0这个参数是重点,它强制从物理网卡发出 ARP 请求,模拟的是"局域网里其他机器问 VIP"这个场景。如果抑制生效,你应该只收到调度器回的一个应答,arping输出的 MAC 是调度器eth0的 MAC。如果你看到两条应答、其中一条是本机eth0的 MAC,说明抑制没生效,回头查参数。
第二处是在客户端上,先清 ARP 缓存再问:
ip neigh flush dev eth0 arping -I eth0 -c 3 192.168.10.100这里得到的 MAC 必须、也只能是调度器的 MAC。我一般会把这条输出截图存到交付文档里,因为这是"ARP 抑制确实生效"最直接的证据。
还有一个更彻底的观察手段,在 RS 上挂着抓包,然后去客户端访问一轮:
tcpdump -i eth0 -nn arp and host 192.168.10.100正常情况下,你不应该看到 RS 主动发出 VIP 的 ARP 广播。如果看到了,那就是抑制没做对,或者业务进程有异常行为。
注意:某些云环境或者虚拟化平台(尤其是开了"混杂模式"或"MAC 地址欺骗防护"的)会在虚拟交换机层面做 ARP 代理,你在虚机里怎么调参数都没用,因为应答是平台代答的。遇到这种情况,要么找平台方关掉这个特性,要么放弃在这个环境里跑 DR 模式,改用其他方案。这一点在真机迁云的时候特别容易中招。
3. 调度器上的 LVS 配置实操
调度器这边的活相对清爽,因为不需要跟 ARP 斗智斗勇。我习惯先用ipvsadm手工把规则建起来,验证通了再交给 keepalived 托管。直接上 keepalived 也能跑,但出了问题你分不清是 LVS 层的问题还是 keepalived 层的问题,排查成本翻倍。
3.1 装软件与确认内核支持
LVS 的转发能力在内核里,用户态只需要一个管理工具。先确认内核模块在位:
modprobe ip_vs lsmod | grep ip_vs cat /proc/net/ip_vs/proc/net/ip_vs能读出内容,说明内核这块是通的。然后装工具,RHEL 系用yum install -y ipvsadm,Debian 系用apt install -y ipvsadm。装完ipvsadm -v看一眼版本就行。
顺带说一个经常被问的问题:DR 模式需不需要开net.ipv4.ip_forward?很多教程一上来就让你sysctl -w net.ipv4.ip_forward=1。从内核报文路径看,DR 模式的包是在LOCAL_IN钩子被 IPVS 截获,处理后经LOCAL_OUT重新封装发出,走的是"本机输入到本机输出"这条链路,并不经过FORWARD链,所以严格来说ip_forward不是 DR 模式的必选项。不过开了也不会坏事,而且很多环境里还有其他转发需求。我的做法是不纠结,按团队基线统一开或者统一不开,不要在集群里搞成一半开一半不开,那才是麻烦。
3.2 手工建一个 DR 模式的虚拟服务
假设我先只建一台 RS,验证单机能通,再加第二台。这是我一直推荐的节奏:先通一台,再加一台,最后才上高可用。三步走,每一步都有明确的验证点,出问题知道往哪缩。
第一步,建虚拟服务,算法用加权轮询:
ipvsadm -C ipvsadm -A -t 192.168.10.100:80 -s wrr第二步,加第一台 RS,-g就是 gatewaying,也就是DR模式(这个名字来源于ipvsadm的历史,别被字母误导):
ipvsadm -a -t 192.168.10.100:80 -r 192.168.10.11:80 -g -w 1第三步,确认规则:
ipvsadm -Ln输出的Prot LocalAddress:Port Scheduler Flags那一行,Flags列会显示rr之类的算法和ActiveConn、InActConn统计。此时从客户端访问 VIP,应该能正常打开页面。如果你用的是我上面那套地址,注意客户端访问的 URL 是http://192.168.10.100/,不能是http://192.168.10.11/,后者是绕过调度器直连 RS,测了也没意义。
验证第一台通了,再加第二台:
ipvsadm -a -t 192.168.10.100:80 -r 192.168.10.12:80 -g -w 3这里的-w是权重。-w 3表示这台机器承担 3 份流量,另一台-w 1承担 1 份,总共 4 份,机器性能 4:1 的关系就按这个比例给。权重不影响能否工作,只影响比例,初期调通阶段可以先都设 1。
ipvsadm里的-t表示 TCP 虚拟服务,-u是 UDP。如果你要转发的不是 HTTP,是数据库、缓存、自定义协议,改端口号就行,-g保持不变。UDP 的话注意要额外考虑会话超时,ipvsadm --set可以调三个超时参数(TCP、TCP FIN、UDP)。
3.3 调度算法怎么选,别只认识 rr
很多人一上来就rr,能跑就行。但算法选得对,能省掉后面一大堆"为什么分发不均"的工单。我把常用的几个列一下,配合我自己的使用场景说明。
| 算法 | 含义 | 我一般什么时候用 |
|---|---|---|
| rr | 纯轮询 | 后端配置完全一致、请求耗时均匀的场景(静态资源) |
| wrr | 加权轮询 | 机器配置有差异,新老机器混跑过渡期 |
| lc | 最少连接 | 请求耗时差异大,长连接偏多的场景 |
| wlc | 加权最少连接 | lc 基础上机器性能有差异,我的默认选择 |
| sh | 源地址哈希 | 需要天然会话粘性,但后端没做共享会话 |
| dh | 目的地址哈希 | 反向代理缓存类场景,同一个目标打到同一台 |
我个人的默认答案是wlc。原因很实际:绝大多数业务请求的耗时不是均匀的,rr在后端某台机器偶发慢请求时会雪上加霜——它不看负载,照样往里塞。wlc会根据当前活动连接数动态调整,慢的那台自然接得少,这是最朴素的负反馈。真正需要rr的场景是后端响应时间几乎恒定(比如纯静态文件服务),否则没有必要。
sh(源地址哈希)要慎用。它确实能保证同一个客户端 IP 固定落到同一台 RS,看起来解决了会话问题,但代价是负载可能极度不均。如果你们的客户端大量来自少数几个出口 IP(比如某个办公网的 NAT 出口),那这几台机器会承担绝大多数流量,其余机器吃灰。会话保持更推荐下面要说的持久连接,或者干脆把 session 挪到 Redis 这种集中存储。
3.4 会话保持的隐藏副作用
ipvsadm -A的-p参数是持久连接(persistence)超时,单位秒:
ipvsadm -E -t 192.168.10.100:80 -s wlc -p 60这条命令把虚拟服务改成wlc算法并开启 60 秒持久连接。含义是:同一个客户端 IP 在 60 秒内的所有请求,全部转发到它第一次命中的那台 RS。日志里登录状态、购物车这类依赖本地 session 的功能,靠这个能续上。
但它有个副作用,是压测时最容易造成的误判:你打开压测工具,设 100 并发,然后发现所有请求都打在同一台机器上,另外几台连接数是 0。这时候不要急着怀疑算法或者负载均衡坏了,先看有没有-p参数。压测机通常只有一个源 IP,开了持久连接之后,在超时窗口内它天然只会命中一台。要验证分发效果,要么把-p临时去掉,要么用多个源 IP 压测。
注意:持久连接是基于源 IP 的,如果客户端经过多层 NAT,大量用户共享一个出口 IP,那这"一个用户"实际上代表了一群人,持久连接会让这一群人都压在同一台机器上。上线前务必跟自己确认:客户端的源 IP 分布是什么样的。
4. Keepalived 接管:从手工命令到高可用
手工ipvsadm建好的规则,重启就没了,而且调度器本身是单点。生产环境必须交给 keepalived 托管:它一方面用 VRRP 协议在主备之间漂移 VIP,另一方面接管 IPVS 规则的生成和后端健康检查。这一套下来,才算一个能交付的LVS集群。
4.1 主备节点的 keepalived 配置拆解
先装包:yum install -y keepalived。然后写/etc/keepalived/keepalived.conf。主节点配置我一般这么写:
global_defs { router_id LVS_LB_01 enable_script_security script_user root } vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass Lvs2024X } virtual_ipaddress { 192.168.10.100/24 dev eth0 label eth0:0 } } virtual_server 192.168.10.100 80 { delay_loop 6 lb_algo wlc lb_kind DR persistence_timeout 0 protocol TCP real_server 192.168.10.11 80 { weight 3 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 connect_port 80 } } real_server 192.168.10.12 80 { weight 1 HTTP_GET { url { path /health.html status_code 200 } connect_timeout 3 nb_get_retry 2 delay_before_retry 3 } } }备节点只改三处:router_id换个名字、state BACKUP、priority降到 90。其余部分逐字复制,尤其是virtual_router_id(必须相同,这是区分不同 VRRP 组的标识)和authentication(必须相同,否则认证失败,两边都会认为对方是哑的)。virtual_router_id在同一广播域里不能跟别的集群撞,撞了会出现两个不相干的集群互相抢 VIP 的诡异现象。
几个参数的含义值得说明。advert_int 1是心跳间隔 1 秒,主节点每秒发一次 VRRP 通告,备节点 3 个周期没收到就接管,所以故障切换大约在 3 秒左右。delay_loop 6是健康检查的间隔,6 秒查一次后端。lb_kind DR明确告诉 keepalived 用直接路由,它内部会把这翻译成ipvsadm的-g。
enable_script_security和script_user root是给后面可能用到的自定义健康检查脚本准备的。新版 keepalived 默认不允许以 root 执行脚本,不提前配好,后面加MISC_CHECK会报权限错误,然后你会在日志里找半天。
4.2 健康检查不只是"端口通不通"
TCP_CHECK是最常用的,它只做一件事:尝试跟后端建立 TCP 连接。端口能连上就算健康,不能连上就摘掉。优点是不依赖业务逻辑,缺点也很明显:进程假死的时候它测不出来。我遇到过好几次后端进程因为死锁卡住,监听套接字还在,TCP_CHECK一切正常,但所有请求都超时,集群整个卡住,keepalived 在日志里安静得像什么都没发生。
后来我统一改成HTTP_GET,让后端提供一个轻量的健康页,返回固定的字符串。这样不仅验证了端口,还验证了应用确实能处理请求、能读模板、能连数据库(如果健康页里带一次简单的 DB 查询)。配置就像上面那段,path指向健康页,status_code必须是 200。
健康页的内容我建议写得抠门一点:不要在里面查大表、不要走复杂的业务逻辑,就做一个纯粹的存活探针,能返回 200 即可。这个页面的访问量等于后端数 × (1/delay_loop),很可观,写重了会自己把自己压垮。
对于更复杂的判断(比如返回体里要包含某个特定字符串,或者需要调用一个外部脚本检查中间件),用MISC_CHECK:
MISC_CHECK { misc_path "/etc/keepalived/check_web.sh" misc_timeout 5 misc_dynamic }脚本返回 0 表示健康,返回 1 表示异常,返回 2 到 255 之间的值会被当作新的权重(这就是misc_dynamic的作用,可以让权重随负载动态浮动,适合做简单的弹性调度)。这个脚本要记得提前chmod +x。
4.3 主备切换的几个真实坑
第一个坑是防火墙拦 VRRP。VRRP 不是 TCP/UDP,协议号是 112,iptables 常规的-p tcp规则拦不到它,但默认策略是 DROP 的话照样会丢。表现为:主节点正常,备节点日志里一直刷"VRRP 报文超时、我成为 MASTER 了",然后两个节点同时持有 VIP,形成脑裂。放行方式我在 RHEL 系上是:
firewall-cmd --permanent --add-rich-rule='rule protocol value="vrrp" accept' firewall-cmd --reload或者更简单也更保险的做法:把两台调度器接在同一个交换机上,并且时时检查ip addr里 VIP 出现在几台机器上。这是我最推荐的"健康检查",人肉查,不依赖工具。
第二个坑是nopreempt的使用。有些场景希望主节点恢复后不要抢回 VIP(比如主节点上跑着长连接业务,抢回会断),可以在两个节点都配nopreempt。但注意nopreempt只对state BACKUP的节点生效,而且它要求state不能是MASTER。配错了不生效也不会报错,很阴。
第三个坑是单播改造。有些二层网络环境(尤其是跨机柜的)会抑制 VRRP 的组播报文,导致心跳不通。这时候可以用单播:
unicast_src_ip 192.168.10.10 unicast_peer { 192.168.10.20 }备节点上反过来写。单播配置下virtual_router_id仍然要一致。
第四个坑是连接表同步。keepalived 支持把 IPVS 的连接表在主备之间同步,切换时已建立的连接不会断:
lvs_sync_daemon_interface eth0这个要在global_defs里配,两边都配,用同一个接口。注意这个同步是"尽力而为"的,不是强一致,切换瞬间仍然有小概率丢连接,不要指望它做到零感知。
5. 跑起来之后怎么验证
规则配完、keepalived 起来、后端页面能打开,很多人到这里就收工了。但能访问和正确工作是两回事。我交付前的验证分三层:分发是否真的生效、回包路径是否真的绕开了调度器、故障时是否真的能自动切走。
5.1 从统计数字看分发效果
最直接的观察点就是ipvsadm的统计:
ipvsadm -Ln --statsConns列是累计连接数,InBytes、OutBytes是进出字节数。连续访问几次 VIP,看两台的Conns是否都在涨。如果只有一台在涨,回头查-p持久连接,或者查 ARP 抑制。
再往下看一层连接明细:
ipvsadm -Lnc这个会列出当前的连接表,包含源 IP、目的 IP、状态、超时,能看出具体哪些客户端连到了哪台后端。这个命令在排查"某个特定用户访问异常"的时候特别有用,因为你可以精确确认他的请求到底落到了哪台机器上。
5.2 抓包看清两条路径
这是我最有说服力的一步,也是我最喜欢给人演示的一步。在调度器上抓:
tcpdump -i eth0 -nn -e 'host 192.168.10.100 and tcp port 80' -c 10加-e是为了看 MAC 地址。你会看到请求包的源 MAC 是客户端的、目的 MAC 是某台 RS 的(注意,不是调度器自己的 MAC),而 IP 层的目的地址仍然是 VIP。这一条包就完整地证明了 DR 模式的工作方式:二层改 MAC,三层不动。
再到 RS 上抓同一时段:
tcpdump -i eth0 -nn 'host 192.168.10.50 and tcp port 80' -c 10你应该能看到 RS 发出的响应包,源 IP 是192.168.10.100,目的 IP 是客户端。而在调度器上,你抓不到这些响应包——因为响应根本不经过它。这个"在调度器上抓不到回包"的观察,是判断 DR 模式是否真的生效的黄金标准。如果调度器上能抓到回包,说明网关指错了,RS 在把响应往调度器上送。
5.3 做一次真实的故障演练
上线前我一定会做一次摘除演练:手动把其中一台 RS 的 Web 服务停掉,然后持续访问 VIP,观察两件事——第一,ipvsadm -Ln里那台的Weight是否变成 0(keepalived 摘除后权重置 0,但条目还在,方便它恢复后自动加回);第二,客户端访问是否出现明显的中断或者错误页。
这里有个经验值:TCP_CHECK的delay_before_retry 3和nb_get_retry 3组合起来,最坏情况下健康检查要花十几秒才能判定一台机器挂了。这十几秒内的请求全部落在坏机器上,用户看到的是超时。如果业务对这段时间不能容忍,把delay_loop调小、retry次数减少,但代价是网络抖动时容易误摘。这个平衡只能根据业务容忍度来定,没有万能参数。
再做一个主备切换演练:在 master 上执行systemctl stop keepalived,然后用ip addr确认 VIP 漂到了 backup 上,客户端访问不中断或只有极短中断。这个动作我建议一个月做一次,因为"从来没切过"的高可用,等于没有高可用。
6. 常见问题与排查技巧实录
前面讲的都是"按正确路径走",这一节讲"走歪了怎么回来"。下面这张表是我这些年攒下来的,基本覆盖了 DR 模式部署里 90% 的故障现象。
| 现象 | 最可能的原因 | 第一手排查动作 |
|---|---|---|
| 访问 VIP 直接超时,RS 上完全没日志 | 调度器没有 VIP,或者 ipvs 规则没生效 | 调度器ip addr+ipvsadm -Ln |
| 只有一台 RS 有连接数,其余为 0 | ARP 抑制失效,VIP 被 RS 抢走 | 客户端arping看 MAC 是不是调度器的 |
| 页面能打开但极慢,或大文件传输卡死 | RS 默认网关指向了调度器,回包绕路 | RS 上ip route看 default |
| 请求到了 RS 但连接被重置 | RS 防火墙拦截,或者 SELinux 限制 | iptables -L -n、getenforce |
| 本机 ping 不通网关,但能上外网 | VIP 掩码写成了 /24 污染路由 | ip route、ip addr show lo |
| 主备同时持有 VIP | VRRP 报文被防火墙拦了,脑裂 | 两台都跑ip addr,抓 112 协议 |
| 压测时所有请求打一台 | 开了持久连接-p | ipvsadm -Ln看 Flags 有没有persistent |
| 后端恢复后不来流量 | 权重还是 0,或健康检查还没过 | ipvsadm -Ln看 Weight 列 |
6.1 三个我踩过的坑,写下来省你一天
第一个坑:克隆虚拟机导致的"幽灵 VIP"。早期我用虚拟化平台批量克隆 RS,克隆的时候源机器上还绑着 VIP,克隆出来的机器一开机就带着lo上的 VIP,而且arp_ignore参数如果是后来才补的,中间那段时间全网 ARP 一片混乱。后来我改规矩:所有 RS 的初始化脚本都做幂等处理,开头先ip addr del一次,不管有没有,删了再说。这个习惯救了我很多次。
第二个坑:后端是容器,VIP 配不进容器里。有一阵子把 RS 从物理机换成容器,发现容器里的进程根本没法给回环加地址,加NET_ADMIN能力又不符合安全基线。这时候 DR 模式在容器里就走不通了,要么用 hostNetwork,要么退回到宿主机做一层转发。我想说的是:DR 模式对网络栈有要求,不是所有运行环境都能满足。方案选型的时候先问一句"我们的服务跑在哪,能不能动网络栈",比事后改架构便宜得多。
第三个坑:健康检查把机器全摘了。有一次上线做过变更,防火墙加了一条限流规则,源地址是调度器 IP 的健康检查请求被限流了,于是 keepalived 判定所有后端都挂了,ipvsadm -Ln里所有真实服务器变成灰色,整个集群返回 503。这事教育我:健康检查的源 IP 要单独放行,而且要在防火墙规则里加注释,不然下次别人改规则的时候又会踩。我现在的习惯是在规则里写清楚:
iptables -I INPUT -s 192.168.10.10 -p tcp --dport 80 -j ACCEPT -m comment --comment "keepalived health check, DO NOT REMOVE"6.2 上线前的一张自查清单
最后把我每次交付前会过一遍的清单列出来,你可以直接拿去当 checklist 用。
- 调度器和所有 RS 在同一网段、同一 VLAN,二层互通(
arping能互相问到)。 - 所有 RS 的
arp_ignore=1、arp_announce=2,且是在绑 VIP之前设置的。 - 所有 RS 的 VIP 绑在
lo上,掩码/32。 - 所有 RS 的默认网关指向真实路由器,不是调度器。
- 从客户端
arpingVIP,返回的 MAC 只有调度器的。 ipvsadm -Ln里所有 RS 的Weight非 0,ActiveConn有分布。- 在调度器上抓包能看到目的 MAC 被改写,抓不到回包。
- 手动停一台 RS 的服务,权重归零,客户端无持续报错。
- 手动停 master 的 keepalived,VIP 漂到 backup,访问不中断。
- 防火墙放行了 VRRP(协议 112)、业务端口、健康检查源 IP。
- 所有机器时间同步。
这张清单我第一次整理的时候觉得有点啰嗦,但每次跳过其中某一条,最后都要花几倍的时间补回来。特别是"从客户端 arping VIP"这一条,它能在 30 秒内告诉你 ARP 抑制到底有没有生效,比翻日志快多了。
我个人在实际操作中的体会是,LVS 的 DR 模式本身配置量很小,难点全在环境的约束条件上:二层必须通、VIP 必须藏好、网关不能指错、防火墙不能挡。这四条只要有一条不满足,表现出来都是"配置没错但就是不工作",而报错信息往往什么也不告诉你。所以我的建议一直是:先花二十分钟把这几条硬约束逐条验证一遍,再去碰ipvsadm和keepalived.conf。顺序反了,你会在一堆正常的配置里找一个不存在的问题。