04-NetBox IP资产审计实战:从状态维护到自动化治理
- 这篇文章专门解决“IP 已经纳管,但状态仍靠人维护”的问题,把状态判断改成可执行的自动治理规则。 - 方案由两个脚本组成:一个负责事实采集,一个负责状态决策与通知,避免采集逻辑和治理逻辑缠在一起。 - 它适合作为 1.0 的补强篇,帮助你把 NetBox 从台账系统推进到可持续治理系。

摘要
- 这篇文章专门解决“IP 已经纳管,但状态仍靠人维护”的问题,把状态判断改成可执行的自动治理规则。
- 方案由两个脚本组成:一个负责事实采集,一个负责状态决策与通知,避免采集逻辑和治理逻辑缠在一起。
- 它适合作为 1.0 的补强篇,帮助你把 NetBox 从台账系统推进到可持续治理系统。
从“记录资产”到“状态驱动治理”的一次完整落地
一、为什么要做 IP 资产自动化审计 2.0
审计状态
在前一阶段(NetBox 进阶指南 1.0)中,已经实现:
- NetBox 作为统一 IP 管理平台
- 基于 Tag 的资产纳入自动化体系
- 实现“资产可查、状态可看”
但实际运维中发现一个关键问题:
IP 在 NetBox 中存在,并不代表它仍然可用。
1.1 现实问题
- IP 长期未使用但仍为 active
- IP 不可达但状态未更新
- 状态严重依赖人工经验
1.2 2.0 目标
让 IP 状态由“事实驱动”,而不是人工维护
二、最终实现效果
上线后实现:
- 每日自动 ICMP 探测
- 7 天不通 → 自动标记 deprecated
- 30 天不通 → 自动删除
- 钉钉自动通知
- 无异常只发送“正常汇总”
核心原则
只汇报变化,不制造噪音
三、整体方案拆解
治理闭环
系统拆为两个核心脚本:
| 脚本 | 职责 |
|---|---|
| ICMP 探测 | 判断 IP 是否存活 |
| 审计脚本 | 决策状态、删除、告警 |
四、前置准备
4.1 NetBox 自定义字段
路径:
Admin → Custom Fields → IP Address
新增字段:
| 字段名 | 类型 | 用途 |
|---|---|---|
| last_seen | DateTime | 最近成功时间 |
| icmp_fail_days | Integer | 连续失败天数 |
4.2 运行环境
- Linux(Ubuntu / CentOS)
- Python ≥ 3.9
- 可访问 NetBox API
- 可 ping 目标 IP
五、目录结构
/opt/ip-audit
├── icmp_last_seen_update.py
├── ip_audit_report.py
├── .env
├── logs
└── venv
六、环境准备
cd /opt/ip-audit
python3 -m venv venv
source venv/bin/activate
pip install pynetbox requests python-dotenv tqdm
七、环境变量配置
NB_URL=http://你的-netbox-地址
NB_TOKEN=你的-netbox-token
DINGTALK_WEBHOOK=https://oapi.dingtalk.com/robot/send?access_token=xxxx
DINGTALK_AT_MOBILES=138xxxxxx
AUDIT_TITLE=IP资产自动化审计
八、ICMP 探测脚本(事实采集)
只负责采集,不做决策
#!/usr/bin/env python3
import os
import subprocess
from datetime import datetime
from zoneinfo import ZoneInfo
from concurrent.futures import ThreadPoolExecutor, as_completed
import pynetbox
from tqdm import tqdm
from dotenv import load_dotenv
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
load_dotenv(os.path.join(BASE_DIR, ".env"))
NB_URL = os.getenv("NB_URL")
NB_TOKEN = os.getenv("NB_TOKEN")
LAST_SEEN_FIELD = "last_seen"
FAIL_FIELD = "icmp_fail_days"
PING_TIMEOUT = 1
MAX_WORKERS = 80
nb = pynetbox.api(NB_URL, token=NB_TOKEN)
now = datetime.now(ZoneInfo("Asia/Shanghai"))
def icmp_check(ip):
addr = ip.address.split("/")[0]
result = subprocess.run(
["ping", "-c", "1", "-W", str(PING_TIMEOUT), addr],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
return ip, result.returncode == 0
def main():
ips = list(nb.ipam.ip_addresses.all())
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as pool:
futures = [pool.submit(icmp_check, ip) for ip in ips]
for future in tqdm(as_completed(futures), total=len(futures)):
ip, alive = future.result()
cf = ip.custom_fields or {}
fail_days = int(cf.get(FAIL_FIELD) or 0)
if alive:
ip.custom_fields[LAST_SEEN_FIELD] = now.isoformat()
ip.custom_fields[FAIL_FIELD] = 0
else:
ip.custom_fields[FAIL_FIELD] = fail_days + 1
ip.save()
if __name__ == "__main__":
main()
九、IP 审计脚本(决策 + 告警)
#!/usr/bin/env python3
import os
import json
import requests
from datetime import datetime, timedelta, timezone
from dotenv import load_dotenv
import pynetbox
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
load_dotenv(os.path.join(BASE_DIR, ".env"))
NB_URL = os.getenv("NB_URL")
NB_TOKEN = os.getenv("NB_TOKEN")
DINGTALK_WEBHOOK = os.getenv("DINGTALK_WEBHOOK")
AT_MOBILES = os.getenv("DINGTALK_AT_MOBILES", "").split(",")
TITLE = os.getenv("AUDIT_TITLE", "IP资产自动化审计")
nb = pynetbox.api(NB_URL, token=NB_TOKEN)
FAIL_FIELD = "icmp_fail_days"
LAST_SEEN_FIELD = "last_seen"
now = datetime.now(timezone.utc)
offline_threshold = now - timedelta(days=30)
def send_msg(content):
at_text = " ".join([f"@{m}" for m in AT_MOBILES if m])
data = {
"msgtype": "markdown",
"markdown": {
"title": TITLE,
"text": f"### {TITLE}\n\n{content}\n\n---\n通知人员: {at_text}"
},
"at": {"atMobiles": AT_MOBILES}
}
requests.post(DINGTALK_WEBHOOK, json=data, timeout=10)
def main():
ips = nb.ipam.ip_addresses.all()
offline = []
deleted = []
for ip in ips:
cf = ip.custom_fields or {}
fail_days = int(cf.get(FAIL_FIELD) or 0)
last_seen_raw = cf.get(LAST_SEEN_FIELD)
if fail_days >= 7 and ip.status.value != "deprecated":
ip.status = "deprecated"
ip.save()
offline.append(ip.address)
if last_seen_raw:
last_seen = datetime.fromisoformat(last_seen_raw.replace("Z", "+00:00"))
if last_seen < offline_threshold:
deleted.append(ip.address)
ip.delete()
if not offline and not deleted:
send_msg(
"✅ 今日资产审计完毕\n\n"
"所有已纳入审计的 IP 状态均正常。"
)
return
msg = "📊 审计汇总\n\n"
if offline:
msg += f"🚨 新增离线 IP:{len(offline)}\n" + "\n".join(offline) + "\n\n"
if deleted:
msg += f"🗑️ 已删除 IP:{len(deleted)}\n" + "\n".join(deleted)
send_msg(msg)
if __name__ == "__main__":
main()
十、定时任务
# ICMP 探测
0 3 * * * /opt/ip-audit/venv/bin/python /opt/ip-audit/icmp_last_seen_update.py >> /opt/ip-audit/logs/icmp.log 2>&1
## 审计与告警
0 8 * * * /opt/ip-audit/venv/bin/python /opt/ip-audit/ip_audit_report.py >> /opt/ip-audit/logs/audit.log 2>&1
十一、运行效果说明
场景一:全部正常
✅ 今日资产审计完毕
所有 IP 状态正常
场景二:7天不可达
- 自动标记 deprecated
- 钉钉告警
场景三:30天未存活
- 自动删除 IP
- 自动清理垃圾数据
场景四:恢复在线
- 自动恢复 active
- 系统具备自愈能力
十二、整体价值总结
从“靠人记” → “靠数据跑”
实现:
- IP 状态实时可信
- 自动清理无效资产
- 降低运维成本
- 减少告警噪音
十三、后续扩展
未来可扩展:
- SNMP 存活检测
- TCP / HTTP 探测
- 设备联动审计
- 机柜 / 机房级联动