一、当硬件与业务“各说各话”:监控的割裂之痛

想象一下,你管理着一座现代化的数据中心。服务器、交换机、存储设备,这些硬件就像大楼的钢筋水泥,是业务系统稳定运行的基石。同时,你的业务应用,比如网站、数据库、API服务,就像大楼里繁忙的办公室和生产线。

现在问题来了:你有一套非常出色的业务监控系统(比如Prometheus),它能告诉你“生产线”的吞吐量、“办公室”的响应速度,一切关于软件和业务逻辑的指标都一目了然。但大楼本身的“健康状况”呢?比如,哪台服务器的电源快不行了?哪个CPU的温度已经高得报警?内存条有没有报错?这些硬件的“悄悄话”,通常由设备厂商自带的带外管理工具(如iDRAC, iLO, BMC)通过Redfish接口说出来。

于是,你不得不面对这样的场景:运维同学需要同时盯着两套甚至多套监控界面。业务指标一切正常,但突然某台服务器因为电源故障宕机,导致业务中断。在Prometheus的仪表盘上,你只能看到服务突然掉线,却无法提前看到“电源健康状态:警告”这个关键的前置信号。这种硬件监控与业务监控平台之间的“割裂”,让故障的预防和根因定位变得异常困难,也增加了运维的复杂度和心理负担。

二、Redfish与Prometheus:两位“专家”的握手

要解决这个问题,我们需要让这两位“专家”——Redfish和Prometheus——能够对话。

Redfish 你可以把它理解为一个标准的“硬件健康检查报告生成器”。它是由DMTF(分布式管理任务组)制定的一套基于RESTful API的规范,几乎所有的现代服务器(戴尔、惠普、联想、超微等)都支持。通过Redfish API,我们可以用HTTP请求的方式,远程、安全地获取到服务器硬件的详细信息,比如:

  • 系统信息(型号、序列号)
  • 健康状态(整体健康、电源、温度、风扇)
  • 组件详情(CPU、内存、磁盘、网卡的状态和指标)
  • 日志信息(系统事件日志,包含错误和警告)

Prometheus 则是业务监控领域的“数据收集与告警中枢”。它不直接产生数据,而是定期去各个目标“抓取”(Scrape)指标数据。这些指标需要以特定的文本格式(Prometheus exposition format)暴露出来。Prometheus将这些时间序列数据存储起来,并提供强大的查询语言(PromQL)和灵活的告警规则配置能力,最终通过Grafana等工具进行炫酷的可视化。

所以,我们的核心思路就是:开发或使用一个“翻译官”(Exporter),这个翻译官定期通过Redfish API去查询硬件状态,然后将查询到的JSON格式的“健康报告”,翻译成Prometheus能听懂的“指标文本格式”,并暴露一个HTTP端点。最后,让Prometheus来抓取这个端点,这样硬件指标就和业务指标汇聚在同一个数据库和可视化平台里了。

三、动手搭建桥梁:一个完整的Exporter示例

下面,我们将用一个完整的Go语言示例,来演示如何构建这个“翻译官”。我们选择Go语言,因为它编译部署简单、性能好,是编写Prometheus Exporter的常用语言。

技术栈:Go语言 (Prometheus Go客户端库)

// 文件名:redfish_exporter.go
// 描述:一个简单的Redfish Exporter,用于抓取服务器电源和温度状态。

package main

import (
    "encoding/json"
    "flag"
    "fmt"
    "log"
    "net/http"
    "time"

    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
    "github.com/stmcginnis/gofish" // 一个优秀的Redfish Go客户端库
    "github.com/stmcginnis/gofish/redfish"
)

// 定义我们自定义的Prometheus指标
var (
    // 定义一个Gauge类型指标,表示电源健康状态(1=OK, 0=Warning, -1=Critical)
    powerSupplyHealth = prometheus.NewGaugeVec(
        prometheus.GaugeOpts{
            Name: "redfish_power_supply_health_status", // 指标名称
            Help: "Health status of the power supply (1 OK, 0 Warning, -1 Critical).", // 帮助信息
        },
        []string{"server", "power_supply_id"}, // 标签,用于区分不同服务器和不同电源
    )
    // 定义一个Gauge类型指标,表示温度传感器读数
    temperatureSensorValue = prometheus.NewGaugeVec(
        prometheus.GaugeOpts{
            Name: "redfish_temperature_celsius",
            Help: "Temperature reading in degrees Celsius.",
        },
        []string{"server", "sensor_name"},
    )
)

// 初始化函数,向Prometheus注册我们的自定义指标
func init() {
    prometheus.MustRegister(powerSupplyHealth)
    prometheus.MustRegister(temperatureSensorValue)
}

// 主要的收集函数,负责连接Redfish并更新指标
func collectMetrics(redfishHost, user, password string) {
    // 配置Redfish客户端连接
    config := gofish.ClientConfig{
        Endpoint: fmt.Sprintf("https://%s", redfishHost),
        Username: user,
        Password: password,
        Insecure: true, // 注意:生产环境应使用有效证书,此处为示例方便设为true
    }

    // 创建客户端连接
    client, err := gofish.Connect(config)
    if err != nil {
        log.Printf("连接Redfish失败 (%s): %v", redfishHost, err)
        // 连接失败时,将相关指标设为“不可用”状态(如NaN)
        powerSupplyHealth.WithLabelValues(redfishHost, "N/A").Set(-2)
        return
    }
    defer client.Logout()

    // 获取服务根路径
    service := client.Service
    systems, err := service.Systems()
    if err != nil {
        log.Printf("获取系统信息失败 (%s): %v", redfishHost, err)
        return
    }

    // 遍历所有系统(通常一台物理机只有一个系统)
    for _, system := range systems {
        // 1. 收集电源信息
        power, err := system.Power()
        if err == nil && power != nil {
            for _, ps := range power.PowerSupplies {
                statusValue := mapHealthToNumber(ps.Status.Health) // 将健康状态转为数字
                powerSupplyHealth.WithLabelValues(redfishHost, ps.MemberID).Set(statusValue)
                log.Printf("服务器[%s] 电源[%s] 状态: %s (指标值: %.0f)", redfishHost, ps.MemberID, ps.Status.Health, statusValue)
            }
        }

        // 2. 收集温度信息(通过Thermal接口)
        thermal, err := system.Thermal()
        if err == nil && thermal != nil {
            for _, fan := range thermal.Fans {
                // 这里也可以收集风扇转速,示例略
            }
            for _, temp := range thermal.Temperatures {
                temperatureSensorValue.WithLabelValues(redfishHost, temp.Name).Set(float64(temp.ReadingCelsius))
                log.Printf("服务器[%s] 温度传感器[%s] 读数: %.1f °C", redfishHost, temp.Name, temp.ReadingCelsius)
            }
        }
    }
}

// 辅助函数:将Redfish健康状态字符串转换为Prometheus指标的数字
func mapHealthToNumber(health redfish.Health) float64 {
    switch health {
    case redfish.OKHealth:
        return 1
    case redfish.WarningHealth:
        return 0
    case redfish.CriticalHealth:
        return -1
    default:
        return -2 // 未知状态
    }
}

func main() {
    // 定义命令行参数
    redfishHost := flag.String("redfish.host", "192.168.1.100", "Redfish服务器IP地址")
    redfishUser := flag.String("redfish.user", "admin", "Redfish用户名")
    redfishPass := flag.String("redfish.password", "password", "Redfish密码")
    listenAddr := flag.String("web.listen-address", ":9101", "Exporter监听的地址和端口")
    scrapeInterval := flag.Duration("scrape.interval", 60*time.Second, "抓取Redfish指标的间隔")
    flag.Parse()

    // 启动一个后台goroutine,定时执行指标收集
    go func() {
        ticker := time.NewTicker(*scrapeInterval)
        defer ticker.Stop()
        for ; true; <-ticker.C { // 首次立即执行,之后按间隔执行
            collectMetrics(*redfishHost, *redfishUser, *redfishPass)
        }
    }()

    // 设置Prometheus指标抓取端点
    http.Handle("/metrics", promhttp.Handler())
    // 设置一个简单的健康检查端点
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte(`<html>
            <head><title>Redfish Exporter</title></head>
            <body>
            <h1>Redfish Exporter</h1>
            <p>请访问 <a href="/metrics">/metrics</a> 来获取监控指标。</p>
            </body>
            </html>`))
    })

    log.Printf("启动Redfish Exporter,监听地址: %s", *listenAddr)
    log.Fatal(http.ListenAndServe(*listenAddr, nil))
}

如何使用这个Exporter:

  1. 环境准备:确保安装Go(1.16+),并执行 go mod init redfish_exportergo mod tidy 来管理依赖。
  2. 编译go build -o redfish_exporter redfish_exporter.go
  3. 运行
    ./redfish_exporter \
      --redfish.host=192.168.1.100 \
      --redfish.user=admin \
      --redfish.password=YourSecurePassword \
      --web.listen-address=:9101
    
  4. 验证:打开浏览器访问 http://你的服务器IP:9101/metrics,你应该能看到类似下面的Prometheus格式指标:
    # HELP redfish_power_supply_health_status Health status of the power supply (1 OK, 0 Warning, -1 Critical).
    # TYPE redfish_power_supply_health_status gauge
    redfish_power_supply_health_status{server="192.168.1.100",power_supply_id="PSU.Slot.1"} 1
    # HELP redfish_temperature_celsius Temperature reading in degrees Celsius.
    # TYPE redfish_temperature_celsius gauge
    redfish_temperature_celsius{server="192.168.1.100",sensor_name="CPU1 Temp"} 45.5
    
  5. 配置Prometheus:在Prometheus的 prometheus.yml 配置文件中,添加一个新的抓取任务:
    scrape_configs:
      - job_name: 'redfish'
        static_configs:
          - targets: ['你的exporter服务器IP:9101'] # 这里填写运行上述exporter的机器地址
        scrape_interval: 60s # 与exporter的收集间隔保持一致或略长
    
    重启Prometheus后,硬件指标就正式纳入监控体系了!

四、更优实践与高级话题:使用官方Redfish Exporter

虽然自己写Exporter很有学习价值,但在生产环境中,我们更推荐使用社区维护的、功能更全面的项目。例如,Prometheus社区官方维护的 redfish_exporter

它是一个更成熟、支持更多Redfish模型和指标的解决方案。其部署通常通过Docker完成,配置则通过文件或环境变量。

示例:使用Docker运行官方Redfish Exporter

# docker-compose.yml 示例
version: '3.8'
services:
  redfish-exporter:
    image: quay.io/prometheuscommunity/redfish-exporter:latest
    container_name: redfish-exporter
    ports:
      - "9101:9101" # 暴露指标端口
    environment:
      - REDFISH_EXPORTER_CONFIG=/config/config.yaml # 指定配置文件路径
    volumes:
      - ./config:/config # 将本地配置目录挂载到容器内
    restart: unless-stopped
# ./config/config.yaml 配置文件示例
modules:
  default:
    username: admin
    password: YourSecurePassword
    timeout: 10s
  server-node1: # 自定义模块名
    host: 192.168.1.100
    username: admin
    password: YourSecurePassword
    metrics_path: /redfish/v1/ # Redfish API根路径
  server-node2:
    host: 192.168.1.101
    username: admin
    password: AnotherSecurePassword

运行 docker-compose up -d 后,访问 http://localhost:9101/metrics?target=server-node1,Prometheus就会去抓取 server-node1 定义的服务器指标。在Prometheus配置中,可以使用 http://redfish-exporter:9101/metrics?target=${1} 配合服务发现来动态抓取多台主机。

五、融合可视化与告警:在Grafana中统一视图

当硬件指标流入Prometheus后,最激动人心的部分就来了——统一的可视化。在Grafana中,你可以创建这样的仪表盘:

  1. 顶层视图:将服务器硬件健康状态(如整体状态、电源、温度)与部署在该服务器上的关键业务应用(如QPS、响应延迟、错误率)放在同一个面板中。一眼就能看出“业务波动是否与底层硬件异常相关”。
  2. 预警面板:创建一个专门的面板,用醒目的颜色(红/黄)展示所有处于Warning或Critical状态的硬件组件列表。
  3. 趋势分析:绘制CPU温度、风扇转速、电源输入功率等指标的历史趋势图。结合机房温湿度数据,可以分析散热效率,甚至预测潜在的硬件故障。
  4. 关联告警:在Prometheus Alertmanager中配置告警规则。例如:
    # prometheus规则文件示例
    groups:
    - name: hardware_alerts
      rules:
      - alert: PowerSupplyWarning
        expr: redfish_power_supply_health_status <= 0 # 状态为0或-1
        for: 2m # 持续2分钟
        annotations:
          summary: "服务器 {{ $labels.server }} 的电源 {{ $labels.power_supply_id }} 状态异常"
          description: "当前健康状态为: {{ $value }} (1=OK,0=Warning,-1=Critical)。请立即检查。"
      - alert: CPUOverTemperature
        expr: redfish_temperature_celsius{server=~".*", sensor_name=~".*CPU.*"} > 85
        for: 5m
        annotations:
          summary: "服务器 {{ $labels.server }} 的 {{ $labels.sensor_name }} 温度过高"
    
    这样,硬件告警就能和业务告警通过同一个渠道(如钉钉、企业微信、PagerDuty)发送给运维人员,实现真正的统一告警管理。

六、方案全景审视:场景、优劣与避坑指南

应用场景

  • 数据中心统一监控:适用于拥有数十台至上万台物理服务器的数据中心,希望将硬件监控从分散的厂商工具中解放出来。
  • 云服务提供商/托管服务:需要向客户展示其物理基础设施的健康状态,提升服务透明度与可信度。
  • ** DevOps/SRE团队**:追求故障快速定位的团队,当业务出现问题时,能迅速排除或确认硬件因素。
  • 成本优化与预测性维护:通过分析长期的硬件指标(如电容老化导致的功耗微增),预测硬件寿命,规划更经济的更换周期。

技术优点

  1. 统一平台,降低复杂度:运维只需关注一个监控平台(Prometheus+Grafana),无需在多套系统间切换。
  2. 关联分析能力增强:硬件指标与业务指标在时间线上完美对齐,为故障根因分析提供了前所未有的便利。
  3. 利用成熟生态:直接享用Prometheus强大的抓取、存储、查询、告警生态,以及Grafana丰富的可视化插件。
  4. 标准化与自动化:Redfish是行业标准,使得监控脚本和工具可以跨厂商通用,易于集成到自动化运维流水线中。
  5. 无侵入性:通过带外管理口(BMC)收集数据,完全不影响主机内业务系统的性能。

潜在缺点与挑战

  1. 安全性考量:Redfish接口权限很高,Exporter需要存储和管理这些凭证。必须确保Exporter本身的服务安全(如使用HTTPS、设置防火墙规则),并定期轮换密码。
  2. 性能与规模:频繁查询Redfish API可能对BMC造成压力。在大规模部署时,需要合理规划Exporter的部署模式(是每个机架一个,还是集中式)、抓取间隔,并注意避免对管理网络造成拥堵。
  3. 数据模型差异:不同厂商、不同型号的设备对Redfish标准的支持程度和实现细节可能有差异。Exporter需要做好兼容性处理,或者需要针对特定设备进行微调。
  4. 指标爆炸:一台服务器通过Redfish可能暴露数百个指标,需要仔细筛选,只收集关键指标,避免造成Prometheus存储和查询的压力。

注意事项

  • 测试先行:在生产环境全面铺开前,先在少量服务器上进行充分测试,验证Exporter的稳定性、数据准确性和对BMC的影响。
  • 最小权限原则:为Exporter创建专用的Redfish只读账户,避免使用管理员账户。
  • 监控Exporter本身:别忘了用Prometheus监控你的Redfish Exporter(比如up指标、抓取耗时),确保这个“桥梁”本身是健康的。
  • 文档与标签规划:设计好Prometheus指标的标签体系(如serverdatacenterrackmodel),这关系到后期能否高效地聚合和查询数据。

七、总结:从割裂到融合,构建韧性基础设施

将Redfish监控数据集成到Prometheus,绝不仅仅是一项技术集成。它代表着运维理念从“孤立看待系统组件”到“整体审视服务栈”的演进。通过这座桥梁,冰冷的硬件信号被转化为了可度量、可预警、可关联分析的数据流,与业务指标共同编织成一张细密的监控防护网。

这项工作的最终价值,在于提升系统的“可观测性”和运维的“前瞻性”。它让运维团队在硬件故障影响业务之前就能获得警报,在业务出现问题时能快速厘清责任边界。当硬件与业务的监控数据在同一时空背景下呈现时,我们才真正拥有了构建高韧性、高可维护性基础设施的数据基石。虽然实施过程中需要注意安全、性能和兼容性等细节,但其带来的运维效率提升和风险降低收益,对于任何严肃对待基础设施管理的团队而言,都是非常值得投入的。