运维笔记

HPE iLO 6 自动化脚本实战:用 Python 和 Redfish API 批量管理 ProLiant Gen11

Infrastructure 技术可视化

别再用 Web 界面点来点去了

说实话,HPE iLO 6 的 Web 界面做得不错,但如果你管着几十上百台 ProLiant Gen11,一个个登录点鼠标,那效率低得让人抓狂。上周我们刚上架了 30 台 DL380 Gen11,全部要改 iLO 账号密码、配置 NTP 和 SNMP、升级固件——用 Web 搞?三天都干不完。

iLO 6 全面支持 Redfish API,这是行业标准(DMTF 定义的),不是 HPE 自己搞的私有协议。这意味着你可以用标准 HTTP REST 调用,通过 Python 脚本批量管理。而且 iLO 6 默认启用了 Redfish,不需要额外配置。

但有个坑:网上很多教程还停留在 python-hpilo 那个库,那是基于 iLO 的旧版专有协议(RIBCL),iLO 5 之后就不推荐用了。iLO 6 必须走 Redfish,别走弯路。

Redfish API 架构速览

iLO 6 的 Redfish 实现遵循标准的资源树结构:

graph TD
    A["/redfish/v1/"] --> B["Systems"]
    A --> C["Chassis"]
    A --> D["Managers (BMC)"]
    A --> E["EventService"]
    A --> F["AccountService"]
    A --> G["UpdateService"]
    B --> H["/Systems/1/ (Server)"]
    H --> I["Processors"]
    H --> J["Memory"]
    H --> K["Storage"]
    H --> L["EthernetInterfaces"]
    D --> M["/Managers/1/ (iLO)"]
    M --> N["NetworkProtocol"]
    M --> O["LogServices"]
    M --> P["FirmwareInventory"]

所有操作都是 HTTP GET/POST/PATCH/DELETE 请求,返回 JSON。认证方式推荐 Session 模式(先 POST 登录拿 Token,后续请求带 X-Auth-Token 头),当然也支持 Basic Auth。

环境准备

Python 3.8+ 就够了,不需要装什么重型框架。核心依赖:

pip install requests urllib3

官方有个 python-ilorest-library,但说实话太重了,封装了太多层,文档也写得稀烂。我建议直接用 requests 裸写,更可控,出了问题也容易调试。

实战:批量改 iLO 密码

来看一个真实场景:30 台新服务器,默认 iLO 密码都一样,需要批量改成随机密码。

import requests
import json
import secrets
import string
import time

# 关闭 SSL 警告(iLO 自签证书)
requests.packages.urllib3.disable_warnings()

def generate_password(length=20):
    chars = string.ascii_letters + string.digits + "!@#$%^&*"
    return ''.join(secrets.choice(chars) for _ in range(length))

def change_ilo_password(ilo_ip, current_user, current_pass, new_password):
    """
    通过 Redfish API 修改 iLO 管理员密码
    """
    base_url = f"https://{ilo_ip}/redfish/v1"
    
    # 第一步:创建 Session
    session_payload = {
        "UserName": current_user,
        "Password": current_pass
    }
    
    try:
        # 注意:创建 Session 用 POST 到 SessionService 的 Sessions 集合
        session_resp = requests.post(
            f"{base_url}/SessionService/Sessions",
            json=session_payload,
            verify=False,
            timeout=10
        )
        session_resp.raise_for_status()
        
        # 从响应头拿 Token
        auth_token = session_resp.headers.get("X-Auth-Token")
        if not auth_token:
            print(f"[{ilo_ip}] 无法获取认证 Token")
            return False
            
        headers = {
            "X-Auth-Token": auth_token,
            "Content-Type": "application/json"
        }
        
        # 第二步:找到管理员账号的 URI
        # 通常管理员账号在 /redfish/v1/AccountService/Accounts/1
        accounts_resp = requests.get(
            f"{base_url}/AccountService/Accounts",
            headers=headers,
            verify=False,
            timeout=10
        )
        accounts_resp.raise_for_status()
        accounts_data = accounts_resp.json()
        
        # 遍历所有账号,找到 Administrator
        target_account_uri = None
        for member in accounts_data.get("Members", []):
            acct_resp = requests.get(
                f"{base_url}{member['@odata.id']}",
                headers=headers,
                verify=False,
                timeout=10
            )
            acct = acct_resp.json()
            if acct.get("UserName") == current_user:
                target_account_uri = f"{base_url}{member['@odata.id']}"
                break
        
        if not target_account_uri:
            print(f"[{ilo_ip}] 未找到用户 {current_user}")
            # 记得删 Session
            requests.delete(session_resp.headers.get("Location"), headers=headers, verify=False)
            return False
        
        # 第三步:修改密码
        update_payload = {
            "Password": new_password
        }
        patch_resp = requests.patch(
            target_account_uri,
            json=update_payload,
            headers=headers,
            verify=False,
            timeout=10
        )
        
        if patch_resp.status_code == 200 or patch_resp.status_code == 204:
            print(f"[{ilo_ip}] 密码修改成功")
            
            # 测试新密码是否生效
            # 先删掉旧 Session
            session_location = session_resp.headers.get("Location")
            if session_location:
                requests.delete(session_location, headers=headers, verify=False)
            
            # 用新密码创建 Session 验证
            test_resp = requests.post(
                f"{base_url}/SessionService/Sessions",
                json={"UserName": current_user, "Password": new_password},
                verify=False,
                timeout=10
            )
            if test_resp.status_code == 201:
                print(f"[{ilo_ip}] 新密码验证通过")
                test_headers = {"X-Auth-Token": test_resp.headers.get("X-Auth-Token")}
                requests.delete(test_resp.headers.get("Location"), headers=test_headers, verify=False)
                return True
            else:
                print(f"[{ilo_ip}] 警告:新密码验证失败!{test_resp.status_code}")
                return False
        else:
            print(f"[{ilo_ip}] 密码修改失败: {patch_resp.status_code} {patch_resp.text}")
            return False
            
    except requests.exceptions.RequestException as e:
        print(f"[{ilo_ip}] 请求异常: {str(e)}")
        return False
    finally:
        # 确保 Session 被清理
        if 'headers' in locals() and 'session_resp' in locals():
            session_location = session_resp.headers.get("Location")
            if session_location:
                try:
                    requests.delete(session_location, headers=headers, verify=False, timeout=5)
                except:
                    pass

# 批量执行
ilo_list = [
    {"ip": "192.168.1.10", "user": "admin", "pass": "defaultpass"},
    # ... 更多 iLO
]

for ilo in ilo_list:
    new_pass = generate_password()
    success = change_ilo_password(ilo["ip"], ilo["user"], ilo["pass"], new_pass)
    if success:
        print(f"{ilo['ip']} -> {new_pass}")
        # 这里可以记录到密码表或 Hashicorp Vault
    time.sleep(1)  # 别把 iLO 打挂了

这段代码有几个关键点:

  1. Session 管理:Redfish 要求显式创建和删除 Session,不删 Session 会占满 iLO 的 Session 配额(默认 8 个)
  2. PATCH 不是 PUT:修改密码用 PATCH 方法,只需要传要改的字段
  3. 验证机制:改完密码后我习惯用新密码重新建个 Session 验证,防止密码没生效就把旧 Session 删了

批量获取硬件信息

另一个高频需求:批量采集服务器序列号、固件版本、内存配置。

def get_server_inventory(ilo_ip, username, password):
    """
    采集服务器关键硬件信息
    """
    base_url = f"https://{ilo_ip}/redfish/v1"
    
    # 创建 Session
    session_resp = requests.post(
        f"{base_url}/SessionService/Sessions",
        json={"UserName": username, "Password": password},
        verify=False,
        timeout=10
    )
    session_resp.raise_for_status()
    token = session_resp.headers.get("X-Auth-Token")
    headers = {"X-Auth-Token": token}
    
    inventory = {}
    
    try:
        # 获取系统信息
        sys_resp = requests.get(
            f"{base_url}/Systems/1",
            headers=headers,
            verify=False,
            timeout=10
        )
        sys_data = sys_resp.json()
        
        inventory["serial"] = sys_data.get("SerialNumber", "N/A")
        inventory["model"] = sys_data.get("Model", "N/A")
        inventory["bios_version"] = sys_data.get("BiosVersion", "N/A")
        inventory["cpu_count"] = len(sys_data.get("Processors", {}).get("Members", []))
        inventory["total_memory_gb"] = sys_data.get("MemorySummary", {}).get("TotalSystemMemoryGiB", 0)
        
        # 获取 iLO 固件版本
        mgr_resp = requests.get(
            f"{base_url}/Managers/1",
            headers=headers,
            verify=False,
            timeout=10
        )
        mgr_data = mgr_resp.json()
        inventory["ilo_firmware"] = mgr_data.get("FirmwareVersion", "N/A")
        
        # 获取固件清单
        fw_resp = requests.get(
            f"{base_url}/Managers/1/FirmwareInventory",
            headers=headers,
            verify=False,
            timeout=10
        )
        fw_data = fw_resp.json()
        
        inventory["firmware_versions"] = {}
        for member in fw_data.get("Members", []):
            item_resp = requests.get(
                f"{base_url}{member['@odata.id']}",
                headers=headers,
                verify=False,
                timeout=10
            )
            item = item_resp.json()
            inventory["firmware_versions"][item.get("Name", item.get("Id", "unknown"))] = item.get("Version", "N/A")
        
        return inventory
        
    finally:
        # 清理 Session
        requests.delete(session_resp.headers.get("Location"), headers=headers, verify=False, timeout=5)

# 使用示例
info = get_server_inventory("192.168.1.10", "admin", "newpass123")
print(json.dumps(info, indent=2))

输出示例:

{
  "serial": "SGH1234ABC",
  "model": "ProLiant DL380 Gen11",
  "bios_version": "U41 v2.10 (02/10/2026)",
  "cpu_count": 2,
  "total_memory_gb": 512,
  "ilo_firmware": "iLO 6 v2.45",
  "firmware_versions": {
    "System ROM": "U41 v2.10",
    "iLO 6": "2.45",
    "Power Management Controller": "1.20",
    "Intelligent Platform Management Interface": "2.0",
    "Smart Array P408i-a SR Gen11": "8.20"
  }
}

固件升级自动化

iLO 6 的固件升级通过 Redfish 的 UpdateService 实现,支持 HTTP 和 SMB 两种源。我推荐用 HTTP,简单直接。

def update_ilo_firmware(ilo_ip, username, password, firmware_url):
    """
    通过 HTTP 源升级 iLO 固件
    firmware_url: 可访问的固件文件 HTTP 地址
    """
    base_url = f"https://{ilo_ip}/redfish/v1"
    
    # 创建 Session
    session_resp = requests.post(
        f"{base_url}/SessionService/Sessions",
        json={"UserName": username, "Password": password},
        verify=False,
        timeout=10
    )
    session_resp.raise_for_status()
    token = session_resp.headers.get("X-Auth-Token")
    headers = {"X-Auth-Token": token, "Content-Type": "application/json"}
    
    try:
        # 提交升级任务
        update_payload = {
            "ImageURI": firmware_url,
            "UpdateTarget": "Manager",  # 升级 iLO 自身固件
            "TransferProtocol": "HTTP",
            "ForceUpdate": False  # 先不强制,检查兼容性
        }
        
        resp = requests.post(
            f"{base_url}/UpdateService/Actions/UpdateService.SimpleUpdate",
            json=update_payload,
            headers=headers,
            verify=False,
            timeout=30
        )
        
        if resp.status_code == 202:
            # 202 表示任务已接受,返回 Task 监控 URI
            task_uri = resp.headers.get("Location")
            print(f"[{ilo_ip}] 固件升级任务已提交: {task_uri}")
            
            # 轮询任务状态
            while True:
                task_resp = requests.get(
                    task_uri,
                    headers=headers,
                    verify=False,
                    timeout=10
                )
                task_data = task_resp.json()
                state = task_data.get("TaskState", "")
                status = task_data.get("TaskStatus", "")
                
                print(f"[{ilo_ip}] 任务状态: {state} / {status}")
                
                if state == "Completed":
                    print(f"[{ilo_ip}] 固件升级完成!需要重启 iLO")
                    return True
                elif state == "Exception" or state == "Killed":
                    print(f"[{ilo_ip}] 固件升级失败: {task_data.get('Messages', [])}")
                    return False
                
                time.sleep(30)  # 每 30 秒检查一次
        else:
            print(f"[{ilo_ip}] 提交升级任务失败: {resp.status_code} {resp.text}")
            return False
            
    finally:
        requests.delete(session_resp.headers.get("Location"), headers=headers, verify=False, timeout=5)

踩坑提醒:iLO 6 固件升级后会自动重启,重启期间 iLO 会断连大约 3-5 分钟。如果你在批量升级,建议串行执行,或者至少错开时间。

性能对比:Redfish vs 旧协议

维度Redfish API (iLO 6)旧 RIBCL 协议 (python-hpilo)
协议标准DMTF 行业标准,跨厂商HPE 私有,iLO 5 后弃用
认证方式Session Token / Basic Auth基于 IPMI 的加密通道
数据传输JSON over HTTP(S)二进制 XML over TCP 17990
批量操作效率高(HTTP Keep-Alive)低(每个连接都握手)
文档质量官方 Redfish 文档 + 交互式 API 浏览器文档基本停更
社区支持活跃,Python/Go/PowerShell 都有库基本只有遗留系统在用
安全性HTTPS + Token,支持 RBAC端口 17990 经常被防火墙封
学习曲线中等(理解 REST 即可)高(私有协议,调试困难)

常见问题

Q: iLO 6 的 Redfish API 地址是多少? A: 默认是 https://<iLO-IP>/redfish/v1/,可以用浏览器打开,iLO 会返回 JSON 格式的根目录。

Q: 为什么我的 PATCH 请求返回 405 Method Not Allowed? A: 检查你 PATCH 的 URI 是否支持 PATCH。有些资源只支持 GET。另外确认 Content-Type 是 application/json

Q: 批量操作时 iLO 连接数上限是多少? A: iLO 6 默认最多 8 个并发 Session。超过会返回 503。所以批量脚本一定要及时删除不再使用的 Session。

Q: 自签名证书导致 Python 报 SSL 错误怎么办? A: 开发环境用 verify=False 跳过验证。生产环境建议给 iLO 导入 CA 签发的证书,代码里用 verify=/path/to/ca-bundle

Q: 升级 iLO 固件后,之前的 Session 和配置会保留吗? A: Session 不会保留(iLO 重启了),但用户账号和网络配置会保留。固件升级脚本里要重新认证。

社区声音

Reddit 上 r/devops 有个帖子讨论得很热烈:“Transitioning from 6.5 years in IT Infra to DevOps. I built an end-to-end GitOps pipeline on Azure & some Python automation.” 帖主说他在传统基础设施干了 6 年多,现在转向 DevOps,发现自动化 iLO 管理这种 “最后一公里” 问题反而最难搞。评论区一致认为:硬件管理自动化是 DevOps 落地最容易被忽视的环节。

还有一个帖子提到 iLO 7 的目录服务支持问题,有人回复说:“Use OneView if you want a supported GUI solution, or write a simple REST API script for automated, multi-generation bulk iLO management.” 这也印证了我们的方向——脚本化 Redfish 调用才是批量管理的正确姿势。

最佳实践总结

实践说明
总是创建和销毁 Session不销毁 Session 会耗尽 iLO 连接数
使用 Session Token 而非 Basic AuthToken 有过期机制,更安全
批量操作加间隔至少 1 秒间隔,防止 iLO 过载
记录所有变更密码变更、固件升级都要记录审计日志
先用单个 iLO 测试别直接往 100 台机器上跑未验证的脚本
处理 HTTP 202 状态码异步任务需要轮询,不要假设立即完成
固件升级前检查兼容性ForceUpdate=True 可能导致不兼容版本被刷入
配置管理用 Infra-as-Code把 iLO 配置存到 Git,用 CI/CD 推送变更

总结

HPE iLO 6 的 Redfish API 把硬件管理拉到了和云 API 同样的水平。用 Python 写几个脚本,就能把以前需要几天的手动操作压缩到十几分钟。关键是别用那些过时的库,直接怼 requests 调 REST API,既轻量又可控。

下一步,可以考虑把 iLO 管理集成到 Ansible 或 Terraform 的工作流里,实现真正的从裸机到 OS 部署的全自动化。不过那是另一个话题了。

Elvin Hui

关于作者:Elvin Hui

Elvin 拥有 10+ 年企业级数据中心、云原生架构和网络安全经验。持有 CCNA、AWS 解决方案架构师认证。我致力于将一线的“踩坑”经验沉淀为真实、硬核的技术指南,拒绝空洞理论。