第一章:IB链路状态监控脚本——从ibstat到自动化告警
各位同学,咱们今天聊点实在的。IB网络运维里,链路状态监控可以说是最基础、也最容易被忽视的一环。我见过不少团队,网络出问题了才手忙脚乱去查,结果发现链路早就降速了——嗯,这种坑我踩过不止一次。
说白了,IB链路状态监控脚本的核心就四件事:获取端口状态、解析速率与宽度、异常告警、日志记录与轮询。今天咱们把这四件事拆开揉碎了讲清楚。
1.1 为什么需要监控IB链路状态?
你想想看,IB网络里动辄几十Gbps甚至上百Gbps的带宽,一旦链路出问题,影响面非常大。我遇到过最典型的情况:某条链路从4X降到了1X,带宽直接缩水75%,但业务还在跑,只是慢得像蜗牛。没人发现,直到用户投诉。
所以,监控链路状态不是为了好看,是为了第一时间发现问题。我个人习惯是每30秒轮询一次,关键链路甚至10秒一次。
核心监控指标:
- 端口状态:Active / Down / Init
- 链路速率:如 40Gbps、56Gbps、100Gbps
- 链路宽度:1X、2X、4X、8X、12X
- 物理状态:LinkUp / LinkDown
1.2 ibstat命令——我们的眼睛
ibstat是Mellanox(现NVIDIA)提供的命令行工具,专门用来查看IB端口状态。它的输出长这样:
CA 'mlx5_0'
CA type: MT4123
Number of ports: 1
Port 1:
State: Active
Physical state: LinkUp
Rate: 40
Base lid: 2
LMC: 0
SM lid: 1
Capability mask: 0x02514868
Port GUID: 0x248a070300a1b2c3
Link layer: InfiniBand
注意看,State和Physical state是我们要重点关注的。Rate字段显示的是链路速率,单位是Gbps。但这里有个坑——Rate显示的是单通道速率,不是总带宽。比如Rate=40,如果是4X链路,总带宽就是40×4=160Gbps。这个细节我在项目里吃过亏,后来才搞清楚。
1.3 解析脚本——把文本变成结构化数据
光看命令行输出肯定不行,咱们得写脚本自动解析。Python里用正则表达式或者简单的字符串匹配就能搞定。我习惯用re模块,因为ibstat的输出格式相对固定。
来看一个简单的解析函数:
import re
import subprocess
def get_ib_port_status():
"""
执行ibstat并解析端口状态
返回字典列表,每个端口一个字典
"""
result = subprocess.run(['ibstat'], capture_output=True, text=True)
output = result.stdout
ports = []
current_port = {}
for line in output.split('\n'):
# 匹配端口号
port_match = re.match(r'Port\s+(\d+):', line)
if port_match:
if current_port:
ports.append(current_port)
current_port = {'port': int(port_match.group(1))}
continue
# 匹配状态
state_match = re.match(r'\s+State:\s+(\w+)', line)
if state_match:
current_port['state'] = state_match.group(1)
continue
# 匹配物理状态
phys_match = re.match(r'\s+Physical state:\s+(\w+)', line)
if phys_match:
current_port['physical_state'] = phys_match.group(1)
continue
# 匹配速率
rate_match = re.match(r'\s+Rate:\s+(\d+)', line)
if rate_match:
current_port['rate'] = int(rate_match.group(1))
continue
if current_port:
ports.append(current_port)
return ports
这段代码我用了好几年,基本没出过问题。但要注意,不同版本的ibstat输出格式可能略有差异。我在CentOS 7和Ubuntu 20.04上都跑过,格式基本一致,但如果你用的是旧版本,建议先跑一次看看输出。
1.4 异常状态告警逻辑
拿到状态数据后,怎么判断是否异常?我个人总结了一套规则:
| 状态 | 物理状态 | 含义 | 是否告警 |
|---|---|---|---|
| Active | LinkUp | 正常 | 否 |
| Down | LinkDown | 链路断开 | 是(紧急) |
| Init | LinkUp | 正在初始化 | 是(警告) |
| Active | LinkDown | 异常状态 | 是(紧急) |
| Down | LinkUp | 罕见异常 | 是(紧急) |
为什么会这样?因为State和Physical state的组合能反映出真实情况。比如State=Active但Physical state=LinkDown,这明显是硬件或驱动层面的问题。我曾经遇到过一块HCA卡,State显示Active,但物理链路早就断了,数据根本发不出去。这种坑,踩一次就记住了。
告警逻辑的伪代码:
def check_alert(port):
if port['state'] == 'Down' and port['physical_state'] == 'LinkDown':
return 'CRITICAL', f"端口{port['port']}链路断开"
elif port['state'] == 'Init':
return 'WARNING', f"端口{port['port']}正在初始化"
elif port['state'] == 'Active' and port['physical_state'] != 'LinkUp':
return 'CRITICAL', f"端口{port['port']}状态异常"
elif port['rate'] < 40: # 假设期望速率至少40Gbps
return 'WARNING', f"端口{port['port']}速率异常: {port['rate']}Gbps"
else:
return 'OK', f"端口{port['port']}正常"
1.5 日志记录——别让告警白喊
告警发出去之后,一定要记录日志。我见过最离谱的情况:告警邮件发了,但没人看,也没人查历史记录。结果链路断了三天才发现。
日志记录我建议用Python的logging模块,同时写文件和标准输出:
import logging
from datetime import datetime
def setup_logger():
logger = logging.getLogger('ib_monitor')
logger.setLevel(logging.DEBUG)
# 文件日志
fh = logging.FileHandler(f'/var/log/ib_monitor_{datetime.now().strftime("%Y%m%d")}.log')
fh.setLevel(logging.INFO)
# 控制台日志
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
logger.addHandler(fh)
logger.addHandler(ch)
return logger
日志格式里一定要有时间戳和级别。我习惯每天一个日志文件,方便归档和排查。另外,日志文件要定期清理,不然硬盘会被撑爆——嗯,这个坑我也踩过。
1.6 轮询机制——别让脚本睡死过去
轮询说白了就是定时执行。最简单的办法是用time.sleep(),但这样有个问题:如果某次执行卡住了,整个轮询就停了。我建议用schedule库或者自己写一个简单的定时器。
来看一个基于schedule的实现:
import schedule
import time
def monitor_job():
logger = setup_logger()
ports = get_ib_port_status()
for port in ports:
level, message = check_alert(port)
if level != 'OK':
# 发送告警(邮件、钉钉、微信等)
send_alert(level, message)
logger.warning(f"{level}: {message}")
else:
logger.info(message)
# 每30秒执行一次
schedule.every(30).seconds.do(monitor_job)
if __name__ == '__main__':
logger = setup_logger()
logger.info("IB链路监控脚本启动")
while True:
schedule.run_pending()
time.sleep(1)
这里有个小技巧:time.sleep(1)让CPU不至于空转,同时又能及时响应定时任务。如果你需要更精确的轮询,可以用APScheduler库,但大多数场景下schedule就够用了。
1.7 整体架构图
下面这张图展示了整个监控脚本的核心流程,我画了SVG版本,方便你理解:
从图上你能看到,整个流程是闭环的:轮询触发→执行ibstat→解析数据→判断告警→记录日志→发送通知→等待下一轮。每个模块各司其职,互不干扰。
1.8 避坑指南
我曾经踩过的坑:
- ibstat权限问题:普通用户可能没有权限执行ibstat,记得用sudo或者把用户加入相应组。
- 速率单位混淆:ibstat显示的Rate是单通道速率,总带宽要乘以链路宽度。别搞混了。
- 日志文件不轮转:日志文件会越来越大,建议用logrotate或者Python的RotatingFileHandler。
- 告警风暴:链路抖动时,每30秒发一次告警会刷屏。建议加一个去重机制,比如同一个告警5分钟内不重复发送。
我的小建议:
刚开始写监控脚本时,别想着一步到位。先跑起来,能看日志就行。然后慢慢加告警、加轮询、加持久化。迭代开发比一次性搞个大而全的东西靠谱得多。
好了,第一章的内容就到这里。链路状态监控是IB网络运维的基石,把这部分搞扎实了,后面的性能监控、故障定位才能站得住脚。下一章咱们聊聊如何用Python解析更复杂的ibdiagnet输出,敬请期待。
公众号:蓝海资料掘金营,微信deep3321