NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

使用 Zig 在 arm64 上引导 Uber 的基础设施

  • 2023-07-04
    北京
  • 本文字数:5447 字

    阅读完需:约 18 分钟

使用Zig在arm64上引导Uber的基础设施

简   述


2021 年 11 月,我们决定评估 arm64 架构在 Uber 的可行性。我们的大多数服务是用 Go 或 Java 编写的,但我们的构建系统只能编译成 x86_64。现在,得益于开源合作,Uber 拥有了一个独立于系统的构建工具链,可以无缝地支持多种架构。我们使用这个工具链来引导 arm64 主机。本文将分享我们是如何着手去做这件事情的,以及我们早期的想法、遇到的问题、达成的一些成就和未来的方向。


我们从 2021 年 11 月开始使用专门的 Linux/x86_64 基础架构,而到了 2023 年 1 月,我们有:


  • 用于生产环境服务器架构(x86_64 和 arm64)的 C++ 工具链,由 zig cc 提供支持;

  • 一些在 arm64 硬件上运行的核心基础设施服务,为未来的扩展提供了可能性。


让我们来看看我们是如何做到的。


为什么要考虑 arm64 架构


所有的主流云供应商都在 arm64 上投入巨资,再加上 arm64 与古老的 x86_64 相比所表现出来的平台优势(能耗、价格、计算性能),我们觉得很有必要认真考虑让 arm64 成为我们平台的一部分。


于是,我们开始尝试自己去探究。我们的第一个目标如下所述:


在 arm64 架构上运行一个大型的应用程序,并对可能节省的成本进行度量。


其中一个关键点是最小化运行和基准测试消耗多个核心的服务所需的工作量。我们找到了两种截然不同的方法:


  • 在并行区域或现有区域中的独立集群提供基本的 arm64 支持,并在那里运行测试(实验质量);

  • 让所有的核心基础设施都知道现在不止一种架构,然后像生成其他 SKU 一样生成 arm64 主机并测试应用程序。


考虑到最小化工作量是我们优先考虑的事项,所以第一个选项似乎更适合我们。毕竟,我们为什么要把时间和金钱投入到有可能被放弃的东西上呢?我们考虑运行一个“并行区域”,它具备 arm64 架构,但在其他方面与生产环境是分离的(并且质量要求更为宽松,方便我们快速前进)。


不久之后,我们有了一个更重要的支持 arm64 的理由:如果我们可以在 arm64 上运行工作负载,就可以让平台的能力多样化,从而让自己处于一个更有利的位置。于是,我们的使命变成了(直到今天仍是如此):


通过在 arm64 上部署一些生产应用程序来降低 Uber 的计算成本、增加容量多样性,以及使我们的平台现代


我们最初是带着原型思维开始的,但现在却有了 180 度转变,形成了一个指导原则:


没有 hack,所有的内容都在主线上(也就是说,没有长期的分支或补丁)。


既然我们的核心基础设施需要提供一流的 arm64 支持,那么这个项目就很自然地被分成两个部分:


  1. 第一个任务是将包含了我们几乎所有基础架构代码的 Go 代码库编译成 arm64 二进制文件;

  2. 修改与构建、存储、下载和执行代码相关的所有东西(构建主机、工件存储和调度器),让它们知道现在存在两种架构。


那么如何编译成 arm64 二进制文件?当然是直接在 arm64 主机上进行原生构建,或者通过交叉编译。我们有必要先来了解一下原生编译和交叉编译的差异和要求。


原生编译和交叉编译的基础知识


一些我们可能不太熟悉的术语:


  • 二进制文件是由源代码编译而来的机器代码程序。

  • 工具链是将源代码编译为二进制文件所需的一组工具,通常包括预处理器、编译器、链接器等。

  • 密闭(hermetic)工具链是指无论在什么样的环境下,只要给定相同的输入,总是产生相同输出的工具链。这里的“密闭”是指它不使用来自主机的文件,并且包含编译文件所需的所有东西。

  • 主机(host)是指编译二进制文件的机器。

  • 目标平台(target)是指运行二进制文件的机器。

  • 在进行原生编译时,主机和目标是相同的平台(即操作系统、处理器架构和共享库是相同的)。

  • 在进行交叉编译时,主机和目标是不同的平台(例如,从 macOS arm64 (M1) 编译成 x86_64 Linux)。有时候,目标机器可能无法编译代码,但可以运行。例如,一块智能手表可以运行已编译的代码,但不能运行编译器,因此我们可以使用交叉编译器为手表编译程序。

  • sysroot 是目标平台文件系统的归档。例如,特定于目标平台的头文件、共享库、静态库。通常是交叉编译工具链所必需的,下面将会讨论。

  • aarch64 或 arm64 是指处理器架构。


下图显示了如何通过原生编译(左)和交叉编译(右)将源文件 main.c 编译成可执行文件。



图 1:输入文件 main.c 原生编译(左)或交叉编译(右)为 aarch64 架构。


原生编译只需要较少的配置和准备工作就可以使用,因为这是大多数编译器工具链的默认模式。从表面上看,我们可以在云供应商的平台上启动一些 arm64 虚拟机,并从那里开始引导我们的工具。但是,我们所有的服务器都使用相同的基础镜像,包括构建主机。基础镜像包含许多从 Go 代码库编译出来的内部工具。因此,我们遇到了一个先有鸡还是先有蛋的问题:如何为我们的第一个 arm64 构建主机编译工具?


示例:使用 GCC 和 Clang 进行交叉编译


让我们在 x86_64 Linux 主机上编译一个 C 文件,目标平台是 Linux aarch64:



GCC 调用目标平台特定的可执行文件(aarch64-linux-gnu-gcc),而 Clang 接受目标平台作为命令行参数(-target <…>):



表面上看,用 GCC 和 Clang 交叉编译 C 源文件似乎很容易,但背后都发生了什么?


基于 LLVM 的 C/C++ 工具链


“clang”使用哪些文件来构建最终的可执行文件?我们来跟踪一下:



以下是这些相关的文件:


  • (没有显示出来的)工具:C 编译器(Clang)和链接器(ld)。

  • /usr/aarch64-linux-gnu/include 中的头文件。这些通常是 GNU C 库头文件。有些程序使用 Linux 内核的公共头文件,但本例中没有。头文件是特定于目标平台的。

  • 编译的、特定于目标架构的库:

  • 动态链接器 /usr/aarch64-linux-gnu/lib/ld-linux-aarch64.so.1;

  • C 库,共享对象:/usr/aarch64-linux-gnu/lib/libc.so.6;

  • 程序加载器:crt.o。

  • 其他库:libgcc 和 libc_nonshared。


现在我们已经知道交叉编译器使用了哪些东西,我们可以将依赖项分为两类:


  • 特定于主机的工具(编译器、链接器和其他与目标平台无关的程序);

  • 特定于目标平台的库和头文件,它们是为目标平台编译最终程序所必需的。


Uber 需要支持以下这些目标平台:


  • Linux x86_64(带有 glibc 2.28);

  • Linux x86_64(带有 glibc 2.31);

  • Linux x86_64(带有 musl);

  • Linux arm64(aarch64,带有 glibc 2.31);

  • Linux arm64 (aarch64,带有 musl)。


在撰写本文时,GCC 和 LLVM 都不能交叉编译 macOS 二进制文件。因此,我们维护了一个专门的构建集群来编译 macOS 目标平台。交叉编译 macOS 目标平台是非常有必要的,但我们目前还没有做到这一点。


以下是我们目前支持的主机平台:


  • Linux x86_64:构建集群、DevPod 和开发者笔记本电脑;

  • macOS x86_64:老一代 macOS 开发者笔记本电脑;

  • macOS aarch64(Apple Silicon):新一代 macOS 开发者笔记本电脑。


下图画出了主机工具链、sysroot 以及它们之间的关系,每个主机工具链(左)都可以使用任意特定于目标平台的 sysroot(右):



图 2:基于 LLVM 的工具链需要每个主机和目标平台的 tarball(“sysroot”)


为了支持这些主机和目标平台,我们需要维护 8 个压缩文件:3 个工具链(每个主机架构需要一个编译的 LLVM)和 5 个目标平台的 sysroot。一个典型的 LLVM 工具链需要 500 到 700MB(压缩包),一个典型的 sysroot 需要 100 到 150MB(压缩包)。在编译代码之前,加上其他工具,总共需要下载和解压约 1.5GB 的压缩文件。Linux x86_64 的 Go 1.20 工具链压缩包为 95MB,是编译代码所需的最大的下载文件。


为了完整起见,我们来看一下 GCC。你可能还记得之前提到 GCC 交叉编译器是 aarch64-linux-gnu-gcc,这意味着每个主机和目标平台都需要一个完整的工具链。因此,如果我们要使用基于 GCC 的工具链,就需要维护 35=15 个工具链。如果我们添加一个新的主机平台(例如 Linux aarch64)和两个目标平台(分别针对 x86_64 和 aarch64 的 Linux glibc.2.36),那么需要维护的压缩包数量将跃升至 4


7=28 个!


在购买 Bazel 工具链时,我们评估了 GCC 和基于 LLVM 的工具链。LLVM 更受青睐,因为它需要维护的压缩文件数量的增长是线性的(而不是 GCC 那样的二次幂增长)。但我们能做得更好吗?


Zig 工具链


Zig 采用了不同的方式:它对所有受支持的目标平台使用了相同的工具链。



它在编译时使用了哪些文件?如果我们跟踪一下会发现,它只使用了来自 Zig SDK 的文件(中间文件放在 /tmp 目录下)。主机系统没有受到任何影响,这意味着 Zig 是完全独立的。


为什么 Zig 能做到这样,而 Clang 却不能?Clang 和 Zig 之间主要的差异是什么?Zig 需要的依赖项与 Clang 一样,我们来看一下:


  • 工具:C 编译器(Clang)和链接器(lld)。

  • 它们被静态地链接到 Zig 二进制文件中,对于 macOS,Zig 实现了自己的链接器。

  • /usr/aarch64-linux-gnu/…中的头文件。

  • Zig 捆绑了多个版本的 glibc、musl libc、linux 内核和其他一些头文件,并自动包含它们。

  • 编译好的特定于目标平台的库:动态链接器、glibc(多版本)、程序加载器。

  • Zig 根据具体的平台在后台动态编译所有这些文件。

  • 其他库:libgcc 和 libc_nonshared。

  • Zig 重新实现了这些库中的函数。


因此,Zig 可以用一个工具链编译所有受支持的目标平台。为了支持我们的 3 个主机和 5 个目标平台,我们需要从 https://ziglang.org/download 下载 3 个 Zig tarball 文件:



图 3:每个主机平台需要 1 个工具链。同一工具链可以编译所有目标平台。


Zig 作者 Andrew Kelley 在他的博客中更详细地解释了 Zig 在 Clang 之上添加了哪些东西。不管我们希望支持多少个目标平台,只需要一个主机工具链,这是非常诱人的。


我们尝试做一些其他工具链无法做到的事情:在 Linux 机器上交叉编译和链接 macOS 可执行文件:



尽管在 2021 年底,Zig 还只是一项未经验证的新技术,但一个主机平台一个 tar 包和交叉编译 macOS 目标的能力赢得了团队的青睐。我们开始使用 Zig,将 zig cc 整合到我们的 Go 代码库中。


Bazel 与 Zig


对于 Bazel 来说,只有一个 C++ 工具链(在本例中是 Zig SDK)是不够的:它还需要一些粘合代码,一个工具链配置。2022 年 2 月,Go 代码库对 zig cc 的初步支持是通过添加到一个配置标志来实现的:


bazel build –config=hermetic-cc <…>
复制代码


最开始所有的东西都不正常,大部分的测试都无法构建通过,更不用说执行了。我们开始慢慢解决这些问题。到 2022 年 9 月,所有测试都通过了。自 2023 年 1 月起,Zig 工具链可以将 Uber Go 代码库中的所有 C 和 C++ 代码编译到 Linux 目标平台。


Uber 自 2022 年 4 月以来一直在运行 Zig 生成的二进制文件,因此我们对 Zig 信心满满。Bazel 和 Zig 之间的粘合代码最初放在 Adam Bouhenguel 的代码库 bazel-zig-cc 中,后来被 Motiejus Jakštys 克隆并进一步开发,最终转到了 https://github.com/uber/hermetic_cc_toolchain。


因为与 Zig 软件基金会合作,我们可以寻求对我们来说重要的解决方案。Zig 的人帮助我们发现和修复 Go 和 Zig 中的问题。因为在 2021 年合作进展顺利,Uber 决定将合作关系延长到了 2023 年和 2024 年。Zig 软件基金会所做的所有工作都是开源的,这让更大社区从中受益。


对 arm64 支持的进展


等到工具链足够成熟,可以进行 arm64 平台编译,我们就开始在内部加强对 arm64 的支持。例如:


  • 当开发人员在 Go 代码库中定义了 Docker 镜像(使用 rules_docker,它相当于 Dockerfile,只是是在 Bazel 中使用),CI 将编译 x86_64 和 arm64 的依赖代码,并且如果无法编译就不允许通过。

  • 我们将 Go 代码库中所有的 Debian 包编译到了 arm64 并发布,尽管它们中的大部分不是我们必需的。与 Docker 镜像类似,CI 确保它们可以编译到 arm64 和 x86_64。目前不可能在我们的 Go 代码库中声明一个不能编译到 arm64 的新的 Debian 包。


在能够将程序编译为 arm64 之后,我们开始采用所有可以存储、下载和执行原生二进制文件的系统。现在,我们有:


  • 开发环境中的 arm64 主机,就像其他 x86_64 主机一样;

  • 运行在 arm64 主机上的几个核心基础设施服务(例如,内部构建的容器调度器和支配程序);

  • 继续扩大 arm64 的使用和支持。


我们 2023 年的计划包括:


  • 为 arm64 增加 Kubernetes 支持;

  • 在 Kubernetes 的 arm64 主机上运行面向客户的服务。


Uber 有使用 Zig 语言吗


可以说有,也可以说没有。例如,ermet_cc_toolchain 中的启动器是我们用 Zig 编写的。嵌入到可执行文件中的运行时库(compiler-rt)是用 Zig 编写的。总而言之,我们的大多数 Go 服务都涉及到了一点 Zig,并且是用 Zig 编写的工具链编译的。


尽管如此,我们还没有将用 Zig 编写的生产应用程序引入到我们的代码库中(虽然工具链已经完全设置好了),因为目前公司中只有少数人知道这门语言。


总  结


截止 2023 年 1 月 16 日,所有发布到生产环境的 C/C++ 代码都通过 hermect_cc_toolchain 进行编译。因为 Zig 现在是我们 Go 代码库的关键组成部分,因此 hermetic_cc_toolchain 的维护得到了财务(与 Zig 软件基金会的合作将到 2024 年底)和 Uber 员工工时的支持。


虽然可以在 arm64 硬件上运行我们的核心基础设施,但我们还没有准备好运行面向客户的应用程序。我们的下一步是在 arm64 上试验面向客户的应用程序,这样就可以测试它的性能并决定未来的方向。


原文链接:


https://www.uber.com/en-SG/blog/bootstrapping-ubers-infrastructure-on-arm64-with-zig


声明:本文由 InfoQ 翻译,未经许可禁止转载。


今日好文推荐


十七年来奇葩大崩溃!为不让OpenAI和谷歌白拿数据,Reddit 收取巨额API 费用还诽谤开发者,社区爆发大规模抗议


“偷”代码建起公司、学历造假、6天拿下1亿美元却拖欠工资,这位AI独角兽CEO屡遭质疑后亲自回应了


市值暴涨10519%,原来全世界搞大模型的企业都在给这位华人打工!


2023-07-04 16:052648

评论

发布
暂无评论
发现更多内容

windows 系统下 workerman 在同一个运行窗口中开启多个 websocket 服务

极客飞兔

windows 经验分享 websocket workerman 多服务

瞄准2023教育春招,百度营销多措并举,推出创新型行业营销解决方案

Geek_2d6073

云图说丨初识华为云安全云脑——新一代云安全运营中心

华为云开发者联盟

云计算 后端 华为云 华为云开发者联盟 企业号 3 月 PK 榜

关于“字节跳动被要求出售 TikTok 股票”的看法

这我可不懂

字节跳动 抖音 TikTok

【干货】常见库存设计方案-各种方案对比总有一个适合你

Java你猿哥

Java 架构 微服务 系统设计 后端

硬核!阿里大佬都在内卷的SpringBoot从入门到实战笔记

程序知音

Java 编程语言 springboot Java进阶 后端技术

未来智安再获安全牛《网络安全优质初创企业HOT50》推荐

未来智安XDR SEC

视频下载软件:MediaHuman YouTube Downloader 中文版

真大的脸盆

Mac 视频下载 Mac 软件 下载视频 视频下载工具

前端进阶:在 Web 中使用 C++,我让学妹另眼相看 | 技术分享

LigaAI

c++ 程序人生 前端 webassembly 企业号 3 月 PK 榜

基于 RocketMQ Connect 构建数据流转处理平台

Apache RocketMQ

官宣:OpenDAL 成功进入 Apache 孵化器

Databend

网易携手昇腾AI打造玉知-悟空图文理解大模型,做更“懂你”的产品

Geek_2d6073

阿里是如何使用分布式架构的?阿里内部学习手册分享

Java你猿哥

Java 分布式 微服务 分布式架构

百度“文心一言”申请服务测试企业达7.6万,股价拉升涨幅近15%

Geek_2d6073

基于阿里云数据库TiDB的性能压测初体验

TiDB 社区干货传送门

性能测评 6.x 实践

测试的底层逻辑

京东科技开发者

Java 测试 代码 企业号 3 月 PK 榜

cortex ingester 基于 hash ring 进行 token 管理

jupiter

Prometheus 一致性hash Cortex Mimir

MybatisX整合Spring Boot,真香!

Java你猿哥

Java Spring Boot 后端 mybatis ssm

“奇遇未来”专访:一个小众、专业的产品经理实训品牌

Geek_2d6073

HummerRisk 使用教程:主机检测

HummerCloud

TiDB容器化的管理利器--TiDB Operator

TiDB 社区干货传送门

集群管理 新版本/特性发布 安装 & 部署 新版本/特性解读

基于 TiCDC 的 TiDB 复制集群的计划内和计划外切换验证步骤

TiDB 社区干货传送门

性能调优 实践案例 故障排查/诊断 数据库架构选型

阿里P8裸辞真实心路历程,他底气来源于Java高阶面试合集

Java你猿哥

Java Spring Boot ssm 面经 八股文

板边器件距离不够,导致元器件无法焊接,怎么办?

华秋电子

中国半导体市场份额进一步提升,2023年将迎全新发展良机

华秋电子

你可能并不了解 Milvus

Zilliz

SaaS Milvus 社区活动

openGemini正式加入openEuler SIG-DB ,携手开展全方面技术创新

openEuler

数据库 Linux 开源 操作系统 openEuler

数据湖选型指南|Hudi vs Iceberg 数据更新能力深度对比

袋鼠云数栈

数据湖

华秋工艺分享:第八道主流程之丝印文字流程

华秋电子

详解ResNet 网络,如何让网络变得更“深”了

华为云开发者联盟

人工智能 神经网络 华为云 华为云开发者联盟 企业号 3 月 PK 榜

搭建阿里云 TiDB 的灾备,让我安欣睡个好觉

TiDB 社区干货传送门

实践案例 安装 & 部署 备份 & 恢复 数据库架构设计

使用Zig在arm64上引导Uber的基础设施_编程语言_Uber 工程播客_InfoQ精选文章