裸金属BMC失联36小时:一根IPMI网线引发的“静默”事故与自动化治理实录

前奏:双十一大促后的寂静杀手

去年11月,我们一批轻云互联的裸金属物理机刚刚经历完一轮大促流量洪峰,正当我以为可以喘口气时,监控平台的一条告警像针一样扎进眼球:node-23(杭州C区)BMC ping不通。第一反应是机房的带外交换机又抽风了,但登上跳板机敲下 ping 10.10.9.23 时,延迟正常,TTL正常。BMC IP活着,但管理面死活连不上。

此时,业务网卡、内网联通性全部正常,CPU负载低于1.0。这台机器就像一头不吭声的老黄牛,表面温顺,内核可能已经在暴雷。直觉告诉我,这不是网络问题,是带外管理平面的协议栈出了问题。

第一线排查:IPMI协议的“薛定谔状态”

我直接祭出 ipmitool 进行带外探测,命令如下:

# 从跳板机执行
ipmitool -I lanplus -H 10.10.9.23 -U admin -P '****' mc info

输出卡死,直到超时。再次换用 RMCP 纯UDP协议探测:

# 抓包检查 RMCP 端口(623)
tcpdump -i eth0 -nn -vvv port 623 or port 5900 or port 443

诡异的一幕出现了:RMCP的Discover请求有去无回,但SOL(Serial-over-LAN)的SSH端口(2200)却能建立TCP握手。这说明 BMC的Web管理进程或IPMI会话管理进程已经僵死,但网络协议栈还在低位运行。这种故障最恶心,你无法远程重启BMC,因为IPMI本来就是用来干这事的,现在等于钥匙锁在保险箱里。

暗雷探查:进得去“后门”,却拧不开“门锁”

既然 SOL 端口活着,我用 ipmitool sol activate 尝试挂载串口控制台:

ipmitool -I lanplus -H 10.10.9.23 -U admin -P '****' sol activate

结果进入了BMC的Linux子系统Shell(很多厂商BMC底层是OpenBMC或定制Linux)。这真是绝佳的观测窗口。执行 top 查看进程,发现 phosphor-host-ipmid 这个守护进程的CPU占用率高达98%,且处于 D 状态(不可中断睡眠)。

这是一次典型的 内核IO锁死。罪魁祸首找到了:BMC的IPMID进程在尝试读取某些硬件传感器(可能是电源功耗或风扇转速)时,被内核的I2C总线驱动卡死,进了死锁。

根治手术:给BMC的“心脏”做支架

通过SOL进入的Linux Shell权限极高(root)。我通过挂载BMC的根文件系统查看日志,核心报错如下:

# 在BMC shell内操作
cat /var/log/messages | grep -i "i2c" | tail -20

[ 1687.259319] i2c_i801 SMBus controller down
[ 1687.259458] platform ebr-sensors-bb: Failed to read from sensor (addr 0x4d)
[ 1687.259512] kernel: INFO: task kipmi0 blocked for more than 120 seconds.

这里又牵扯出一个极容易被忽略的物理机特性:裸金属的BMC监控芯片与CPU的SMBus直连。如果主板上有某些PCIe设备(比如NVIDIA A100 GPU或NVMe SSD转接卡)占用了SMBus地址冲突,会导致BMC在某一瞬时状态切换时发生I2C死锁。

幸运的是,这个BMC的Linux系统支持强制加载内核模块来复位总线。我执行了:

# 在BMC Shell,强制重置SMBus控制器
rmmod i2c_i801
modprobe i2c_i801

# 重启IPMI守护进程
systemctl restart phosphor-host-ipmid

# 验证传感器读取
sensors

瞬间,IPMI响应恢复正常。远程 ipmitool mc info 秒回。

自动化加固:故障必须在发生前被“掐死”

这次事故虽未造成业务停机,但暴露了一个深层隐患:物理机的带外管理设备是独立于宿主机的“定时炸弹”,它同样会死机、会卡死,且比宿主机更隐蔽。

我们不可能每天手动去SOL激活排查。于是我写了一套自动化探针,部署在所有物理机的宿主机侧,用于监控BMC的健康状态并自动处理这种“哑火”故障。

探针脚本:不只是ping,而是“预检心跳”

#!/bin/bash
# /usr/local/bin/bmc_health_check.sh
# 用于检测BMC的IPMI进程是否僵死,并自动通过SOL通道进行复位

BMC_IP="10.10.9.23"
BMC_USER="admin"
BMC_PASS='your_password'
SOL_PORT="2200"

# 1. 检测IPMI标准端口是否响应
if ! ipmitool -I lanplus -H $BMC_IP -U $BMC_USER -P $BMC_PASS mc info &>/dev/null; then
    # 2. 检测SOL端口(SSH)是否还活着
    if nc -z -w5 $BMC_IP $SOL_PORT &>/dev/null; then
        # 3. 通过SOL执行“软复位”
        # 此处使用 expect 脚本自动化,避免手动交互
        expect <<EOF
        set timeout 10
        spawn ssh -p $SOL_PORT root@$BMC_IP
        expect "password:"
        send "$BMC_PASS\r"
        expect "# "
        send "rmmod i2c_i801 && modprobe i2c_i801 && systemctl restart phosphor-host-ipmid\r"
        expect "# "
        send "exit\r"
EOF
        sleep 5
        # 4. 再次检测IPMI是否恢复
        if ipmitool -I lanplus -H $BMC_IP -U $BMC_USER -P $BMC_PASS mc info &>/dev/null; then
            echo "$(date) [RECOVERED] BMC IPMI stuck, SOL recovery successful." >> /var/log/bmc_auto_recovery.log
        else
            echo "$(date) [CRITICAL] BMC SOL recovery failed, manual intervention needed." >> /var/log/bmc_auto_recovery.log
        fi
    else
        echo "$(date) [CRITICAL] BMC and SOL both dead, network-level issue." >> /var/log/bmc_auto_recovery.log
    fi
fi

物理机专属的“D状态”检测逻辑

上述脚本只能解决IPMI进程僵死。但我们要连BMC的Linux内核死锁也要预防。更聪明的做法是直接监控BMC的内存和进程状态,但很多BMC并不开放SSH到宿主机。退而求其次,我在宿主机上监控 /sys/class/i2c-dev/ 的busy状态,如果SMBus被锁,对CPU上某些设备的 sensors 命令读取也会阻塞。

这里要特别强调一个冷门参数:让BMC的传感器轮询频率降低,能大幅降低I2C总线冲突概率。通过IPMI设置传感器事件触发阈值:

# 将指定传感器的扫描周期从默认的0.5Hz降为0.2Hz
ipmitool sensor set "GPU_TEMP" 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.25

# 若主板支持,则关闭对无意义的PCIe电源插槽的监控
ipmitool raw 0x30 0x49 0x00 0x01

这两个命令因地制宜,因主板型号而异,但思路是:减少BMC的I2C读操作频度,避免高频轮询撞上PCIe设备的高峰数据传输,造成锁冲突。

运维视角的降维打击:把BMC视作“第一公民”

大多数人在云服务器上没有这种困扰,因为虚拟化层屏蔽了这些硬件泥沼。但裸金属物理机的魅力和痛点都在这里,你需要同时伺候好两个CPU:一个是运算的x86/ARM核,另一个是管理板的PowerPC/ARM核。轻云互联后来给我们提供了一个隐藏功能,允许在控制台上直接配置BMC的IPMI参数和固件版本回滚,这确实规避了不少远程手搓SOL的尴尬。

最后附上一条排查此类问题的黄金法则:任何关于物理机的监控告警,永远优先排查带外管理面,再排查业务面。因为带外常常是独立于业务内核的“套娃”,一旦它出现问题,你所有自动化运维工具都会瞬间变成瞎子。以上,就是一台物理机BMC的“心梗”急救实录。