news 2026/10/19 14:12:54

LVS TUN模式原理与部署实践:隧道封装、跨网段四层负载均衡详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LVS TUN模式原理与部署实践:隧道封装、跨网段四层负载均衡详解

去年单位内部一个视频文件下载系统做扩容,后端从 3 台涨到 8 台,单机 nginx 转发实在顶不住了。我翻出当年那套 LVS 方案,准备用 TUN 模式重新搭。搭建过程中发现,网上关于 LVS 的资料虽然多,但 TUN 模式大多只讲一句话——“用隧道封装,后端可以直接回包”,真正落地时那些内核参数、路由细节、MTU 问题才是重头戏。这篇文章就把这次完整部署过程、逐跳抓包观察、以及最后总结出的排错经验一并写出来,给正在做四层负载均衡的朋友做个参考。

1. 为什么调度器只剩“半条链路”,TUN 模式依然能撑住整个集群

1.1 LVS 三种工作模式的本质差别

先讲清楚一个概念:LVS 三种模式的分水岭在于“响应流量要不要回到调度器”。

NAT 模式是双向都过调度器。客户端把包发给调度器,调度器改目的 IP 和端口发给后端,后端响应后再回到调度器,调度器改写源 IP 和端口还给客户端。这个模式的优点是后端配置简单,后端甚至不用知道 VIP 的存在,缺点是调度器成了出口瓶颈,响应数据一大,网卡先扛不住。

DR 模式则让响应绕开调度器。调度器和后端必须处于同一个二层网络,调度器收到请求后,把以太网帧的目的 MAC 改成后端的 MAC,IP 头原封不动发出去。后端处理完请求后,发现客户端 IP 不在本机网段,走默认路由直接回包。由于二层转发不修改 IP 报文,整个链路非常高效,这也是生产环境最常用的模式。

TUN 模式解决的是 DR 的一个硬伤:二层网络限制。TUN 允许调度器和后端不在同一个二层,甚至不在同一个机房,只要三层路由可达就能工作。调度器收到请求后,不修改原始 IP 包的任何内容,而是把这个原始 IP 包整个封装在一个新的 IP-in-IP 隧道包里,外层目的 IP 写成后端的真实 IP,然后扔给网络。后端解封装拿到原始包,发现目标 IP 是 VIP,而 VIP 本来就在本机环回口上,于是交给本机应用处理,响应包同样绕开调度器直接发给客户端。

从调度器压力这个维度看,TUN 和 DR 都是“半条链路”,请求进来过调度器,响应直接由后端回客户端。调度器的 CPU 和带宽压力主要集中在外层封装操作和请求转发上。

1.2 TUN 的隧道封装到底封的是什么

很多人第一次看 TUN 模式的抓包会懵:怎么在调度器出口看到两层 IP 头?

打个比方,普通 IP 包就像一封写好的信,DR 模式是让快递员照着信封上的收件人地址,把信送到同小区另一栋楼;而 TUN 模式是把这封信连同信封一起,塞进一个大一号的快递袋,快递袋上写的是后端服务的地址。后端收到后拆开快递袋,里面还是原来那封信,收件人写的是 VIP。这个“大一号的快递袋”就是 IP-in-IP 隧道。

具体封装格式是外层 IP 头加内层完整 IP 包,外层协议号固定为 4。外层源 IP 是调度器用于和后端通信的 DIP,外层目的 IP 是后端 RIP。内层 IP 头仍然保留着客户端的源 IP 和 VIP 目的 IP,TCP 端口号没有任何改变,因此后端应用看到的访问来源就是真实客户端地址。

外层IP头: src = DIP (调度器IP) dst = RIP (后端真实IP) protocol = 4 (IP-in-IP) 内层完整包: src = 客户端IP dst = VIP protocol = TCP/UDP 载荷 = 原始应用数据

之所以要保持内层 IP 头完全不变,是因为后端在处理完请求后要直接以 VIP 作为源 IP 回包给客户端。如果内层 IP 头被改成了别的目标,后端应用就无法感知到正确的 VIP,响应包源地址也会出错。

2. 从 SYN 到响应,TUN 模式下数据包的真实旅行路径

2.1 请求方向:四跳链路逐个拆解

我在测试环境里搭了一套最小的 TUN 集群,用 tcpdump 逐跳抓包,把整个过程看得非常清楚。

客户端 IP 是 172.16.1.10,调度器 DIP 是 192.168.50.10,两台后端 RIP 分别是 192.168.50.20 和 192.168.50.21,VIP 是 10.90.1.100。注意这里特意让 VIP 和 DIP 处于不同的 IP 段,就是为了验证 TUN 确实能跨网段工作。

第一跳,客户端把 SYN 包发给 VIP。这一跳和普通访问没有任何区别,客户端路由表里只需要有一条指向 VIP 的路径,或者直接把 VIP 所在网段指向调度器。

第二跳,调度器网卡收到目标 IP 为 VIP 的包。因为 IPVS 已经配置了 VIP:80 到后端的转发规则,包会进入 IPVS 的处理逻辑。调度器根据负载均衡算法选中一台后端,比如 192.168.50.20,然后生成一个封装包。外层源 IP 是调度器自己的 DIP,外层目的 IP 是 RIP,内层原封不动还是客户端到 VIP 的包。

# 在调度器 eth0 上抓包,同时能看到两种形态的流量 tcpdump -nn -i eth0 'tcp port 80' tcpdump -nn -i eth0 'proto 4'

第一个 tcpdump 看到的是客户端访问 VIP 的原始 TCP 包,第二个 tcpdump 看到的才是封装后从调度器出去的 IPIP 包。我第一次抓包时差点以为网卡重复收包,其实只是同一份数据流在不同层的两种呈现。

第三跳,封装包经过三层网络到达后端。后端内核收到外层目的 IP 是自己 RIP 的包后,发现协议号是 4,于是交给 IPIP 隧道模块解封装,把内层原始 IP 包取出来继续走本机协议栈。

# 在后端上分别抓隧道接口和解封装后的流量 tcpdump -nn -i eth0 'proto 4' tcpdump -nn -i tunl0 'tcp port 80'

eth0 上看到的是调度器发来的封装包,tunl0 上看到的是解封装后的原始包。如果后端没有开启 rp_filter 导致丢包,tunl0 上通常能直接看到客户端发来的 SYN。

第四跳,内层包的目标 IP 是 VIP,而后端的 VIP 绑定在 lo 接口上,内核认为这是发往本机的包,于是送进 TCP 协议栈,应用正常 accept 连接。

2.2 响应方向:彻底绕开调度器

后端应用处理完请求后,回包源 IP 是 VIP,目标 IP 是客户端 IP。它不需要再经过调度器,原因在于后端查路由表时,发现客户端 IP 不在任何直连网段,于是走默认路由发出响应。

我在后端 eth0 上抓响应包时,能看到源 IP 是 10.90.1.100,目标 IP 是 172.16.1.10 的数据包。只要后端出口路由能到客户端,整个响应链路就完全独立于调度器。

这带来的显著收益是:响应流量再大,也不会占用调度器的带宽。比如一个视频下载服务,请求可能只有几 KB,响应却有几百 MB,调度器只需处理那几 KB 的请求和封装开销,后端直接把大流量发给客户端,整个集群的吞吐上限一下就拉高了。

3. 两台后端服务器,复用标准 VIP 的落地全过程

3.1 角色规划与系统准备

下面是一套我实际部署时使用的规划,读者可以直接照抄改 IP。

角色网卡 IPVIP系统
调度器 Director192.168.50.1010.90.1.100 (eth0)Ubuntu 22.04
后端 RS1192.168.50.2010.90.1.100 (lo)Ubuntu 22.04
后端 RS2192.168.50.2110.90.1.100 (lo)Ubuntu 22.04

开始前先在所有机器上确认内核模块是否加载:

modprobe ipip modprobe ip_vs lsmod | grep -E 'ipip|ip_vs'

这里需要解释一下,ipip 模块会在系统里自动创建一个 tunl0 隧道接口。虽然不同内核版本对 tunl0 的默认创建方式略有差异,但一般只要 modprobe ipip 成功,ip addr show tunl0就能看到那个接口。

3.2 后端 RS 的 VIP 绑定与隧道接口配置

后端的操作分为三步:启用隧道接口、绑定 VIP、关闭反向路径过滤。

先让 tunl0 接口处于 up 状态,同时把默认的隧道地址清掉,避免它自动使用本机 IP 带来路由混乱:

ip link set tunl0 up ip addr flush dev tunl0

然后把 VIP 绑到 lo 接口上。这里有个细节:为什么要绑在 lo 而不是直接绑在 eth0?因为后端不能对外宣告自己拥有 VIP,否则调度器和后端在同一个广播域时,客户端的 ARP 请求可能被后端抢先应答,导致流量绕过调度器直接打在后端上,LVS 的负载均衡逻辑就废了。

ip addr add 10.90.1.100/32 dev lo

最后调整内核参数。这是 TUN 模式最容易踩的坑,详见下一节,先把命令写上:

cat >> /etc/sysctl.conf <<EOF net.ipv4.conf.all.rp_filter = 0 net.ipv4.conf.tunl0.rp_filter = 0 net.ipv4.conf.lo.rp_filter = 0 EOF sysctl -p

后端应用照着监听 VIP 端口即可。我用 nginx 做测试时,直接写了server { listen 10.90.1.100:80; },这样只有访问 VIP 的流量才会进到这个应用,直接访问后端 RIP 的 80 端口则不会命中。

3.3 调度器上的 ipvsadm 配置

调度器上先把 VIP 配置到对外网卡上。如果场景是外部客户端访问,一般这个 IP 就是映射到公网的入口地址:

ip addr add 10.90.1.100/32 dev eth0 sysctl -w net.ipv4.ip_forward=1

然后配置 IPVS 规则。这里的重点是最后那个-i参数,它代表 tunnel 模式,也是和 DR 模式之间唯一的命令差别。DR 用的参数是-g,NAT 用的参数是-m,很多人配置完发现包没被正确封装,就是因为把-i写成了-g。

ipvsadm -A -t 10.90.1.100:80 -s rr ipvsadm -a -t 10.90.1.100:80 -r 192.168.50.20:80 -i -w 1 ipvsadm -a -t 10.90.1.100:80 -r 192.168.50.21:80 -i -w 1

验证规则:

ipvsadm -L -n -v IP Virtual Server version 1.2.1 Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 10.90.1.100:80 rr -> 192.168.50.20:80 Tunnel 1 0 0 -> 192.168.50.21:80 Tunnel 1 0 0

到这一步,从客户端访问http://10.90.1.100就应该能看到两台后端轮询响应了。如果直接 curl 不透,优先检查防火墙是不是把 IPIP 协议(协议号 4)挡掉了。

4. TUN 模式上线后,我们处理过的四个诡异“软钉子”

4.1 rp_filter 导致回程包被内核静默丢弃

反向路径过滤(rp_filter)是 Linux 内核默认开启的防伪功能,它要求:某个接口收到一个源 IP 为 X 的包,内核会检查 X 是不是应该从同一个接口回来。如果不是,就把包丢掉。

TUN 模式恰好天然违反这个规则。客户端访问 VIP 的包是先从隧道接口 tunl0 进去的,源 IP 是客户端 IP;而后端响应这个客户端时,是从 eth0 出口走的。对于内核来说,同一个源的包从 tunl0 进来、从 eth0 出去,完全是非对称路径,于是一大批回程包在协议栈里直接被丢了。

症状很隐蔽:客户端发 SYN,后端能看到请求,也能看到 TCP 栈回 SYN-ACK,但客户端永远收不到响应,连接一直卡在 SYN_SENT。我用ss -tnp查看后端 TCP 连接状态时,能看到连接处于 SYN_RECV,但包就是发不出去。

解决办法就是前面提到的关闭 rp_filter。需要特别注意 sysctl 的生效层级,conf/all和conf/具体接口都要设置,只设一个可能在部分内核版本上不生效。

sysctl -w net.ipv4.conf.all.rp_filter=0 sysctl -w net.ipv4.conf.tunl0.rp_filter=0 sysctl -w net.ipv4.conf.eth0.rp_filter=0 sysctl -w net.ipv4.conf.default.rp_filter=0

4.2 隧道封装带来的 MTU 和 TCP MSS 问题

IP-in-IP 封装会额外增加 20 字节的外层 IP 头。如果后端应用发出的响应包原本是标准的 1500 字节,封装之后会变成 1520 字节左右,超过链路 MTU,就需要分片。

在有些交换机或者云环境下,分片包会被直接丢弃,最终表现为:小文件下载正常,大文件传一会儿就卡死,或者干脆连不上。

处理思路有两个。第一个是直接在隧道链路上把 MTU 调小,比如把调度器和后端隧道接口的 MTU 都设成 1480。第二个是在后端上对 SYN 包做 MSS 钳制,告诉客户端“这条路径只能承载较小的包”。我在测试环境里用的是后端 iptables 钳制 MSS 的方式:

iptables -A OUTPUT -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1400

如果服务跑在 nginx 这类七层服务后面,也可以在 nginx 配置里用listen 443 ssl;配合 MTU 相关调优,但四层场景下 iptables 更直接。

4.3 后端 VIP 的 ARP 广播污染

VIP 绑定在后端 lo 接口上之后,如果不对 ARP 行为做限制,后端网卡会响应针对 VIP 的 ARP 请求。虽然 TUN 模式允许跨网段,但在混合网络环境下(比如后端和部分客户端处于同一二层),一旦后端把 ARP 应答出去了,客户端可能会直接找到后端,把调度器晾在一边。

这个问题的标准处理方法和 DR 模式一致:设置内核 ARP 参数,让后端绝不回应 VIP 的 ARP。

cat >> /etc/sysctl.conf <<EOF net.ipv4.conf.all.arp_ignore = 1 net.ipv4.conf.all.arp_announce = 2 EOF sysctl -p

同时确认 VIP 绑在 lo 接口时没有使用广播标志。我在服务器上执行ip addr show dev lo,看到10.90.1.100/32后,就顺手在 lo 上加了noprefixroute,确保它不会参与默认路由计算。

4.4 安全组和主机防火墙对协议号 4 的拦截

很多第一次搭 TUN 的人会在调度器和后端之间配了严格的 iptables,结果忘了放行 IPIP 协议。iptables 匹配这个协议时,既可以直接用协议号 4,也可以用协议名 ipip。

iptables -I INPUT -p ipip -s 192.168.50.10 -j ACCEPT

如果集群放在虚拟化平台上,安全组策略里也要显式放通 IP-in-IP。这个地方最容易无声无息地导致整个集群不通,因为 ICMP 能通,TCP 却连不上,排查半天才发现是安全组没有协议号 4 的放行项。

5. 什么时候值得上 TUN,什么时候应该果断放弃

5.1 优先选择 TUN 的两类场景

一是后端跨网段、跨机房的场景。比如调度器在 A 地机房,后端有两台在 B 地机房,它们之间只有三层路由可达,DR 的 MAC 改写方案完全失效,NAT 又会让调度器被响应流量压垮,这时候 TUN 几乎是唯一兼顾“后端跨网段”和“响应绕行”的四层方案。

二是响应远大于请求的场景。视频点播、大文件下载、镜像分发一类的服务,请求包可能只有几 KB,单次响应却是几十 MB 甚至几个 GB。这类流量如果全部走 NAT 模式经调度器转发,调度器的出口带宽就是集群上限,后端再多也无法突破;TUN 让大流量直接走后端出口,调度器只需要处理轻量级的请求封装,集群整体支撑能力完全不在一个量级。

5.2 TUN 的缺点不能逃避

最明显的问题是封装带来的性能和运维复杂度。每一次请求转发都要做一次 IP 包封装和解封装,虽然现代内核处理起来非常快,但对于极端追求低延迟的业务,这 20 字节的额外开销和隧道处理耗时看得见摸得着。

另一个问题是后端必须侵入式配置。后端需要绑 VIP、需要加载隧道模块、需要关闭 rp_filter,这些操作对于一个由别人托管的应用环境来说可能无法接受。如果后端节点完全不受控,TUN 模式的部署阻力会非常大,倒是 NAT 模式更适合事后接入。

对比项NATDRTUN
后端是否掌握 VIP不需要需要绑 vip需要绑 vip
响应是否过调度器是否否
后端是否需要同二层不需要需要不需要
调度器转发方式修改 IP 头修改 MAC 帧封装 IPIP 包
跨机房部署可但响应回流难不可可
对后端系统配置要求低中高

5.3 决策思路:先看响应流量方向,再看二层约束

我在选型时有个简单的判断顺序:如果后端和调度器必须跨二层部署,直接选 TUN;如果都在同一个二层,优先 DR,因为 DR 省掉了封装开销;如果后端环境不允许绑定 VIP 或做任何内核参数调整,那就老老实实上 NAT,牺牲一点调度器带宽,换回部署灵活性。

6. 自建集群后续运维,绕不开的几个细节

集群通了之后,运维工作才开始。我这次部署后做了三件小事,对日常维护帮助很大。

第一件事是在调度器上配置了针对后端的探测任务,用自定义脚本定期 curl 后端的 VIP 端口,连续失败三次就把该后端从 IPVS 规则中移除。命令很简单,核心是 remove 与 add 的组合:

ipvsadm -d -t 10.90.1.100:80 -r 192.168.50.20:80 ipvsadm -a -t 10.90.1.100:80 -r 192.168.50.20:80 -i -w 0

第二件事是给后端各节点的回程路由做了检查和可视化记录。因为 TUN 模式下后端的 eth0 不仅要承接解封装前的 IPIP 包,还要直发响应包,一旦后端机器上有多条默认路由或者策略路由配置错误,很容易出现响应包从错误出口发出导致客户端访问异常的情况。检查方法其实不复杂,看ip route里的默认路由下一条是否指向正确的网关即可。

第三件事是抓包定位问题时,我固定了一个组合拳:调度器 eth0 上看原始请求和封装出口,后端 tunl0 上看内层包头,后端 eth0 上看响应包。三个位置各抓三分钟,基本能把整个链路的问题定位到具体设备。特别是响应方向有问题时,只看后端 eth0 的出包情况和调度器 eth0 的情况对比,就能判断调度器到底有没有被流量殃及。

这套 TUN 模式集群运行到现在,最大的感触是:它在架构上提供的自由度,是 NAT 和 DR 给不了的,但前提是必须接受它对后端系统配置的侵入性。按我的习惯,能用 DR 就优先 DR,一旦遇到必须跨网段、跨机房的场景,TUN 依然是那个最可靠的四层负载均衡方案。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 14:10:52

泰坦尼克数据集实战:二分类建模与特征工程完整流程解析

泰坦尼克数据集&#xff0c;大概是这个圈子里被跑过最多遍的入门项目了。当年我刚开始接触机器学习时&#xff0c;导师扔过来的第一份资料就是它。现在回头看&#xff0c;这891行数据确实足够经典&#xff1a;既有缺失值要处理&#xff0c;又有类别特征要编码&#xff0c;还能玩…

作者头像 李华
网站建设 2026/10/19 14:09:19

AI辅助教材创作如何降查重?五层方法论与全流程实操

1. 项目概述与核心需求拆解1.1 这个项目解决什么问题做教材、写讲义、编课程资料&#xff0c;这事在教育培训行业里是刚需。以前靠人工一个字一个字地磨&#xff0c;一份像样的教材动辄几万字&#xff0c;从大纲设计到素材整理&#xff0c;再到逐章逐节地写、改、校&#xff0c…

作者头像 李华
网站建设 2026/10/19 14:09:12

开源CLI代码审查工具:本地LLM+Git驱动的可审计Code Review方案

1. 项目概述&#xff1a;一个真正能落地的开源代码审查 CLI 工具“open-code-review”这个名字乍一听像某个 GitHub 上刚建的空仓库&#xff0c;但如果你最近在团队里被拉进过几次“人工 Code Review 拉锯战”&#xff0c;或者自己写完 PR 后盯着 diff 界面发呆超过 15 分钟——…

作者头像 李华
网站建设 2026/10/19 14:07:43

Windows下用Bat脚本实现Laya项目一键压缩解压

这两年做Laya项目&#xff0c;最让我上瘾的其实不是引擎本身&#xff0c;而是把那些重复到吐的流程用脚本串起来。今天想聊一个看着不起眼、但用好了能帮你省下大量摸鱼时间的技能——在Windows环境下用Cmd批处理脚本&#xff08;Bat&#xff09;处理Laya项目里的日常杂活&…

作者头像 李华
网站建设 2026/10/19 14:07:41

C# TCP通信实战:TcpClient/TcpListener用法与粘包心跳处理

做C#的后端同学&#xff0c;基本都会有一阵子绕不开TCP通信。我最早做设备对接项目时&#xff0c;傻乎乎地直接用Socket类&#xff0c;Bind、Listen、Accept、BeginReceive一路写下来&#xff0c;光处理半包粘包就折腾了一周。后来换成TcpClient和TcpListener这套高层封装&…

作者头像 李华
网站建设 2026/10/19 14:07:40

HFSS Help.pdf高效检索指南:从文档结构到仿真排错实战

简介&#xff1a;《HFSS Help.pdf》是ANSYS HFSS 2020 R1官方帮助文档的完整PDF版&#xff0c;面向从事高频电磁仿真、天线设计、信号完整性及微波电路研发的工程师和研究人员&#xff0c;可当作随查随用的工具手册。压缩包内共1个PDF文件&#xff0c;约105.29MB&#xff0c;内…

作者头像 李华