Hetzner Cloud 网络栈

Hetzner详解其云虚拟机背后的网络栈,从采用Open vSwitch和自研Flusskrebs编排层的原因,到每台主机上的数据包过滤规则,并解释了该架构是如何演进而来的。

中文
复制
Hetzner Cloud 的 VM 主机网络栈示意图

本文是系列文章的第一篇,介绍 Hetzner Cloud 背后网络栈的演进及其架构。在这篇文章中,我们会回顾迄今为止所做的开发工作,以及当前基于 Open vSwitch 的网络栈是如何运作的。虽然 Cloud 服务器和 Load Balancer 的网络栈大体相似,但我们会聚焦于 Cloud 服务器和虚拟化主机(VM host),因为它们提供的服务和功能更多。

为简洁起见,我们不会涉及 Cloud Control Plane(它管理所有服务器、Load Balancer、Private Network 等),只关注主机上的网络栈。

我们的做法

所有云产品都需要多样且可靠的连接方案,让我们的客户——以及客户的客户——能够访问运行在我们网络中的服务。

在 Hetzner,我们总体上信奉自己拥有、自己运维的解决方案,信奉先有积木、再在其上继续搭建。网络,尤其是我们云产品的网络连接,也不例外。

尽管多年来技术不断变化,我们始终偏好把可变部分放在主机上,而不是搭建一套智能而复杂的物理网络。因此,我们的设计让物理网络为主机提供稳定的 IP 连接,由主机来提供更高层的网络功能,包括防火墙、Private Network 以及辅助性基础设施服务。

把这些服务和网络功能跑在主机上,我们在设计和构建产品时就有了很大的自由度和灵活性,不受硬件厂商所能提供的东西限制。这样一来,团队和技术之间的职责划分清晰,而且(大多数)出错时可以把影响范围限制在单台主机内。这也意味着我们必须完全拥有这套方案,并按自己的需求调整它——不过这本来就是我们的基因。

一点历史

Hetzner 历来采用不同的技术为其云产品提供网络连接。

早期,2011 年推出的 Hetzner vServer 使用 HDD、原生 Linux bridge 和静态路由。它提供双栈连接,每台主机最高 1 Gb/s。最终,这套架构上运行了大约 25,000 个实例。

后续产品于 2015 年 9 月推出,采用基于 Ceph 的超融合架构。它转向通过 BGP 进行动态路由,并使用 Linux bridge 配合 IPv4 的 1:1 NAT 以及完全路由的 IPv6 前缀。这带来了更好的 IP 移动性,并能在不影响客户的情况下进行主机维护。2016 年,主机链路升级为 2x 10 Gb/s。这套架构一直沿用到 2018 年,实例规模增长到约 50,000 个。

2018 年 Hetzner Cloud 上线时,我们放弃了 NAT,转向直接的 IPv4 路由。2019 年 7 月,我们引入了基于 Open vSwitch 的数据平面,并增加了对基于 VXLAN 封装的私有云网络的支持。2020 年 11 月,我们开始将公网部分迁移到 Open vSwitch,这使我们得以在 2021 年 3 月推出基于 Open vSwitch 流和 netfilter 的有状态云防火墙。

VM 主机的组成部分

我们已经说明,网络栈的重要部分运行在 VM 主机上;那么,要让一台云服务器正常运行,它们必须提供什么?

对大多数服务器来说,需要能够访问互联网,这样才能从家中或国外访问系统及其服务。有些服务器使用私有网络,可以替代公网,也可以与公网并存,这些私有网络通过一个只有你的系统能访问的私有虚拟网络,将服务器、Load Balancer 以及可能的专用服务器连接起来。私有网络流量使用 Virtual eXtensible LAN(VXLAN)封装,每个私有网络都有唯一的 Virtual Network Identifier(VNI)。主机网络栈确保只有接入相应私有网络的成员才能收发流量。

为了控制从互联网对你服务的访问,我们提供了云防火墙功能。它可以控制对服务器特定端口或协议的访问,也能限制服务器可以发出哪些流量。这些控制应尽可能靠近服务器,因此放在宿主机上完成。如果在中心位置构建有状态防火墙,就需要额外的硬件和网络叠加层来把“干净”的流量送到服务器,还会给高可用、集中式的连接跟踪带来大量复杂性。

默认情况下,我们的云服务器镜像配置为使用动态主机配置协议(DHCP)来动态配置 IPv4 地址和路由。因此,我们必须提供 DHCP 服务器,让服务器能够为公网和私网接口请求其 IPv4 网络配置。

在大多数镜像中,cloud-init 用于配置系统的某些部分,例如配置 IPv6 网络、为私网接口触发 DHCP、挂载云 Volume 等。完成这些任务时,cloud-init 必须从我们的元数据服务器(地址为 http://169.254.169.254/)查询本地系统信息或用户特定的配置项。除了 cloud-init 之外,元数据服务器还被许多集成使用,包括但不限于我们的 CSI driver、hc-utils、Flatcar Linux 的 Afterburn 和 Ignition 等发行版工具,以及 Talos Linux。

DHCP 服务器和元数据服务器都运行在每台 VM 宿主机本地,以获得最大的弹性和可用性。

除了这些面向用户的服务,VM 宿主机还运行着一批内部服务,它们确保你的服务器正常运行、网络栈获得正确的信息以配置连通性,并让我们的监控了解正在发生什么。

不过,本文将聚焦网络栈,深入看看它的内部运作。

Open vSwitch

大部分网络数据路径由 Open vSwitch(简称 OVS)实现,这是一个开源的多层虚拟交换机。它的数据路径早已包含在 Linux 内核中,用户空间/控制平面的软件包也已在所有主流 Linux 发行版中提供。

对于一些虚拟化环境来说,它是默认的网络栈,许多现成方案都自带 OVS 支持,包括但不限于 Proxmox VE、OpenStack 和 oVirt 这几个常见的选择。不过这些我们都没用,而是用自研工具管理 KVM 及其周边组件。

Open vSwitch 是一套多功能的网络方案,功能非常丰富,我们的使用场景和部署只用到其中一部分。它使用 OpenFlow,这是一个开放协议,用于配置网络设备的数据平面,规定数据包该如何转发。每个通信方向都必须有一条 flow,指明数据包如何处理、发往何处。

这些 flow 可以是精确匹配,比如通过 ARP、NDP、ICMP 等与网关通信,发往同一私有网络内其他成员的流量,或者连接互联网上某个服务。

Open vSwitch 编排——Flusskrebs

要为服务器和 Load Balancer 提供网络连接,并配置应用到服务器上的各类 Firewall,我们必须安装相应的 flow,Open vSwitch 才能提供这些连接和功能。

编排 Open vSwitch 的常见方案是 Open Virtual Network(OVN)控制平面项目,它与 OVS 本身同期启动,目标是管理一批运行 Open vSwitch 的节点。它提供了一层抽象,可以配置逻辑路由和交换机,再转换成 OpenFlow。

在把 Open vSwitch 引入 Hetzner Cloud 技术栈时,我们决定不走这条路,而是自研了一套方案,叫 Flusskrebs(字面意思是“河蟹”,也可以理解为“flow crab”🦀)。它用 Python 编写,对外提供 REST API 供 Cloud 控制平面中主机本地的部分调用,负责配置每台 VM 或 Load Balancer 正常运行所需的 flow,其中包括实现用户或我们后端配置的 Firewall 规则所需的 flow,比如默认阻断出站 SMTP。

控制器同时充当每台主机上私有网络接口的中央 DHCP 服务器。

VM host 网络栈

了解了总体架构思路和这些基础组件之后,我们来看看 VM host 网络栈的更多细节。下图展示了 VM host 网络栈内部运作的简化概览。

Hetzner Cloud 的 VM host 网络栈示意图,展示 Open vSwitch、Flusskrebs 编排与 VM 之间的数据流向

大多数主机使用两个 10 Gb/s 上行接口,在 Linux 上通过 LACP 绑定为一个链路聚合组(LAG)或 bond,连接到两台物理交换机,二者组成虚拟 chassis,以提升可靠性和带宽。为了进一步提高可靠性,今年早些时候我们开始迁移到使用 BGP 的两个原生路由上行链路,分别连接到两台独立的交换机。注意,上行接口并不直接连接到 OVS 网桥,而是由 Linux 系统在上行链路与 OVS 之间进行路由。这样一来,主机的可达性不再依赖 OVS,也简化了主机安装与维护。

中央 Open vSwitch 网桥是提供网络连接与功能的主要组件,主机上的所有云服务器都连接到它。每台服务器可以有一个公网接口,另外最多还可以有三个私有网络接口。以下模板用于生成允许服务器向网络发送出口流量的流规则:

已复制到剪贴板


# Only process packets with correct source MAC address

table=0, priority=9999, in_port={vm_port_no}, eth_src={mac}, actions=set_field:1->reg0,set_field:{vm_port_no}->reg5,resubmit(,1)

# Only accept packets with an IPv4/IPv6 address associated with the server

table=3, priority=9999, in_port={vm_port_no}, ip_src={ip}, ip, actions=resubmit(,4)

table=3, priority=9999, in_port={vm_port_no}, ipv6_src={ip}/64, ipv6, actions=resubmit(,4)

table=3, priority=9999, in_port={vm_port_no}, arp, arp_op=2, arp_spa={ip}, actions=resubmit(,4)

发往服务器的入口流量通过以下流规则转发,每个 IPv4 或 IPv6 前缀各一条。主机网络栈使用一个众所周知的“虚拟网关 MAC”向 VM 标识自身,该地址始终为 d2:74:7f:6e:37:e3。

已复制到剪贴板


table=22, priority=9999, in_port={internet_port_no}, ip, ip_dst={ip}, actions=mod_dl_src:d2:74:7f:6e:37:e3,{vm_port_no}

table=22, priority=9999, in_port={internet_port_no}, ipv6, ipv6_dst={ip}/64, actions=mod_dl_src:d2:74:7f:6e:37:e3,{vm_port_no}

DHCP 和 metadata 服务器等基础设施服务也连接到 Open vSwitch,并与每台服务器一起运行在各自专属的 Linux Network Namespace 中。

注意,上图所示的 udhcpd DHCP 服务器只负责服务器的公网接口。如前所述,Flusskrebs 控制器包含一个中央 DHCP 服务器,处理所有私有网络接口。连接公网 DHCP 服务器的流规则同样简单直接:

已复制到剪贴板


table=1, priority=9999, in_port={vm_port_no}, udp, udp_dst=67, actions={service_port_no}

table=1, priority=9999, in_port={service_port_no}, udp, udp_src=67, actions={vm_port_no}

除了 Open vSwitch,Linux netfilter 的连接跟踪也被用于实现有状态的云防火墙。由于连接跟踪表是全局共享的,我们必须防止它溢出,并确保所有服务器和客户都能公平使用。这项工作由 ctcount 完成——这是另一个内部项目,它记录连接跟踪条目,并强制限制每台服务器最多 80,000 个活跃的并发连接。一旦服务器达到这个上限,就无法再建立新连接,除非先关闭一条已有连接。

现状

这套基于 Open vSwitch 的数据平面一直表现良好,即便在超过一百万台云服务器的情况下,今天大体上依然如此。但我们已经触碰到一些限制,希望用一套更专用的网络栈来提升可扩展性、韧性和灵活性。过去几年里,我们的 SDN 团队一直在构建这样的方案:它针对我们的需求量身定制,同时易于运维,并且能为私有网络 IPv6 等新功能打下坚实基础。

我们将在本系列的下一部分介绍后续的进展、我们最新完全自研的网络栈,以及它的工作原理。敬请期待!

在 Hetzner Summit 上,我们做了一场题为“The new Hetzner Cloud network stack - A technical deep dive into how we deliver your packets”的分享,聊了我们当前的网络栈以及我们所构建的东西。录像应该很快会在 Summit 页面上线。

来源: Hetzner← 返回首页