一台跑着几十个租户的多租户宿主机,防火墙规则突破两万条之后,业务侧最先感知到的变化不是带宽跑满、也不是 CPU 打满,而是新建连接开始排队。健康检查偶尔超时一次又自己恢复,短连接接口的首字节时间忽长忽短,登录时敲完回车要愣一下才出提示。第一反应通常是往上加配置:CPU 换个更大的,连接跟踪表的上限调高一档,再来一轮内核参数微调。折腾完当天确实缓和了一阵,第二天规则又多了几百条,症状原样回来。
这类现场见多了之后会形成一个判断:慢的根源不是机器不够快,是规则的组织方式撑不住这个规模。参数能挪动的是水位线,挪不动增长曲线。规则从几百条长到几万条,中间隔着的不是"同一件事做更多次",而是"匹配这件事在结构上变了性质"。
没有谁一开始就设计两万条规则。涨上去的路径基本就那几条,而且是叠加出现的。
多租户宿主机是最典型的一条。每个租户上线时带一批放行需求:办公网出口几个段、监控探针几台、数据库只开放给应用层的几个地址、对端联调方临时开一个端口。单看每个租户都只有十几条,租户数往上走,总量就是乘法关系。更麻烦的是这些地址会变,变的时候通常只加不开删,因为没人敢确认一条两年前加的规则现在还用不用。
容器平台节点是另一条。业务容器滚动发布一次,宿主机上可能就要补一批端口放行、 service 网段、健康检查源地址。发布频率越高,规则沉淀越快。发布系统是自动写规则的,人反而不看,于是规则表变成一份没人读过、但每天都在增长的账本。
边缘网关和做封禁的业务是第三种,也是最容易失控的一种。风控系统每判定一批恶意来源就往表里塞一批拒绝规则,或者反过来,白名单业务每接入一个合作方就加一批放行。这种来源的规则增速跟业务流量正相关,且几乎没有自然衰减——被封的地址很少有人去解封,合作方下线的白名单也很少有人去清理。
三条路径的共同点是:每一条规则在加进去的当下都是合理的,问题出在它们被放在同一个平面上。不管目的差别多大,都往同一条链尾部追加,于是匹配从一个"按维度定位"的动作退化成了"按顺序翻找"。
iptables 的模型说起来很简单。表(table)下面挂链(chain),链是一条有顺序的规则列表。数据包进入某条链之后,从第一条开始逐条比对,每条规则里写的匹配字段——源地址、目的地址、协议、端口、入接口、连接状态——都要跟包对一遍,对上了就执行这条规则的动作,动作是终止类的(比如接受、丢弃)就结束,是非终止类的(比如打标记、记日志)就继续往下比对,一直走到链尾落到链的默认策略。
这里有两个乘数必须分开看。
第一个乘数是规则条数。链里有一万条规则、命中目标排在第一万条,那这个包就得走完前面九千九百九十九条的全部匹配字段。走完整条链才落到默认策略的包更惨,它没有任何提前退出的机会。所以平均开销随条数往上走,且走的是线性关系,不是对数关系。
第二个乘数是单条规则的匹配字段数。一条只比源地址的规则和一条同时比源地址段、目的端口范围、协议、入接口、连接状态的规则,成本完全不是一个量级。很多团队在加规则时习惯"顺手多写几个条件更保险",字段越写越全,等于在链长已经很长的前提下又把每一格的单价抬了上去。条数和字段数相乘,才是真实开销。
更关键的一点是这个开销花在哪个路径上。首包要完整走一遍规则,后续属于同一条连接的包靠连接跟踪表里的状态直接判定,不再重走这些规则。所以规则变长最先伤的是新建连接的速率,而不是已建立连接的吞吐。这也解释了为什么现场现象总是"新建连接排队、健康检查抖动",而长连接业务看起来还很正常——不是网络变差了,是每建一条新连接要付的过路费变贵了。
顺带说一句,链的顺序在这里是有实际意义的。把命中率最高的规则往前挪、把终止动作明确的规则放在前面,都能让大量包提前退出。这属于结构优化里最便宜的一档,不需要换工具,但天花板也低——它只是在同一条清单里调整翻找顺序,没有改变"翻找"这件事本身。
匹配开销影响的是数据面,变更代价影响的是控制面。这两件事经常被混为一谈,但它们会同时恶化。
iptables 的规则变更在语义上更接近整表替换。用 iptables-restore 一类方式加载时,是拿一份新的规则集去替换旧的,而不是只把增量的那几条插进去。规则总量小的时候这件事几乎无感;到了几千上万条,单次替换要重建的结构就变多了,变更期间要拿的锁、要重新提交的内容都跟着涨。如果变更频率还很高——比如风控系统每分钟推一批封禁——控制面的开销就会变成另一个热点。
这时候常见的一个误区是"那就攒批处理"。攒批确实能降频率,但会让封禁的生效延迟变长,安全侧通常不接受。另一个误区是写脚本去逐条插入,看起来只动了一条,实际走的还是整表替换的路径,只是把一次大替换拆成了很多次大替换,反而更糟。
真正能解决问题的是让"高频变动的那一批"和"基本不动的那一批"在结构上分开。这也是后面集合化改造的核心动机之一:变的程度和变的代价应该对齐。一份两万条的静态基础策略,一年改几次,每次整体替换无所谓;一批每几分钟就要增删的封禁地址,绝不该躺在同一份要整体替换的表里。
规则规模变大之后,另一个被放大的问题是"规则挂在了哪一层"。
连接跟踪和地址转换相关的处理挂在特定的表与链上,跟过滤用的表不是同一套挂载点。同一条"允许某地址访问"的意图,写在过滤链上、写在地址转换前的链上、写在地址转换后的链上,匹配到的地址可能是转换前的原始地址,也可能是转换后的地址。规则少的时候,写错了很快能从现象反推出来;规则成千上万条之后,一条写错位置的规则可能只在某几个租户、某几个端口的组合下才表现出异常,排查成本极高。
规则变更与已有连接之间也存在相互影响。连接跟踪表里已经建立的状态不会因为规则被替换就自动消失,于是会出现一种很别扭的情况:新规则明明已经把某个来源拒绝了,但它的既有长连接还在正常通信,直到这条连接自然结束或被清理。反过来也一样,一条被删掉的放行规则对应的既有连接可能还活着。在做封禁的时候这个现象尤其容易误判,很多人会以为是规则没生效,其实是生效在了新建连接上、没作用到老连接上。
所以规模上去之后,规则变更需要连带考虑的不只是"新规则对不对",还有"旧状态怎么处置"。哪些连接需要主动清理、哪些可以等自然结束、变更要不要避开业务高峰,这些都要提前想清楚,而不是等出现"规则生效了又不像生效了"的工单再去补。
不存在一条通用的"到第 N 条就必须换"的线。同样两万条规则,放在一台只跑内网管理流量的机器上和放在一台扛外网接入的边缘网关上,结论完全不同。相对于绝对条数,更值得盯的是下面几个信号。
第一个信号是规则增长速度和业务增长速度脱钩。业务量没怎么涨,规则条数却持续往上走,说明规则只在加、没在收敛,这是结构问题最早的征兆,通常出现在几千条量级就能看出来。
第二个信号是变更开始需要挑时间窗。如果加几条规则这种事从"随时能做"变成"要等低峰",说明控制面开销已经到了能被人感知的程度。
第三个信号是排查一条规则的影响范围开始耗时。想确认某个地址到底被哪几条规则管着,需要翻很多行、跨好几条链才能拼出答案,说明规则已经失去了按维度定位的能力。
第四个信号是同一维度出现了大量结构重复的规则。比如几百条规则除了源地址不一样、动作和其余字段完全一样,这是集合化改造收益最直接的地方,也是判断"该不该换组织方式"最硬的一条依据。出现这种重复,就意味着你付了 N 次匹配的钱,实际只买到了一次查找的信息量。
这四个信号里出现任意两个,就该认真考虑结构改造了,而不是继续调参数。至于具体落在几千条还是几万条,取决于业务的新建连接速率和单条规则的字段复杂度,需要以自有业务的实际观察为准,没有可以照抄的数字。
改造的第一步通常不是换工具,而是重新切分链。多数规则表之所以难维护,是因为它只按"加入的先后"和"大概属于哪个业务"来组织,没有按匹配维度分层。
一个比较实用的切法是先把流量按方向和处理阶段分开:入口的、出口的、要做地址转换的、要做标记的,各走各的链。然后在每一条链内部再按维度分段——先处理连接状态(已建立的、相关的直接放行,让绝大多数包在前几条就退出),再处理明确的放行白名单,再处理按地址维度批量判定的部分,最后才是需要逐条精细比对的例外和默认策略。
这样切的好处有两层。数据面上,绝大多数包在前几层就能命中终止动作,不需要翻完整条链;维护上,任何人想找"某个地址在哪被管着",只需要去对应的那一层,不用从头读到尾。iptables 里可以用自定义链加跳转实现这套分层,不需要换工具链就能拿到相当一部分收益。
但要说清楚它的天花板:分层只是把一条长链拆成几条短链,链内的线性比对性质没有变。如果某一层里躺着几千条"只有源地址不同"的规则,分层救不了它。这一层必须交给集合。
集合解决的是一个很具体的问题:一大批规则如果只在某一个维度上不同,那么它们本质上不是 N 条规则,而是一次"这个包的某个字段在不在某个集合里"的查询。
在 iptables 体系下,ipset 是干这件事的搭档。把几万个要封禁的地址放进一个哈希类型的集合,规则表里只需要一条引用这个集合的规则。匹配时不再逐条比对,而是对包的地址做一次哈希查找,命中就执行动作。集合里加一个地址,是往集合里加元素,不是往规则表里插一条规则——这就把前面说的变更代价问题一并解决了:高频增删发生在集合内部,规则表本身保持稳定。
nftables 把这个能力做进了原生语法。集合(set)和映射(map)都是一等公民,不需要外部工具配合。除了哈希类型的集合,nftables 还支持区间类型的结构,连续的地址段或端口范围可以高效地表示;映射则更进一步,把"匹配到某个键"和"取出对应的值/动作"合在一起,一批"不同来源走不同处置"的规则可以收敛成一次查表。
这两者的差别不在于"能不能做",而在于一致性和表达力。ipset 是 iptables 之外的独立组件,集合和规则是两套东西,各有各的语法、各有各的生命周期,维护时要同时理解两边的状态;nftables 里集合和规则在同一个语法体系里声明,可以嵌套、可以带超时、可以被多条规则共用,表达方式也更多。规模越大、维度越杂,这个差别越明显。
另一个实际差别是协议族的合并。传统工具链上 IPv4 和 IPv6 规则长期是两套独立的表,同一个策略要写两遍、维护两遍,出错的机会也翻倍。nftables 用统一的框架处理不同协议族,同一份策略思路可以用一套规则表达。对规则规模已经很大的环境来说,少维护一套等价的规则表,本身就是减负。
| 组织方式 | 匹配开销随规模怎么变 | 变更代价 | 适合的规模 | 改造时最容易踩的坑 |
|---|---|---|---|---|
| 单条链上顺序追加规则(iptables 默认用法) | 随规则条数与单条规则的匹配字段数一起线性增长,排在链尾和落到默认策略的包成本最高 | 整表替换语义,条数越多单次变更越重,高频变更场景压力明显 | 几百条以内、业务与来源都比较固定的简单环境 | 以为调内核参数能解决增长问题;规则只加不删,尾部堆积大量失效条目 |
| 按方向与处理阶段拆分成多条链,链间跳转 | 靠分层让多数包提前退出,摊薄平均开销,但链内仍是线性比对 | 比单链好定位,但改动仍受整表替换语义影响 | 上千条、维度之间边界清晰、能明确分层的环境 | 跳转让读写路径变长,跳转关系复杂后反而更难判断一个包的完整去向 |
| iptables 配合 ipset 做集合匹配 | 同维度的海量地址/端口收敛为一次哈希查找,开销与集合元素数量的关联远弱于逐条比对 | 增删发生在集合内部,规则表保持稳定,适合高频增删 | 几千到几万条,且大量规则只在单一维度上不同的场景 | 集合与规则两套语法和生命周期要分开维护;集合类型选错会直接影响查找效率和内存占用 |
| nftables 原生集合与映射(哈希、区间等结构) | 同维度规则合并为一次查找,区间与映射可进一步压缩规则表达,多协议族共用一套框架 | 集合、映射与规则在同一语法体系内声明,可带超时等属性,变更粒度更细 | 上万条以上、多维度混杂、需要长期演进的策略体系 | 直接把旧规则逐条翻译过来,把线性结构原样搬进新框架,等于没做改造 |
| 策略外移到上游清洗、旁路网关或业务侧限流 | 本机规则规模不再随外部威胁或租户数量线性膨胀,压力转移到更靠前的一层 | 集中处置,单点变更影响面大,需要更谨慎的灰度与回滚 | 封禁名单持续膨胀、单台机器已无法独立承载策略的边缘场景 | 外移后本机仍留一份旧规则忘了清理,两套策略互相打架,现象更难解释 |
集合化能解决的是"同一维度大量规则"的问题,解决不了"策略总量本身超出了单台机器该承担的职责"。判断该不该外移,看的不是条数,是规则的来源和存活期。
如果规则主要来自业务自身的访问控制——租户之间隔离、端口开放清单、内网互访策略——那它天然属于这台机器,规模再大也应该在本地用分层加集合的方式组织好。这类规则跟着业务生命周期走,有明确的增删时机,是可以被治理的。
如果规则主要来自外部对抗——恶意来源封禁、扫描器屏蔽、异常区域限流——情况就不一样了。这类名单的增速由外部决定,你控制不了它的上限,而且几乎不会自然衰减。把它们堆在每台业务机的规则表里,等于让每一台机器都为整个平台收到的攻击面付匹配成本。这类策略更适合放在更靠前的一层:上游清洗、旁路网关,或者在业务侧做限流。
判断标准可以很简单:规则的增删是不是由你自己业务的生命周期决定的。是,就留在本机治理好;不是,就应该往前面挪。这个判断跟机器配置高低没关系——更快的机器只是把曲线往后推,没有改变曲线的形状。
外移之后还有一件事容易被忽略:本机上那批旧规则要清干净。见过不少环境做完外移,边上还留着一份几年前的封禁规则,平时不痛不痒,某天新策略跟它冲突了,排查的人要同时看懂两套来源完全不同的策略才能解释现象。外移的收益,很大一部分来自"本机规则变简单了",留着旧表就等于没拿到这部分收益。
规则表进入几千条量级之后,维护方式也需要跟着改,否则结构改造的成果会被日常操作慢慢吃掉。
第一,规则要有来源标注和归属信息。几千条规则里,如果看不出某条是谁在什么时候因为什么加的,就没人敢删,不敢删就只会涨。可以用注释字段或命名约定把归属写进去,这件事前期看着琐碎,后期是唯一能支撑清理的依据。
第二,变更要走可回滚的方式。整表替换的语义下,一次错误加载可能直接把管理通道切断。常见做法是加载新规则后给一个确认窗口,确认没问题再固化,超时自动回退到上一份。规模越大,这个机制越不能省。
第三,定期做失效规则的清理和重复规则的合并。判断依据就是前面说的"同一维度大量结构重复"——看到了就合并成集合。清理要有节奏,比如按季度做一轮,配合来源标注推进,不要指望哪天一次性理清。
第四,规则变更要考虑对既有连接的影响,尤其是做封禁的时候。提前想清楚已有连接是等自然结束还是主动处置,避免"规则明明生效了却还在通信"的误判。
第五,把规则的条数、链的结构、集合的元素数量纳入长期观察。重点看趋势而不是绝对值:条数增长是否跟业务增长同步、某一层的规则是不是在快速膨胀。趋势比快照有用,它能在问题变成故障之前给出提示。
iptables 能不能平滑迁到 nftables?可以做,但不要把它当成一次格式转换。工具链提供了把旧规则翻译成新语法的途径,翻译出来的结果是一份语法正确、结构照旧的规则表——如果原来是两万条线性规则,翻译完还是两万条线性规则,性能问题原样保留。真正的迁移应该和前面说的分层、集合化一起做:先想清楚新的组织结构,再往里填内容,翻译工具只适合用来对照检查有没有遗漏,不适合直接产出终态。
规则顺序错了会怎么样?分两种情况。如果错的是终止动作的前后关系——比如一条宽泛的放行排在了一条精细的拒绝前面——那拒绝规则永远不会命中,表现为"规则写了没效果"。如果错的是非终止动作(打标记、记日志)的位置,通常不会失效,但拿到的中间状态不对,后续的规则会在错误的输入上做判断,表现得更隐蔽。规模大了之后,后者的排查成本远高于前者。
已有连接会不会受规则变更影响?会受影响,但不是你直觉上那种"立刻生效"。已建立的连接在连接跟踪表里已有状态,后续包走状态判定,不会重新走一遍过滤规则。所以新加的拒绝规则主要作用在新建连接上,既有的长连接可能继续通信直到结束或被清理。做封禁时如果不处理这部分状态,就会出现"封了但还在通"的观感。反过来,删掉放行规则后,对应的既有连接也可能还活着。
要封禁几万个 IP,该用什么结构?不要写成几万条规则。哈希类型的集合是更合适的选择,规则表里只保留一条引用集合的规则,几万个地址作为集合元素存在。这样做有两个直接收益:匹配从逐条比对变成一次查找;增删元素不会引起规则表整体替换。如果这些地址有连续性,区间类型的结构还能进一步压缩表达。这类名单通常变动频繁,放进集合后变更频率对控制面的压力会明显下降。
都上集合了,还需要拆链分层吗?需要,两者解决的是不同层面的问题。集合解决的是"同一维度一大堆"的匹配效率,分层解决的是"不同维度的规则不要混在一起互相干扰"。没有分层,所有集合会挤在同一条链上依次比对;没有集合,某一层内部的几千条同维度规则依然要逐条走完。实际改造里,一般是先分层,再在每一层内部把能集合化的部分集合化。
规则多久清理一次合适?没有固定周期,看增长速度。如果规则条数的增长明显快于业务量增长,就该启动清理,不用等到某个固定时间点。清理的前提是有来源标注——不知道一条规则是谁加的、为什么加的,就没有判断它能不能删的依据。所以比起"多久清一次",更该先解决的是"能不能说清楚每条规则的来路"。
换成 nftables 之后原来的脚本还能用吗?大部分不能直接沿用,两套语法和命令结构不一样,需要重写。这也是迁移要考虑的隐性成本之一:不只是规则本身,连带着变更脚本、配置管理里的模板、监控采集的命令都要跟着改。规模越大的环境,这部分连带改造的工作量越不能低估,通常应该和组织结构改造排在同一个窗口里做,而不是分两次。
说回最开始那台宿主机。它真正需要的不是更大的机器,而是把那两万条规则重新组织一遍:按方向和处理阶段分层,把"只有地址不同"的大批规则收敛成集合,把来源属于外部对抗的那部分策略往上游挪一层。做完之后规则条数会大幅下降,更重要的是匹配方式从"翻清单"变成了"查表"。这种把规则压到几万条的机器,我一般会先看一万网络的物理机形态——深耕 IDC 19 年(成立于 2007 年),内核和工具链能按自己的需要定。
回到最初的判断:规则从几百条长到几万条,慢的根源在匹配的组织方式,不在机器性能。iptables 的每条链是一条顺序规则列表,数据包按顺序逐条比对,开销跟规则条数、跟每条规则的匹配字段数一起长;nftables 引入集合与映射之后,大批同维度规则可以收敛成一次查找,更换的实质是规则的组织方式,不是换一个更快的执行引擎。
落到操作上,顺序大致是:先按方向和处理阶段把链拆开,让绝大多数包能在前几层命中终止动作;再把同一维度上大量重复的规则做成集合,用一次查找替代逐条比对;然后判断哪些策略属于业务自身、哪些属于外部对抗,把后者往上游清洗、旁路网关或业务侧限流挪;最后把来源标注、可回滚变更、定期清理这些维护机制补上,避免改造成果被日常操作慢慢吃掉。
至于规模边界,不存在可以照抄的条数。判断依据是前面提到的几个信号:增长是否与业务脱钩、变更是否开始需要挑时间窗、排查一条规则的影响范围是否越来越耗时、是否出现了大量结构重复的规则。出现两个以上就该动手,具体的触发点需要以自有业务的实际观察为准。
本文所述内容为 Linux 内核网络过滤框架与 iptables、nftables、ipset 等工具链的公开机制梳理,涉及链与表的结构、集合与映射的组织方式、连接跟踪与地址转换所处层次等,均基于上述组件的公开行为特征,不包含任何测试、基准或性能数值数据,也不涉及针对特定硬件的性能指标。文中关于规模增长的判断均为趋势性描述,用于说明结构差异,不构成性能承诺;具体到某一业务的触发点和改造收益,需要以自有环境在实际负载下的观察为准。
文中涉及的服务器与机房相关产品信息,可参见一万网络官网 www.idc10000.net( https://www.idc10000.net/ )上的相关服务器产品页,具体以官网实时报价与合同为准。
Copyright © 2013-2020 idc10000.net. All Rights Reserved. 一万网络 科技有限公司 版权所有 深圳市科技有限公司 粤ICP备07026347号
本网站的域名注册业务代理北京新网数码信息技术有限公司的产品