赫兹纳(Hetzner)的赫兹纳云(Hetzner Cloud)服务于2018年上线时,采用基于三层网络的简单网络堆栈:每台云服务器分配到一个公网IPv4地址和一个IPv6 /64子网,流量经由数据中心的物理网络直接路由,当时还没有私有网络、防火墙或负载均衡器。后续私有网络、防火墙与负载均衡器等功能陆续加入,底层网络架构也随之持续调整。
正文

TL;DR
所有云产品都需要灵活且可靠的连接方式,以便我们的客户以及他们自己的客户能够访问运行在我们网络中的服务。自 vServer 产品起步以来,我们用过多种技术来提供这种连接,但它们都依赖主机,而不是依赖智能网络。随着 Hetzner Cloud 推出,以及私有网络和防火墙等网络功能上线,我们引入了 Open vSwitch。我们构建了一个自定义方案来编排 Open vSwitch,尤其是处理流量所需的流表。我们把网络栈设计成自己拥有并能自行运维的方案,并且具备可继续构建的基础模块。

这篇博客是系列的第一部分,介绍 Hetzner Cloud 背后网络栈的演进及其架构。在这一篇中,我们将回顾至今为止的开发工作,以及当前基于 Open vSwitch 的网络栈如何运作。虽然 Cloud 服务器和负载均衡器的网络栈相似,但我们将重点放在 Cloud 服务器和虚拟化主机(VM 主机)上,因为它们提供更多服务和功能。

为简洁起见,我们不讨论管理所有服务器、负载均衡器、私有网络等的 Cloud 控制平面,而只关注主机上的网络栈。

我们如何运作
在 Hetzner,我们通常信奉这样的原则:方案要由自己拥有并能自行运维,并且具备可以继续构建的基础模块。网络,尤其是我们云产品的网络连接,也不例外。

尽管这些年来技术不断变化,我们始终更青睐把“活动部件”放在主机上的方案,而不是搭建一套智能且复杂的物理网络。因此,我们把架构设计成:物理网络为主机提供稳定的 IP 连通性,而主机提供更高层的网络功能,包括防火墙、私有网络以及辅助基础设施服务。

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


一点历史
早期,2011 年推出的 Hetzner vServers 使用 HDD、原生 Linux 网桥和静态路由,提供每主机最高 1 Gb/s 的双栈连接。这套环境最终运行了大约 25,000 个实例。
后续产品在 2015 年 9 月推出,采用基于 Ceph 的超融合架构,切换到通过 BGP 的动态路由,以及使用 1:1 NAT 处理 IPv4、完全路由 IPv6 前缀的 Linux 网桥设置。这带来了更好的 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 主机上;那么,要让一台 cloud server 正常运作,主机必须提供什么呢?
多数服务器需要互联网可达性,以便系统及其服务能从本地或海外访问。有些服务器会使用私有网络,替代公共网络或与其并存,私有网络通过只有你的系统能访问的私有虚拟网络连接服务器、负载均衡器,也可能包括独立服务器。私有网络流量使用 Virtual eXtensible LAN(VXLAN)封装,每个私有网络有唯一的 Virtual Network Identifier(VNI)。主机网络栈确保只有接入对应私有网络的成员才能发送或接收流量。
为了控制从互联网访问服务,我们提供云防火墙功能。它可以控制对服务器某些端口或协议的访问,也能限制哪些流量可以离开服务器。这应当尽可能靠近服务器完成,因此放在主机上。如果集中构建有状态防火墙,就需要额外硬件和网络 overlay 把“干净”的流量送到服务器,并且会给高可用、集中式连接跟踪带来很多复杂性。
默认情况下,我们的 cloud server 镜像配置为使用 Dynamic Host Configuration Protocol(DHCP)动态配置 IPv4 地址和路由。因此,我们必须提供 DHCP 服务器,让服务器能为公共和私有网络接口请求其 IPv4 网络配置。
在大多数镜像中,cloud-init 用于配置系统的某些部分,例如配置 IPv6 网络、为私有网络接口触发 DHCP、挂载 cloud Volumes 等。为此,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)指定数据包如何处理,以及需要发往哪里。
这些流可以是精确匹配,例如通过 ARP、NDP、ICMP 等与网关通信、发往私有网络其他成员的流量,或连接互联网上某个服务的连接。
Open vSwitch 编排 - Flusskrebs
为了给服务器和负载均衡器提供网络连接,并配置应用到服务器上的防火墙,我们必须为 Open vSwitch 安装所需的流,以提供这些连接和功能。
编排 OVS 的常见方案是 Open Virtual Network(OVN)控制平面项目,它与 OVS 一同启动,用于维护运行 OVS 的节点集群。它提供一层抽象,可以配置逻辑路由和交换机,再翻译成 OpenFlow。
在 Hetzner Cloud 引入 Open vSwitch 时,我们决定不走这条路,而是自研了名为 Flusskrebs(字面意思是“river crab”或“flow crab” 🦀)的定制方案。它用 Python 编写,提供 REST API 供 Cloud 控制平面的主机本地部分使用,并配置每台 VM 或负载均衡器正常工作所需的流。这包括实现用户或后端配置的防火墙规则的流,例如默认阻止出站 SMTP。
该控制器还在每台主机上充当私有网络接口的中央 DHCP 服务器。
VM 主机网络栈
有了总体架构思路和这些基础模块后,我们来更详细地看看 VM 主机网络栈。前面这张图展示了 VM 主机网络栈内部机制的简化概览。
大多数主机使用两个 10 Gb/s 上行接口,通过 LACP 在 Linux 上捆绑成链路聚合组(LAG)或 bond,连接到两台组成虚拟机箱的物理交换机,以提高弹性和带宽。为进一步提升弹性,我们今年早些时候已开始迁移到两条使用 BGP 的原生路由上行链路,分别连接到两台独立交换机。注意,上行接口并不直接连接到 OVS 网桥,而是由 Linux 系统在上行链路和 OVS 之间路由。这样,主机可达性不依赖于 OVS,也简化了主机安装和维护。
一个中央 Open vSwitch 网桥是提供网络连接和功能的主组件,主机上所有 cloud server 都连接到它。每台服务器可以有一个公共网络接口,以及最多三个私有网络接口。用于渲染允许服务器出向流量的流表模板如下:
仅处理源 MAC 地址正确的数据包
table=0, priority=9999, inport={vmportno}, ethsrc={mac}, actions=setfield:1->reg0,setfield:{vmportno}->reg5,resubmit(,1)
仅接受与服务器关联的 IPv4/IPv6 地址的数据包
table=3, priority=9999, inport={vmportno}, ipsrc={ip}, ip, actions=resubmit(,4)
table=3, priority=9999, inport={vmportno}, ipv6src={ip}/64, ipv6, actions=resubmit(,4)
table=3, priority=9999, inport={vmportno}, arp, arpop=2, arp_spa={ip}, actions=resubmit(,4)
入向服务器的流量使用以下流转发,每个 IPv4 或 IPv6 前缀分别对应一条。主机网络栈使用一个众所周知的“虚拟网关 MAC”向 VM 标识自己,该 MAC 始终是 d2:74:7f:6e:37:e3。
table=22, priority=9999, inport={internetportno}, ip, ipdst={ip}, actions=moddlsrc:d2:74:7f:6e:37:e3,{vmportno}
table=22, priority=9999, inport={internetportno}, ipv6, ipv6dst={ip}/64, actions=moddlsrc:d2:74:7f:6e:37:e3,{vmportno}
DHCP 和元数据服务器等基础设施服务也连接到 Open vSwitch,并与每台服务器一起运行在各自独立的 Linux Network Namespace 中。
注意,上图中显示的 udhcpd DHCP 服务器只负责服务器的公共接口。如前所述,Flusskrebs 控制器包含处理所有私有网络接口的中央 DHCP 服务器。连接公共 DHCP 服务器的流同样简单:
![]()
table=1, priority=9999, inport={vmportno}, udp, udpdst=67, actions={serviceportno}
table=1, priority=9999, inport={serviceportno}, udp, udpsrc=67, actions={vmportno}
除了 Open vSwitch,我们还使用 Linux netfilter 连接跟踪来实现有状态的云防火墙功能。由于连接跟踪表是全局共享的,我们必须防止其溢出,并确保所有服务器和客户公平使用。这由另一个内部项目 ctcount 完成,它跟踪连接跟踪条目,并强制限制每台服务器最多 80,000 个活动并发连接。如果某台服务器达到该上限,在已有连接关闭之前,无法再打开新连接。
现状
这种基于 Open vSwitch 的数据平面一直很好地服务着我们,即便在拥有超过一百万台 Cloud 服务器的今天也大多仍然如此。不过,我们遇到了一些限制,希望用更专用的网络栈来提升可扩展性、弹性和灵活性。过去几年里,我们的 SDN 团队一直在构建这套为需求量身定制、易于运维的方案,并为新功能(例如私有网络的 IPv6)打下坚实基础。
我们将在本系列的下一篇中介绍这段旅程的后续步骤、我们最新完全自研的网络栈及其工作原理,敬请期待!
在 Hetzner Summit 上,我们有一场名为“The new Hetzner Cloud network stack - A technical deep dive into how we deliver your packets”的演讲,讨论了当前网络栈以及我们构建的内容。录像可在 Summit 页面 观看。
评论
讨论以技术审视和吐槽为主,分歧主轴是新网络架构是否用复杂度换取了灵活性并牺牲了性能,以及 Hetzner 的低价能否抵消稳定性与支持体验上的问题。对架构效率的严厉批评虽集中在少数深度评论中,但配合停机、存储速度、实名认证等体验抱怨,整体气氛偏向质疑;只有少数人仍肯定其性价比和数据中心运维能力。
认为该架构过度复杂,转发路径过长,会牺牲性能并隐藏成本
- 批评者列出数据包从 guest 用户态经 virtio_net、OVS 数据路径、网桥、bonding 到物理网卡的多跳路径,几乎每步都有内存分配、工作队列和上下文切换,最低也会插入约 5 毫秒延迟,负载下更会膨胀;通道绑定默认配置还会造成乱序,拖慢 nginx 等应用。该内核网络路径的消耗不会体现在 top 或常见指标里,只变成客户账单上的浪费容量,对 NFS、S3、NVMeoE、RoCE 等 ML 训练场景尤其不利。(少数人,但技术细节最集中) > 它还会造成大量乱序投递并显著拖慢用户应用,因为通道绑定似乎只是保留了默认配置,整体效率极低。(@spwa4)
认为这是常见 BGP EVPN/VXLAN 架构,类似 OpenStack,并不新鲜
- 架构本质是把 VTEP 从 ToR 交换机下推到宿主机,属于 BGP EVPN/VXLAN,在当下数据中心已相当标准,也与 OpenStack 的网络架构相似。有人补充,Hetzner 似乎还在网络命名空间里跑 udhcpd,而不是直接用 cloud-init 的数据 ISO;防火墙大概率基于 nftables,虚拟化应仍是 KVM/QEMU(是否经 libvirtd 未确认)。(较多人) > 这看起来是把 VTEP 下推到宿主机、而非放在 ToR 交换机上的 BGP EVPN/VXLAN 架构,也和 OpenStack 的网络架构一致。(@throw0101a)
认为稳定性、支持和存储速度等实际体验拖了后腿
- 有用户报告 Hetzner 虚拟机因 DNS 事故停机约 13 小时,而他在 DigitalOcean 的十二年里未遇到过;新用户称 Storage Box 在 1000/400 光纤下只有约 3MB/s,几乎用不满分配的 10TB。一条玩笑把文章标题“How we roll”讽刺为“一夜之间涨三倍价、周末不回支持工单”,集中代表对支持响应与稳定性的不满。(较多人) > 文章标题所说的“How we roll”在我们这儿是一夜之间把价格涨到三倍,且周末不回支持工单。(@Lucasoato)
认为实名认证(KYC)过于严苛,把潜在用户挡在门外
- 有人想注册 Hetzner,却被索要大量资料,包括身份证件照片;他此前使用的三大云和 OVH 都没有要求这么多,另一家欧洲服务商新建账户也不要 ID,因此判定 Hetzner 过于 paranoid 并拒绝使用。(少数人) > 我曾想成为 Hetzner 客户,但他们索要大量数据,包括身份证件照片,其他三大云和 OVH 都没要这么多,所以我拒绝了。(@HackerThemAll)
认为性价比、配置和数据中心运维仍有吸引力
- 有用户承认 Hetzner 的 VM 比 DigitalOcean 更便宜、配置更高;有人表示喜爱其低价,正基于 Hetzner 构建自托管或半托管的类 Vercel 开源平台;还有人希望 Hetzner 把独立服务器带到美国,并称赞其数据中心运维能力很强。(少数人) > Hetzner 的虚拟机比 DigitalOcean 更便宜、配置也更高。(@tasuki)
有价值的补充
- 理想性能对比:真正优化的网络路径可做到从 guest 用户态到物理网卡 ring buffer 仅 0 到 1 次上下文切换,例如用 io_uring、把硬件直通给虚拟机,或在 VM 中跑 VPP/DPDK;同机房虚拟机即使负载超过 98%,ping 也能稳定低于 0.1 毫秒。文中架构以及多数超大规模云厂商(约 2 到 3 次上下文切换,启用隧道时更多)都未接近这一最优状态。
- 实现与逆向趣闻:有评论称 Hetzner 似乎在尝试用 Python 实现 Open vSwitch 相关功能;一位评论者回忆自己 2014 年前后曾用 Java 做过类似实现,需要靠抓取并逆向网络流量,来还原一个自带状态的中间件的需求。
- 名称冷知识:Krebs 在德语中既是“螃蟹”也是“癌症”,有人一直以为只是“癌症”,所以看到叫 “flow cancer” 的名字觉得奇怪;还有人把“河蟹”联想为是否在影射中国防火长城(GFW)。