Charith Amarasinghe 2025年3月21日

大规模零接触 Bare Metal 服务器部署

我们都已经习惯了点击一个按钮,就能在云端获得一台运行的 Linux 机器。但是,当你构建自己的云平台时,首先需要构建这个“按钮”。

最近,我们一直在撰写关于以机架为单位构建 Metal 基础设施 的文章。

在我们的 上一篇博文 中,我们谈到了构建物理基础设施的挑战。在本文中,我们将讨论如何在硬件安装完成后对其进行运维。

整理你的乐高积木

你已经构建了梦寐以求的服务器,它具有双冗余网卡和多个冗余 NVMe 驱动器,以提高弹性。你订购了 100 台,并将它们全部安装在机架上,并按照详细的图表进行布线。你带着你的 USB 存储设备去数据中心,然后重启进入你最喜欢的 Linux 发行版的安装程序,结果却看到了“选择你的网络接口”屏幕,上面显示了十几个难以理解的接口名称。

这就是我们面临的第一个障碍——如何将硬件的物理布局映射到操作系统看到的内容?

一台具有 12 个 NVMe 托架的 Supermicro 服务器 - 猜猜哪个托架是 Linux 看到的 /dev/nvme0n2?(这是一个陷阱问题)。

一台具有 12 个 NVMe 托架的 Supermicro 服务器 - 猜猜哪个托架是 Linux 看到的 /dev/nvme0n2?(这是一个陷阱问题)。

💡

在购买按单定制的服务器时,务必包含明确的说明,指定每个网卡或 NVMe 驱动器应安装在哪个位置。否则,你可能会得到不同订单之间存在多种不同配置的情况。

首先退一步,讨论 Linux 如何命名设备是有帮助的。

当主机启动 Linux 时,操作系统会枚举连接的硬件。最常见的是,设备连接到 PCIe 总线,Linux 开始按照总线的层次结构枚举这些设备。当 Linux 在遍历过程中遇到设备时,udev 守护进程将收到一个事件,并将多个标识符与该设备关联起来——然后它将使用这些标识符来形成一个名称,然后将其分配给它在 /dev 和其他位置创建的设备节点。

这种方法的结果是,设备名称可能非常不稳定,尤其是在启动之间硬件布局发生变化或枚举顺序不确定时。如果你在过去使用过 Linux,你就会知道插入新的 PCIe 卡然后启动,结果发现网络中断的痛苦。尽管可以对 SystemD 提出许多批评,但它确实成功地 解决了 v197 以来网络接口的这些问题。但是,存储设备命名仍然是一个碰运气的游戏,最好通过设备序列号来实现。

我们解决这种不可预测性的方法是依靠 Redfish——一个用于连接到服务器主板的板载管理控制器 (BMCs) 的 HTTP API。Redfish API 可以枚举板上的硬件,详细说明 PCIe 卡、NVMe 驱动器、它们的序列号和/或 MAC 地址以及它们的物理位置。

从我们的一个存储节点抓取的 Redfish 系统对象的提取

从我们的一个存储节点抓取的 Redfish 系统对象的提取

一旦安装好机架,我们的第一步就是构建一个设备标识符的 CSV 文件——主机名、BMC MAC 地址、BMC 密码以及其他一些详细信息。然后,我们通过 gRPC 将此数据推送到一个名为 MetalCP 的内部控制平面。

MetalCP 运行一个 Temporal worker,它实现了一个主机导入工作流。对于每台服务器,我们都会启动一个工作流,该工作流会经历以下步骤:

  1. 将设备与其在我们的内部 DCIM 工具 (Railyard) 中的表示相匹配
  2. 通过 Tailscale 连接到数据中心的管理网络
  3. 连接到数据中心的管理路由器,并识别分配给 BMC 的 DHCP 租约(通过其 MAC)
  4. 通过此 IP 连接到服务器的 BMC,并抓取所有可用数据
  5. 创建硬件布局的内部 Protobuf 表示
  6. 使用从抓取中发现的 MAC 地址,为 BMC 和主机上的管理网卡创建静态 DHCP 租约
  7. 使用有关服务器的所有详细信息更新数据库

在大多数情况下,导入工作流只需不到一分钟即可完成,并且 Temporal 确保从任何瞬态故障中恢复。

主机导入工作流的时间线

主机导入工作流的时间线

主机工作流存储的数据库记录包含你可以了解到的有关服务器的所有信息。我们生成:

然后,我们将此硬件规格与已知配置的列表进行匹配。这些配置编码了诸如分配给特定 PCIe 插槽的网络接口名称和 NVMe 驱动器托架标识符之类的详细信息。硬件配置就像 Golang 中的一组条件语句一样简单,这些条件语句与特定类型服务器的关键区分因素相匹配,以及一个包含稳定接口和驱动器名称的配置对象。

一个硬件识别函数示例,pb.SystemInfo 是我们从 Redfish 抓取的原始数据的编码

一个硬件识别函数示例,pb.SystemInfo 是我们从 Redfish 抓取的原始数据的编码

一个自定义插件将此硬件配置对象公开给 Ansible,使我们能够使用 Jinja 模板表达式引用 NVMe 磁盘和网络接口名称。例如,托架 0 中的 NVMe 驱动器可以唯一地寻址为 /dev/disks/by-id/nvme-{{ drive_bays.DiskBay0.device_model }}_{{ drive_bays.DiskBay0.serial_number }}

这种方法使我们能够以我们想要的任何形式构建配置,而不会留下任何机会。导入工作流还会标记硬件中的故障;如果服务器未报告网卡或 RAM 的 DIMM,则工作流将失败,因为硬件将与已知配置不匹配。到目前为止,我们已经通过这种机制识别了具有故障 RAM 的服务器和网卡安装在错误插槽中的服务器。

![](https://blog.railway.com/_next/image?url=https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2Fa63b5cbc-d4d5-4113-9555-0919a5dd0f1f%2F06af4a60-b475-4589-8ef7-b584190d630b%2FCleanShot_2025-02-28_at_21.08.58.gif%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Content-Sha256%3DUNSIGNED-PAYLOAD%26X-Amz-Credential%3DASIAZI2LB466VJQ23CTP%252F20250324%252Fus-west-2%252Fs3%252Faws4_request%26X-Amz-Date%3D20250324T123058Z%26X-Amz-Expires%3D3600%26X-Amz-Security-Token%3DIQoJb3JpZ2luX2VjEJT%252F%252F%252F%252F%252F%252F%252F%252F%252F%252FwEaCXVzLXdlc3QtMiJIMEYCIQCqaacQj1NEG4lJu37Vkqei%252FaAGM8uS1QpsYW4mPGHvsQIhAJxk8p2S1fWWIKibofFEePx6pEqjPujCq%252Fih%252F1jV7cvLKogECO3%252F%252F%252F%252F%252F%252F%252F%252F%252F%252FwEQABoMNjM3NDIzMTgzODA1IgzkIyxgVA2meRB252BeS8q3AP4EyH14F5emZsP0wSZ05LmHn9O47sKEznbGC4%252FP5sp4IBFYbHtzLXKCytdv90xUHfrRW9d%252BrYW56mK3LZc%252B65m6LR%252FHmc2lcxRnBB%252FnCiGdszvW14ejsSNKti2GH0K5IdGdz1tzb7M8rL4GDf6xXtjKbTMqI07rjtE65%252Bw%252BzRkJJDIH%252BxOkfu5jrmtdVtWeBoBKwNVXLS7eAha1pl8Ue1%252FH21ZZbsoxSJCws4r4%252B7I8fB%252F6kELg3pbkvUtp9quLmMqJ%252FfF%252Fn9S7YayQxgBL1RBOPJX5mn1vdvorT5QKjiGwGoSLBYHlj4V1uZ8L15mNPxckza%252BrCD34iJitW0MVj%252FlY33oz0pXuoHSLPLkK5FVzwo9wz7jD7uX1M664vsaRnFLJpl4QlBJl7ghqawX2gvv0O84KvMCjlOiOca6ojr7IH6I1ZAOKyfD0x0N7o0M77KAFmab3Jy0lz8CJGvzrz%252Fy8f%252Fs4uvQXctZqzHZTJRsJOPS8VQaGyEo1M4vGnKq1IN4NxNNlM1EvMKBUgj92FT%252BRXuPFBh3hjiIFkuJhuGKChFSXCqBAS7q1Sawll0GBHaBDZiSlX2lVS%252FxnudZrFUP4tgZXWR8QgDhKDNc5c7VfymIYcOsgZ4gWIcq3PDDGj4W%252FBjqkAeOP66vSw%252B%252BsrSla61sObnFvKzH838xla5ZfdC0viU1e1qycRZKzP3GhaMztX4gEqs9ltJfH0ACfxIRi5Y8Gvp6LaWPIyVPbCvFCsMj1FQcWBJ984QlIeihCnF%252FE6YhKc0NN%252B9t7TaVlb%252FtO