Skip to content

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 addr
text
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/24up.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 addresses
text
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 01
text
── 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-111
text
  ┌──────────┬──────────────┬───────┬────────────────────┐
  │ 租户      │ 内网地址      │ 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 sessions
text
    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 tables
text
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 addvrf route addadd address tenant-vrfset interface nat44
隔离依据会话键里的 fib 号,地址相同也不冲突
最易漏的一步nat44 vrf route,漏了就断网且 NAT 错误计数为零
容量按租户单独算,租户之间不共享端口

第 2 章还剩最后一篇:超时与会话管理

实验环境基于 netlab + containerlab + VPP 26.06