14. 固定的公网身份:弹性公网 IP
NAT 网关解决了一个真实的问题,让 VPC 里的 VM 能够访问公网。但它解决问题的方式,本身就带着一个根本性的限制:NAT 是一个共享出口。所有 VM 的流量汇聚到 NAT 网关,换上同一个(或几个)公网 IP 出去。公网看到的是 NAT 网关的 IP,不是 VM 自己的 IP。VM 在公网上没有身份,它是匿名的。
匿名有时候是好事。大量 VM 只需要下载软件包、调用外部 API,不需要被公网认识,共享出口既省 IP 又省钱。但匿名也意味着两件事做不到:公网无法主动找到你,你也无法证明"我就是我"。如果你在 VPC 里部署了一个 Web 服务器,希望用户从浏览器直接访问它,NAT 网关帮不了你。
14.1 NAT 做不到的事
NAT 网关的 conntrack 表是由出向流量触发建立的。VM 先发包出去,NAT 网关记录映射关系,回包到达时根据映射还原目的地址。这个过程有一个的前提:必须是 VM 先发起连接。
如果没有 VM 先发包出去,conntrack 表里就没有对应的记录。公网发来的包到达 NAT 网关时,NAT 网关查不到任何映射,它不知道这个包应该送给哪台 VM,只能丢弃。这不是配置问题,而是 NAT 的工作原理决定的:conntrack 是被动建立的,没有出向就没有映射,没有映射就无法反向翻译。
这意味着,一个部署在 VPC 里的 Web 服务器,即使它已经启动并监听了 80 端口,公网用户也无法主动连接它,因为 NAT 网关不知道该把入向流量送给谁。
第二个限制是身份不固定。NAT 网关做 NAPT 时,端口映射是动态分配的,VM-A 这次用了 40001 端口,下次可能用 40002。即使 NAT 网关的公网 IP 不变,端口映射关系也在不断变化。对外暴露的不是一个稳定的身份,而是一个不断变化的端口号。
这两个限制在很多场景下是致命的:
- Web 服务器需要被公网用户直接访问
- 邮件服务器需要固定 IP 做反向 DNS 验证,很多邮件服务商会检查发件 IP 的 PTR 记录,IP 不固定意味着邮件大概率被判为垃圾邮件
- API 服务需要稳定的出口 IP,供合作方加白名单,"你的出口 IP 是什么?""呃,不确定,每次可能不一样",这个对话在实际的对接中并不罕见
需求很清楚:某些 VM 需要一个固定的、专属的公网 IP 地址。不是共享的,不是动态的,而是"这个公网 IP 就是我的"。
14.2 EIP 的绑定:一个公网 IP 与一台 VM 的关联
弹性公网 IP(EIP,Elastic IP) 是一个独立的公网 IPv4 地址资源。它不属于任何 VM,也不属于任何子网,它是一个可以被"绑定"和"解绑"的独立对象,像一张可以贴在不同信封上的邮票。
绑定操作很简单:用户把 EIP(比如 203.0.113.50)绑定到 VM-A(私有 IP 10.0.1.100)。绑定完成后,VM-A 对外的公网身份就是 203.0.113.50。公网用户访问 203.0.113.50,流量会被送到 VM-A;VM-A 访问公网,出去的包源 IP 也是 203.0.113.50。
但绑定这个动作,底层发生了什么?
三件事同时发生:
- 控制面在边界网关上建立 1:1 NAT 映射,203.0.113.50 ↔ 10.0.1.100。这不是 NAPT 的 N:M 映射,而是一个公网 IP 完整地对应一个私有 IP,所有端口都属于这台 VM
- 通过 BGP 向公网宣告路由,告诉公网的路由器"203.0.113.50 可以通过我到达",让全球的流量知道怎么找到这个 IP
- 更新 VPC 内部的路由规则,让 VM-A 的出向流量经过 EIP 的 NAT 节点,而不是走 NAT 网关的默认路由
图 14.1:EIP 绑定时底层的三个动作
sequenceDiagram
participant User as User (Console)
participant CP as Control Plane
participant GW as Border Gateway
participant BGP as BGP Router
participant RT as VPC Route Table
User->>CP: Bindind EIP 203.0.113.50 to VM-A (10.0.1.100)
CP->>GW: Create 1:1 NAT mapping<br/>203.0.113.50 ↔ 10.0.1.100
CP->>BGP: Announce route for 203.0.113.50
CP->>RT: Add specific route:<br/>VM-A egress → EIP NAT node
与 NAT 网关的本质区别在于映射关系。NAT 网关是 N:M 映射,几百台 VM 共享几个公网 IP,端口动态分配,conntrack 表里挤满了来自不同 VM 的记录。EIP 是 1:1 映射,一个公网 IP 专属于一台 VM,所有 65535 个端口都归它一个人用,不存在端口竞争。
还有一个细节值得注意:VM 自己并不知道 EIP 的存在。你登录 VM-A,执行 ip addr,看到的仍然是 10.0.1.100。EIP 的地址转换发生在 VM 外部,边界网关或宿主机上,对 VM 完全透明。VM 不需要做任何配置,不需要知道自己的公网 IP 是什么。这个设计让 EIP 的绑定和解绑可以在不重启 VM、不修改任何配置的情况下完成。VM 浑然不知自己已经有了公网身份,这种"无感"恰恰是好的设计。最好的基础设施,是让使用者感觉不到它的存在。
14.3 FloatingIP:EIP 的流量路径
EIP 绑定后,流量是怎么走的?理解这个问题的关键是一个概念:FloatingIP。
FloatingIP 是什么?
FloatingIP(浮动 IP)是一种网络设计模式:一个 IP 地址不固定绑定在某台设备的网卡上,而是"浮动"在外部的某个网络节点上,通过 NAT 或路由规则与内部的真实 IP 建立映射。之所以叫"浮动",是因为这个映射关系可以随时改变,今天指向 VM-A,明天可以切换到 VM-B,而 IP 地址本身不变。OpenStack 最早普及了这个术语,云厂商的 EIP 本质上就是 FloatingIP 的产品化包装。FloatingIP 的核心价值在于:让公网身份可以独立于具体的 VM 实例存在和迁移。
FloatingIP 的核心思想是,EIP 不是真的"配置在 VM 的网卡上",而是"浮动"在 VM 外部的某个网络节点上。这个节点负责做 1:1 的地址转换。EIP 本质上就是一个固定的、专属的 NAT,只不过它只服务一台 VM。
出向流量路径:VM-A 发包,源 IP 是 10.0.1.100 → 包到达 EIP 的 NAT 节点 → SNAT 把源 IP 替换为 203.0.113.50 → 包进入公网。
入向流量路径:公网用户访问 203.0.113.50 → 公网路由把包送到 EIP 的 NAT 节点 → DNAT 把目的 IP 替换为 10.0.1.100 → 包通过 VPC 内部路由送到 VM-A。
图 14.2:EIP 的出向与入向流量路径
sequenceDiagram
participant VM as VM-A (10.0.1.100)
participant NAT as EIP NAT Node (203.0.113.50)
participant Web as Public User
Note over VM,Web: Egress (VM → Internet)
VM->>NAT: Src: 10.0.1.100 → Dst: 198.51.100.1
Note over NAT: SNAT: 10.0.1.100 → 203.0.113.50
NAT->>Web: Src: 203.0.113.50 → Dst: 198.51.100.1
Note over VM,Web: Ingress (Internet → VM)
Web->>NAT: Src: 198.51.100.1 → Dst: 203.0.113.50
Note over NAT: DNAT: 203.0.113.50 → 10.0.1.100
NAT->>VM: Src: 198.51.100.1 → Dst: 10.0.1.100
NAT 节点的位置因云厂商而异。有的放在专门的边界网关上,集中管理,运维简单,但所有 EIP 流量都要绕到边界网关,多一跳延迟。有的下沉到 VM 所在的宿主机上,地址转换在本机完成,流量路径最短,延迟最低,但每台宿主机都要承担 NAT 的职责。这是一个典型的集中 vs 分布式的权衡,没有绝对的优劣,取决于云厂商对延迟和运维复杂度的偏好。
与 NAT 网关的路径对比也值得一提。NAT 网关是一个集中的、共享的 NAT 设备,所有 VM 的出向流量都要经过它。EIP 的 NAT 可以分布式执行,如果 NAT 节点下沉到宿主机,EIP 的流量路径可能比 NAT 网关更短。
1:1 映射还带来了 conntrack 的简化。NAT 网关的 conntrack 表需要记录 N:M 的动态映射,几百台 VM 的连接挤在一张表里,端口竞争激烈,表项数量巨大。EIP 的映射关系是固定的,一个公网 IP 始终对应一个私有 IP,理论上静态 1:1 NAT 不需要 conntrack 来做地址翻译,因为映射关系永远不变。但实际实现中通常仍然使用 conntrack 来跟踪连接状态,安全组需要它来判断"这个包属于已有连接吗",流量统计和计费也需要它。关键区别在于:EIP 的 conntrack 表只服务于一台 VM 的连接,压力远小于 NAT 网关。
这里有一个容易被忽略的问题:如果 VM-A 同时绑定了 EIP,VPC 的路由表里又有 0.0.0.0/0 → NAT 网关的默认路由,VM-A 的出向流量走哪条路?答案是路由表的优先级决定。绑定 EIP 后,控制面会为 VM-A 添加一条更具体的路由,优先级高于默认路由。VM-A 的出向流量走 EIP,其他没有绑定 EIP 的 VM 继续走 NAT 网关。最长前缀匹配的规则在这里再次发挥了作用。
14.4 EIP 的迁移:网络身份的漂移
EIP 最有意思的能力,不是"给 VM 一个公网 IP",这件事静态公网 IP 也能做。EIP 真正的价值在于:它可以在 VM 之间迁移。
操作很简单:把 EIP 从 VM-A 解绑,再绑定到 VM-B。操作完成后,203.0.113.50 指向的不再是 VM-A(10.0.1.100),而是 VM-B(10.0.2.200)。对公网用户来说,IP 地址没变,他们甚至不知道背后的 VM 换了。
底层发生了三件事:
- 控制面更新 NAT 映射:203.0.113.50 ↔ 10.0.1.100 变为 203.0.113.50 ↔ 10.0.2.200
- BGP 路由不需要变化,公网 IP 没变,只是内部映射变了,公网路由器不需要感知这个变化
- 旧的 conntrack 记录失效,VM-A 上已有的 TCP 连接会断开,因为 NAT 节点上的映射已经指向了 VM-B,VM-A 的回包无法被正确翻译
图 14.3:EIP 从 VM-A 迁移到 VM-B
graph LR
subgraph Before
EIP1[EIP: 203.0.113.50] -->|1:1 NAT| VMA[VM-A<br/>10.0.1.100]
end
subgraph After
EIP2[EIP: 203.0.113.50] -->|1:1 NAT| VMB[VM-B<br/>10.0.2.200]
end
Before -->|Unbind + Rebind| After
迁移速度是这个能力的关键指标。EIP 迁移的核心延迟在于控制面更新 NAT 映射和路由生效的时间,通常在秒级。相比之下,DNS 切换需要等待 TTL 过期,可能是几分钟甚至几小时。EIP 迁移比 DNS 切换快一到两个数量级。
这个能力让 EIP 成为高可用架构的基础。一个典型的场景:主 VM 运行着 Web 服务,备 VM 待命。监控系统检测到主 VM 故障(比如健康检查连续失败),自动触发 EIP 从主 VM 迁移到备 VM。对外的公网 IP 不变,客户端无感知,除了已有的 TCP 连接会断开需要重连。整个切换过程在秒级完成。这就像一个演员中途退场,替补演员穿上同一套戏服上台,观众看到的角色没变,只是扮演者换了。
如果你还记得第 11 章讲过的弹性网卡(ENI),会发现两者有异曲同工之处。ENI 漂移是 VPC 内部的网络身份迁移,私有 IP 和 MAC 地址跟着 ENI 从一台 VM 移到另一台。EIP 迁移是公网身份的迁移,公网 IP 从一台 VM 移到另一台。两者可以配合使用:ENI 漂移保证 VPC 内部访问不中断,EIP 迁移保证公网访问不中断。
但 EIP 迁移有一个不可避免的代价:已有 TCP 连接会断开。conntrack 记录在旧的 NAT 节点上,迁移后这些记录失效,正在进行的连接无法继续。对于短连接场景(HTTP 请求),这个中断几乎无感,客户端重试一次就连上了新的 VM。对于长连接场景(WebSocket、数据库连接),这个中断需要应用层自己处理重连逻辑。这是一个工程上的现实,网络层能做到秒级切换 IP,但无法帮应用层保持已有的 TCP 状态。
14.5 带宽限速:流量在哪里被管住
EIP 不只是一个 IP 地址。每个 EIP 还关联着一个带宽上限,比如 100Mbps。超过这个带宽的流量会被限速或丢弃。
限速在哪里执行?在 EIP 的 NAT 节点上。通过令牌桶(Token Bucket)或类似的 QoS 机制,NAT 节点对经过的流量进行速率控制。这意味着限速和地址转换发生在同一个节点上,如果 NAT 节点下沉到宿主机,限速也在宿主机执行;如果 NAT 节点在边界网关,限速也在边界网关执行。
图 14.4:EIP 限速的执行位置
graph LR
subgraph NAT_NODE["EIP NAT Node"]
direction LR
IN["Ingress<br/>Traffic"] --> RL1["Rate Limiter<br/>(Token Bucket)"]
RL1 --> DNAT["DNAT"]
DNAT --> VM_IN["→ VM"]
VM_OUT["VM →"] --> SNAT["SNAT"]
SNAT --> RL2["Rate Limiter<br/>(Token Bucket)"]
RL2 --> OUT["Egress<br/>Traffic"]
end
style NAT_NODE fill:#e3f2fd,stroke:#1976d2
style RL1 fill:#ffcdd2,stroke:#c62828
style RL2 fill:#ffcdd2,stroke:#c62828
红色 = 限速点(Token Bucket 算法)。入向限速在 DNAT 之前执行,出向限速在 SNAT 之后执行。注意不对称性:出向丢包不浪费公网带宽,入向丢包时公网带宽已被消耗。
这里有一个不对称性值得注意:出向限速和入向限速的代价完全不同。
出向限速相对简单,包还没离开云的网络,在 NAT 节点上丢弃超额的包,不浪费任何外部带宽。但入向限速就不一样了:流量已经穿越了公网,从用户的设备出发,经过运营商的骨干网,到达云的边界。即使在入口处把超额的包丢弃,公网带宽已经被消耗了。
这个不对称性解释了一个看似反直觉的现象:DDoS 攻击即使被成功拦截,仍然会消耗带宽资源。攻击流量从全球各地涌向你的 EIP,即使云厂商在入口处全部丢弃,这些流量已经占用了从攻击源到云边界之间的所有链路带宽。入向流量的"伤害"在到达你之前就已经发生了,这也是为什么 DDoS 防护需要在尽可能靠近攻击源的地方进行清洗,而不是在目的地拦截。这个话题留到后面再展开。
14.6 EIP 与 NAT 网关的选择
EIP 和 NAT 网关解决的是不同的问题,但它们的能力有重叠的部分,都能让 VM 访问公网。什么时候用哪个?
EIP 适合的场景:
- VM 需要被公网主动访问,Web 服务器、API 服务器、游戏服务器,任何需要接受入向连接的场景
- VM 需要固定的出口 IP,合作方按 IP 做白名单,反向 DNS 验证,或者审计要求出口 IP 可追溯
- 高可用场景需要 IP 漂移能力,主备切换时,公网 IP 跟着业务走
NAT 网关适合的场景:
- 大量 VM 只需要出向访问公网,下载软件包、调用外部 API、推送日志,不需要被公网访问
- 不需要每台 VM 都有独立的公网 IP,几百台 VM 共享几个公网 IP,节省地址资源和成本
两者并不互斥。同一个 VPC 内,部分 VM 绑定 EIP(对外提供服务),其他 VM 通过 NAT 网关访问公网(只需出向)。路由表通过更具体的路由(EIP)和默认路由(NAT 网关)自动分流,绑定了 EIP 的 VM,出向流量走 EIP;没有绑定 EIP 的 VM,出向流量命中 0.0.0.0/0 默认路由,走 NAT 网关。
图 14.5:EIP 与 NAT 网关在同一 VPC 内的协同
graph TB
subgraph VPC
VMA[VM-A<br/>EIP: 203.0.113.50] -->|Egress via EIP| EIP_NAT[EIP NAT Node]
VMB[VM-B<br/>No EIP] -->|Egress via default route| NATGW[NAT Gateway<br/>198.51.100.10]
VMC[VM-C<br/>No EIP] -->|Egress via default route| NATGW
end
EIP_NAT --> Internet[Public Internet]
NATGW --> Internet
Internet -->|Access 203.0.113.50| EIP_NAT
EIP_NAT -->|DNAT| VMA
一句话概括:NAT 网关是"共享出口",EIP 是"专属身份"。前者让 VM 匿名地访问公网,后者让 VM 在公网上有了自己的名字。两者的选择不是技术优劣的问题,而是业务需求的问题,你的 VM 需要被认识,还是只需要能出去?
还有一个经常被问到的问题:一台 VM 能不能绑定多个 EIP?答案取决于云厂商的实现,但大多数云厂商支持通过多块弹性网卡(ENI)来实现,每块 ENI 绑定一个 EIP。这在某些场景下是必要的:一台 VM 上运行了多个服务,每个服务需要独立的公网身份;或者一台 VM 需要同时对外暴露不同的 IP 地址,用于不同的业务域名。但多 EIP 也意味着更复杂的路由规则和更高的成本,每个 EIP 都是独立计费的资源。多一个 IP 多一份灵活,也多一份管理负担。