1. 谁需要给服务器做带宽限制我为什么盯上 wondershaper1.1 差点被一条业务打瘫的周末如果没有真正经历过出口流量被打满的事故你很难体会到“带宽管理”这四个字的分量。我有过一次印象很深的经历公司一条 300Mbps 的专线出口入口交换机的流量统计突然拉满业务接口响应时间从正常的 80ms 一路飙到 800ms 以上邮件报警连着发了二十多封。排查下来不是攻击也不是机房故障而是有个部门在做大量数据迁移三台机器同时往外推数据把整条专线的队列直接打爆。这种场景并不少见。现代企业的网络出口带宽是共享的可业务系统对延迟和抖动极其敏感。文件备份、日志采集、容器镜像拉取、监控数据上报这些“非核心流量”平时没什么但在特定时刻会突然吃掉几乎全部带宽。等你发现核心业务卡顿的时候往往已经过了 10 分钟而那 10 分钟里客户可能已经因为超时问题提交了十几个工单。问题在于很多服务器管理员的第一反应是加带宽或者找网络团队在交换机上做 QoS。但实际情况是厂商给的带宽套餐不一定能立刻升级而交换机上的 QoS 配置涉及整个网络架构的变更周期长、审批慢临时救不了火。更常见的场景是我们管理的是服务器本身网络设备根本不在自己手里。这时候最靠谱的做法就是直接在服务器操作系统这一层做带宽限制把“谁可以占多少带宽”这件事掌握在自己手上。我在浪潮信息 KeyarchOS以下简称 KOS上做这层限制时选的是 wondershaper准确版本是 1.2.1-2。这个工具不是什么新型技术它在上世纪九十年代末就出现了但至今仍在很多发行版仓库里保留原因很简单它把复杂到可以劝退大多数人的 tc 流量整形命令封装成了一个带配置文件的脚本让你能像设置环境变量一样设置限速参数。1.2 为什么选 wondershaper 而不是自己敲 tcLinux 内核自带的 tcTraffic Control是真正负责带宽限制的核心组件。 tc 能实现的功能极强但它的命令体系非常劝退你需要在命令行里手动指定根 qdisc、子 class、叶子 class、过滤器还要把速率、ceil、burst、mtu 这些参数一个个配好。即使你照着文档敲成了一次改起来也麻烦而且没有统一的配置文件去管理换台机器又要重新敲一遍。wondershaper 本质上是一个 Bash 脚本它把 tc 和 iptables 的常用操作包起来用几个环境变量来表达配置意图。对于机房出口带宽管理、多业务共用一台服务器、边缘节点做流量整形这类需求它几乎是最小可用的方案。更重要的是wondershaper 的优势不在功能多而在“稳定”。它默认使用 HTB 队列规则配合 ifb 虚拟接口来实现对出方向和入方向流量的双向限制。HTB 这种队列规则在带宽分配上的特点是允许每个类借用别人的空闲带宽但绝不能突破自己的上限这种机制非常符合日常业务场景。你给部门 A 分配 100Mbps部门 A 高峰期用满高峰期过后整个链路的剩余带宽它也能继续借用不会出现带宽白白浪费的情况。和部署一套完整的 QoS 策略相比wondershaper 没有任何守护进程常驻不额外占用 CPU 和内存只在启动时执行几条 tc 和 iptables 命令然后通过内核中的队列模块去实现调度。可以说它把“轻量”和“有效”平衡得刚刚好。2. KeyarchOS 环境适配检查安装前必须搞清的三件事2.1 KeyarchOS 与 CentOS 生态的兼容性浪潮信息 KeyarchOS 是浪潮基于 Linux 内核构建的企业级服务器操作系统它和 CentOS、Rocky Linux 这类系统在软件包体系上保持兼容使用的是 RPM 包管理。这就意味着凡是能在 CentOS 仓库里安装的软件在 KOS 上大多也能直接安装或者只需要很小的调整。我在安装 wondershaper 之前其实经历了短暂的犹豫这个软件不是一个随系统预装的内核模块而是独立的分发包它是否完全适配 KOS 的目录结构和 systemd 服务机制实测下来只要 KOS 保留了标准的 /etc/sysconfig、/usr/sbin 和 systemd 体系wondershaper 的运行路径就没有任何障碍。KOS 本身就是面向数据中心和企业场景的操作系统这些标准路径是完全保留的。在这里也想提醒一点很多人拿到 KOS 之后下意识觉得“这不是 CentOS软件怕是装不上”。其实不用有这样的顾虑关键看两件事第一是包管理接口是否兼容第二是内核是否包含所需的网络模块。只要这两条没问题绝大多数 Linux 网络工具都可以平滑迁移。wondershaper 恰恰就是一个对系统依赖非常少的工具它需要的不是额外的服务框架而是内核网络子系统本身。2.2 依赖项三件套tc、iptables、ifb 内核模块wondershaper 的核心依赖是 tc 命令、iptables 工具和 ifb 内核模块。这三样东西在标准 Linux 服务器上通常都是默认存在的但也不是百分之百最好在安装前逐项确认。tc 命令来自 iproute2 包负责创建和配置队列规则。检查方式tc -v能输出版本号就说明没问题iptables 负责做数据包标记用于入向流量的重定向。检查方式iptables -L -n能正常返回规则列表即可ifb 是内核的一个虚拟网卡模块全称 Intermediate Functional Block它专门用来接收进入网卡的流量再把流量重新走一遍 tc 队列从而实现对“入向带宽”的限制。检查方式lsmod | grep ifb如果没有任何输出说明模块还没加载。前两个都比较容易解决缺什么就装什么。ifb 模块稍微特殊一点它在有些精简内核配置里没有被编译进去。如果你用的是 KOS 的标准内核一般都已经包含了 ifb通过modprobe ifb就能加载。如果真的提示找不到模块那基本上就是内核包不完整需要检查内核版本对应的 kernel-modules-extra 包是否已经安装。我当时还犯过一个低级错误只检查了 tc 命令是否存在却没有检查该命令是否真的可以访问到内核的 netlink 接口。后来在配置时才发现tc 命令能正常输出版本号但执行队列操作时却提示权限不足。原因是这台机器的 systemd 服务中我的服务运行在最简化的安全沙箱环境里禁用了 netlink 访问权限。这个问题放在后面章节细说。2.3 网卡命名与系统初始化方式网卡名称对 wondershaper 来说是个关键参数因为它需要给指定的网卡接口挂上队列规则。新版 Linux 系统的网卡命名规则不再是简单的 eth0而是 ens160、enp3s0 这样基于设备位置的名称。安装前必须用ip link show看清楚你要限制的流量到底走哪张网卡。这里有个非常容易弄反的点网卡名搞混配了和没配一样但系统不会报错。比如你的服务器有两张网卡一张是业务网卡一张是管理网卡。你把 wondershaper 的 IFACE 设成了管理网卡那么业务流量会照常跑满而管理网卡的带宽反而被限制到了你设定的值。由于管理网卡平时没啥流量这种错误极难发现等你确认业务流量不受控制时规则已经挂上去几天了。另外还要确认系统的初始化方式是 systemd 还是早期的 SysVinit。KOS 默认使用 systemd直接通过 systemctl 管理服务没问题。但 wondershaper 1.2.1-2 这个版本本身还是保留着之前的 init 脚本风格所以在配置自启动时需要做一点适配我会在后面的章节里写具体操作。3. wondershaper-1.2.1-2 完整安装步骤与配置姿势3.1 获取软件包rpm/yum 两种路径在 KOS 上安装 wondershaper 有两种路径第一种是直接用系统当前的软件仓库安装第二种是下载 RPM 包手动安装。仓库安装的前提是仓库源里已经包含了这个软件包KOS 的默认仓库是否包含 wondershaper 取决于版本和仓库镜像配置不过 CentOS 的 EPEL 仓库里一定有。我建议先试仓库安装因为依赖解析省心yum install -y wondershaper如果提示找不到软件包就需要手动指定已经下载到本地的 RPM 文件rpm -ivh wondershaper-1.2.1-2.noarch.rpm这里有个细节wondershaper 的包名带 noarch因为它是个纯脚本包没有任何二进制可执行文件不依赖 CPU 架构。你只需要确认脚本的依赖包 iptables、iproute2 已经存在安装过程就不会报错。如果在 rpm 安装过程中提示依赖缺失把依赖也一并装上比如yum install -y iproute iproute-tc iptables装完之后验证一下版本rpm -qa | grep wondershaper1.2.1-2 这个具体版本号其实是相对早期的版本和后来的 1.4.x 相比配置文件路径有所差异。这个版本默认会去读 /etc/sysconfig/wondershaper这一点务必记牢别按照新版文档把配置写到 /etc/wondershaper/wondershaper.conf 里那样启动脚本会直接找不到配置。3.2 配置文件里到底该填什么wondershaper 1.2.1-2 的配置方式非常直观用 vi 或 nano 编辑 /etc/sysconfig/wondershaper 文件即可。文件内容一般是# 设置要控制的网卡接口 IFACEeth0 # 下行带宽上限单位是 kbps DOWNLINK10240 # 上行带宽上限单位是 kbps UPLINK2048这三个参数的含义需要解释清楚。假设你的服务器是一台出口网关所有流量都会经过 eth0 转发DOWNLINK 表示这台服务器允许向外部发送数据的速度上限。注意这里的“下行”是从服务器所在网络对外部而言也就是说外部使用者从这台服务器下载数据的速度上限。UPLINK 表示这台服务器从外部接收数据的速度上限也就是外部往这台服务器上传数据时能被系统接收的速度上限。单位永远是 kbps不是 KB/s也不是 Mbps。1024 kbps 才等于 1 Mbps如果你把 10240 误认为是 10MB/s实际配置出的就是 10Mbps相差了八倍。我之前配置的时候就踩过这个单位坑。业务方要求下载速度限制在 20MB/s我随手填了个 20480结果实测下载速度只有 2.5MB/s翻了半天文档才发现单位问题。后来我习惯先在注释里写清楚# DOWNLINK10240 表示 10Mbps约等于 1.25MB/s # UPLINK2048 表示 2Mbps约等于 0.25MB/s这样配置非常明确对后来接手的人也友好。3.3 启动、停止、自启的完整命令配置写好之后启动 wondershaper 的方式是service wondershaper start如果你的系统提示没有 service 命令或者你想用 systemd 风格管理systemctl start wondershaper因为 wondershaper 1.2.1-2 自带的是 SysV init 脚本systemd 通常也能识别并执行它。不过有一点要注意这个脚本启动后并不会常驻后台它是一次性执行 tc 规则执行完就退出。所以用 systemctl status 查看时可能会显示状态异常或 failed但只要检查 tc 规则存在实际上已经生效了。启动后立刻验证service wondershaper status tc qdisc show dev eth0tc 命令如果输出类似qdisc htb 1: dev eth0 root refcnt 2 ...这样的行说明限速规则已经挂上去。如果需要停用service wondershaper stop此时 tc qdisc show 应该返回空说明规则已清空。开机自启方面老的 init 脚本在 systemd 下可以直接执行chkconfig wondershaper on更稳妥的方法是手动写一个 systemd unit 文件我后面专门写一节。4. 限速这么简单为什么能保证“稳定”tc 链路与原理实拆4.1 千层套路qdisc、class、filter想要真正理解 wondershaper 为什么不抽风就得先知道它往内核里写了一套什么样的规则。tc 的核心概念有三个qdisc排队规则、class类别、filter过滤器。qdisc 可以理解为网卡出口处的一个调度队列。默认情况下Linux 网卡出口只有一个简单的 FIFO 队列数据来了就按顺序发谁都不限制。wondershaper 做的第一件事就是把这种简单的队列换成一个带分类能力的 HTB 队列然后在 HTB 根队列下面创建若干个子类别每个子类别有自己独立的带宽参数。class 是 qdisc 下面的通道。你可以把一个 class 理解成一条独立的限速管道。wondershaper 会在总带宽这个根管道下划分出两个主要的子管道一个用于控制常规流量一个用于保证某些特殊流量不被饿死。每个管道都有 rate保证速率和 ceil最大速率两个参数。rate 表示这个类别保证的带宽ceil 表示最多能借到多少带宽。HTB 的巧妙之处在于它允许一个类别在空闲时把自己的带宽借给其它类别但无论如何任何一个类别的实时速率都不能超过 ceil。filter 的作用是把流量分流到不同的 class 里去。可以是按源 IP、目的 IP、端口也可以是按 iptables 打上的标记。wondershaper 使用 iptables 的 mangle 表给特定方向的流量打标记然后 filter 根据这些标记把数据包丢进对应的 HTB 子类别。这整套逻辑下来限速就不再是一个简单的“一刀切”操作而是可以精细到总带宽 300Mbps 中常规业务最多跑到 250Mbps剩下的 50Mbps 专门留给 SSH 和运维通道流量特别大的场景下业务类可以借用运维类的空闲带宽但一旦运维通道需要会立刻被收回去。这样的分配策略才是“稳定”二字的核心来源。4.2 出站和入站整形的不同实现HTB 与 ifb对很多刚接触的人来说最容易困惑的点是为什么不能只用 tc 在网卡上直接限制入向流量原因在于网卡的入口方向没有传统的发送队列概念。tc 的队列调度是作用于“出口”的也就是说你只能在数据包被发送出去之前对它做排队和限速。而数据包从网络进来时已经到达了网卡内核没有足够的时间在“接收”方向做完整的队列调度。wondershaper 对这个问题有一个经典的解决方案使用 ifb 虚拟网卡。工作流程是这样的先用 modprobe 加载 ifb 模块并创建 ifb0 虚拟网卡把真实网卡比如 eth0的入向流量通过 tc 的 ingress qdisc 和 mirred 重定向动作全部导入 ifb0相当于给真实的入口数据做了一个“转口”数据先进入 ifb0 的出口队列然后才轮到真正的处理逻辑在 ifb0 上再挂一套 HTB 队列这样就实现了入向带宽的限制。整个过程可以用一句话概括把入向流量伪装成出向流量然后用出向限速的方式去限制它。这也是 wondershaper 里 UPLINK 参数真正生效的底层路径。这里有一个非常实用的排查思路如果你发现只限住了服务器向外发送的速度但接收速度完全不受控大概率就是 ifb0 没有被正确创建或者 reroute 规则没有生效。可以手动检查ip link show ifb0如果不存在就用modprobe ifb ip link set ifb0 up ip link set dev eth0 up再重新启动 wondershaper。4.3 TCP 的天然脾气是稳定限速的最大变量带宽限制做得稳不稳很大程度上不是 tc 的问题而是 TCP 协议的天然脾气问题。TCP 是一种“自适应”协议发送端会根据网络状况动态调整窗口大小。当带宽限制生效时数据包开始在队列里排队如果队列长度不合适就会出现随机丢包然后 TCP 会把它误判为“网络拥堵”把窗口砍半导致传输速度断崖式下降。所以你会发现有些限速工具配置之后传输速度不仅没有稳定在上限附近反而一直在一半甚至更低的水平抖动。这其实是队列和 TCP 之间的配合出了问题而不是限速没有生效。wondershaper 默认使用了 HTB 的 burst 参数这个参数允许每个类别在一个极短时间内超过 rate 限制以吸收突发的数据包从而避免在队列里产生过度排队。但默认值不一定适用于所有网卡和带宽矩阵。在我的实际使用中把 DOWNLINK 设置成线路带宽的 70% 到 80%同时配合一个合理的 burst 值就能让 TCP 的拥塞控制算法少误判带宽曲线非常平滑。如果你想更深一步可以在服务器上查看 TCP 重传率指标。如果重传率明显偏高优先压低 DOWNLINK而不是盲目调大 burst。宁可让 TCP 使用率略低于物理上限也比用满后疯狂重传要稳定得多。5. 安装完的稳定性调优从“能用”到“好用”5.1 别把 DOWNLINK/UPLINK 写成物理上限我刚配置 wondershaper 时最大的冲动就是把 DOWNLINK 直接填成运营商给的物理带宽数值。比如专线是 300Mbps我就在 DOWNLINK 里填 307200。实际效果并不好因为 300Mbps 是物理链路层的峰值速率而 wondershaper 限制的是应用层可用的传输速率TCP/IP 包头、以太网帧头、重传开销都会占掉一部分。正确做法是留出 20% 到 30% 的余量。300Mbps 的线DOWNLINK 配成 210Mbps 到 240Mbps 之间换算成 kbps 就是 215040 到 245760。这样配置之后表面上损失了一点最高速度但换回的是极低的队列延迟和丢包率。UPLINK 的参数更是要克制。很多公司都是从运营商那里买了上下行不对等的宽带下行很大上行只有一点点。如果 UPLINK 配置过大入向流量在 ifb0 的队列里越积越多会造成 ACK 包延迟从而影响整体 TCP 吞吐。建议 UPLINK 设置为运营商实际上行带宽的 60% 到 70%宁可少收不可过载。5.2 给特殊业务开“绿通”wondershaper 虽然本身不提供复杂的分类规则但它依赖的是 iptables所以完全可以通过 iptables 的标记机制给指定的源 IP 或端口单独开一条“绿色通道”。这个功能在运维场景里特别实用比如测试环境和生产环境共用一台服务器出口我希望 SAP 系统的端口完全不受限而其它业务整体限速。实现思路是这样的先在 iptables 的 mangle 表里给特定流量打上标记然后在 tc 的 filter 里把这些优先标记分配到独立的 HTB class这个 class 的 rate 和 ceil 不设上限或设置很高。比如我想给 10.10.10.10 的 443 端口流量不限速iptables -t mangle -A PREROUTING -s 10.10.10.10 -p tcp --dport 443 -j MARK --set-mark 10 tc filter add dev eth0 parent 1:0 protocol ip prio 1 handle 10 fw classid 1:10然后单独给 class 1:10 设置一个足够大的 rate。当这条规则的 prio 高于默认规则时数据包就会优先匹配到这条独立通道不会被默认队列限速。这相当于在整条限速管道的旁边开了一条直达车道。这类配置有一个坑就是一旦手动执行过之后如果重启 wondershaper它默认会清空所有 tc 规则和 iptables 规则你手动加的过滤器也会被冲掉。建议把自定义规则写成一个独立的 shell 脚本在 wondershaper 启动之后接着执行这样既能复用 wondershaper 的整体框架又能保有灵活的扩展能力。5.3 做成 systemd 服务并配合监控验证wondershaper 1.2.1-2 自带的 init 脚本在 systemd 环境里虽然能运行但和 systemd 的原生生命周期管理配合得不算完美特别是在开机顺序上有时候会输给网络配置脚本。为了确保规则在机器重启后一定生效我建议手动写一个更清晰的 systemd 服务单元。新建 /etc/systemd/system/wondershaper.service[Unit] DescriptionWondershaper traffic shaping service Afternetwork.target [Service] Typeoneshot RemainAfterExityes ExecStart/usr/sbin/wondershaper start ExecStop/usr/sbin/wondershaper stop [Install] WantedBymulti-user.target然后重新加载并启用systemctl daemon-reload systemctl enable wondershaper.service systemctl start wondershaper.service这样服务会跟随系统启动自动执行。用 oneshot RemainAfterExit 的组合是刻意的wondershaper 不会被当作长期运行的守护进程所以用 Typeoneshot 告诉 systemd 这个服务执行完命令就退出但 RemainAfterExityes 会让 systemd 仍然认为服务处于 active 状态方便你用 systemctl status 检查。监控验证方面最直观的方法是配合tc -s查看实时的统计字节数和丢包数tc -s qdisc show dev eth0 tc -s class show dev eth0我平时还会把这些指标采集到 Grafana 里以五分钟一个点的粒度观察带宽使用曲线的波动情况。如果一个带宽限制配置合理曲线应该是一条相对平稳的直线偶尔带一点小尖峰如果曲线像锯齿一样频繁大起大落说明队列和 TCP 配合得不够好需要考虑调整 burst 或调低上限。6. 常见故障排查我从两台机器上踩过的坑6.1 RTNETLINK answers: File exists这个错误最经典几乎每个用过 tc 的人都见过。它意味着你重复添加了同一个根 qdisc。常见的原因有两种第一种是之前已经执行过一次限速脚本第二次再次执行 start 时tc 尝试在同一个网卡上创建第二个根 qdisc内核直接拒绝。解决办法是先执行 stop把已有规则清理干净再重新添加service wondershaper stop service wondershaper start如果 stop 之后仍然提示 File exists很可能是因为网卡上还存在手动添加的规则没有清理干净。可以用一个粗暴但有效的方式tc qdisc del dev eth0 root tc qdisc del dev ifb0 root第二种原因是系统里存在多个脚本同时操作同一张网卡比如 NetworkManager 或者其他脚本也往 eth0 上挂了队列。排查方法是tc qdisc show dev eth0看看是不是已经有一条非 wondershaper 创建的规则这类交叉冲突需要停掉另一方才能收场。6.2 modprobe 找不到 ifb 模块ifb 模块缺失在云服务器上特别常见因为云厂商的定制内核会把不常用模块摘掉保持镜像精简。错误信息大致是modprobe: FATAL: Module ifb not found in directory /lib/modules/...这种情况下先确认内核版本uname -r然后查看该版本下有没有 ifb.ko 相关文件find /lib/modules/$(uname -r) -name *ifb*如果没有说明缺少内核模块包。在 KOS 上可以尝试安装对应的内核扩展包然后重新加载模块yum install -y kernel-modules-extra modprobe ifb装好之后再执行lsmod | grep ifb看到模块出现就可以继续了。这里也提醒一下如果服务器用的不是 KOS 默认内核而是第三方编译的定制内核最好先向供应商确认 ifb 模块是否被包含否则即使安装了额外的模块包也无效。6.3 限速生效但延迟飙升这个坑比较隐蔽限速的确是生效了但应用反馈说“网络变卡了”ping 延迟从 2ms 涨到了 150ms。排查下来是 burst 参数设置太小导致突发流量全部积压在队列里。HTB 的 rate 是长期平均速率burst 是瞬间能突破的量。如果把 burst 设得过小那么即便总速率没超过限制一个稍大的数据帧也可能触发排队排队越多时延越高。解决方法是适当增大 burst可以在配置文件中添加额外参数或者直接用 tc 命令调整现有 class 的 bursttc class change dev eth0 classid 1:20 htb rate 20mbit burst 50k调完之后注意观察平均时延和重传率。如果时延降下来了但带宽利用率也降到很低就说明 burst 给大了数据包在排队层的控制力减弱需要往回调一点。调这类参数时没有绝对公式我的经验是每次调整 25%观察稳定的时间窗口至少在 24 小时以上再做下一步变更。6.4 重启后规则消失这种问题通常和 systemd 服务的启动顺序有关。虽然我写了 Afternetwork.target但在某些网卡配置较慢的场景下network.target 并不等同于“网卡已经 ready”有可能脚本执行时网卡还没起来tc 命令全部执行失败。验证方法很简单重启后立刻执行tc qdisc show dev eth0如果输出是空的而服务状态又是 active那基本可以判断是启动顺序问题。改进方案是给服务加一个更明确的启动依赖或者用 systemd 的 ExecStartPre 等待网卡出现ExecStartPre/bin/sh -c for i in $(seq 1 30); do ip link show eth0 break; sleep 1; done这样在启动时会最多等待 30 秒确保网卡真正存在后再执行限速配置。这个方法不算优雅但在实际生产环境里非常可靠。7. 个人使用体会与最终建议在 KOS 上跑 wondershaper 这段时间我最大的体会是经典的网络工具不一定新但稳定性和可维护性往往是新一代复杂方案没法替代的。wondershaper 1.2.1-2 版本确实有一些老旧的痕迹比如配置文件路径和 init 脚本风格但只要理解了它的底层链路这些问题都只是顺手就能解决的细节完全不影响它在生产环境中的价值。最后再分享一个实战技巧如果一台服务器需要同时管控多个业务方向不要试图在一个网卡上堆很多复杂的 tc 规则那样排错极难。更稳妥的做法是把不同业务的流量拆到不同网卡或不同 ifb 设备上分别配置 wondershaper每个限速策略相互隔离。这样任何一个网卡的规则出现问题都不会波及其他业务。配合日志和监控系统持续观察带宽管理才能真正从“可用”走向“稳定”。