第314篇:Python 自动化脚本集
关键词
Python 脚本、配置备份、自动巡检、批量变更、合规检查、异常检测、运维工具集
一、脚本架构
1.1 项目结构
network_scripts/
├── config/
│ └── devices.yaml # 设备清单
├── lib/
│ ├── __init__.py
│ ├── connector.py # 连接管理
│ ├── logger.py # 日志工具
│ └── utils.py # 通用工具
├── scripts/
│ ├── backup_config.py # 配置备份
│ ├── daily_inspection.py # 每日巡检
│ ├── deploy_vlan.py # VLAN 批量部署
│ ├── compliance_check.py # 合规检查
│ ├── health_check.py # 健康检查
│ └── anomaly_detect.py # 异常检测
├── templates/ # Jinja2 模板
├── output/ # 输出目录
├── logs/ # 日志
└── requirements.txt # 依赖
1.2 公共模块
#!/usr/bin/env python3
# lib/connector.py — 连接管理
"""
统一连接管理模块
支持 Netmiko / NAPALM / NCClient 三种后端
"""
from netmiko import ConnectHandler
from netmiko.exceptions import (
NetmikoTimeoutException,
NetmikoAuthenticationException,
)
import yaml
import os
from typing import Dict, Optional, List
class DeviceConnector:
"""设备连接管理器"""
def __init__(self, config_path: str = "config/devices.yaml"):
self.devices = self._load_config(config_path)
def _load_config(self, path: str) -> Dict:
"""加载设备配置"""
with open(path, "r", encoding="utf-8") as f:
return yaml.safe_load(f)["devices"]
def get_device(self, name: str) -> Optional[Dict]:
"""获取单台设备信息"""
for dev in self.devices:
if dev["name"] == name:
return dev
return None
def get_devices_by_role(self, role: str) -> List[Dict]:
"""按角色获取设备"""
return [d for d in self.devices if d.get("role") == role]
def connect_netmiko(self, device: Dict) -> Optional[ConnectHandler]:
"""Netmiko 连接"""
try:
conn = ConnectHandler(
device_type=device.get("device_type", "huawei"),
host=device["mgmt_ip"],
username=device.get("username", "admin"),
password=device.get("password", ""),
timeout=30,
)
return conn
except NetmikoTimeoutException:
print(f" [超时] {device['name']}: 连接超时")
except NetmikoAuthenticationException:
print(f" [认证失败] {device['name']}: 用户名/密码错误")
return None
def send_commands(
self, conn: ConnectHandler, commands: List[str]
) -> str:
"""安全执行命令"""
try:
output = conn.send_config_set(commands)
return output
except Exception as e:
return f"命令执行错误: {e}"
def backup_config(self, conn: ConnectHandler) -> str:
"""获取运行配置"""
return conn.send_command("display current-configuration")
def close(self, conn: ConnectHandler):
"""关闭连接"""
if conn:
conn.disconnect()
#!/usr/bin/env python3
# lib/logger.py — 日志工具
import logging
import os
from datetime import datetime
def setup_logger(name: str, log_dir: str = "logs") -> logging.Logger:
"""配置日志器"""
os.makedirs(log_dir, exist_ok=True)
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG)
# 文件日志
today = datetime.now().strftime("%Y%m%d")
fh = logging.FileHandler(
os.path.join(log_dir, f"{name}_{today}.log"),
encoding="utf-8",
)
fh.setLevel(logging.DEBUG)
# 控制台日志
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
# 格式
formatter = logging.Formatter(
"%(asctime)s [%(levelname)s] %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
fh.setFormatter(formatter)
ch.setFormatter(formatter)
logger.addHandler(fh)
logger.addHandler(ch)
return logger
#!/usr/bin/env python3
# lib/utils.py — 通用工具
from datetime import datetime
import re
def parse_uptime(uptime_str: str) -> int:
"""解析运行时间字符串为秒数"""
total_seconds = 0
patterns = [
(r"(\d+) year", 365 * 24 * 3600),
(r"(\d+) week", 7 * 24 * 3600),
(r"(\d+) day", 24 * 3600),
(r"(\d+) hour", 3600),
(r"(\d+) minute", 60),
(r"(\d+) second", 1),
]
for pattern, multiplier in patterns:
match = re.search(pattern, uptime_str)
if match:
total_seconds += int(match.group(1)) * multiplier
return total_seconds
def ip_in_subnet(ip: str, subnet: str) -> bool:
"""检查 IP 是否在子网内"""
from ipaddress import ip_address, ip_network
try:
return ip_address(ip) in ip_network(subnet, strict=False)
except ValueError:
return False
def timestamp() -> str:
"""获取当前时间戳"""
return datetime.now().strftime("%Y%m%d_%H%M%S")
def human_size(bytes_val: int) -> str:
"""字节转可读格式"""
for unit in ["B", "KB", "MB", "GB", "TB"]:
if bytes_val < 1024:
return f"{bytes_val:.1f}{unit}"
bytes_val /= 1024
return f"{bytes_val:.1f}PB"
class ConfigDiff:
"""配置差异比较"""
@staticmethod
def diff(old_config: str, new_config: str) -> str:
"""比较两个配置的差异"""
old_lines = old_config.splitlines()
new_lines = new_config.splitlines()
old_set = set(line.strip() for line in old_lines if line.strip())
new_set = set(line.strip() for line in new_lines if line.strip())
added = new_set - old_set
removed = old_set - new_set
lines = []
if added:
lines.append("=== 新增配置 ===")
for line in sorted(added):
lines.append(f"+ {line}")
if removed:
lines.append("\n=== 删除配置 ===")
for line in sorted(removed):
lines.append(f"- {line}")
return "\n".join(lines) if lines else "无差异"
二、配置备份脚本
#!/usr/bin/env python3
# scripts/backup_config.py — 配置备份脚本
"""
网络设备配置备份脚本
功能:备份所有设备的 running-config,保存到日期目录
"""
import sys
import os
# 添加项目根目录到路径
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
from lib.connector import DeviceConnector
from lib.logger import setup_logger
from lib.utils import timestamp
from concurrent.futures import ThreadPoolExecutor, as_completed
import os
# 日志
log = setup_logger("backup")
def backup_device(device):
"""备份单台设备"""
connector = DeviceConnector()
conn = connector.connect_netmiko(device)
if not conn:
return device["name"], False, "连接失败"
try:
# 获取配置
config = connector.backup_config(conn)
if not config:
return device["name"], False, "配置为空"
# 保存文件
backup_dir = f"output/backup/{timestamp()}"
os.makedirs(backup_dir, exist_ok=True)
filename = f"{backup_dir}/{device['name']}_running.cfg"
with open(filename, "w", encoding="utf-8") as f:
f.write(config)
log.info(f"✓ {device['name']}: 配置已备份 ({len(config)} 字符)")
return device["name"], True, filename
except Exception as e:
log.error(f"✗ {device['name']}: {e}")
return device["name"], False, str(e)
finally:
connector.close(conn)
def main():
"""主函数"""
log.info("=== 全网配置备份开始 ===")
connector = DeviceConnector()
devices = connector.devices
log.info(f"设备总数: {len(devices)}")
success = 0
failed = 0
with ThreadPoolExecutor(max_workers=5) as ex:
futures = {ex.submit(backup_device, dev): dev for dev in devices}
for future in as_completed(futures):
name, ok, msg = future.result()
if ok:
success += 1
else:
failed += 1
log.info(f"=== 备份完成: 成功 {success}, 失败 {failed} ===")
if failed > 0:
sys.exit(1)
if __name__ == "__main__":
main()
三、每日巡检脚本
#!/usr/bin/env python3
# scripts/daily_inspection.py — 每日巡检脚本
"""
网络设备每日自动巡检
收集:设备健康状态、接口状态、CPU/内存、日志摘要
输出:HTML 巡检报告
"""
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
from lib.connector import DeviceConnector
from lib.logger import setup_logger
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime
import json
log = setup_logger("inspection")
def inspect_device(device):
"""巡检单台设备"""
connector = DeviceConnector()
conn = connector.connect_netmiko(device)
if not conn:
return device["name"], {"status": "failed", "error": "连接失败"}
result = {
"name": device["name"],
"mgmt_ip": device["mgmt_ip"],
"role": device.get("role", "unknown"),
"status": "success",
"timestamp": datetime.now().isoformat(),
}
try:
# 1. 系统信息
version = conn.send_command("display version")
result["version"] = version.split("\n")[0] if version else "N/A"
# 2. 设备名称
hostname = conn.send_command(
"display current-configuration | include sysname"
)
result["hostname"] = hostname.replace("sysname ", "").strip()
# 3. CPU 使用率
cpu = conn.send_command("display cpu-usage")
# 解析 CPU 行
for line in cpu.split("\n"):
if "CPU Usage" in line:
result["cpu"] = line.strip()
break
elif "cpu-usage" in line.lower():
result["cpu"] = line.strip()
# 4. 内存使用率
mem = conn.send_command("display memory-usage")
for line in mem.split("\n"):
if "Memory Usage" in line or "Memory:" in line:
result["memory"] = line.strip()
break
# 5. 接口状态摘要
interfaces = conn.send_command(
"display interface brief"
)
up_count = interfaces.count("up")
down_count = interfaces.count("down") - interfaces.count("undo shutdown")
result["interfaces"] = {
"total": up_count + down_count,
"up": up_count,
"down": down_count,
}
# 6. 日志告警
logs = conn.send_command(
"display logbuffer | include error|down|failed|critical"
)
result["log_alerts"] = len([l for l in logs.split("\n") if l.strip()])
# 7. 环境状态
env = conn.send_command("display environment")
result["environment"] = {}
for line in env.split("\n"):
if "temperature" in line.lower():
result["environment"]["temperature"] = line.strip()
elif "fan" in line.lower():
result["environment"]["fan"] = line.strip()
elif "power" in line.lower():
result["environment"]["power"] = line.strip()
log.info(f"✓ {device['name']}: CPU={result.get('cpu', 'N/A')}, "
f"接口 UP={result.get('interfaces', {}).get('up', 0)}, "
f"告警={result.get('log_alerts', 0)}")
except Exception as e:
result["status"] = "error"
result["error"] = str(e)
log.error(f"✗ {device['name']}: {e}")
finally:
connector.close(conn)
return device["name"], result
def generate_html_report(results):
"""生成 HTML 巡检报告"""
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
healthy = sum(1 for r in results.values() if r["status"] == "success")
total = len(results)
# 只保留成功的结果用于 HTML
success_results = {
k: v for k, v in results.items() if v["status"] == "success"
}
html = f"""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>网络设备每日巡检报告</title>
<style>
body {{ font-family: Arial, sans-serif; margin: 20px; }}
h1 {{ color: #333; }}
.summary {{ background: #f0f8ff; padding: 15px; border-radius: 5px; }}
.device {{ border: 1px solid #ddd; margin: 10px 0; padding: 10px; border-radius: 5px; }}
.success {{ border-left: 4px solid #4CAF50; }}
.failed {{ border-left: 4px solid #f44336; }}
.error {{ border-left: 4px solid #ff9800; }}
.ok {{ color: green; }}
.warn {{ color: orange; }}
.critical {{ color: red; }}
table {{ border-collapse: collapse; width: 100%; }}
th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
th {{ background: #4CAF50; color: white; }}
</style>
</head>
<body>
<h1>📋 网络设备每日巡检报告</h1>
<p>生成时间: {now}</p>
<div class="summary">
<p>设备总数: {total} | 健康: {healthy} | 异常: {total - healthy}</p>
</div>
<table>
<tr>
<th>设备</th><th>角色</th><th>CPU</th><th>内存</th>
<th>接口(UP/总)</th><th>告警</th><th>状态</th>
</tr>"""
for name, r in success_results.items():
cpu_str = r.get("cpu", "N/A")
mem_str = r.get("memory", "N/A")
intf = r.get("interfaces", {})
intf_str = f"{intf.get('up', 0)}/{intf.get('total', 0)}"
alerts = r.get("log_alerts", 0)
status_class = "ok" if alerts == 0 else ("warn" if alerts < 5 else "critical")
status_str = "正常" if alerts == 0 else f"{alerts} 条告警"
html += f"""
<tr>
<td>{name}</td>
<td>{r.get('role', '-')}</td>
<td>{cpu_str}</td>
<td>{mem_str}</td>
<td>{intf_str}</td>
<td class="{status_class}">{status_str}</td>
<td>{r.get('status')}</td>
</tr>"""
# 异常设备
failed_results = {
k: v for k, v in results.items() if v["status"] != "success"
}
if failed_results:
html += """</table>
<h2>❌ 异常设备</h2>
<table><tr><th>设备</th><th>错误</th></tr>"""
for name, r in failed_results.items():
html += f"<tr><td>{name}</td><td>{r.get('error', '未知')}</td></tr>"
html += "</table>"
else:
html += "</table>"
html += """
<hr>
<p><em>自动生成,如有问题请联系网络团队</em></p>
</body>
</html>"""
output_dir = "output/inspection"
os.makedirs(output_dir, exist_ok=True)
filename = f"{output_dir}/inspection_{datetime.now().strftime('%Y%m%d')}.html"
with open(filename, "w", encoding="utf-8") as f:
f.write(html)
log.info(f"巡检报告已生成: {filename}")
return filename
def main():
log.info("=== 每日巡检开始 ===")
connector = DeviceConnector()
results = {}
with ThreadPoolExecutor(max_workers=5) as ex:
futures = {ex.submit(inspect_device, dev): dev for dev in connector.devices}
for future in as_completed(futures):
name, result = future.result()
results[name] = result
# 生成报告
report_file = generate_html_report(results)
print(f"巡检完成,报告: {report_file}")
if __name__ == "__main__":
main()
四、VLAN 批量部署
#!/usr/bin/env python3
# scripts/deploy_vlan.py — VLAN 批量部署脚本
"""
VLAN 批量部署脚本
从 YAML 配置读取 VLAN 规划,批量下发到指定设备组
"""
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
from lib.connector import DeviceConnector
from lib.logger import setup_logger
import yaml
log = setup_logger("vlan_deploy")
def load_vlan_plan(plan_file: str) -> dict:
"""加载 VLAN 规划"""
with open(plan_file, "r", encoding="utf-8") as f:
return yaml.safe_load(f)
def deploy_vlans(device, vlans):
"""在设备上部署 VLAN"""
connector = DeviceConnector()
conn = connector.connect_netmiko(device)
if not conn:
return device["name"], False, "连接失败"
try:
commands = []
for vlan in vlans:
commands.append(f"vlan {vlan['id']}")
if "name" in vlan:
commands.append(f"name {vlan['name']}")
commands.append("quit")
log.info(f" {device['name']}: 正在创建 {len(vlans)} 个 VLAN...")
output = conn.send_config_set(commands)
# 验证
verify = conn.send_command("display vlan summary")
conn.save()
log.info(f"✓ {device['name']}: VLAN 部署成功")
return device["name"], True, output
except Exception as e:
log.error(f"✗ {device['name']}: {e}")
return device["name"], False, str(e)
finally:
connector.close(conn)
def main():
"""主函数"""
import argparse
parser = argparse.ArgumentParser(description="VLAN 批量部署")
parser.add_argument("--plan", required=True, help="VLAN 规划 YAML 文件")
parser.add_argument("--group", help="目标设备组(如 access, core)")
parser.add_argument("--device", help="目标设备名(单个)")
args = parser.parse_args()
# 加载规划
plan = load_vlan_plan(args.plan)
vlans = plan.get("vlans", [])
target_device_names = plan.get("target_devices", [])
if not vlans:
log.error("VLAN 规划为空")
return
log.info(f"=== VLAN 批量部署 ===")
log.info(f"待创建 VLAN: {[v['id'] for v in vlans]}")
# 获取目标设备
connector = DeviceConnector()
if args.device:
dev = connector.get_device(args.device)
devices = [dev] if dev else []
elif args.group:
devices = connector.get_devices_by_role(args.group)
else:
devices = [d for d in connector.devices
if d["name"] in target_device_names]
if not devices:
log.error("未找到目标设备")
return
log.info(f"目标设备: {[d['name'] for d in devices]}")
# 部署
success = 0
for dev in devices:
name, ok, msg = deploy_vlans(dev, vlans)
if ok:
success += 1
log.info(f"=== 完成: {success}/{len(devices)} ===")
if __name__ == "__main__":
main()
五、合规检查脚本
#!/usr/bin/env python3
# scripts/compliance_check.py — 合规检查脚本
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
from lib.connector import DeviceConnector
from lib.logger import setup_logger
import yaml
from concurrent.futures import ThreadPoolExecutor, as_completed
log = setup_logger("compliance")
# 合规检查规则
COMPLIANCE_RULES = {
"SSH 版本": {"command": "display ssh server status",
"check": lambda o: "SSH version : 2" in o},
"NTP 配置": {"command": "display ntp-service status",
"check": lambda o: "clock " in o or "synchronized" in o},
"SNMP 配置": {"command": "display snmp-agent sys-info version",
"check": lambda o: "v3" in o or "SNMP" in o},
"口令加密": {"command": "display current-configuration | include password",
"check": lambda o: "cipher" in o or "hash" in o},
"日志服务器": {"command": "display logbuffer | include info-center loghost",
"check": lambda o: "loghost" in o or "192.168" in o},
}
def check_device(device):
"""检查单台设备合规性"""
connector = DeviceConnector()
conn = connector.connect_netmiko(device)
if not conn:
return device["name"], {"status": "failed", "error": "连接失败"}
result = {
"name": device["name"],
"status": "checked",
"checks": {},
}
try:
for rule_name, rule in COMPLIANCE_RULES.items():
output = conn.send_command(rule["command"])
passed = rule["check"](output)
result["checks"][rule_name] = {
"passed": passed,
"output": output[:100] + "..." if len(output) > 100 else output,
}
except Exception as e:
result["status"] = "error"
result["error"] = str(e)
finally:
connector.close(conn)
# 汇总
total = len(result["checks"])
passed = sum(1 for c in result["checks"].values() if c["passed"])
result["summary"] = f"{passed}/{total}"
result["compliant"] = passed == total
log.info(f"{'✓' if result['compliant'] else '✗'} {device['name']}: "
f"{result['summary']} 合规")
return device["name"], result
def main():
log.info("=== 合规检查开始 ===")
connector = DeviceConnector()
all_results = {}
with ThreadPoolExecutor(max_workers=5) as ex:
futures = {ex.submit(check_device, dev): dev for dev in connector.devices}
for future in as_completed(futures):
name, result = future.result()
all_results[name] = result
# 输出汇总
print("\n=== 合规检查汇总 ===")
compliant = sum(1 for r in all_results.values()
if r.get("compliant"))
print(f"总设备: {len(all_results)}, 合规: {compliant}, "
f"不合规: {len(all_results) - compliant}")
print()
for name, result in all_results.items():
if not result.get("compliant", False):
print(f"✗ {name}: {result.get('summary', 'N/A')}")
for rule, check in result.get("checks", {}).items():
if not check["passed"]:
print(f" - {rule}: 不合规")
print()
if __name__ == "__main__":
main()
六、健康检查与告警
#!/usr/bin/env python3
# scripts/health_check.py — 健康检查脚本
"""
网络设备健康检查
检查项:CPU > 80%, 内存 > 80%, 接口 down, 温度异常
输出:告警清单
"""
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
from lib.connector import DeviceConnector
from lib.logger import setup_logger
import re
from datetime import datetime
log = setup_logger("health")
ALARM_THRESHOLDS = {
"cpu_max": 80,
"memory_max": 80,
"interface_down_max": 5,
"temperature_max": 50,
}
def extract_cpu_percent(cpu_str):
"""从 CPU 字符串提取百分比"""
match = re.search(r"(\d+)%", cpu_str or "")
return int(match.group(1)) if match else 0
def extract_memory_percent(mem_str):
"""从内存字符串提取百分比"""
match = re.search(r"(\d+)%", mem_str or "")
return int(match.group(1)) if match else 0
def health_check_device(device):
"""健康检查单台设备"""
connector = DeviceConnector()
conn = connector.connect_netmiko(device)
if not conn:
return device["name"], {"status": "failed", "error": "连接失败"}
alarms = []
info = {"name": device["name"], "status": "healthy"}
try:
# CPU 检查
cpu_output = conn.send_command("display cpu-usage")
cpu_pct = extract_cpu_percent(cpu_output)
if cpu_pct > ALARM_THRESHOLDS["cpu_max"]:
alarms.append(f"CPU 使用率 {cpu_pct}% 超过阈值 {ALARM_THRESHOLDS['cpu_max']}%")
info["cpu"] = cpu_pct
# 内存检查
mem_output = conn.send_command("display memory-usage")
mem_pct = extract_memory_percent(mem_output)
if mem_pct > ALARM_THRESHOLDS["memory_max"]:
alarms.append(f"内存使用率 {mem_pct}% 超过阈值 {ALARM_THRESHOLDS['memory_max']}%")
info["memory"] = mem_pct
# 接口检查
intf_output = conn.send_command("display interface brief")
down_ints = []
for line in intf_output.split("\n"):
if "*down" in line or "down" in line:
# 排除管理口
if "GigabitEthernet" in line or "Eth-Trunk" in line:
down_ints.append(line.split()[0] if line.split() else "unknown")
if len(down_ints) > ALARM_THRESHOLDS["interface_down_max"]:
alarms.append(f"接口 DOWN 数 {len(down_ints)} 超过阈值 {ALARM_THRESHOLDS['interface_down_max']}")
info["interfaces_down"] = len(down_ints)
info["down_interfaces"] = down_ints
# 温度检查
env_output = conn.send_command("display temperature all")
for line in env_output.split("\n"):
match = re.search(r"(\d+)", line)
if match and "temperature" in line.lower() or "Current" in line:
temp = int(match.group(1))
if temp > ALARM_THRESHOLDS["temperature_max"]:
alarms.append(f"温度异常: {line.strip()}")
info["temperature"] = env_output[:200]
except Exception as e:
info["status"] = "error"
info["error"] = str(e)
finally:
connector.close(conn)
if alarms:
info["status"] = "alarm"
info["alarms"] = alarms
for alarm in alarms:
log.warning(f"⚠ {device['name']}: {alarm}")
else:
log.info(f"✓ {device['name']}: 健康")
return device["name"], info
def main():
log.info("=== 健康检查开始 ===")
connector = DeviceConnector()
all_alarms = []
for dev in connector.devices:
name, info = health_check_device(dev)
if info.get("status") == "alarm":
all_alarms.append(info)
# 告警汇总
if all_alarms:
print(f"\n⚠ 共发现 {len(all_alarms)} 台设备异常:")
for alarm in all_alarms:
print(f"\n{alarm['name']}:")
for a in alarm.get("alarms", []):
print(f" • {a}")
else:
print("\n✓ 所有设备健康检查通过")
if __name__ == "__main__":
main()
七、定时任务集成
#!/usr/bin/env python3
# schedule_tasks.py — 定时任务调度
"""
使用 schedule 库定时执行运维脚本
"""
import schedule
import time
import subprocess
import sys
import os
from lib.logger import setup_logger
log = setup_logger("scheduler")
def run_script(script_name: str):
"""运行脚本"""
script_path = os.path.join("scripts", script_name)
log.info(f"启动定时任务: {script_name}")
try:
result = subprocess.run(
[sys.executable, script_path],
capture_output=True,
text=True,
timeout=600,
)
if result.returncode == 0:
log.info(f"✓ {script_name} 执行成功")
for line in result.stdout.split("\n")[-5:]:
if line.strip():
log.info(f" {line}")
else:
log.error(f"✗ {script_name} 执行失败: {result.stderr[:200]}")
except subprocess.TimeoutExpired:
log.error(f"✗ {script_name} 执行超时")
except Exception as e:
log.error(f"✗ {script_name}: {e}")
def main():
"""配置定时任务"""
# 每天凌晨 2:00 配置备份
schedule.every().day.at("02:00").do(
run_script, "backup_config.py"
)
# 每天上午 8:00 每日巡检
schedule.every().day.at("08:00").do(
run_script, "daily_inspection.py"
)
# 每周一 9:00 合规检查
schedule.every().monday.at("09:00").do(
run_script, "compliance_check.py"
)
# 每 30 分钟健康检查
schedule.every(30).minutes.do(
run_script, "health_check.py"
)
log.info("=== 定时任务调度器启动 ===")
log.info("已配置任务:")
log.info(" 02:00 - 配置备份")
log.info(" 08:00 - 每日巡检")
log.info(" 周一 09:00 - 合规检查")
log.info(" 每 30 分钟 - 健康检查")
while True:
schedule.run_pending()
time.sleep(60)
if __name__ == "__main__":
main()
八、最佳实践总结
Python 自动化脚本开发规范:
1. 代码组织
┌─ 公共模块复用(connector/logger/utils)
├─ 配置与代码分离(YAML 设备清单)
├─ 单一职责(每个脚本只做一件事)
└─ 并行执行(ThreadPoolExecutor)
2. 错误处理
┌─ 所有异常必须捕获
├─ 有意义的错误消息
├─ 失败设备不影响其他设备
└─ 关键操作有重试机制
3. 日志与审计
┌─ 记录所有操作
├─ 时间戳 + 设备名 + 操作内容
├─ 日志分级(DEBUG/INFO/WARNING/ERROR)
└─ 变更操作记录到审计日志
4. 执行安全
┌─ 先备份后变更
├─ 变更后验证
├─ 设置执行超时
└─ 保留回滚能力
下篇预告:第315篇 — IPAM 地址管理,将介绍 IP 地址管理(IPAM)的核心概念、工具选型和自动化集成方案。