keepalived+LVS高可用负载均衡实战:从原理配置到故障切换
刚接手这套系统的时候我其实对keepalivedLVS是有点抵触的——毕竟公司里不少人在推云负载均衡谁还愿意自己搭一套四层转发呢。直到有一次半夜接到电话后端两台Web服务器一张网卡傻掉前面那台单点Nginx直接把所有流量拒之门外我才真正下决心把原来那套“一台LB扛所有”的架构换掉。后来在几十台机器的规模上跑通了keepalivedLVS高可用方案稳定跑了两年多中间做过十几次演练结论是这个组合在四层流量分发和网关高可用这个场景里依旧是性价比最高、最可控的选择之一。这篇文章我就把从原理到配置、从验证到排障的完整过程整理出来分成五个部分按我自己上线的顺序写希望能给正在评估或已经准备动手的人一些参考。1. 为什么我在生产环境最终选了keepalivedLVS这套组合1.1 单点故障是怎么暴露出来的先说背景。我负责的是一套面向内部业务系统的统一入口流量从外部打到一台Nginx上再由Nginx反代到后面的若干应用节点。早期并发量不高一台Nginx四核八线程妥妥够用。可等到日活上来之后问题开始显现某一台后端节点发布上下线稍微慢一点Nginx的upstream探活会把流量都堆到剩余节点赶上Nginx自己所在宿主机网卡故障或者内核异常整个入口就彻底断了。网上很多人讲高可用喜欢从“要买两台机器”开始讲。但真实生产里单点故障往往是慢慢暴露的——先是监控图上某个指标曲线消失再是凌晨收到“入口不可达”的告警最后才是你被迫在半夜爬起来切流量。我那次就是凌晨两点一条“所有域名超时”的告警直接把值班群炸了。我当时的第一反应是加一台Nginx做主备。但仔细一想Nginx本身就是七层组件它在转发能力、并发上限上也有自己的软肋更关键的是Nginx主备切换即使做了前置的VIP漂移、健康检查、失败重试这些逻辑都需要自己写脚本或者依赖第三方组件。与其在七层上纠结不如把四层的流量入口先做扎实——四层这一层稳了上层Nginx也好、应用也好压力都会小很多。就是因为这个思路我盯上了LVS。1.2 keepalived和LVS各管哪一段很多人把keepalived和LVS当成一回事其实分工非常清晰。LVSLinux Virtual Server负责的是流量转发它工作在TCP/IP协议栈的四层把进来的请求按照调度算法分发给后端的RealServer。keepalived则负责两件事一是提供VIP的高可用漂移能力二是对后端的RealServer做健康检查。可以这么理解LVS是“干活的手”keepalived是“管活的大脑”。LVS本身不感知机器故障它只按照规则把包发出去哪怕后端机器已经宕了它也一样发。所以必须由keepalived定时去探测各个RealServer的状态一旦发现某个节点不健康就把这个节点从LVS的转发列表里摘掉同时keepalived还在Director之间跑VRRP协议保证主Director挂了之后备用Director能立刻接管VIP继续对外提供服务。这两者合在一起才形成了完整的“负载均衡高可用”闭环。单用LVS没有健康检查和VIP漂移出问题该切还得人工单用keepalived没有LVS的转发能力它也只能漂移一个没人用的VIP。所以生产环境里这俩组件几乎是绑定出现。1.3 和Nginx、云SLB放到一起怎么取舍我见过不少团队在这个选型上犹豫。简单说下我的判断方案优点缺点适用场景LVSkeepalived性能极高、四层转发延迟低、不依赖外部厂商、可控性强配置有门槛DR模式要处理ARP七层能力要交给上层组件高并发入口、数据库/缓存前端的流量分发、自建机房Nginx主备配置直观、七层路由/重写能力强、社区资料多高并发下CPU开销高、切换依赖额外组件中小规模Web入口、需要HTTP层逻辑的网关云SLB免运维、弹性好、自带防护依赖云厂商、费用随流量增长、个别场景有连接数限制没有专职运维的团队、预算充足的云上架构实际生产里我更多把它们当成互补关系最外层用LVS做四层分发后面挂一组Nginx做七层路由和SSL终结再往后才是应用集群。这样LVS只管“把请求发给谁”Nginx只管“请求到了之后怎么处理”各干各擅长的事排查问题时边界也清楚。2. 动手前必须先想清楚架构规划与IP编排2.1 机器角色划分Director、RealServer、VIP我见过不少人拿到keepalivedLVS就直接开配结果IP段没规划好、回环地址互相冲突折腾几天才搞清楚问题在哪。其实部署前把下面三组角色理清楚后面会省很多事。Director也就是负载均衡器负责转发流量。生产环境至少要两台一主一备主备都需要安装LVS和keepalived。RealServer真正处理业务的服务器跑着Nginx、Tomcat或者其他服务。LVS做负载均衡时后端的RealServer只需要回应请求不需要安装LVS相关组件。VIPVirtual IP对外暴露的虚拟IP也就是客户端真正访问的地址。VIP在正常情况下绑定在主Director上故障时漂移到备Director上。我习惯把所有角色的IP和职责先写进一个表格在部署前和团队对一遍。比如我最近一次上线用的规划是这样角色主机名IP地址说明Director主lb01192.168.1.11初始MASTER持VIPDirector备lb02192.168.1.12BACKUP故障时接管VIPRealServerrs01192.168.1.21业务节点ARealServerrs02192.168.1.22业务节点BVIP-192.168.1.100对外公布地址网关-192.168.1.1统一出口这个规划里有一个细节容易踩坑Director的两台机器和RealServer最好在同一个二层网络里因为DR模式后文会详细讲要求所有节点能在二层互通。如果你把Director放在一个网段、RealServer放在另一个网段DR模式基本就要泡汤只能考虑NAT模式或者改架构了。2.2 DR模式为什么是首选以及它带来的ARP问题LVS有三种工作模式NAT、DR、TUN。我直接说结论自建机房的内网服务首选DR模式。原因有三个RealServer返回给客户端的响应包不用经过Director转发效率最高Director本身不成为链路瓶颈配置相对简单。但DR模式有个必须解决的问题客户端访问VIP时ARP广播会问“VIP在哪台机器上”正常情况下VIP绑在主Director上主Director会响应。可如果RealServer也把VIP配到了自己的loopback接口上RealServer也可能响应ARP这就出大事了——客户端发往VIP的包可能被RealServer抢走而RealServer再转发又不知道该送给谁整个链路就乱了。解决办法是让RealServer抑制对VIP的ARP响应这就是网上所有DR模式教程里都会出现那几句sysctl配置的原因。后面配置章节我会给出完整脚本这里先把原理讲明白你必须让VIP在各个RealServer上“存在但不出声”就像每个人都把门牌号写在自己屋里的镜子上但临街的马路上只挂主Director的招牌。2.3 各网卡和网关的边界条件除了IP还建议关注下面几个边界条件Director上VIP绑定的网卡建议用单独的接口或者子接口比如eth0:1不要和业务IP混在同一张主接口上方便观察和抓包。VRRP协议使用组播地址224.0.0.18端口112。有些交换机开了组播过滤或者开启了DHCP Snooping可能导致keepalived的心跳报文被丢掉主备两台机器会互认为对方故障出现“脑裂”。上线前务必确认交换机的组播配置。RealServer上的默认路由不要指向VIP也不要指向Director的IP而是指向正常的物理网关。否则响应包可能绕回Director造成环路。我在第一次部署时就是因为RealServer的默认路由写得不对导致返回包经过DirectorDirector又把它转发回RealServer形成了一个奇怪的闭环压测时延迟高得离谱。后来把RealServer的路由恢复成走物理网关症状立刻消失。这个细节大家务必注意。3. 生产环境下的全套配置实操3.1 keepalived主备配置逐项解读接下来是重头戏。先说keepalived的配置我使用的是CentOS 7系列系统keepalived版本1.3.5配置文件路径是/etc/keepalived/keepalived.conf。主Directorlb01的配置global_defs { router_id LVS_MASTER enable_script_security } vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 511111 } virtual_ipaddress { 192.168.1.100/24 dev eth0 label eth0:1 } track_script { chk_lvs } } virtual_server 192.168.1.100 80 { delay_loop 6 lb_algo wrr lb_kind DR persistence_timeout 50 protocol TCP real_server 192.168.1.21 80 { weight 1 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 connect_port 80 } } real_server 192.168.1.22 80 { weight 1 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 connect_port 80 } } }几个关键参数我要单独拿出来说因为配置时真容易搞混virtual_router_id同一个VRRP实例的两台机器必须一致取值范围0到255。如果有多套keepalived高可用组共处在同一二层网络这个ID不能重复否则会互相干扰。priority主备切换的依据。数值越大优先级越高。备机一般建议比主机低50左右不要只低1避免网络抖动时频繁切换。advert_intVRRP心跳间隔单位秒。默认1即可不要为了追求“快速切换”把它改到0.2或者0.1那样会在高负载下产生大量组播包反而加大交换机负担。persistence_timeout会话保持时间。如果业务没有强会话要求建议设成0或者很小的值。我之前设了50秒结果压测时发现同一IP的请求总是打在同一台RealServer上以为调度算法失效后来才明白是persistence起了作用。备Directorlb02的配置和主基本相同只改下面几个地方router_id改成LVS_BACKUPstate改成BACKUPpriority改成50注意VRRP协议不是严格依赖state字段来判断谁当老大的真正起作用的是priority。所以就算你两台都写MASTER只要priority有高低它照样能选出主。但生产上还是建议state写清楚方便别人看配置的时候理解你的意图。3.2 LVS转发规则什么时候生效ipvsadmkeepalived配置好之后LVS的规则其实是由keepalived动态写入内核的。也就是说你不需要手动去执行ipvsadm的添加命令keepalived会根据virtual_server段的内容自动生成规则。但有一个前提keepalived配置里的virtual_server要和VIP端口对应好。比如VIP是192.168.1.100端口是80那么客户端访问192.168.1.100:80时LVS才会把请求转发到RealServer。如果你业务端口是8080VirtualServer段也要同步改成8080。为了确认规则是否生效我用的是这套验证命令ipvsadm -L -n正常情况下你会看到类似输出IP Virtual Server version 1.2.1 (size4096) Prot LocalAddress:Port Scheduler Flags - RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 192.168.1.100:80 wrr persistent 50 - 192.168.1.21:80 Route 1 0 0 - 192.168.1.22:80 Route 1 0 0如果列表里看不到任何规则多半是keepalived没起来或者配置格式有报错。看日志用tail -f /var/log/messageskeepalived的报错会写在里面。如果Forward那一列显示的不是Route而是Masq说明当前工作模式不是DR而是NAT。需要回到配置里检查lb_kind DR是否写对。3.3 RealServer上的VIP绑定与环境收敛脚本RealServer本身不用装keepalived和LVS但必须在loopback接口上绑定VIP并调整ARP参数。这是我上线前一直在用的脚本建议放到/etc/rc.local或者systemd服务里开机自启#!/bin/bash VIP192.168.1.100 ifconfig lo:0 $VIP netmask 255.255.255.255 broadcast $VIP up route add -host $VIP dev lo:0 # 关闭对VIP的ARP响应 echo 1 /proc/sys/net/ipv4/conf/lo/arp_ignore echo 2 /proc/sys/net/ipv4/conf/lo/arp_announce echo 1 /proc/sys/net/ipv4/conf/all/arp_ignore echo 2 /proc/sys/net/ipv4/conf/all/arp_announce sysctl -p这里我要特别解释一下arp_ignore和arp_announce这两个参数到底在干嘛arp_ignore1只回答目标IP是本机IP的ARP请求。因为VIP虽然在lo上但lo不对外响应所以RealServer就不会去抢答VIP的ARP请求。arp_announce2在ARP请求中使用最优本机地址而不是使用可能错误的源地址。这能确保RealServer发送的ARP请求来自真实的内网IP而不是VIP。同时注意RealServer上的业务服务比如Nginx监听的是0.0.0.0:80这样VIP绑到lo上后请求到达lo上的VIPNginx一样能接住。如果你让Nginx只监听内网IP而不监听VIP那VIP的流量到了机器上也没有进程接收健康检查就会出问题。4. 故障切换的真实过程与验证手段4.1 主Director宕机后发生了什么配置完成、服务正常后你一定会想到底一切换流量会不会断我们看看真实发生的过程。假设主Directorlb01因为断电直接宕机整个过程如下备用Directorlb02在3个advert_int周期内没有收到lb01的VRRP心跳报文触发超时。lb02把自己的状态从BACKUP提升为MASTER并在eth0上绑定VIP 192.168.1.100。lb02发送免费ARPGratuitous ARP通知同一二层网络内的交换机“VIP的MAC地址已经变了请更新转发表”。后续客户端发往VIP的请求到达交换机后被转发到lb02。lb02上的LVS规则接管流量按调度算法继续分发到后端的RealServer。从客户端视角来看它在TCP层连接的是VIP只要TCP连接还在维持请求几乎不会有感知如果是新建立的连接在切换完成的几秒内可能会出现连接超时或者拒绝。这也是为什么keepalived的advert_int、priority这些参数会影响切换速度和质量。我在机房做过一次真实的断电网演习从拔掉主Director电源到lb02收到VIP并开始正常转发耗时大约4到6秒。期间有一批新请求建立连接时RST但已建立的连接没断。对大多数内部系统来说这个时间窗口是可以接受的如果是金融交易或者秒杀这类超敏业务就需要在更上层做重试和降级策略了。4.2 如何优雅地做切换演练既然上了高可用就不能只在出了事故时验证。我习惯每一到两个月做一次主动切换演练方法是手动停掉主Director上的keepalived进程systemctl stop keepalived观察指标包括备用Director是否在预期时间内接管VIP用ip addr show eth0看VIP是否出现。业务侧是否有大量5xx或连接失败要有基本监控至少用脚本发HTTP请求探测。主Director恢复后VIP是否自动回切默认是会回切的因为priority更高。回切其实是双刃剑。好处是恢复初始架构坏处是一旦主Director回来后可能引发一次新的连接中断。所以很多生产团队会把主备配置成“不抢占”模式也就是nopreempt。做法是在两组配置的vrrp_instance里都加上nopreempt这样VIP不会自动飘回原主除非原主明确丢失了VIP。我个人更倾向保留抢占前提是你在低峰期做恢复。这个看团队的运维习惯没有绝对对错。4.3 健康检查到底该用什么方式keepalived对后端RealServer的健康检查有三种方式TCP_CHECK、HTTP_GET、SSL_GET、MISC_CHECK。我只讲两个生产里常用到的。TCP_CHECK适合纯TCP端口探活。比如后端是Nginx监听80就用connect_port 80。但这里有个坑TCP_CHECK只验证端口能连上不验证业务逻辑是否正常。如果Nginx进程还活着、端口还开着但upstream已经打满、返回全是502TCP_CHECK照样认为节点健康依然会把流量分过去。所以对于HTTP服务我更推荐HTTP_GET。它不仅检查端口还会实际请求一个URL如果返回码非2xx/3xx就判定节点异常。配置示例real_server 192.168.1.21 80 { weight 1 HTTP_GET { url { path /healthz status_code 200 } connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } }注意后端必须要有一个/healthz这样的轻量接口并且这个接口不能被限流或者鉴权。我曾经遇到过某团队把健康检查请求指向了登录页结果换季时验证码服务故障把所有RealServer都检查成了异常流量雪崩。健康检查的接口最好返回纯文本不连数据库、不查缓存只做进程级探活最多检查一下关键依赖是否在线。5. 我在生产环境踩过的坑和优化记录5.1 配置里最容易写错的三个地方第一个是virtual_router_id冲突。有一次我们和另一个部门共用了一台交换机他们的keepalived也用了ID 51结果两边VRRP互相抢VIP表现为VIP一会在一台机器上一会又消失。排查先用tcpdump -i eth0 vrrp抓包发现同一个ID来源不止一组MAC最后定位到是ID冲突。改掉之后就彻底安静了。第二个是persistence_timeout设置过长导致调度不均。上面提过这个参数适合有会话保持需求的业务但没有需求的时候千万别乱开。把它设成0后调度算法立刻恢复均匀分布。第三个是RealServer的arp_announce参数配错。注意lo和all两个作用域都要设置只设lo不够因为内核在某些场景会读取all下的配置。我踩过一次只设了lo的亏结果vip还是时不时被RealServer响应排查半天才发现all下的值还是默认的0。5.2 长时间运行的性能瓶颈检查LVS本身性能很强百Gbps吞吐的都有但DR模式下有个不可避免的开销它需要维护每个连接的状态。如果业务长连接非常多Director的状态表会膨胀内存占用持续上涨。可以通过ipvsadm -L -n --stats观察连接数也可以通过cat /proc/net/ip_conntrack | wc -l查看连接跟踪条目的规模。我遇到过这样一个场景后端服务是WebSocket长连接每台机器几万个连接维持在线LVS状态表一路涨到几十万条Director内存占用居高不下。优化方向是缩短连接超时、定期清理无效连接。但在LVS层直接清理会影响已建立的会话所以我最终把长连接业务从LVS后端挪走单独用一层网关承载LVS只负责普通HTTP请求。这件事给我的教训是LVS适合短连接和普通会话长连接场景要提前想清楚连接跟踪的运维成本。另外建议给ipvsadm的调度算法留一点思考空间。我默认用wrr加权轮询简单且平均。但如果后端节点性能差异明显建议用lc最小连接数让连接数少的节点多接一点流量。具体用哪个没有标准答案压测数据就是最好的依据。5.3 监控告警的补充脚本最后这部分是我的私藏。keepalived本身只负责切换但它不会替你做完整的业务监控。我建议在Director上部署一个简单的监控脚本每30秒检查一次VIP归属和LVS规则是否存在#!/bin/bash VIP192.168.1.100 if ! ip addr show eth0 | grep -q $VIP; then echo VIP lost on $(hostname) at $(date) /var/log/lvs-monitor.log fi if ! ipvsadm -L -n | grep -q $VIP; then echo LVS rule missing on $(hostname) at $(date) /var/log/lvs-monitor.log fi这个脚本只做记录不做自动处理但配合告警组件基本能把“VIP丢了”“规则没了”“keepalived挂了”这类潜在故障第一时间暴露出来。比单纯看进程还活着要靠谱得多。还有一个我个人的小习惯每次调整keepalived.conf之后先跑一遍keepalived -t -f /etc/keepalived/keepalived.conf做语法检查再重启服务避免因为一个标点符号让整个高可用链路在夜里悄悄失效。这套组合我已经维护了相当长的时间它不见得是最花哨的架构但每次出问题它都能按预期切换、按预期转发。如果你也要在生产环境中搭高可用入口我建议先从这套配置跑起把切换演练做扎实再去想那些更复杂的编排方案。四层稳了上层才能安心。