随着托管 VPC、三方子公司 VPC 及沙箱类业务规模的扩大,隔离 VPC 逐渐成为云网络承载敏感业务的默认形态。本文分析了隔离 VPC 出公网场景下传统 SNAT 模式的结构性缺陷,提出以 EIP 网关为统一公网身份绑定点的标准路径方案,并从控制面(LBC isolated 模式)与数据面(DPVS isolate_public)两侧阐述其设计与实现。
一、背景
公司云平台上的 隔离VPC受合规约束,其网络模型必须满足三个条件:
- 默认拒绝 :业务与其他内网域(10/11 段)之间不建立任何隐式通路,互通必须显式申请;2. 公网身份专属 :出公网的源地址必须是业务专属的隔离公网 IP,禁止混用平台公共出口------这是 EIP 绑定关系、计费归属与安全策略生效的前提;3. 路径可审计:每一条放行的访问关系都能映射到明确的策略与工单。
满足以上约束的 VPC 即隔离 VPC。隔离机制的实现选择在安全组层面,采用"BASE 放行 + VPC 兜底拒绝"的两层策略模型。
由此形成三条默认策略:
🔹 入向 :classiclink 打通的流量默认放行,由安全组策略做细粒度控制;🔹 出向 :本 VPC 网段允许;10/8、11/8 内其他网段拒绝;基础架构 IP(DNS 等)白名单放行;🔹 出公网:转发面不限制,但源地址经 SNAT 转换为隔离公网 IP,在公网身份层面完成二次隔离。
选择安全组而非独立转发面的原因:实现成本最低,粒度最广(VM 维度 + 优先级语义),且与既有 ACL 工单体系天然衔接。
二、现状与问题
2.1 已有标准路径体系
经过数年建设,隔离 VPC 的跨域访问已形成覆盖三个方向的标准路径矩阵:

其中出公网路径支撑了沙箱、托管业务等所有隔离 VPC 的对外访问,是使用频度最高、链路最复杂的一条。
2.2 传统 SNAT 模式的两个缺陷
传统(normal)SNAT 模式下,同一 snat_rule 的各 SNAT 网关绑定互不相同的 公网EIP,并由每台网关独立完成 BGP 宣告。该模型在隔离 VPC 场景下存在结构性缺陷:
**公网IP浪费:**同一个隔离网段落在不同的SNAT网关,必须使用不同的公网IP,来保证出入同台。一般来说,集群中有几台机器,就至少有几个公网IP,对于只能加白极少数IP的业务,就无法其满足。
**无法统一公网出口:**当前架构公网有统一入口,即EIP网关。但SNAT网关需要通过BGP 宣告将回程流量吸入,导致出口有SNAT和EIP两个。

2.3 方案选型
改造方案的选择上有过权衡:让 SNAT 网关直接做 EIP 绑定(保持直出,回程由每台网关各自宣告),还是引入独立的 EIP 网关作为统一出口。前者省去了隧道开销,但公网身份分散在各网关,绑定与计费策略需要在每个转发点落地,路径不可审计;后者将公网身份收敛到单一绑定点,代价是控制面需要新增端口块编排能力、数据面需要 VXLAN 二次封装。综合评估后选择后者,即把"EIP 归属"从转发面剥离为独立职责。
三、方案设计
标准出公网路径由四项核心决策构成:

端口块分配算法(确定性 NAT):总端口空间 1025--65500(64476 个),按 max_snat_nodes 预切为等长 block,每台 master 网关独占一个 block;控制面将 (网关 IP, port_low, port_high) 三元组持久化至 WanIPPortAlloc 表,并同步至 EIP 网关用于回程映射。
该设计把"运行时端口协商"问题转化为"控制面静态规划"问题:任一会话的源端口必然落在其承载网关的 block 内,EIP 网关只需按 (wan_ip, 端口区间) 二元组即可确定回程隧道,无需维护会话状态。
配套的一致性约束:
- max_snat_nodes 禁止在线变更 ------block 边界为配置的派生值,运行期变更将导致已分配 block 与新边界错位重叠;2. block 只增不缩 ------网关下线后其 block 不回收,保留索引位以维持分配记录稳定性;3. 上限显式化------网关规模超出 max_snat_nodes 时以错误码 PORT_BLOCKS_EXHAUSTED 拒绝,禁止运行时重切 block。
四、整体架构与实现
4.1 端到端编排视图

业务视角的开通流程完全声明式:建 snat_rule → 建 wan_ip(mode=isolated),其余由平台自动编排。
4.2 控制面:LBC isolated 模式
入口为创建 wan_ip 时的 mode: "isolated"(idc 必填,控制面据此区分传统路径与 isolated 路径)。编排流程如下:

流程要点:
🔹 网关发现 :从 etcd 在线 Agent 注册信息中过滤 role=master 的 SNAT 网关(GetMasterSnatAgents),无可用网关时返回 NO_MASTER_AGENT 并回滚 wan_ip,保证不产生半配置状态;🔹 端口映射下发 :调用 fipctl 新增的 snat-port-mapping 接口,按网关粒度下发 (floating_ip, port_range, snat_node_ip) 三元组;fipctl 落自身 etcd 后由 Agent 异步执行 fipadm snatadd,完成 EIP 网关侧的回程映射;🔹 弹性扩容:新增网关通过 POST /lvs/v2.0/wanips/{id}/port-allocs 从空闲 block 中补分配;block 耗尽时显式报 PORT_BLOCKS_EXHAUSTED,不做隐式扩容。
fipctl 地址按机房路由(fipctl_urls 的 idc → URL 映射)。需要指出的是,调用时取不到地址会整段跳过仅记 warn 日志------fipctl 不可用不阻塞主流程是设计取舍,代价是漏配静默失败,应在初始化阶段补齐存在性校验。
三条一致性约束:
- max_snat_nodes 禁止在线变更 ------block 边界按当前配置实时派生,已有 isolated wan_ip 运行期改值会导致已分配 block 与新边界错位重叠。该值必须在首批 isolated wan_ip 创建前固化;2. block 只增不缩 ------网关下线后其 block 不回收,保留索引位以维持分配记录稳定性;3. 上限显式化------网关规模超出 max_snat_nodes 时以 PORT_BLOCKS_EXHAUSTED 拒绝,禁止运行时重切 block 的"自愈"行为。
4.3 数据面:DPVS isolate_public
标记下发链路。 控制面将 isolate_public 标记自 keepalived 配置起,经 dpvs_dest_compat 兼容结构跨进程传递,最终落到每个 worker lcore 的 dp_vs_dest 上供数据面逐包读取;EIP 网关隧道端点由 nat_gw_ip / nat_gw_vni 全局配置(v3 起支持 per-dest 覆盖)定义:
nginx
# keepalived.conf --- RS 粒度声明
real_server 10.1.1.11 0 {
vni 100011
hip 10.16.0.166
isolate_public # 该 VM 公网流量统一牵引至 EIP 网关
}
# dpvs.conf --- EIP 网关隧道端点
vtep_ip_defs {
nat_gw_ip 10.220.30.1
nat_gw_vni 7000001
}
去程:三段式出路径决策。 去程封装位于 __dp_vs_out_xmit_snat4(),在 SNAT 源地址改写完成后按优先级三段判断:

三段设计的意义在于存量路径零变更:未打标记的 RS 保持原路径逐字节不变,这是"追加模式"类改造能够安全上线的前提。
回程:去标记化与 MAC 方向交换。 回程封装位于 __dp_vs_xmit_snat4(),沿请求进入的原隧道返回 VM,内层以太网头按方向交换(dmac/smac 对调)。实现上存在两条由故障教训固化下来的不变量:
不变量 1 · 回程判断去标记化回程仅依据连接自身携带的隧道属性(conn->vxlan_id、vtep_peer_addr),不读取 dest->isolate_public。RS 标记可能被 keepalived 重载在连接存活期内改写,而回程隧道在建连时即已确定------依赖标记将导致标记变更后存量连接回包路径错误。
不变量 2 · 内层 MAC 按方向交换请求方向内层帧为 src=VM_MAC / dst=GW_MAC,回程必须对调,否则 VM 侧直接丢弃。实现上利用 vxlan_encap() 的 (dmac, smac) 参数序,回程调用点交换实参顺序即可完成。
关键陷阱:SNAT 方向语义与直觉相反。 SNAT 模式下连接由内网 VM 发起,dp_vs_in() 将 VM→公网方向标记为 DPVS_CONN_DIR_OUTBOUND (对应 conn->packet_out_xmit),而公网→VM 的回包方向为 INBOUND(对应 conn->packet_xmit)------与"入请求/出响应"的直觉相反。v1 版本按函数名直觉将封装挂载于 FNAT 出路径(__dp_vs_out_xmit_fnat4),而隔离公网 VM 的流量归属 SNAT 服务,根本不会进入该路径,最终整体回退。
配套机制:sapool 端口区间。 数据面配合控制面的端口块规划:inet_ifaddr 新增 sapool_low / sapool_high 并透传至 sa_pool_create(),源端口仅在区间内分配。Agent 侧下发命令相应演变为:
bash
dpip addr add {ip}/32 dev dpdk0 sapool low {port_low} high {port_high}
该命令的 low/high 参数与控制面 WanIPPortAlloc 记录中的端口块一一对应,多实例部署下各网关的源端口区间互不重叠,从机制上消除了 EIP 网关侧的四元组冲突。同时 isolated 模式下 Agent 跳过 BGP 宣告流程(wan_ip 的可达性由 EIP 网关负责)。
五、故障定位
上述编排链路同时定义了故障域边界,排障时按段定位:

值得强调的是 fipctl_urls 漏配这类静默失败:控制面调用被跳过而不报错,故障在链路末端才显现,排查成本远高于 fail-fast。因此对跨系统依赖的配置项,应在初始化阶段做存在性校验,而非依赖运行期日志。
六、后续计划
✅ fipctl_urls 等跨系统配置项增加初始化期存在性校验,将静默失败转为 fail-fast;✅ backup SNAT 节点参与端口块分配与自动故障转移;✅ 端口块支持动态缩容与回收,配合网关下线流程闭环;✅ Agent 健康检查与异常节点自动摘除,降低人工介入