MegaRAID Nagios监控插件CLI运行正常但LibreNMS返回异常排查
MegaRAID Nagios插件LibreNMS调用异常排查
故障现象
插件原始代码如下:
#!/usr/bin/env python3 import paramiko import os.path import sys OK = 0 WARNING = 1 CRITICAL = 2 DEPENDENT = 3 UNKNOWN = 4 active = str("Active") online = str("Online") optimal = str("Optimal") k = str("OK") degrade = str("Degraded") fail = str("Failed") client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect( hostname='<hostname>', username='<service account>', key_filename=os.path.join(os.path.expanduser('~'), ".ssh", "id_rsa.pub") ) stdin,stdout,stderr = client.exec_command("sudo /opt/MegaRAID/MegaCli/MegaCli64 -ShowSummary -a0") check = str(stdout.read().decode('ascii')) client.close() OK_STR = str("RAID is OK!") WARN_STR = str("Warning! Something is wrong with the RAID!") CRIT_STR = str("CRITICAL! THE RAID IS BROKEN") UNK_STR = str("Uh oh! Something ain't right?") print(check) if (degrade) in (check): print(WARN_STR) and sys.exit(WARNING) elif (fail) in (check): print(CRIT_STR) and sys.exit(CRITICAL) elif str('Exit Code: 0x00') in (check): print(OK_STR) and sys.exit(OK) else: sys.exit(UNKNOWN) and print(UNK_STR)
已知运行表现:
- 普通用户在CLI终端手动执行时,输出内容、退出码均符合设计预期,通过
echo $?验证退出码逻辑正确 - LibreNMS前端调用该插件时,无法捕获任何stdout输出,插件固定返回退出码1,与终端运行表现完全不一致
根因定位
故障由4个叠加问题导致,终端运行正常是因为个人用户环境的配置兜底了代码错误:
- SSH密钥路径配置错误
代码中key_filename指定的是id_rsa.pub公钥文件,SSH公钥认证本地必须读取私钥完成认证,公钥仅需要部署在远端服务器。终端运行时,本地SSH Agent会自动缓存已加载的私钥,自动完成认证,代码里配置的错误公钥路径实际未生效;而LibreNMS运行插件的用户为独立服务用户(通常为librenms),无SSH Agent缓存,读取公钥做认证直接失败。 - 无异常捕获逻辑
代码未对SSH连接、命令执行阶段的任何异常做捕获,一旦出现认证失败、权限不足、网络波动、目录不可写等问题,Python进程会直接抛出traceback到stderr,以退出码1终止,正好匹配LibreNMS侧看到的固定返回1、无stdout输出的现象。 - 运行环境权限不匹配
LibreNMS的服务用户默认家目录权限、.ssh目录配置与个人登录用户不同:一是没有配置远端主机的host key记录,AutoAddPolicy写入known_hosts时可能因目录不存在、权限不足报错;二是SSH连到远端后执行sudo命令,默认需要tty终端,paramiko的exec_command默认不分配tty,会直接触发sudo权限错误,且代码未读取stderr内容,完全无法感知该错误。 - 分支逻辑存在短路bug
代码中所有状态分支都使用print(xxx) and sys.exit(xxx)写法,Python中print()函数返回值为None(布尔假),and运算遇到假值会直接短路,后续sys.exit逻辑存在失效风险;最后else分支将sys.exit放在and前面,执行退出后后续print完全不会触发,直接导致状态字符串丢失。
修复方案
按以下步骤逐一修正即可:
- 修正SSH密钥配置:将
key_filename路径改为私钥路径,即把路径末尾的id_rsa.pub改为id_rsa;给librenms用户配置独立的.ssh目录,目录权限设为700,私钥文件权限设为600,属主全部为librenms用户,确保进程可正常读取私钥、写入known_hosts。 - 补全全链路异常捕获:将SSH连接、命令执行逻辑全部包裹在try-except块中,所有错误信息统一打印到stdout后返回UNKNOWN退出码,同时读取命令执行的stderr内容,与stdout合并做状态判断。
- 修正分支逻辑:拆分所有
print + sys.exit的and写法,先打印状态字符串,再执行退出操作,避免短路运算导致逻辑失效、输出丢失。 - 配置远端免密与sudo权限:将librenms用户的公钥部署到被监控节点对应服务账号的
~/.ssh/authorized_keys中,文件权限设为600;在被监控节点的sudoers配置中,给对应服务账号添加MegaCli命令的NOPASSWD权限,同时添加配置Defaults:<对应服务账号名> !requiretty,关闭sudo的tty校验。
修复后参考代码
#!/usr/bin/env python3 import paramiko import os.path import sys OK = 0 WARNING = 1 CRITICAL = 2 DEPENDENT = 3 UNKNOWN = 4 degrade = "Degraded" fail = "Failed" OK_STR = "RAID is OK!" WARN_STR = "Warning! Something is wrong with the RAID!" CRIT_STR = "CRITICAL! THE RAID IS BROKEN" UNK_STR = "Uh oh! Something ain't right?" def exit_with_code(code, msg): print(msg) sys.exit(code) try: client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) key_path = os.path.join(os.path.expanduser('~'), ".ssh", "id_rsa") client.connect( hostname='<hostname>', username='<service account>', key_filename=key_path, timeout=10 ) stdin,stdout,stderr = client.exec_command("sudo /opt/MegaRAID/MegaCli/MegaCli64 -ShowSummary -a0", get_pty=True) check_out = stdout.read().decode('ascii', errors='ignore') check_err = stderr.read().decode('ascii', errors='ignore') client.close() check = check_out + check_err except Exception as e: exit_with_code(UNKNOWN, f"{UNK_STR} SSH connect failed: {str(e)}") print(check) if degrade in check: exit_with_code(WARNING, WARN_STR) elif fail in check: exit_with_code(CRITICAL, CRIT_STR) elif 'Exit Code: 0x00' in check: exit_with_code(OK, OK_STR) else: exit_with_code(UNKNOWN, UNK_STR)
内容的提问来源于stack exchange,提问作者Alex Noel
相关产品推荐
相关产品推荐

