服务器上架交付时,最耗时间的往往不是插线缆,而是初始化:核对固件版本、调整 BIOS 参数、设置 PXE 启动顺序、装完系统再翻一遍 SEL 日志确认没有硬件告警。这些操作全都发生在 BMC 网页里——点一下是几十秒的事,批量做就是灾难。
更麻烦的不是慢,是不一致。手工点击时人是会走神的:改到第 3 台忘了翻某个下拉框,第 6 台误以为上一台已经改过。整批跑完,你拿不出任何东西证明它们的参数完全一致。
本文记录如何用 Redfish API 把这套流程全面脚本化,涵盖资源树定位、主机与 BMC 的内网通路、五类交付动作的 curl 实现、etag 与翻页处理,以及一份踩坑清单。
适用机型:DGX H 系列(BMC 与 SBIOS 出厂即启用 Redfish)
覆盖范围:资源树定位、内网通路、五类交付动作的 curl 实现、etag 与翻页处理、踩坑清单
一、Redfish 凭什么能替掉网页
IPMI 已经是带外管理的事实标准,但交付阶段真正要动的那些东西——BIOS 参数、启动顺序、固件版本——它一个都碰不到。Redfish 是 DMTF 定义的标准,走 HTTPS + JSON,覆盖范围是 IPMI 的超集。
| 维度 | IPMI / ipmitool | Redfish |
|---|---|---|
| 协议 | RMCP+ 二进制 | HTTPS + JSON |
| 能干的活 | 电源、传感器、SEL、账号 | 上述全部,外加 BIOS 设置、启动顺序、固件更新、虚拟媒体、配置备份、遥测、功率封顶 |
| 输出可读性 | 命令行参数,得记 | JSON,看一眼就懂 |
| 自动化 | 可以,但每个厂商参数不一致 | 标准端点,curl 直接调 |
关键差别就一行:交付要动的 BIOS、启动顺序、固件,只有 Redfish 能碰。
而且端点是标准的——所有请求都从 https://<bmc-ip>/redfish/v1 往下找。想知道"改启动顺序该打哪个地址",看一眼资源树就行。
主机与 BMC 之间的内部通路
交付场景里有一条非常实用的东西:主机和 BMC 之间有一条内部网络通路。你可以在主机上配一个 169.254.0.0/16 网段的接口(BMC 侧预置 169.254.0.17),脚本既不用绕管理网,也不用把 BMC 暴露在业务网里:
sudo ifconfig <主机侧网卡> 169.254.0.18 netmask 255.255.0.0
配完之后,curl、nvfwupd 直接打 169.254.0.17 就行:
nvfwupd -t ip=169.254.0.17 username=<bmc-user> password=<password> show_version
二、资源树:一个入口,五棵子树
所有请求都从 /redfish/v1 往下找。按用途分成五棵子树:

/redfish/v1/Managers/BMC—— 账号、重置、SEL 日志、虚拟媒体、备份/redfish/v1/Systems/DGX—— SBIOS 设置、启动顺序、Secure Boot、电源/redfish/v1/Chassis/DGX—— 传感器:温度、功率、风扇/redfish/v1/UpdateService—— 固件清单、上传、激活、强制更新/redfish/v1/TelemetryService—— GPU 托盘指标、功率封顶(NodeManager)
想干什么,先定位到哪棵子树,路径就确定了。
三、交付要做的五件事,逐个 curl 化
下面的命令里,<bmc-user>、<password>、<bmc-ip> 三个占位符按现场替换。-k 是跳过证书校验——BMC 用的是自签证书,不加会直接失败。

第一步:核固件清单
交付第一步是核固件,不是刷固件。机型 SOP 上写死了各组件应该是什么版本,先对表,对不上的才动手。一上来就刷,刷错版本反而添乱。
curl -k -u <bmc-user>:<password> --request GET
'https://<bmc-ip>/redfish/v1/UpdateService/FirmwareInventory'
返回的是一个集合,DGX H 系列上一眼能看到 Members@odata.count 是 66 上下。每一项是 CPLDMB_0、HostBMC_0、PSU_0 这类组件名,版本号在 Oem.Ami.FirmwareInventory 里成对给出。
需要升级时,按组件分两类目标:
| 要更新的部分 | target 怎么写 | 固件包 |
|---|---|---|
| GPU 托盘(整套 GPU 托盘组件) | HGX_0 |
nvfw_HGX_DGXH100-H200x8_xxxxxx.x.x.fwpkg |
| 主板托盘(BMC / BIOS / CPLD / PSU / PCIe Switch 等) | HostBMC_0、HostBIOS_0、CPLDMB_0 等 |
nvfw_DGX_xxxxxx.x.x.fwpkg |
上传和刷写:
echo '{"Targets":["/redfish/v1/UpdateService/FirmwareInventory/HostBMC_0"]}' > parameters.json
curl -k -u <bmc-user>:<password> -H 'Expect:' --location --request POST
https://<bmc-ip>/redfish/v1/UpdateService/upload
-F 'UpdateParameters=@parameters.json;type=application/json'
-F UpdateFile=@<fw_bundle>
那个 -H 'Expect:' 不是可有可无的。不加的时候 curl 会先发一个 Expect: 100-continue 探路,部分 BMC 固件对这个处理得不好,表现是卡住不动、也不报错。
另外固件默认只升不降(版本必须高于当前值才写)。要强行覆盖,先改这个开关:
curl -k -u <bmc-user>:<password> --request PATCH
'https://<bmc-ip>/redfish/v1/UpdateService'
--header 'If-Match: *'
--header 'Content-Type: application/json'
--data-raw '{"HttpPushUriOptions" : {"ForceUpdate": true}}'
改动成功返回 204;如果你设的值和当前值一样,会返回 400。以前核固件要一台台登进去对,现在一条 curl 把 66 个组件的版本全拉下来。
第二步:改 BIOS 设置
BIOS 参数不是想改就能改。属性名写错一个字母,BMC 不一定报错,可能就静静忽略了。所以第一步不是改,是先把这台机器支持的属性清单拉下来。三跳:
# 1) 列出所有注册表,找到 BIOS 属性注册表
curl -k -u <bmc-user>:<password> --location --request GET
'https://<bmc-ip>/redfish/v1/Registries'
# 2) 拿注册表 URI(名字形如 BiosAttributeRegistry106.1.0.6)
curl -k -u <bmc-user>:<password> --location --request GET
'https://<bmc-ip>/redfish/v1/Registries/BiosAttributeRegistry106.1.0.6/'
# 3) 把属性定义 JSON 拉下来
curl -k -u <bmc-user>:<password> --location --request GET
'https://<bmc-ip>/redfish/v1/Registries/BiosAttributeRegistry106.en-US.1.0.6.json'
--output BiosAttributeRegistry106.en-US.1.0.6.json
这份 JSON 是属性字典:每个属性名对应默认值、显示名、帮助文本、是否只读、是否需要重启才生效。改参数前先在这儿确认属性名拼写,写错了 BMC 不一定报错,可能就静静忽略了。
看当前生效值:
curl -k -u <bmc-user>:<password> --location --request GET
'https://<bmc-ip>/redfish/v1/Systems/DGX/Bios'
写"下次启动生效"的值,PATCH 到 Bios/SD:
curl -k -u <bmc-user>:<password> --location --request PATCH
'https://<bmc-ip>/redfish/v1/Systems/DGX/Bios/SD'
-H 'Content-Type: application/json' -H 'If-Match:*'
--data-raw '{"Attributes" : {"IPMI002":"IPMI002DoNothing", "IPMI201":"IPMI201Donotloganymore"}}'
上面这两个属性是 SEL 日志满的时候系统怎么办。IPMI002 是写满之后的动作,IPMI201 是满了还继续记录与否。交付时按客户要求选,不选的话默认行为可能是停止记录,之后出问题就没日志可查了。一次 PATCH 可以带多个属性。
⚠️ BIOS 属性的改动,全部需要一次断电再上电(power cycle)才生效。而且如果你改完属性紧接着又刷了 BIOS 固件,还得再来一次 power cycle。判断是否落地的方法很简单:Bios 端点返回当前值,Bios/SD 返回"下次启动生效"的值,两个对不上就说明还没落地。
第三步:改启动顺序
启动顺序不对,PXE 装机流程接不上,机器就躺在机柜里等。这一步必做,也最容易写错,因为它要先读再写。
先读当前顺序:
curl -k -u <bmc-user>:<password>
https://<bmc-ip>/redfish/v1/Systems/DGX/SD
-H "content-type:application/json" -X GET -s | jq .Boot.BootOrder
拿到一串 Boot0000、Boot000F 这样的编号。光看编号不知道谁是谁,得再查一遍:
curl -k -u <bmc-user>:<password>
https://<bmc-ip>/redfish/v1/Systems/DGX/BootOptions/00{0,1}{0,4,5,6,7,8,9,A,F}
-H "content-type:application/json" -X GET -s | jq | grep -e "UefiDevicePath|Name"
这里用了个 shell 的大括号展开,一次把 Boot0000、Boot0001、Boot0004 一直到 Boot0010 全查了,不用写循环。
返回里三个字段有用:
DisplayName—— 设备的可读名字,比如UEFI: PXE IPv4 Nvidia Network Adapter - 00:AA:BB:00:00:01、DGX OSName—— 对应的启动项编号UefiDevicePath里的MAC(<地址>,0x1)—— 这块网口的 MAC。多个口名字长得一样的时候,靠这个区分
确认好顺序之后写回去:
curl -k -u <bmc-user>:<password>
https://<bmc-ip>/redfish/v1/Systems/DGX/SD
-H "content-type:application/json"
-H 'If-Match: "<etag>"'
--data '{"Boot":{"BootOrder": ["Boot0004","Boot0000","Boot0005","Boot0006","Boot0007","Boot0008","Boot0009","Boot000A","Boot000F","Boot0010"]}}'
-X PATCH
数组是全量覆盖,不是增量。没写进数组的启动项不会自动保留在末尾。正确做法是:把读回来的完整列表拿出来,只把要提前的那一项挪到最前面,其余原样跟上去。
写完再读一遍确认:
curl -k -u <bmc-user>:<password>
https://<bmc-ip>/redfish/v1/Systems/DGX/SD
-H "content-type:application/json" -X GET -s | jq .Boot.BootOrder
这个顺序会一直保留到下次被改写,或者 SBIOS 被重置。以前要登网页翻菜单,现在读出来、改、写回去,一套动作三行命令。
第四步:电源循环
改完 BIOS、改完启动顺序,别急着看结果——那些值都还只是"下次启动生效",没断电重上电,根本不落地。你进系统一看还是老样子,会怀疑自己白改了半天。
Redfish 的电源操作全在同一组端点上,差别只在 ResetType:
| 想要的效果 | ResetType |
|---|---|
| 开机 | On |
| 直接断电 | ForceOff |
| 软关机 | GracefulShutdown |
| 直接重启 | ForceRestart |
| 软重启 | GracefulRestart |
| 断电再上电 | PowerCycle |
curl -k -u <bmc-user>:<password> --request POST
--location 'https://<bmc-ip>/redfish/v1/Systems/DGX/Actions/ComputerSystem.Reset'
--header 'Content-Type: application/json'
--data '{ "ResetType": "PowerCycle" }'
⚠️ 交付场景里要的是 PowerCycle 不是 ForceRestart。改 BIOS 参数这类操作,重启(restart)不算数,要的是断电再上电——有些参数只在完整的上电初始化流程里才被读进去。
另外注意:管理板(HMC)上不支持 ForceRestart、GracefulRestart、GracefulShutdown 这三种,别拿同一套脚本无脑刷。
第五步:收口核查
前面四步都在写,最后一步得扫一遍,确认没写坏什么。机器交付出去才发现 SEL 里躺着一条 DIMM 告警,那才叫丢人。
curl -k -u <bmc-user>:<password> --location --request GET
'https://<bmc-ip>/redfish/v1/Managers/BMC/LogServices/SEL/Entries'
curl -k -u <bmc-user>:<password> --location --request GET
'https://<bmc-ip>/redfish/v1/Chassis/DGX/Sensors'
这两个端点都要翻页,而且不会提醒你。SEL 和 Chassis/DGX/Sensors 每次都只返回 75 个成员,剩下的靠 Members@odata.nextLink 给地址。只看一页就以为"日志挺干净",后面可能还藏着一条 DIMM 告警。
翻页就是顺着 nextLink 里的 $skip 往下走:
curl -k -u <bmc-user>:<password> --request GET
'https://<bmc-ip>/redfish/v1/Managers/BMC/LogServices/SEL/Entries?$skip=75'
判断依据是返回值里有没有 nextLink:有就继续拉。
批量交付的时候,这几步串起来就是一个循环:读清单文件里的 BMC IP,对每台依次跑上面五步,把每台的返回按 IP 存成独立文件。哪台失败、失败在哪一步,都在文件名和日志里。
四、六个踩过的坑
1. 第一次上手,资源树从哪进都不知道。所有请求都从 https://<bmc-ip>/redfish/v1 往下找,但机型路径里的 DGX 是固定的,不是变量——写成 Systems/1 之类会直接 404。
2. 改完 BIOS 不 power cycle,等于没改。改完就重启,进系统一看参数没变,会误以为是权限问题。判断标准:Bios 返回当前值,Bios/SD 返回"下次启动生效"的值,两个对不上就是没落地。
3. -H 'Expect:' 刷固件时必须加。不加就卡住,而且不报错。这类"静默失败"比报错难查得多——curl 那个 Expect: 100-continue 探路会把部分 BMC 卡住。
4. SEL 和 Sensors 会静默截断。75 条一页,不翻页就以为看全了。判断依据是返回值里有没有 nextLink。
5. 写操作尽量带 etag。etag 对不上会返回 412。这不是麻烦,是保护——防止两个进程同时改同一台。注意不同固件文档写法可能不同(有的写成 if-None-Match),以实际返回为准。
6. 别把写操作并发化。BMC 只有一颗,多个 PATCH 同时打过去,轻则返回 500,重则把 BMC 搞到要重启。正确做法是读可以并发、写串行,一台改完确认了再下一台。
五、这套东西省了哪些事
- 参数可对账:每台机器的固件版本、BIOS 当前值、启动顺序都能拉成文本,交付记录里附一份,比"我点过了"有说服力
- 一致性有保障:同一段脚本跑 8 次,参数不会因为人飘而漂移
- 不用登网页:机器还在机柜走线阶段就能操作,只要 BMC 通了电和网
- 可回滚:配置文件写前先 GET 存档,改坏了能按原值写回
- 能接进交付流水线:跟 PXE 装机、配置下发串成一个脚本,一条命令批量下发
六、批量脚本的骨架
把上面的流程串成一个可复用的脚本,大致是这个结构:
#!/usr/bin/env bash
# 批量 BMC 初始化:读 - 写 - 读回校验,三步走
set -euo pipefail
BMC_LIST="bmc-list.txt" # 每行一个 BMC IP
USER="<bmc-user>"
PASS="<password>"
rf() { # 统一封装 curl,简化后续调用
local ip="$1"; shift
curl -k -sS -u "${USER}:${PASS}" "$@"
}
while read -r ip; do
[ -z "$ip" ] && continue
echo "=== 处理 ${ip} ==="
# 1) 归档当前配置(回滚用)
rf "$ip" "https://${ip}/redfish/v1/Systems/DGX/Bios"
-o "backup/${ip}-bios.json"
rf "$ip" "https://${ip}/redfish/v1/Systems/DGX/SD"
-o "backup/${ip}-boot.json"
# 2) 下发 BIOS 设置(写操作串行)
rf "$ip" "https://${ip}/redfish/v1/Systems/DGX/Bios/SD"
-X PATCH -H 'Content-Type: application/json' -H 'If-Match:*'
--data-raw '{"Attributes":{"IPMI002":"IPMI002DoNothing","IPMI201":"IPMI201Donotloganymore"}}'
# 3) 读回 BootOrder,改好再写
order=$(rf "$ip" "https://${ip}/redfish/v1/Systems/DGX/SD" | jq -c '.Boot.BootOrder')
new=$(echo "$order" | jq -c '["Boot0004"] + (. - ["Boot0004"])')
rf "$ip" "https://${ip}/redfish/v1/Systems/DGX/SD"
-X PATCH -H 'Content-Type: application/json'
--data "{"Boot":{"BootOrder":${new}}}"
# 4) power cycle,让改动落地
rf "$ip" "https://${ip}/redfish/v1/Systems/DGX/Actions/ComputerSystem.Reset"
-X POST -H 'Content-Type: application/json'
--data '{"ResetType":"PowerCycle"}'
# 5) 收口核查(含翻页)
skip=0
while :; do
page=$(rf "$ip" "https://${ip}/redfish/v1/Managers/BMC/LogServices/SEL/Entries?$skip=${skip}")
echo "$page" | jq -c '.Members[]' >> "log/${ip}-sel.jsonl"
next=$(echo "$page" | jq -r '.Members@odata.nextLink // empty')
[ -z "$next" ] && break
skip=$((skip + 75))
done
done < "$BMC_LIST"
⚠️ 执行前务必确认:
- 目标 BMC IP 列表准确——脚本会真的改配置并断电重启
- BIOS 属性名在目标机型上真实存在(先拉注册表核对)
- 每台操作前已归档
Bios与SD,便于回滚 - 避开业务高峰期,power cycle 会中断正在运行的服务
结语
带外管理的价值不在于"能远程开机",而在于把一批机器配置成完全一致的状态,并且能拿出证据证明它们确实一致。手工点击在这两点上都是做不到的——人一定会飘。
Redfish 的门槛其实不高:记住 /redfish/v1 是唯一入口,剩下的顺着资源树找就行。真要上手,建议先拿一台机器把五步各跑一遍,把 curl 输出存下来对照,然后再改写成循环。
本文技术方法与命令为笔者整理并实测验证。案例原型参考自技术社区公开文章《一台台登 BMC 改配置,我改用 Redfish API 批量下发》(公众号「闫工的算力工具箱」),在此致谢并推荐阅读原文:https://mp.weixin.qq.com/s/1LodZCcde80ABg-kOEzyhA。本文为原创整理与扩展,转载请注明出处。文中示意图版权归原作者所有。
文章评论