虚拟化与封装
物理数据中心的局限性
数据中心的组织方式是固定且结构化的。相同的服务器被组织到机架中,而机架又以某种固定的拓扑结构排列。这种方式有一些好处,例如,它为我们提供了一种自然的层级地址分配方式。
然而,当我们考虑应用程序如何在数据中心中托管时,数据中心的固定组织方式就显现出一些缺点。假设谷歌推出了一项新服务,想要在现有的数据中心中托管。如果我们将该应用程序直接部署在物理服务器上,就需要有人物理安装一台新的服务器(带有自己的 IP 地址)来运行这个应用。如果服务扩展,可能还需要安装更多服务器。如果服务器出现故障,我们就得等待有人来修复它。核心问题在于,更改物理基础设施非常困难,但我们常常需要快速且频繁地添加新主机、扩展现有主机以及迁移主机。
将应用程序部署在物理服务器上还会带来扩展问题。假设谷歌的新服务非常轻量,但出于安全原因需要专用服务器。我们就必须为这个轻量服务分配一整台物理服务器,而服务器的大部分计算能力可能会被闲置。
这种方式还存在路由问题。假设我们想将服务迁移到数据中心大楼的另一个区域(例如,因为大楼的某个部分正在进行维护)。首先,需要有人物理移动服务器。此外,在我们的层级地址模型中,我们需要为该服务分配一个与其新物理位置对应的新 IP 地址。理想情况下,应用程序希望保持相同的地址,而不受其在数据中心中位置的影响。

虚拟化
我们可以使用虚拟化来解决这些问题,在保持数据中心严格物理结构的同时,为应用程序提供更大的灵活性。虚拟化允许我们在一台物理服务器内部运行一个或多个虚拟服务器。
虚拟服务器给应用程序一种错觉,让它们以为自己运行在一台专用的物理机器上。但实际上,多台虚拟服务器可能运行在同一台物理机器上。当应用程序尝试与硬件(如磁盘、网卡)交互时,它实际上是在与软件中的虚拟机监控程序(hypervisor) 进行交互。虚拟机监控程序为每个虚拟应用程序提供与真实硬件相同的接口。虚拟机监控程序本身运行在实际的物理硬件上,并且可以将应用程序的请求(如磁盘写入、网络数据包发送)转发到硬件层面。
借助虚拟化,如果我们有一个新应用程序,可以让虚拟机监控程序为该应用启动一个新的虚拟机。由于虚拟机监控程序运行在软件中,因此无需在物理数据中心安装任何新服务器。同样,我们可以完全通过软件将主机迁移到另一台物理机器上。

虚拟化允许多个应用程序共享一台物理服务器。这些应用程序可以相互隔离,并且可以由不同的人管理。这使我们能够更高效地利用数据中心的计算资源,也让数据中心能够容纳更多的主机。例如,一个包含 40 台服务器的机架可以拥有超过 40 个终端主机。
虚拟交换机
物理服务器有一个网卡和一个 IP 地址,但我们需要让每个虚拟机都觉得自己有专用的网卡和地址。此外,交换机现在可能有多个虚拟机连接到同一个物理端口。
为了管理同一物理机器上的多个网络连接,服务器需要一个虚拟交换机。这个虚拟交换机在服务器上以软件形式运行(它不是物理路由器),并执行与真实交换机相同的操作(如转发数据包)。每个虚拟机都连接到虚拟交换机,而虚拟交换机又连接到网络的其他部分。

注意:交换机通常运行在专用硬件上以最大限度地提高效率。虚拟交换机可以在通用 CPU 上以软件形式运行,因为它们只需要支持少数几个虚拟机(容量低于普通交换机通常需要处理的容量)。
底层网络与叠加网络
通过虚拟化,我们现在有虚拟主机运行在物理服务器之上。与物理服务器不同,虚拟主机可以快速创建、关闭和更改。
虚拟机不一定使用与物理服务器相同的寻址方案。物理服务器的 IP 地址由物理数据中心的拓扑结构(如 pods、机架)定义。相比之下,虚拟机的 IP 地址通常由一些现实世界的层级结构(如国家、组织)定义。特别是,同一物理服务器上的虚拟主机不一定都具有相同的 IP 前缀,因此我们不能使用相同的聚合技巧来进行扩展。
如果我们简单地扩展路由方案以支持虚拟机,我们的转发表会迅速变得非常庞大。以前,我们可以通过聚合来实现扩展,例如:“蓝色 pod 中的所有服务器都有相同的 IP 前缀,它们的下一跳都是 R2。” 现在,那个蓝色 pod 中的服务器可能包含数百个虚拟主机,都具有不同的 IP 地址(没有共同前缀)。我们需要为每个虚拟主机单独设置一个转发条目。此外,如果虚拟主机迁移到另一台物理机器(保持相同的 IP 地址),路由协议必须重新发现到该虚拟主机的路径。我们能找到一种方法来避免数据中心为了支持每个虚拟机地址而进行扩展吗?
这里的核心问题是,我们现在有两种不同的寻址系统,一种用于虚拟主机,另一种用于物理主机。两种寻址方案都在 IP 层工作,但在 IP 层内部,现在有两个我们需要考虑的抽象子层。
底层网络(underlay network) 处理物理机器之间的路由。底层网络包含数据中心基础设施,如架顶交换机和 spine 交换机。底层网络扩展性很好,因为我们使用物理数据中心的拓扑结构定义层级地址。
叠加网络(overlay network) 存在于物理拓扑(底层)之上,它只考虑虚拟机之间的路由。实际上,每个虚拟机通常只需要与网络中的其他少数几个虚拟机通信。因此,叠加网络扩展性很好,因为一个虚拟机不需要知道其他所有虚拟机的信息。

理想情况下,我们希望这两个层分别处理寻址。底层网络不需要知道虚拟主机的地址(否则,它的扩展性会很差)。同样,叠加网络也不需要知道数据中心中的每台物理服务器(每个虚拟机只需要知道少数几个其他虚拟机)。
如果我们不告诉底层网络虚拟主机的地址,那么当数据中心交换机收到一个以虚拟 IP 为目的地的数据包时,它会在转发表中查找,找不到任何虚拟 IP,就会丢弃这个数据包。我们需要某种方法来弥合叠加网络(虚拟层面)和底层网络(物理层面)之间的差距。
封装
为了统一叠加层和底层,我们可以使用设计互联网时所用的分层和头部策略!
到目前为止,我们将 IP 视为一个单一的层,每个数据包都有一个单一的 IP 头部,用于理解 IP 寻址系统。
现在我们有两个 IP 子层,具有两种不同的 IP 寻址系统,我们可以在数据包中引入一个额外的头部。例如,我们可以有两个 IP 头部,一个头部用于理解叠加网络,另一个头部用于理解底层网络。或者,我们可以将原始 IP 头部用于底层网络,并引入一种新类型的头部(与 IP 不同)用于叠加网络。

现在,我们的数据包路由策略可以结合叠加网络和底层网络。假设虚拟机 A 想要向虚拟机 B 发送一个数据包。

- 虚拟机 A 创建一个带有单一 IP 头部的数据包,其中包含 B 的虚拟 IP 地址。(请记住,A 是从叠加层的角度考虑的,不知道底层物理 IP 地址。)虚拟机 A 将这个数据包转发到(A 所在物理服务器上的)虚拟交换机。

-
虚拟交换机读取头部以获取 B 的虚拟 IP 地址。然后,虚拟交换机查找与 B 的虚拟 IP 地址对应的物理服务器地址。(我们尚未描述如何实现这一点。)
虚拟交换机添加一个包含 B 的物理服务器地址的额外外部头部。添加头部有时被称为封装。
此时,数据包有两个头部。内部头部(较高层,叠加层,由虚拟机 A 添加)包含 B 的虚拟 IP 地址,外部头部(较低层,底层,由虚拟交换机添加)包含 B 的物理服务器地址。
虚拟交换机根据物理服务器地址将这个数据包转发到下一跳交换机。

- 数据包通过底层网络发送。数据中心中的每个交换机只查看外部头部(底层,物理服务器地址)来决定如何转发数据包。(请记住,数据中心交换机是从底层的角度考虑的,不知道叠加层的虚拟 IP 地址。)


-
最终,数据包到达目标物理服务器的虚拟交换机。虚拟交换机查看外部头部(底层),发现目标物理服务器地址是它自己。
虚拟交换机移除外部头部,露出内部的头部。移除外部头部有时被称为解封装。

最后,虚拟交换机读取内部头部(叠加层)。这告诉虚拟交换机该数据包应该转发到物理服务器上的哪个虚拟机。

在这个过程中,封装使我们能够在两个不同的层考虑路由。底层能够使用物理服务器地址路由数据包,而无需考虑叠加层。同样,叠加层中的虚拟机能够发送和接收数据包,而无需考虑如何在底层中转发数据包。虚拟交换机通过将虚拟机地址转换为物理服务器地址,以及添加和移除额外的底层头部,来桥接这两个层。



带封装的转发表
我们应该在转发表中安装哪些条目来支持带封装的路由?
虚拟机应该安装一条默认路由,将每个数据包转发到物理机器上的虚拟交换机。
虚拟交换机需要实现一些额外的功能来桥接这两个层。特别是,当看到虚拟地址时,应该应用封装(添加一个外层),使用相应的物理地址。转发表包含该服务器上的任何虚拟机可能想要通信的每个目标虚拟机的条目。我们能够支持这种规模,因为我们假设虚拟机不需要与数据中心中的每个其他虚拟机通信。与标准路由算法不同,我们不需要任意到任意的路由(我们不需要到每个其他虚拟机的路径)。
虚拟交换机还需要一条用于解封装数据包的额外规则。如果外部(底层)数据包的目的地是交换机本身,则应该解封装(移除外部头部),并将数据包传递到内部头部中的虚拟机地址。这条规则的规模与服务器上的虚拟机数量成正比,通常足够小,便于管理。
添加这种功能很难吗?幸运的是,虚拟交换机是用软件实现的,因此添加这种功能只需要编写代码(不需要额外的硬件)。但在实践中,封装非常常见,有时也会用硬件实现。
数据中心中的交换机与我们引入虚拟化之前的工作方式完全相同。转发表只包含物理服务器地址,并且我们知道这些地址可以通过基于物理拓扑的聚合技巧进行扩展。
多租户与私有网络
数据中心由单一运营商管理,但不同的组织可能在该数据中心内运行应用程序。例如,谷歌运营的数据中心可能有一些由 Gmail 运行的虚拟服务器,还有一些由谷歌地图运行的虚拟服务器。这种在一个数据中心中托管多个服务的方式称为多租户。
云提供商也使用数据中心为客户提供虚拟机。例如,亚马逊网络服务(AWS)和谷歌云平台(GCP)允许用户在数据中心中启动虚拟机,进行各种操作,完成后销毁虚拟机。
多租户存在一个问题,我们并不总是希望不同的租户能够相互通信。例如,如果客户请求一个虚拟机,他们可能不应该能够连接到数据中心中的每个其他虚拟机。
另一个问题是,数据中心中的租户在选择地址时不会相互协调。例如,假设我们的数据中心有两个租户:百事和可口可乐。每个租户创建自己的私有网络,在其中为虚拟机分配内部 IP 地址。私有网络仅用于数据中心内的主机相互通信,这些主机永远不会从公共互联网被访问。由于这些网络是私有的,两个租户都可以使用相同的专门分配的私有范围(RFC 1918 地址)中的地址。百事的私有网络可能有一个 IP 地址为 192.0.2.2 的虚拟机,而可口可乐的私有网络可能有一个不同的虚拟机,IP 地址也是 192.0.2.2。(在实践中,我们使用私有范围是为了重用 IPv4 地址,因为 IPv4 地址即将耗尽。)

从每个租户的角度来看,这不是问题。百事的 192.0.2.2 永远不会与可口可乐的 192.0.2.2 通信,而且这两个主机都无法被全球互联网访问。然而,这对数据中心来说是个问题。如果我们使用基于目的地的转发,当看到一个目的地为 192.0.2.2 的数据包时,我们无法知道这个地址指的是哪个虚拟机。
实际中出现 IP 地址重复有两个原因。首先,数据中心通常无法控制租户为其虚拟机分配的地址。其次,在 IP 中,使用特定范围作为私有网络是标准做法,这常常导致地址重复。
用于多租户的封装
我们可以再次使用封装的思想来解决这个问题。我们可以添加一个新的头部,其中包含一个虚拟网络 ID,用于标识特定的租户(例如,百事的 ID 为 1,可口可乐的 ID 为 2)。这个新头部不包含转发和路由信息,但提供了额外的上下文。现在,如果一台物理服务器有多个租户的虚拟机,它可以将数据包传递到正确的虚拟网络。


当虚拟交换机收到一个数据包并解开外部(底层)头部时,它会查看我们的新头部来确定数据包的目标租户。然后,它会查看叠加层头部,将数据包转发到属于正确租户的特定虚拟机。
封装堆叠
我们可以多次使用封装的思想,添加多个新头部来同时支持虚拟化和多租户。
首先,虚拟机创建一个标准的 TCP/IP 数据包,带有虚拟 IP 目的地。
在第一个封装步骤中,我们添加一个虚拟网络头部,用于标识发送该数据包的租户。这有助于我们区分使用相同地址的两个租户,还能防止数据包被发送到错误的租户。
在第二个封装步骤中,我们添加一个底层网络头部,用于标识与虚拟 IP 目的地对应的物理服务器地址。

当我们堆叠封装时,抽象层仍然有效。底层网络不需要知道同一数据中心中有多个租户。底层网络只查看最外层头部中的物理服务器地址,并相应地转发数据包。
解封装步骤按相反顺序进行。目标服务器上的虚拟交换机收到一个带有两个额外头部的数据包。
在第一个解封装步骤中,我们移除外部的底层头部。由于数据包已经到达目标物理服务器,这个头部不再需要。
在第二个解封装步骤中,我们使用虚拟网络头部来确定我们应该考虑哪一组虚拟机。物理服务器可能有多个租户的虚拟机,这有助于缩小到单个租户。
最后,我们使用最内层的 IP 头部将数据包发送到正确虚拟网络中的正确虚拟机。

注意:使用封装时,在多路径间进行数据包负载均衡时,我们需要注意读取 5 元组(IP、端口和协议)。幸运的是,现代路由器硬件擅长解析数据包,以理解相关头部在数据包中的位置,即使插入了额外的头部。
在实践中,存在许多不同的封装协议。我们可以使用 IP-in-IP 来支持两个 IP 头部(一个用于叠加层,一个用于底层)。
MPLS 是一个简单的头部,用于添加标识服务(如虚拟网络、租户)的标签。它可用于为多租户添加封装。
随着数据中心的普及,已经开发了许多其他协议,如 GRE、VXLAN 和 GENEVE。这些协议大多在 IP 上运行,因此这些自定义协议是内层的叠加层头部,而常规 IP 是外层的底层头部。
(注:文档部分内容可能由 AI 生成)