目录
一、脚本整体架构与设计目标
该脚本是一个基于 Bash 的自动化工具,主要用于通过 IPMI(Intelligent Platform Management Interface)协议批量查询局域网内设备的电源开启时间(Power On Hours, POH),并将结果结构化记录到日志文件中。其核心设计目标包括:
- 批量设备管理:支持对多个 IP 网段内的设备进行批量查询,适用于数据中心、服务器集群等需要集中监控的场景。
- 结构化日志:通过固定宽度格式化输出,确保日志文件易于阅读和后期数据分析(如导入表格工具)。
- 错误容错:对查询失败的设备进行标记,便于后续排查网络或设备故障。
- 低侵入性:通过
sleep
控制查询频率,避免因批量请求导致网络拥塞。
二、关键模块详细分析
2.1 IPMI 认证信息与全局配置
USERNAME="admin"
PASSWORD="123456"
- 作用:定义 IPMI 接口的登录凭证,用于身份验证。IPMI 默认端口为 623(TCP/UDP),需确保目标设备开启该服务且网络可达。
- 安全性考量:硬编码密码存在安全风险,建议通过环境变量(如
export IPMI_PASSWORD=xxx
)或配置文件读取,避免脚本泄露敏感信息。
2.2 日志系统设计
LOG_DIR="log"
mkdir -p "$LOG_DIR"
- 目录创建逻辑:使用
mkdir -p
确保日志目录存在,避免因目录缺失导致后续写入失败。路径采用相对路径,脚本执行时会在当前工作目录生成log
文件夹。
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
LOG_FILE="${LOG_DIR}/ipmi_poh_time_${TIMESTAMP}.txt"
- 时间戳格式:使用
YYYYMMDD_HHMMSS
格式,确保日志文件名唯一,便于按时间排序和检索。例如ipmi_poh_time_20250516_143000.txt
。
2.3 日志格式化函数 log_message
该函数是脚本的核心组件之一,负责处理输出内容的格式化和多目标输出(控制台 + 日志文件)。
2.3.1 固定宽度格式化设计
local ip_width=15
local days_width=10
local hours_width=10
- 列宽定义:
IP地址
列宽 15 字符,确保 IPv4 地址(如192.168.1.100
共 15 字符)完整显示。天数
和小时数
列宽各 10 字符,预留足够空间应对大数(如9999 days
占 10 字符)。
2.3.2 内容解析逻辑
if [[ "$input" == *"POH Counter"* ]]; then
local ip=$(echo "$input" | awk '{print $1}')
local poh_info=$(echo "$input" | awk '{$1=""; print $0}' | sed 's/^ //')
- IP 提取:通过
awk
截取输入行的第一个字段作为 IP 地址(假设 IP 为每行首字段)。 - POH 信息清洗:删除首字段(IP)后,使用
sed
去除前导空格,确保poh_info
仅包含时间信息(如15 days, 2 hours
)。
if [[ "$poh_info" == *"days"* ]]; then
local days=$(echo "$poh_info" | grep -oE '[0-9]+' | head -n 1)
local hours=$(echo "$poh_info" | grep -oE '[0-9]+' | tail -n 1)
local formatted_poh=$(printf "%-${days_width}s %-${hours_width}s" "$days" "$hours")
else
local formatted_poh="$poh_info"
fi
- 时间解析逻辑:
- 使用正则表达式
[0-9]+
提取数字,head -n 1
取第一个数字(天数),tail -n 1
取最后一个数字(小时数)。 - 若 POH 信息不含 “days”(如设备不支持或返回异常),则直接记录原始信息(如
Query failed
)。
- 使用正则表达式
2.3.3 输出逻辑
echo "$formatted_line"
echo "$formatted_line" >> "$LOG_FILE"
- 双输出机制:同时输出到控制台(便于实时监控)和日志文件(持久化存储),符合 CLI 工具的常规设计模式。
2.4 IP 网段与主机遍历
IP_RANGES=(
"10.1.1"
"10.1.2"
"10.1.3"
"10.1.4"
)
- 网段定义:数组元素为网络前缀(如
10.1.1
对应10.1.1.0/24
),支持同时查询多个子网。实际应用中可扩展为从文件读取或动态生成。
for host in $(seq 1 32); do
TARGET_IP="${network}.${host}"
# ... 执行查询 ...
sleep 0.5
done
- 主机范围:每个子网遍历
1-32
号主机,适用于小规模子网(如机柜内设备按顺序编号)。若子网规模较大(如/24
子网 254 台主机),需调整seq
范围或分批次查询。 - 延迟控制:
sleep 0.5
避免短时间内发送大量 IPMI 请求,降低网络拥塞风险。实际中可根据设备响应速度调整(如0.1-1秒
)。
2.5 IPMI 命令执行与错误处理
output=$(ipmitool -I lanplus -H "$TARGET_IP" -U "$USERNAME" -P "$PASSWORD" chassis poh 2>/dev/null)
poh_info=$(echo "$output" | grep "POH Counter")
- ipmitool 参数解析:
-I lanplus
:指定使用 LAN + 协议(较新的 IPMI 版本),兼容旧版可改用-I lan
。-H $TARGET_IP
:目标设备 IP 地址。-U/-P
:认证用户名和密码。chassis poh
:查询电源开启时间,返回结果通常包含 “POH Counter” 字段。
- 错误抑制:
2>/dev/null
将错误输出(如连接失败)重定向到黑洞,避免日志中充斥无关错误信息,仅记录有效输出或自定义错误提示。
if [ -n "$poh_info" ]; then
log_message "$TARGET_IP $poh_info"
else
log_message "$TARGET_IP Query failed or no response"
fi
- 结果校验:通过判断
poh_info
是否为空,区分有效响应和查询失败。自定义错误信息 “Query failed or no response” 便于快速定位故障设备。
三、日志格式与可读性分析
3.1 日志头部信息
===== IPMI Power On Hours Query Started: 2025年05月16日 星期五 14:30:00 =====
Log File: log/ipmi_poh_time_20250516_143000.txt
=====================================
=== Querying Network: 10.1.1.0/24 ===
IP Address Days Hours
---------- -------- --------
- 结构化头部:包含启动时间、日志路径、分隔符,便于快速了解查询的基本信息。
- 表格化表头:使用
printf
生成固定宽度的列标题和分隔线,确保输出对齐,例如:plaintext
10.1.1.1 15 2 10.1.1.2 Query failed or no response
3.2 典型日志片段分析
10.1.1.5 30 4
10.1.1.6 100 12
10.1.1.7 Query failed or no response
=== Network 10.1.1.0/24 Query Completed ===
- 成功案例:IP
10.1.1.5
的 POH 为 30 天 4 小时,格式化为固定宽度后,列对齐清晰。 - 失败案例:IP
10.1.1.7
无法访问或不支持 POH 查询,记录自定义错误信息。
四、脚本优化与扩展方向
4.1 安全性增强
- 密码管理:
- 改用环境变量读取密码:
PASSWORD="${IPMI_PASSWORD}"
,执行脚本前通过export IPMI_PASSWORD=xxx
设置。 - 使用加密配置文件(如 JSON),通过
jq
工具读取,避免明文存储。
- 改用环境变量读取密码:
- 权限控制:限制脚本执行权限(
chmod 700 ipmi_poh.sh
),避免非授权用户访问。
4.2 性能优化
- 并行查询:
- 使用
xargs -P <线程数>
并行处理 IP,例如:for network in "${IP_RANGES[@]}"; do seq 1 32 | xargs -I {} -P 10 bash -c ' TARGET_IP="${network}.{}" # 执行查询逻辑 ' done
- 优点:大幅缩短总执行时间(尤其是子网规模较大时)。
- 注意:需根据网络带宽和设备负载调整线程数(
-P
参数),避免过载。
- 使用
- 批量 IPMI 命令:若设备支持,可使用 IPMI 的批量操作功能(如 Redfish 协议)减少单次请求开销。
4.3 错误处理增强
- 超时控制:为
ipmitool
添加超时参数(如-timeout 5
),避免因设备无响应导致脚本阻塞。 - 详细错误码:捕获
ipmitool
的返回状态码($?
),区分不同错误类型(如认证失败、网络不可达),记录更精准的错误信息:output=$(ipmitool -I lanplus -H "$TARGET_IP" -U "$USERNAME" -P "$PASSWORD" chassis poh 2>error.log) exit_code=$? if [ $exit_code -eq 0 ]; then # 成功处理 elif [ $exit_code -eq 2 ]; then log_message "$TARGET_IP Authentication failed" else log_message "$TARGET_IP Unknown error (code $exit_code)" fi
4.4 可配置性改进
- 参数化配置:
- 将 IP 网段、主机范围、延迟时间、日志路径等配置项移至独立配置文件(如
config.ini
),通过脚本读取:[ipmi] username = admin password = 123456 [network] ranges = 10.1.1,10.1.2,10.1.3 host_start = 1 host_end = 254 [logging] dir = /var/log/ipmi_poh delay = 0.3
- 将 IP 网段、主机范围、延迟时间、日志路径等配置项移至独立配置文件(如
- 输出格式扩展:支持 CSV、JSON 等格式输出,便于与监控系统(如 Zabbix、Prometheus)集成:
- CSV 格式:
echo "$TARGET_IP,$days,$hours" >> report.csv
- JSON 格式:使用
jq
生成结构化数据。
- CSV 格式:
4.5 兼容性优化
- 多 IPMI 版本兼容:检测
ipmitool
版本,自动适配不同输出格式(如旧版返回 “Power On Hours” 而非 “POH Counter”)。 - 非 IPMI 设备支持:添加对 SNMP、Redfish 等协议的支持,通过条件判断选择查询方式:
bash
if [ "$protocol" == "ipmi" ]; then # IPMI查询逻辑 elif [ "$protocol" == "redfish" ]; then # Redfish API查询逻辑 fi
五、应用场景与最佳实践
5.1 典型使用场景
- 服务器运维:定期查询服务器 POH,评估硬件老化程度,为更换计划提供数据支持。
- 数据中心监控:集成到自动化监控系统,实时告警 POH 异常(如突然归零可能表示硬件故障)。
- 资产审计:通过日志统计设备运行时长,辅助计算折旧成本。
5.2 最佳实践建议
- 定时任务部署:将脚本加入
crontab
,每日 / 每周自动执行查询,例如:crontab
0 0 * * * /path/to/ipmi_poh.sh >/dev/null 2>&1
- 日志归档:配置日志轮转(
logrotate
),避免日志文件过大:/var/log/ipmi_poh/*.txt { daily rotate 7 compress missingok notifempty }
- 结果可视化:使用 Python 脚本或 Shell 工具将日志转换为图表(如柱状图显示各设备 POH),示例代码:
运行
import pandas as pd df = pd.read_csv('log/ipmi_poh_time_20250516_143000.txt', sep='\s+', header=1) df.plot(x='IP Address', y=['Days', 'Hours'], kind='bar')
六、潜在问题与风险提示
6.1 网络负载风险
- 现象:若子网规模大(如 4 个
/24
子网,每个子网 254 台主机),顺序查询需4×254×0.5=508秒
(约 8.5 分钟),并行查询可能导致瞬时流量激增。 - 应对:限制并行线程数,或分时段执行查询(如夜间低峰期)。
6.2 IPMI 接口限制
- 现象:部分设备可能禁用 IPMI 服务、修改默认端口或启用防火墙策略,导致查询失败。
- 应对:提前与设备管理员确认 IPMI 配置,或使用端口扫描工具(如
nmap -p 623
)预检测设备可达性。
6.3 格式兼容性问题
- 现象:不同厂商的 IPMI 返回格式可能不一致(如 Dell、HPE、Supermicro 的 POH 描述差异),导致解析失败。
- 应对:增加多厂商适配逻辑,通过正则表达式匹配多种格式(如
grep -iE 'power on hours|poh counter'
)。
七、总结
该脚本通过简洁的 Bash 语法实现了 IPMI 批量查询与结构化日志功能,适用于中小型网络环境的设备运维。其核心优势在于固定宽度格式化输出的设计,显著提升了日志的可读性和可分析性。未来可通过引入并行处理、参数化配置、多协议支持等优化手段,进一步提升脚本的灵活性和效率。对于企业级应用,建议结合配置管理数据库(CMDB)动态获取 IP 列表,并集成到现有监控平台,形成完整的设备生命周期管理链条。
通过深入分析脚本的架构与实现细节,不仅能掌握 IPMI 工具的自动化使用方法,还可举一反三,将类似的批量处理思路应用于 SNMP 查询、远程命令执行等其他运维场景,提升日常工作的自动化水平。
八、代码
#!/bin/bash
# IPMI login credentials
USERNAME="admin"
PASSWORD="123456"
# Create log directory (if it doesn't exist)
LOG_DIR="log"
mkdir -p "$LOG_DIR"
# Generate timestamped log file name
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
LOG_FILE="${LOG_DIR}/ipmi_poh_time_${TIMESTAMP}.txt"
# Logging function with fixed-width formatting
log_message() {
local ip_width=15
local days_width=10
local hours_width=10
# 直接使用完整输入,避免IP地址被截断
local input="$1"
# 检查是否为IPMI查询结果行
if [[ "$input" == *"POH Counter"* ]]; then
# 提取IP地址
local ip=$(echo "$input" | awk '{print $1}')
# 提取POH信息
local poh_info=$(echo "$input" | awk '{$1=""; print $0}' | sed 's/^ //')
# 格式化IP地址
local formatted_ip=$(printf "%-${ip_width}s" "$ip")
# 提取天数和小时数
if [[ "$poh_info" == *"days"* ]]; then
local days=$(echo "$poh_info" | grep -oE '[0-9]+' | head -n 1)
local hours=$(echo "$poh_info" | grep -oE '[0-9]+' | tail -n 1)
local formatted_poh=$(printf "%-${days_width}s %-${hours_width}s" "$days" "$hours")
else
local formatted_poh="$poh_info"
fi
# 组合格式化后的行
local formatted_line="${formatted_ip} ${formatted_poh}"
else
# 非IPMI结果行保持原样
local formatted_line="$input"
fi
# 输出到控制台和日志文件
echo "$formatted_line"
echo "$formatted_line" >> "$LOG_FILE"
}
# Start logging
log_message "===== IPMI Power On Hours Query Started: $(date)"
log_message "Log File: $LOG_FILE"
log_message "====================================="
# Define IP address ranges
IP_RANGES=(
"10.1.1"
"10.1.2"
"10.1.3"
"10.1.4"
)
# Iterate through each IP range
for network in "${IP_RANGES[@]}"; do
log_message "=== Querying Network: ${network}.0/24 ==="
# Header for POH columns
log_message "$(printf "%-15s" "IP Address") Days Hours"
log_message "$(printf "%-15s" "----------") -------- --------"
# Iterate through all hosts in the network
for host in $(seq 1 32); do
TARGET_IP="${network}.${host}"
# Execute IPMI command and capture output
output=$(ipmitool -I lanplus -H "$TARGET_IP" -U "$USERNAME" -P "$PASSWORD" chassis poh 2>/dev/null)
# Extract POH information (if available)
poh_info=$(echo "$output" | grep "POH Counter")
# Output result
if [ -n "$poh_info" ]; then
log_message "$TARGET_IP $poh_info"
else
log_message "$TARGET_IP Query failed or no response"
fi
# Add delay to avoid overwhelming the network
sleep 0.5
done
log_message "=== Network ${network}.0/24 Query Completed ==="
done
# End logging
log_message "===== IPMI Power On Hours Query Completed: $(date)"