主题
VRF 多租户
前面所有实验都只有一个内网。这一篇上强度:两个租户使用完全相同的内网地址段, 在同一台 VPP 上各走各的路。
为什么会有地址重叠
因为 RFC 1918 就那么几段,而每个组织都在用。
text
收购来的子公司,内网也是 10.0.0.0/8
托管客户 A 和客户 B,各自的 VPC 都用 192.168.0.0/16
同一套模板批量部署的边缘站点,地址规划一模一样让它们改地址?动辄涉及成千上万台设备、无数写死 IP 的配置和脚本, 现实中基本不可能。所以只能让网络设备同时容纳多份相同的地址空间 —— 这就是 VRF。
VRF(Virtual Routing and Forwarding)说白了就是多张互相独立的路由表。 同一台设备上,10.20.0.11 在表 1 里和在表 2 里是两台完全不同的机器。
实验拓扑
这一篇要换一个底座。
注意 VPP 的 eth1 和 eth2:同一个地址 10.20.0.1/24,分属不同 VRF。 这在单一路由表里是不可能的,在 VRF 下再正常不过。
启动多租户底座
bash
./labctl up vrf这会先把 base-gateway 停掉(实验机一次只跑一个底座),再起 natvrf。 想切回去用 ./labctl up base。
确认地址真的重叠
bash
./labctl vppctl vrf show interface addrtext
host-eth1 (up):
L3 10.20.0.1/24 ip4 table-id 1 fib-idx 1
host-eth2 (up):
L3 10.20.0.1/24 ip4 table-id 2 fib-idx 2
host-eth3 (up):
L3 203.0.113.1/24两个接口地址一模一样,table-id 不同。
底座是怎么把接口塞进 VRF 的
netlab 不允许两条链路声明同一个前缀,所以 topology.yml 里租户 B 先用了占位网段 10.21.0.0/24,up.sh 起来后再改成和租户 A 一致。
VPP 侧的关键是这几条 —— 换 VRF 前必须先摘掉地址,VPP 不允许带着地址换表:
bash
vppctl set interface ip address del host-eth1 10.20.0.1/24
vppctl ip table add 1
vppctl set interface ip table host-eth1 1
vppctl set interface ip address host-eth1 10.20.0.1/24配置 VRF NAT
应用配置
bash
./labctl apply vrf 01四件事,缺一不可:
bash
# 1. 声明哪些 VRF 是"租户 VRF"
vppctl nat44 vrf table add 1
vppctl nat44 vrf table add 2
# 2. 每个租户翻译完之后,去哪张表里查外网路由
vppctl nat44 vrf route add table 1 0
vppctl nat44 vrf route add table 2 0
# 3. 每个租户用自己的地址池
vppctl nat44 add address 203.0.113.100 - 203.0.113.101 tenant-vrf 1
vppctl nat44 add address 203.0.113.110 - 203.0.113.111 tenant-vrf 2
# 4. 接口内外侧划分(两个租户接口都是 in,共用一个 out)
vppctl set interface nat44 in host-eth1 out host-eth3
vppctl set interface nat44 in host-eth2 out host-eth3第 2 步最容易漏,后面会专门演示漏掉的后果。
bash
./labctl vppctl vrf show nat44 addressestext
NAT44 pool addresses:
203.0.113.100
tenant VRF: 1
203.0.113.101
tenant VRF: 1
203.0.113.110
tenant VRF: 2
203.0.113.111
tenant VRF: 2对比前面章节的 tenant VRF independent —— 那是不绑定租户、谁都能用的池。
验证
跑自检
bash
./labctl verify vrf 01text
── 2) 各自访问同一台外网服务器 ─────────────────
s1 看到租户 A → 203.0.113.100:44351
s1 看到租户 B → 203.0.113.111:34127
✓ 租户 A 用的是自己的池 203.0.113.100-101
✓ 租户 B 用的是自己的池 203.0.113.110-111text
┌──────────┬──────────────┬───────┬────────────────────┐
│ 租户 │ 内网地址 │ VRF │ 外网看到的地址 │
├──────────┼──────────────┼───────┼────────────────────┤
│ A (hA) │ 10.20.0.11 │ 1 │ 203.0.113.100:44351│
│ B (hB) │ 10.20.0.11 │ 2 │ 203.0.113.111:34127│
└──────────┴──────────────┴───────┴────────────────────┘内网地址一模一样,外网身份完全分开。
隔离的真正依据:会话键里的 fib 号
bash
./labctl vppctl vrf show nat44 sessionstext
i2o 10.20.0.11 proto TCP port 44351 fib 1 ← 租户 A
o2i 203.0.113.100 proto TCP port 44351 fib 0
external host 203.0.113.11:9000
i2o 10.20.0.11 proto TCP port 34127 fib 2 ← 租户 B
o2i 203.0.113.111 proto TCP port 34127 fib 0
external host 203.0.113.11:9000回顾第 1 章的会话表,我们说过 i2o 键里有个 fib 字段 当时一笔带过。现在它的作用显出来了:
text
完整的 i2o 键
┌──────────────┬────────┬────────┬──────────┐
│ 内网 IP │ 端口 │ 协议 │ fib 号 │
├──────────────┼────────┼────────┼──────────┤
│ 10.20.0.11 │ 44351 │ TCP │ 1 │ ← 租户 A
│ 10.20.0.11 │ 34127 │ TCP │ 2 │ ← 租户 B
└──────────────┴────────┴────────┴──────────┘
↑ 地址相同也不冲突,因为 fib 不同o2i 侧两条都是 fib 0,因为外网只有一张表 —— 而且两个租户的外部地址本来就不同, 不需要额外区分。
漏掉 vrf route 会怎样
这是 VRF NAT 最容易踩的坑,值得亲手制造一次。
删掉租户 B 的 vrf route
bash
./labctl vppctl vrf nat44 vrf route del table 2 0
./labctl vppctl vrf show nat44 vrf tablestext
table 1:
[0] vrf-id 0
table 2:
← 空了租户 B 立刻断网:
text
✓ 租户 B 立刻断网 —— vrf route 是 NAT 翻译后查路由的依据这种丢包不记在 NAT 的错误计数里
bash
./labctl vppctl vrf show errors | grep -i nat什么都没有。 因为包不是死在 NAT 节点,而是翻译完之后 在路由查找阶段找不到出口被丢的。
排错时如果只盯着 show errors 的 NAT 计数,会完全找不到线索。 遇到"VRF 里的租户不通、NAT 计数却干干净净",第一件事就是 show nat44 vrf tables 看 route 有没有配。
恢复:
bash
./labctl vppctl vrf nat44 vrf route add table 2 0为什么需要这条命令
NAT 翻译的动作是"把包从租户 VRF 搬到外网 VRF"。翻译本身只改地址, 但改完之后 VPP 要重新查路由决定从哪个口发出去(见 NAT 到底改了什么)—— 这时它需要知道在哪张表里查。
text
包从 host-eth1 进来,属于 VRF 1
↓
NAT 改写源地址 10.20.0.11 → 203.0.113.100
↓
重新查路由:目的 203.0.113.11 在哪?
↓
在 VRF 1 里查? VRF 1 里只有 10.20.0.0/24,没有外网 ✗
那该去哪张表查? ← `nat44 vrf route add table 1 0` 回答了这个问题
↓
在 VRF 0 里查 → 找到 host-eth3 ✓nat44 vrf route add table <租户VRF> <要去查的VRF> 可以配多条, 表示"依次到这几张表里找"。多出口场景下会用到。
规划建议
text
✓ 该这么做 ✗ 别这么做
──────────────────────────────────── ──────────────────────────────
每个租户一个独立 VRF + 独立地址池 多个租户共用一个池
→ 计费、审计、限流都能按租户分开 → 出问题时分不清是谁
地址池规划留出扩容余量 卡着当前需求配池
→ 加租户比扩池容易 → 哈希分布不均会提前打满
外网侧统一放在 VRF 0 每个租户一个外网 VRF
→ vrf route 配置简单 → 除非真的有多个物理出口
给每个租户 VRF 编号留规律 随手分配 VRF 号
→ 比如租户 ID × 10 → 上百个租户后无法维护容量估算要按租户算
每个租户的容量 = 它自己池里的地址数 × 64512 端口。 租户之间不共享端口资源,所以规划时是逐个租户算,不是算总量。
一个常见误区是"我有 100 个公网地址,够几万用户了" —— 如果这 100 个地址按 10 个租户切分,每个租户只有 10 个地址的容量。
小结
| 解决什么 | 多个租户使用重叠的内网地址段 |
| 前提 | 接口先放进各自的 VRF(set interface ip table) |
| 四步配置 | vrf table add → vrf route add → add address tenant-vrf → set interface nat44 |
| 隔离依据 | 会话键里的 fib 号,地址相同也不冲突 |
| 最易漏的一步 | nat44 vrf route,漏了就断网且 NAT 错误计数为零 |
| 容量 | 按租户单独算,租户之间不共享端口 |
第 2 章还剩最后一篇:超时与会话管理。