第326篇:多厂商设备统一管理框架
关键词
多厂商管理、设备抽象、统一接口、适配器模式、厂商适配、设备驱动、跨厂商自动化
一、为什么需要统一管理
1.1 多厂商网络的现实
现实中的多厂商网络:
企业在网设备通常来自多个厂商:
┌──────────────────────────────────────────┐
│ 核心层 │
│ ┌─ Cisco Nexus 9000 (DC 核心) │
│ ├─ Huawei NE系列 (骨干) │
│ └─ Juniper MX (边缘) │
│ │
│ 汇聚层 │
│ ┌─ Huawei S12700 (园区汇聚) │
│ └─ Arista 7280 (DC 接入) │
│ │
│ 接入层 │
│ ┌─ Huawei S5700 (办公接入) │
│ ├─ H3C S5560 (生产网络) │
│ └─ Cisco Catalyst 2960 (旧区) │
└──────────────────────────────────────────┘
每个厂商的差异:
┌─ 命令风格不同:display vs show
├─ 配置语法不同:vlan batch vs vlan database
├─ 管理协议不同:SSH/Telnet/NETCONF/API
├─ 查询输出格式不同
└─ 配置保存方式不同
直接后果:
┌─ 脚本需要为每个厂商写不同的版本
├─ 维护成本随厂商数量指数增长
├─ 新人学习成本高(要会 N 种 CLI)
└─ 统一变更变成"不可能的任务"
1.2 统一管理架构
多厂商统一管理架构:
| 业务逻辑层(Business Logic) ┌─ 备份配置 ├─ 合规检查 ├─ 配置部署 ├─ 状态巡检 └─ 故障排查 统一抽象层(Abstraction Layer) ┌─────────────────────────────────────┐ └─────────────────────────────────────┘ 适配器层(Adapter Layer) ┌──────────┬──────────┬──────────┐ └──────────┴──────────┴──────────┘ ┌──────────┬──────────┬──────────┐ └──────────┴──────────┴──────────┘ 协议层(Protocol Layer) ┌──────────┬──────────┬──────────┐ | UnifiedDeviceManager ┌─ connect() ├─ get_interfaces() ├─ get_bgp_peers() ├─ get_routing_table() ├─ send_commands() └─ save_config() Huawei Adapter H3C Adapter SSH | Cisco Adapter Arista Adapter NETCONF | Juniper Adapter Nokia Adapter RESTAPI | |
|---|---|---|---|---|
二、统一设备抽象
2.1 抽象接口定义
#!/usr/bin/env python3
# device_abstraction.py — 设备抽象接口
from abc import ABC, ABCMeta, abstractmethod
from typing import List, Dict, Optional, Any
from dataclasses import dataclass, field
from enum import Enum
# =============================================
# 通用数据类型
# =============================================
@dataclass
class InterfaceInfo:
"""接口信息"""
name: str
description: str = ""
mac_address: str = ""
ip_address: str = ""
subnet_mask: str = ""
admin_status: str = "up"
oper_status: str = "up"
speed: str = ""
mtu: int = 1500
interface_type: str = "" # GigabitEthernet, 10GE, etc.
vlan_id: Optional[int] = None
errors: Dict = field(default_factory=dict)
counters: Dict = field(default_factory=dict)
@dataclass
class BGPInfo:
"""BGP 信息"""
local_as: int
router_id: str
peers: Dict[str, Dict] # peer_ip -> state
total_prefixes: int = 0
@dataclass
class RouteInfo:
"""路由信息"""
destination: str
nexthop: str
protocol: str # BGP, OSPF, Static, etc.
preference: int
metric: int
interface: str
age: str = ""
@dataclass
class DeviceFacts:
"""设备基本信息"""
hostname: str
vendor: str # huawei, cisco, juniper, etc.
model: str
os_version: str
serial_number: str
uptime: str
cpu_usage: float = 0.0
memory_usage: float = 0.0
# =============================================
# 统一设备接口
# =============================================
class BaseDevice(ABC):
"""设备抽象基类 - 所有厂商适配器必须实现"""
def __init__(self, connection_info: Dict):
self.connection_info = connection_info
self.hostname = ""
self._connected = False
@abstractmethod
def connect(self) -> bool:
"""建立设备连接"""
pass
@abstractmethod
def disconnect(self):
"""断开设备连接"""
pass
# ── 基本信息 ──
@abstractmethod
def get_facts(self) -> DeviceFacts:
"""获取设备基本信息"""
pass
# ── 接口 ──
@abstractmethod
def get_interfaces(self) -> Dict[str, InterfaceInfo]:
"""获取所有接口状态"""
pass
@abstractmethod
def get_interface(self, name: str) -> Optional[InterfaceInfo]:
"""获取指定接口信息"""
pass
# ── BGP ──
@abstractmethod
def get_bgp_peers(self) -> List[Dict]:
"""获取 BGP 邻居信息"""
pass
# ── 路由 ──
@abstractmethod
def get_routing_table(self) -> List[RouteInfo]:
"""获取路由表"""
pass
# ── 配置 ──
@abstractmethod
def get_config(self) -> str:
"""获取当前配置"""
pass
@abstractmethod
def send_commands(
self, commands: List[str]
) -> List[str]:
"""发送配置命令"""
pass
@abstractmethod
def save_config(self) -> bool:
"""保存配置"""
pass
# ── LLDP ──
@abstractmethod
def get_lldp_neighbors(self) -> Dict:
"""获取 LLDP 邻居"""
pass
# ── 连接测试 ──
@abstractmethod
def ping(self, target: str, count: int = 3) -> Dict:
"""Ping 测试"""
pass
2.2 华为 VRP 适配器
#!/usr/bin/env python3
# huawei_adapter.py — 华为 VRP 设备适配器
from netmiko import ConnectHandler
from device_abstraction import (
BaseDevice, InterfaceInfo, BGPInfo,
RouteInfo, DeviceFacts,
)
from typing import List, Dict, Optional, Any
import re
import time
class HuaweiVRPDevice(BaseDevice):
"""华为 VRP 设备适配器"""
def __init__(self, connection_info: Dict):
super().__init__(connection_info)
self._conn = None
def connect(self) -> bool:
try:
self._conn = ConnectHandler(**self.connection_info)
self._conn.enable()
self.hostname = self._conn.find_prompt().rstrip("#>")
self._connected = True
return True
except Exception as e:
print(f"华为设备连接失败: {e}")
return False
def disconnect(self):
if self._conn:
self._conn.disconnect()
self._connected = False
def get_facts(self) -> DeviceFacts:
version_output = self._conn.send_command("display version")
device_output = self._conn.send_command(
"display device"
)
# 解析版本信息
model = ""
os_ver = ""
for line in version_output.splitlines():
if "VRP" in line:
model_match = re.search(r"(\S+) VRP", line)
if model_match:
model = model_match.group(1)
ver_match = re.search(
r"VRP\s+\(R\)\s+software.*Version\s+([\d.]+)",
line,
)
if ver_match:
os_ver = ver_match.group(1)
# 解析序列号
serial = ""
for line in device_output.splitlines():
if "SERIAL" in line:
parts = line.split()
if len(parts) >= 2:
serial = parts[-1]
# 解析 uptime
uptime = ""
for line in version_output.splitlines():
if "uptime" in line.lower():
uptime = line.strip()
return DeviceFacts(
hostname=self.hostname,
vendor="huawei",
model=model,
os_version=os_ver,
serial_number=serial,
uptime=uptime,
)
def get_interfaces(self) -> Dict[str, InterfaceInfo]:
output = self._conn.send_command("display interface brief")
interfaces = {}
for line in output.splitlines():
if not line.strip() or "Interface" in line:
continue
parts = line.split()
if len(parts) < 4:
continue
name = parts[0]
interfaces[name] = InterfaceInfo(
name=name,
admin_status=parts[1].lower(),
oper_status=parts[2].lower(),
)
return interfaces
def get_interface(
self, name: str
) -> Optional[InterfaceInfo]:
interfaces = self.get_interfaces()
return interfaces.get(name)
def get_bgp_peers(self) -> List[Dict]:
output = self._conn.send_command("display bgp peer")
peers = []
for line in output.splitlines():
if "Established" in line or "Idle" in line or "Active" in line:
parts = line.split()
if len(parts) >= 4:
peers.append({
"peer_ip": parts[0],
"as": parts[1],
"state": parts[2],
"uptime": parts[3] if len(parts) > 3 else "",
})
return peers
def get_routing_table(self) -> List[RouteInfo]:
output = self._conn.send_command(
"display ip routing-table"
)
routes = []
for line in output.splitlines():
# 华为路由表格式
# Destination/Mask Proto Pre Cost NextHop Interface
if not line.strip() or "Destination" in line:
continue
parts = line.split()
if len(parts) >= 6:
routes.append(RouteInfo(
destination=parts[0],
protocol=parts[1],
preference=int(parts[2]),
metric=int(parts[3]),
nexthop=parts[4],
interface=parts[5],
))
return routes
def get_config(self) -> str:
return self._conn.send_command(
"display current-configuration"
)
def send_commands(
self, commands: List[str]
) -> List[str]:
results = []
for cmd in commands:
output = self._conn.send_config_set(
[cmd], read_timeout=30
)
results.append(output)
if "Error" in output:
print(f"命令执行错误: {cmd} -> {output}")
return results
def save_config(self) -> bool:
try:
self._conn.save_config()
return True
except Exception:
return False
def get_lldp_neighbors(self) -> Dict:
output = self._conn.send_command(
"display lldp neighbor brief"
)
neighbors = {}
for line in output.splitlines():
if not line.strip() or "Local" in line:
continue
parts = line.split()
if len(parts) >= 4:
local_port = parts[0]
neighbors[local_port] = {
"remote_device": parts[1],
"remote_port": parts[2],
}
return neighbors
def ping(self, target: str, count: int = 3) -> Dict:
output = self._conn.send_command(
f"ping {target} -c {count}"
)
loss = "100%"
rtt_min = rtt_avg = rtt_max = 0
loss_match = re.search(
r"(\d+)% packet loss", output
)
if loss_match:
loss = loss_match.group(1) + "%"
rtt_match = re.search(
r"min/avg/max.*?= (\S+)", output
)
if rtt_match:
rtt_values = rtt_match.group(1).split("/")
if len(rtt_values) >= 3:
rtt_min, rtt_avg, rtt_max = rtt_values[:3]
return {
"target": target,
"packet_loss": loss,
"rtt_min": rtt_min,
"rtt_avg": rtt_avg,
"rtt_max": rtt_max,
"reachable": "100%" not in loss,
}
2.3 Cisco IOS 适配器
#!/usr/bin/env python3
# cisco_adapter.py — 思科 IOS 设备适配器
from netmiko import ConnectHandler
from device_abstraction import (
BaseDevice, InterfaceInfo, RouteInfo, DeviceFacts,
)
from typing import List, Dict, Optional
import re
class CiscoIOSDevice(BaseDevice):
"""思科 IOS 设备适配器"""
def __init__(self, connection_info: Dict):
super().__init__(connection_info)
self._conn = None
def connect(self) -> bool:
try:
self._conn = ConnectHandler(**self.connection_info)
self._conn.enable()
self.hostname = self._conn.find_prompt().rstrip("#>")
self._connected = True
return True
except Exception as e:
print(f"思科设备连接失败: {e}")
return False
def disconnect(self):
if self._conn:
self._conn.disconnect()
self._connected = False
def get_facts(self) -> DeviceFacts:
output = self._conn.send_command("show version")
model = ""
os_ver = ""
serial = ""
uptime = ""
for line in output.splitlines():
if "model" in line.lower():
parts = line.split(",")
for p in parts:
if "model" in p.lower():
model = p.split()[-1]
if "Version" in line and "Software" in line:
ver_match = re.search(
r"Version\s+([\d.()a-zA-Z]+)", line
)
if ver_match:
os_ver = ver_match.group(1)
if "Processor board ID" in line:
serial = line.split()[-1]
if "uptime" in line.lower():
uptime = line.strip()
return DeviceFacts(
hostname=self.hostname,
vendor="cisco",
model=model,
os_version=os_ver,
serial_number=serial,
uptime=uptime,
)
def get_interfaces(self) -> Dict[str, InterfaceInfo]:
output = self._conn.send_command(
"show ip interface brief"
)
interfaces = {}
for line in output.splitlines():
if "Interface" in line or not line.strip():
continue
parts = line.split()
if len(parts) < 4:
continue
name = parts[0]
interfaces[name] = InterfaceInfo(
name=name,
ip_address=parts[1],
admin_status=parts[3].lower(),
oper_status=parts[4].lower(),
)
return interfaces
def get_bgp_peers(self) -> List[Dict]:
output = self._conn.send_command("show bgp summary")
peers = []
for line in output.splitlines():
# Neighbor AS State Up/Down Prefixes
if not line.strip() or "Neighbor" in line:
continue
parts = line.split()
if len(parts) >= 5:
peers.append({
"peer_ip": parts[0],
"as": parts[1],
"state": parts[2],
"uptime": parts[3],
"prefixes": parts[4],
})
return peers
def get_routing_table(self) -> List[RouteInfo]:
output = self._conn.send_command(
"show ip route"
)
routes = []
for line in output.splitlines():
# O 10.0.0.0/24 [110/10] via 192.168.1.1, 00:01:23, GigabitEthernet0/0
route_match = re.match(
r"([A-Z]+)\s+([\d./]+)\s+\[(\d+)/(\d+)\]\s+via\s+"
r"([\d.]+),\s+(\S+),\s+(\S+)",
line,
)
if route_match:
routes.append(RouteInfo(
destination=route_match.group(2),
protocol=route_match.group(1),
preference=int(route_match.group(3)),
metric=int(route_match.group(4)),
nexthop=route_match.group(5),
interface=route_match.group(7),
age=route_match.group(6),
))
return routes
def get_config(self) -> str:
return self._conn.send_command("show running-config")
def send_commands(
self, commands: List[str]
) -> List[str]:
results = []
for cmd in commands:
output = self._conn.send_config_set(
[cmd], read_timeout=30
)
results.append(output)
return results
def save_config(self) -> bool:
try:
self._conn.send_command("write memory")
return True
except Exception:
return False
def get_lldp_neighbors(self) -> Dict:
output = self._conn.send_command(
"show lldp neighbors"
)
neighbors = {}
for line in output.splitlines():
parts = line.split()
if len(parts) >= 5 and parts[0].startswith("Gi"):
neighbors[parts[0]] = {
"remote_device": parts[1],
"remote_port": parts[2],
}
return neighbors
def ping(self, target: str, count: int = 3) -> Dict:
output = self._conn.send_command(
f"ping {target} repeat {count}"
)
return {
"target": target,
"reachable": "Success" in output,
"raw_output": output[:200],
}
三、统一设备管理器
3.1 工厂模式
#!/usr/bin/env python3
# device_manager.py — 统一设备管理器
from device_abstraction import BaseDevice
from huawei_adapter import HuaweiVRPDevice
from cisco_adapter import CiscoIOSDevice
from typing import Dict, Optional
class DeviceFactory:
"""设备工厂 — 根据厂商创建对应的适配器"""
# 厂商适配器注册表
_adapters = {
"huawei": HuaweiVRPDevice,
"huawei_vrp": HuaweiVRPDevice,
"cisco": CiscoIOSDevice,
"cisco_ios": CiscoIOSDevice,
"cisco_xe": CiscoIOSDevice,
# 可继续扩展
# "juniper": JuniperDevice,
# "h3c": H3CDevice,
# "arista": AristaDevice,
}
@classmethod
def register_adapter(cls, vendor: str, adapter_cls):
"""注册新的厂商适配器"""
cls._adapters[vendor] = adapter_cls
@classmethod
def create_device(
cls, connection_info: Dict
) -> Optional[BaseDevice]:
"""根据连接配置创建设备实例"""
device_type = connection_info.get("device_type", "")
vendor = connection_info.get("vendor", "")
# 从 device_type 推断厂商
if not vendor:
for key in cls._adapters:
if key in device_type:
vendor = key
break
if not vendor:
raise ValueError(
f"不支持的设备类型: {device_type},"
f"支持的厂商: {list(cls._adapters.keys())}"
)
adapter_cls = cls._adapters.get(vendor)
if adapter_cls:
return adapter_cls(connection_info)
raise ValueError(f"未知厂商: {vendor}")
class UnifiedDeviceManager:
"""统一设备管理器"""
def __init__(self):
self.devices: Dict[str, BaseDevice] = {}
def add_device(
self, device_id: str, connection_info: Dict
) -> bool:
"""添加并连接设备"""
try:
device = DeviceFactory.create_device(
connection_info
)
if device.connect():
self.devices[device_id] = device
print(
f"设备 {device_id} "
f"({device.hostname}) 连接成功"
)
return True
else:
print(f"设备 {device_id} 连接失败")
return False
except Exception as e:
print(f"添加设备 {device_id} 失败: {e}")
return False
def remove_device(self, device_id: str):
"""移除并断开设备"""
if device_id in self.devices:
self.devices[device_id].disconnect()
del self.devices[device_id]
def get_device(self, device_id: str) -> BaseDevice:
"""获取设备实例"""
return self.devices.get(device_id)
def execute_on_all(
self, method: str, *args, **kwargs
) -> Dict:
"""在所有设备上执行同一方法"""
results = {}
for device_id, device in self.devices.items():
try:
func = getattr(device, method)
results[device_id] = func(*args, **kwargs)
except Exception as e:
results[device_id] = {"error": str(e)}
return results
def collect_facts_all(self) -> Dict:
"""收集所有设备基本信息"""
return self.execute_on_all("get_facts")
def get_interfaces_all(self) -> Dict:
"""获取所有设备接口状态"""
return self.execute_on_all("get_interfaces")
def backup_all(
self, output_dir: str = "backup/"
) -> Dict[str, str]:
"""备份所有设备配置"""
import os
from datetime import datetime
os.makedirs(output_dir, exist_ok=True)
results = {}
for device_id, device in self.devices.items():
try:
config = device.get_config()
timestamp = datetime.now().strftime(
"%Y%m%d_%H%M%S"
)
filename = os.path.join(
output_dir,
f"{device_id}_{timestamp}.cfg",
)
with open(filename, "w") as f:
f.write(config)
results[device_id] = filename
except Exception as e:
results[device_id] = str(e)
return results
四、使用示例
4.1 统一管理多厂商设备
#!/usr/bin/env python3
# demo_multi_vendor.py — 多厂商设备统一管理示例
from device_manager import UnifiedDeviceManager
import json
def main():
# 创建统一管理器
manager = UnifiedDeviceManager()
# 添加各种厂商的设备
devices_config = {
"core-sw01": {
"device_type": "huawei_vrp",
"host": "192.168.1.1",
"username": "admin",
"password": "admin123",
},
"core-sw02": {
"device_type": "cisco_ios",
"host": "192.168.1.2",
"username": "admin",
"password": "admin123",
},
"acc-sw01": {
"device_type": "huawei_vrp",
"host": "192.168.1.10",
"username": "admin",
"password": "admin123",
},
}
for dev_id, conn_info in devices_config.items():
success = manager.add_device(dev_id, conn_info)
print(f"{'✅' if success else '❌'} {dev_id}")
# 统一收集设备信息(无需关心厂商差异)
print("\n=== 设备基本信息 ===")
facts = manager.collect_facts_all()
for dev_id, fact in facts.items():
if isinstance(fact, dict) and "error" in fact:
print(f"{dev_id}: {fact['error']}")
else:
print(
f"{dev_id}: {fact.hostname} "
f"({fact.vendor} {fact.model})"
)
# 统一获取 BGP 信息
print("\n=== BGP 信息 ===")
bgp_all = manager.execute_on_all("get_bgp_peers")
for dev_id, peers in bgp_all.items():
if isinstance(peers, dict) and "error" in peers:
print(f"{dev_id}: {peers['error']}")
else:
print(f"{dev_id}: {len(peers)} BGP peers")
for peer in peers:
print(f" → {peer['peer_ip']} ({peer['state']})")
# 统一备份配置
print("\n=== 备份配置 ===")
backups = manager.backup_all("backup/")
for dev_id, path in backups.items():
print(f"{dev_id}: {path}")
# 断开所有连接
for dev_id in list(manager.devices.keys()):
manager.remove_device(dev_id)
print("\n统一管理完成!")
if __name__ == "__main__":
main()
4.2 跨厂商配置部署
# 跨厂商配置部署示例
def deploy_vlan_across_vendors(
manager: UnifiedDeviceManager,
vlan_id: int,
vlan_name: str,
interfaces: Dict[str, str], # {device_id: interface_name}
):
"""跨厂商部署 VLAN"""
vlan_commands = {
"huawei_vrp": [
f"vlan batch {vlan_id}",
f"vlan {vlan_id}",
f"name {vlan_name}",
],
"cisco_ios": [
f"vlan {vlan_id}",
f"name {vlan_name}",
"exit",
],
}
for dev_id, intf in interfaces.items():
device = manager.get_device(dev_id)
if not device:
print(f"设备 {dev_id} 未连接")
continue
# 获取设备连接信息中的 device_type
device_type = device.connection_info.get(
"device_type", "huawei_vrp"
)
# 获取对应厂商的 VLAN 命令
commands = vlan_commands.get(device_type, [])
# 添加接口配置
if device_type in ("huawei_vrp",):
commands.extend([
f"interface {intf}",
f"port link-type access",
f"port default vlan {vlan_id}",
])
elif device_type in ("cisco_ios", "cisco_xe"):
commands.extend([
f"interface {intf}",
f"switchport mode access",
f"switchport access vlan {vlan_id}",
])
# 执行配置
print(f"在 {dev_id} ({device_type}) 上配置 VLAN {vlan_id}...")
device.send_commands(commands)
device.save_config()
print(f" ✅ {dev_id} 配置完成")
五、扩展新厂商适配器
5.1 适配器开发模板
#!/usr/bin/env python3
# new_vendor_adapter.py — 新厂商适配器开发模板
from device_abstraction import BaseDevice, DeviceFacts, \
InterfaceInfo, RouteInfo
from typing import List, Dict, Optional
class NewVendorDevice(BaseDevice):
"""新厂商设备适配器(模板)"""
def __init__(self, connection_info: Dict):
super().__init__(connection_info)
self._conn = None
def connect(self) -> bool:
"""实现连接逻辑"""
# TODO: 使用厂商特定的连接方式
raise NotImplementedError
def disconnect(self):
"""实现断开逻辑"""
if self._conn:
self._conn.close()
def get_facts(self) -> DeviceFacts:
"""实现设备信息采集"""
# TODO: 解析厂商特定的 show version 输出
raise NotImplementedError
def get_interfaces(self) -> Dict[str, InterfaceInfo]:
"""实现接口采集"""
raise NotImplementedError
def get_interface(
self, name: str
) -> Optional[InterfaceInfo]:
interfaces = self.get_interfaces()
return interfaces.get(name)
def get_bgp_peers(self) -> List[Dict]:
"""实现 BGP 采集"""
raise NotImplementedError
def get_routing_table(self) -> List[RouteInfo]:
"""实现路由表采集"""
raise NotImplementedError
def get_config(self) -> str:
"""实现配置采集"""
raise NotImplementedError
def send_commands(
self, commands: List[str]
) -> List[str]:
"""实现命令下发"""
raise NotImplementedError
def save_config(self) -> bool:
"""实现配置保存"""
raise NotImplementedError
def get_lldp_neighbors(self) -> Dict:
"""实现 LLDP 采集"""
raise NotImplementedError
def ping(self, target: str, count: int = 3) -> Dict:
"""实现 Ping 测试"""
raise NotImplementedError
# ── 注册新适配器 ──
def register():
from device_manager import DeviceFactory
DeviceFactory.register_adapter("new_vendor", NewVendorDevice)
六、最佳实践总结
多厂商统一管理最佳实践:
1. 接口设计原则
┌─ 接口方法粒度适中(不要太细也不要太粗)
├─ 返回统一的数据类型(不是原始文本)
├─ 异常处理标准化(统一异常类)
└─ 支持超时和重试机制
2. 适配器开发
┌─ 每个厂商一个适配器类
├─ 适配器只负责协议转换(不包含业务逻辑)
├─ 输出解析尽可能健壮(应对版本差异)
└─ 适配器单元测试覆盖主要命令
3. 厂商差异处理
┌─ 命令转换:通过适配器封装
├─ 输出解析:正则表达式 + 结构化映射
├─ 能力声明:声明设备支持/不支持的能力
└─ 降级策略:不支持的能力返回友好提示
4. 扩展性
┌─ 新厂商只需实现适配器接口
├─ 注册到工厂即可使用
├─ 不修改现有代码(开闭原则)
└─ 适配器可独立发布和版本管理
5. 生产使用
┌─ 连接池管理(复用连接)
├─ 并发控制(不超限)
├─ 详细日志(便于排查问题)
└─ 监控适配器自身状态
下篇预告:第327篇 — 网络自动化运维平台架构设计,从架构角度设计企业级网络自动化运维平台。
下篇预告:第327篇 — 网络自动化运维平台架构设计,从架构角度设计企业级网络自动化运维平台。