LVS DR模式与Keepalived高可用集群:原理、配置与避坑指南
聊一个生产环境里很常见的场景集群规模上来了流量大了单台服务扛不住自然要引入负载均衡。而负载均衡本身如果挂了整个系统照样瘫。所以负载均衡器也得做高可用。我手上这套方案核心就是LVS的DR模式配合keepalived做健康检测和VIP自动漂移。这篇文章我打算把整个链路从原理到配置讲透适合正在搭集群的运维同学以及想搞明白LVS和keepalived到底怎么配合的开发朋友。先说说我为什么选LVS DR模式。LVS有三种工作模式NAT、TUN和DR。NAT模式最简单但进出流量都走调度器压力全在LVS上带宽和性能容易成瓶颈。TUN模式要封包解包对内核和网络环境要求高普通场景有点过度设计。DR模式则完全不同它只修改数据包的目标MAC地址不碰IP层和传输层内容请求进来由调度器分发响应直接由后端服务器回给客户端。这种“请求走调度响应走后端”的方式让调度器只扛请求流量响应流量不经过它性能和吞吐量都高很多。我实测过在同样的硬件条件下DR模式比NAT模式的并发能力能高出一截尤其适合视频、下载、API网关这类响应体量大的业务。但DR模式有个著名的坑就是ARP问题。后端服务器和调度器共享同一个VIP如果不做特殊处理后端服务器会认为自己才是VIP的主人直接对外宣告自己持有VIP这样调度器就收不到包了整个集群就乱了。解决的办法是给后端服务器的回环接口或者别名接口绑定VIP同时调整内核的ARP参数让它在广播ARP时保持沉默只处理数据包不响应ARP请求。这里具体怎么做我在后面的章节详细拆。接下来讲keepalived的角色。keepalived的核心功能有两个一个是通过VRRP协议实现调度器的主备切换也就是VIP漂移另一个是健康检查持续探测真实服务器的存活状态一旦发现有RS掉线自动从转发列表里摘掉恢复后再加回来。这两个功能正好补上LVS单点故障和管理RS列表的短板。所以这套组合拳才叫高可用集群下的负载均衡。先说整体设计思路。标准拓扑是这样的两台LVS调度器一主一备跑着keepalived共享一个VIP。后端若干台真实服务器RS也绑定同一个VIP但只配置在回环接口上并禁止ARP响应。客户端访问VIP时请求到达主调度器主调度器根据自己维护的转发表把请求发送到某个RS。RS处理完之后直接把响应包发给客户端不再经过调度器。这里有个细节为什么RS的VIP必须绑定在回环接口而不是物理接口因为keepalived在调度器上通过VRRP周期宣告VIP后端RS如果物理接口直接绑VIP会不停发ARP响应导致调度器收不到后续流量。而回环接口默认不对外广播ARP配合内核参数抑制就能既接收数据包又不暴露自己。我最早踩过这个坑直接把VIP绑在RS的eth0上结果整个集群调度器日志全是“neighbor solicitation”报错客户端间歇性超时。再说为什么健康检测要写脚本而不是用keepalived自带的状态检查。keepalived自带的有TCP检查、HTTP_GET检查但只能探测端口通不通、URL返回码对不对。真实业务里服务可能端口通但进程已经假死或者依赖的数据库掉了接口返回503。这时候端口检查完全没反应。我习惯在keepalived的real_server块里用MISC_CHECK调用自定义脚本脚本去实际请求一次业务接口或者检查进程状态文件返回0就健康非0就摘除。这个能力是keepalived的杀手锏也是最容易被忽略的。环境选择上我用的是CentOS 7内核版本3.10LVS用ipvsadm管理keepalived版本1.4。这套组合在现在的运维场景里依然很主流而且CentOS 7的包管理直接可以装不用编译。硬件方面两台调度器配置不用太高2核4G足够后端RS看业务需求。网络必须二层互通因为DR模式靠MAC地址转发不能跨路由。2.1 数据包流转的核心链路把数据流转拆开看整个过程分四步。第一步客户端发送请求目标IP是VIPMAC地址是主调度器的网卡MAC。第二步调度器收到包之后ipvs模块根据虚拟服务规则选出一台RS然后把包的目标MAC改成RS网卡的MAC源IP、目标IP都不动直接发出去。第三步RS收到这个包发现目标IP是VIP而VIP正绑在自己的lo:0接口上于是正常接收并处理。第四步RS处理完业务直接把响应包经自己的默认路由发回客户端。客户端看到的就是一个IP对应了多台服务器完全透明的。这里有个关键点数据包到达RS后RS的内核必须认为这个包是发给自己的所以VIP必须绑定在RS本地。但绑定在回环接口上有个问题回环接口的IP在正常情况下不会响应外部ARP因为回环接口一般不做对外通信。可是如果不做任何配置RS的lo:0绑了VIP后它会默认对这个VIP做ARP宣告导致所有客户端直连VIP时响应被RS抢走。所以必须调整两个内核参数arp_ignore和arp_announce。2.2 arp_ignore和arp_announce的配置逻辑arp_ignore控制的是本机收到ARP请求时什么情况下才回应。默认值是0意思是本机只要任意接口有目标IP就立即响应。这显然不行因为RS上挂着VIP客户端一查VIP的MACRS就跳出来说“我在这儿”可实际流量应该走调度器。所以RS上要把arp_ignore设为1含义是只响应那些目标IP正好落在请求到达的这个网卡上的ARP请求。但VIP在lo:0上外部请求到达的是eth0所以RS对VIP能做到“听而不答”。arp_announce控制的是本机发ARP请求时用什么源IP去宣告。默认是0本机就用自己的IP直出。但RS的eth0上没有VIP而业务响应会走eth0出去如果它发ARP请求询问网关的MAC源地址用了VIP网关就会把VIP的MAC学成RS调度器就废了。所以要设为2意思是只用主网卡上的IP作为ARP源地址完全忽略VIP。配置位置在/etc/sysctl.conf里我习惯用这样的参数net.ipv4.conf.all.arp_ignore 1 net.ipv4.conf.all.arp_announce 2 net.ipv4.conf.lo.arp_ignore 1 net.ipv4.conf.lo.arp_announce 2这里有一个细节光改all还不够还必须单独指定lo接口。我第一次配置时只改了all结果lo上的VIP还是照常回ARP折腾了半天才意识到问题出在这。改完之后执行sysctl -p生效。RS上的VIP绑定我通常用ip命令而不是传统的ifconfig因为ifcfg文件在后续服务器重启时顺序不稳定容易被NetworkManager干扰。ip addr add 192.168.1.100/32 dev lo注意这里掩码是32不是24。因为VIP是一个单主机地址不需要和物理网段有从属关系。这个地址只用来接收数据不做路由宣告。keepalived的配置文件通常放在/etc/keepalived/keepalived.conf。整个文件分三段global_defs定义全局日志和通知参数vrrp_instance定义VRRP协议的VIP和优先级virtual_server定义LVS后端和健康检测。先看我常用的一套模板。global_defs { notification_email { opsexample.com } notification_email_from lvsexample.com smtp_server 192.168.1.10 smtp_connect_timeout 30 router_id LVS_MASTER } vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 1a2b3c4d } virtual_ipaddress { 192.168.1.100/24 dev eth0 label eth0:0 } } virtual_server 192.168.1.100 80 { delay_loop 6 lb_algo wrr lb_kind DR protocol TCP persistence_timeout 600 real_server 192.168.1.11 80 { weight 1 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 2 } } real_server 192.168.1.12 80 { weight 2 MISC_CHECK { misc_path /etc/keepalived/check_rs.sh misc_timeout 5 } } }在vrrp_instance里state MASTER只是初始状态真正决定主备的是priority值。主节点priority要大于备节点这样VRRP选举才会一直选主。virtual_router_id两机必须一致否则VRRP报文不互通两边都会认为自己是主造成VIP冲突。我见过几回因为router_id不一致导致业务中断的事故所以每次搭集群我都会先在两机上执行ip addr compare确认没有同时出现VIP。virtual_server这一段定义的是调度器上ipvsadm的规则。lb_algo wrr表示加权轮询lb_kind DR表示DR模式。protocol基本都走TCP。persistence_timeout表示会话保持时间比如电商场景用户要登录保持600秒是常用值。这个值不能设太大不然后端扩容后流量还是压在一两台机器上负载均衡效果就打折扣。real_server里TCP_CHECK直接探测后端的80端口weight是按权重分配请求。MISC_CHECK是调用外部脚本做更细的健康检测。备节点上同样部署这套配置只需要把state改成BACKUPpriority改成90其他不变。3.2 健康检测脚本的关键写法前面提到MISC_CHECK现在把脚本写详细。这个脚本的核心逻辑是请求一次业务接口看响应码和响应时间。我写了一个可复用的例子放在/etc/keepalived/check_rs.sh。#!/bin/bash CHECK_URLhttp://192.168.1.11:80/healthz RESULT$(curl -s -o /dev/null -w %{http_code} --connect-timeout 3 --max-time 5 $CHECK_URL) if [ $RESULT -eq 200 ]; then exit 0 else exit 1 fi这里有几个注意点。curl的--connect-timeout和--max-time必须设否则后端假死时脚本会被卡住keepalived会认为脚本超时然后误判RS健康。Response code的判断不能只写200有的接口会返回301或302需要按业务设计。我习惯在脚本里记录日志方便排查echo $(date %F_%T) check_rs result$RESULT /var/log/check_rs.log然后给脚本可执行权限keepalived的进程用户需要能访问。如果脚本需要访问外部依赖比如数据库或redis就得更仔细因为一旦依赖服务整体抖动可能所有RS都瞬间被摘除等于把调度器也拖垮。这时我会做“连续N次失败才摘除”的逻辑比如用临时文件记录失败次数连续3次才返回非0避免抖动引发的大面积摘除。3.3 主备切换的触发机制keepalived的主备切换靠VRRP报文。主节点默认每1秒发一个VRRP组播包备节点会持续监听。如果备节点在advert_int的3倍时间内默认3秒没收到主节点的报文就会认为主挂了然后立即提升自己为主绑上VIP接管流量。这个机制很快正常情况下3秒内切换完成。但这里有个盲区如果主节点的keepalived进程活着网络也通但ipvs规则被清了或者后端服务全挂了VRRP报文照发备节点永远不会接管。所以高可用不能只靠VRRP还得在vrrp_instance里配置nopreempt和notify脚本。我的习惯是加一个nopreempt选项否则主节点恢复后又会把自己切回去引起二次抖动。同时在global_defs里配置notify当切换时调用脚本发出短信或者钉钉通知。配置如下vrrp_instance VI_1 { state BACKUP nopreempt ... notify_master /etc/keepalived/master.sh notify_backup /etc/keepalived/backup.sh notify_fault /etc/keepalived/fault.sh }nopreempt的含义是即使节点恢复也不会自动夺回主角色直到当前主节点再次故障。这个对于双活场景很友好能减少频繁切换。通知脚本里我一般会记录时间戳和节点hostname然后调用企业微信或飞书webhook。生产环境里靠人工发现切换已经晚了一定要有告警。实操部分我直接演示一遍完整流程。先在两台调度机上安装keepalived和ipvsadmCentOS下的命令yum install -y keepalived ipvsadm后端RS机器只需配置内核参数和绑VIP不需要装keepalived或ipvsadm。RS上需要开启IP转发吗不需要因为响应是直接发出去的不走本机转发逻辑。唯一要确保的是后端服务的默认网关能指向物理路由器否则响应包出不去。然后按顺序操作第一台调度器写配置文件state MASTERpriority 100启动keepalived。第二台调度器state BACKUPpriority 90启动keepalived。检查master上是否有VIP和IPVS规则执行ip addr show eth0再执行ipvsadm -L -n。把两台RS绑上lo:0的VIP和ARP参数。从客户端curl VIP验证业务。我在实际搭建时发现按顺序检查的窍门是先用ipvsadm -L -n看规则再用tcpdump看RS的ICMP有没有通最后再验证业务。如果直接curl遇到超时会误以为配置有问题浪费时间排查ARP。RS上绑VIP的命令是临时的重启就没了。正式环境得把命令写进rc.local或者systemd unit。我用的是/etc/rc.d/rc.local然后chmod x这个文件。注意现在系统里rc.local默认没有可执行权限要手动加。ip addr add 192.168.1.100/32 dev lo同时把sysctl的参数持久化在/etc/sysctl.conf里。接着验证健康检查。我先停掉RS1上的nginx然后观察master调度器上的ipvsadm规则输出看到RS1的条目被标红或者直接消失就说明健康检查生效了。手动恢复nginx等delay_loop周期过后RS1重新出现。MISC_CHECK脚本的周期由delay_loop控制我设的是6秒所以观察时要有点耐心。生产里遇到最多的问题我整理成一张速查表。现象可能原因排查手段VIP无法ping通VRRP配置不一致或防火墙挡了VRRP组播检查两机的virtual_router_id是否一致放行vrrp协议后端RS收不到请求ARP参数未生效RS没绑VIP在RS上执行ip addr grep VIP检查sysctl -a请求通了但响应卡顿后端RS的物理网卡未配置正确的网关确认RS默认路由指向物理网关健康检测脚本一直失败脚本没设可执行权限或curl依赖被限制手动执行脚本看exit code和日志keepalived莫名其妙重启配置语法错误或权限设置冲突keepalived -t -f检查语法看系统日志主备切换后客户端仍然连旧MAC客户端或交换机ARP缓存未更新设置较短的ARP老化时间或手动清除网关ARP对于ARP缓存问题我在切换后会在客户端机器上执行arp -d强制重查但生产环境里不能每台机器手刷所以要靠交换机的动态ARP学习。把交换机相应VLAN的MAC地址表老化时间调短比如调到60秒能减少切换期间的丢包窗口。还有一个容易忽略的问题就是RS和LVS之间的时间同步。虽然DR不影响数据内容但健康检测脚本里如果用了时间戳或者后端接口校验签名时间不一致会导致检测失败。我习惯在两台调度机和所有RS上统一部署chrony同源同步。后来几回线上排查发现告警都是单机时间偏差导致的误报所以同步服务必须纳入基础监控。演示一个故障模拟。我在主调度器上执行systemctl stop keepalived然后立刻看备机的日志应该能看到VRRP状态从BACKUP变成MASTER。然后在备机上点击ip addr show eth0看到VIP已经绑上了再访问VIP业务正常。整个切换过程大概2秒到3秒因为我设了advert_int 1备机监听到停止后立即接管。如果把advert_int改成0.5切换会更敏感但要保证网络延迟小于300毫秒否则会频繁误判。我一般还是用1秒稳妥。故障模拟里最刺激的一次是我在直播活动前夜做演练把主调度器直接宕机结果备机5秒没接管原因是我在VIP绑定时用了eth0:0标签但备机的eth0接口名在某些云环境里叫ens33标签没匹配上。这个教训就是不同环境网卡命名不一致配置文件写死了interface名就会出事所以我后来全部改用dev eth0 label的方式并且每次部署前先ip link确认接口名。健康检测脚本还有另一个进阶玩法就是用健康检测结果动态调整权重而不是简单摘除。我早期做过一套自定义脚本后端响应时间超过800毫秒就给权重减半超过2秒就摘除。效果比固定weight好很多因为服务端性能劣化到挂了之间有很长一段“亚健康”区间固定检查根本察觉不到。但这里有个复杂度必须提醒就是避免抖动。如果脚本判断权重的依据太敏感瞬时高并发会把所有后端权重削到0然后集群雪崩。所以脚本里要做滑动窗口统计至少基于最近5分钟的平均值。我在实际配置vrrp_instance时也喜欢加一个track_interface也就是跟踪网卡状态。如果eth0网卡冒烟或link downkeepalived会立即进入FAULT状态而不是等VRRP超时。这个对于物理网卡故障特别有用。track_interface { eth0 }再补一点安全设计。在有防火墙的环境里VRRP协议使用的是IP协议号112组播地址是224.0.0.18。如果防火墙策略没放行这个两边的keepalived根本没法通信看起来配置都对但备机就是不管机。另外keepalived配置文件里的auth_pass不要用太简单的虽然它不是加密协议只是明文密码用于认证但太简单的容易被同网段机器干扰。关于VIP的网段选择我建议用和物理网段同一个网段比如我的eth0是192.168.1.0/24VIP就配192.168.1.100/24。因为DR模式要求调度器和RS在同一个二层网络VIP若跨网段交换机路由规则会很麻烦。有些云环境不支持这种“IP漂移但MAC不漂移”的方式这时候就得改走云厂商的SLB或者四层代理。我还遇到过一个看起来像玄学的问题流量大的时候LVS转发正常但偶尔有几条请求超时。后来排查出来是后端RS的网卡收到了大量arp请求因为客户端的网关不停地广播查询VIP而RS上sysctl只改了all但没改lo导致lo上的VIP偶尔回包。我改完lo的参数后这个问题再没出现。所以参数配置不是改完就算了要一行行核对。继续讲扩展。这套负载均衡架构可以水平扩展后面加RS只需要在keepalived配置文件增加real_server块然后reload服务。但注意keepalived的reload不是热加载它会短暂释放VRRP角色造成VIP抖动。我通常的做法是修改完配置文件后先执行keepalived -t确认语法再重启服务。重启的窗口非常短大约几百毫秒一般业务能扛得住但核心交易系统就不行了那样就得用更平滑的架构比如将规则下发到ipvsadm而不动keepalived进程。说到ipvsadmkeepalived实际上是把virtual_server配置翻译成ipvsadm规则所以你可以直接手工执行ipvsadm来调试。比如查看当前连接数ipvsadm -L -n --stats看实时连接ipvsadm -L -n --rate删除某条规则ipvsadm -D -t 192.168.1.100:80这些命令在排查问题时很有用因为keepalived的规则是动态管理的手工改规则可能被自动纠正所以调试最好是直接改配置文件。关于会话保持的细节persistence_timeout不只在同一个连接内生效它会把同一个源IP的请求在一定时间内都发给同一台RS这本来是好事但代价是后端RS之间如果没做session共享用户在切换RS时登录态就丢了。我常用的做法是把它设成600秒然后后端用redis存session。但如果你后端是无状态接口可以设成0让负载更均匀。再分享一个我几次调整后的经验总结DR模式只适合流量的入方向需要负载均衡的场景如果客户端和服务器之间有NAT穿越或者涉及SSL卸载那DR模式就不适用了得走NAT或直连。SSL卸载一般放在后端做LVS不做七层处理。关于监控除了keepalived自身的日志我还会单独部署一个探针从外部每分钟访问一次VIP的healthz接口判断整个集群的“入口健康”。因为keepalived只关心调度器和后端不关心客户端到VIP的链路。这个外部探针可以从同机房的另一台空跑机器发起也可以从监控系统直接拉取。我在某平台迁移时就是靠这个外部探针发现了公网路由异常那个问题keepalived根本感知不到。还要说一个关于合规和安全的常识整个集群里所有IP必须规划清楚我见过有些团队把VIP写在业务配置文件里写死了结果VIP发生漂移后业务还在连旧地址等了半天才发现。这种低级错误其实最好防把VIP做成一个DNS记录业务只认域名不认IP。最后聊聊工具链。这套架构里调度器的日志默认在/var/log/messages里面会记录VRRP状态和健康检查失败信息。为了便于检索我会在global_defs里配置log facility然后单独让rsyslog把kern.*和local0.*写到/var/log/keepalived.log避免和系统日志混在一起。这个做法在后续排障时效率高很多。再补一个细节keepalived的virtual_server里可以配置fallback和quorum。比如我的集群只有两台RS如果一台挂掉keepalived默认还能继续带流量但如果你设置了quorum为2那么RS数不足2时LVS整体会停止转发。这个功能要谨慎使用除非你确定两台都挂了宁可不服务也不要只服务一台否则很容易造成雪崩。我这套方案已经在线稳定跑了一年多期间经历了几次接近事故的抖动每次靠健康检测脚本自动摘除故障RS以及VRRP秒级切换业务影响控制在一次连接超时范围内。我自己的体会是LVS加keepalived这套组合难点不在装和配而在细节。尤其是ARP抑制、脚本超时、VIP漂移这些细节点一旦理解透了后面所有场景都能举一反三。如果你正准备在测试环境验证有个最快捷的方式就是先起三台虚拟机一台当调度器两台当RS网络都在同一个vlan按我上面的配置一步步来。第一次跑通后再加第二台调度器感受一下主备切换的丝滑。整个过程一两个小时能搞定但踩过的坑能省下线上事故时的一整夜。