第327篇:网络自动化运维平台架构设计

关键词

运维平台、平台架构、微服务、自动化引擎、统一门户、API 网关、任务调度、运维中台


一、为什么需要统一平台

1.1 脚本式自动化的局限

从脚本到平台的演进:

阶段 1:脚本自动化(单点突破) ┌──────────────────────────────────────────┐ │ backup.py ← 只做配置备份 │ │ patrol.py ← 只做巡检 │ │ deploy.py ← 只做部署 │ │ check.py ← 只做合规检查 │ │ │ │ 问题: │ │ ┌─ 每个脚本独立运行 │ │ ├─ 没有统一的凭证管理 │ │ ├─ 没有调度和编排 │ │ ├─ 没有用户权限控制 │ │ ├─ 没有审计日志 │ │ └─ 工程师还是需要记住所有脚本名 │ └──────────────────────────────────────────┘

阶段 2:平台化(能力集结) | 网络自动化运维平台 ┌────────────────────────────────────┐ | 统一门户(Web UI + API) ┌─ 备份 → 无感执行 ├─ 巡检 → 一键触发 ├─ 部署 → 审批后自动执行 └─ 合规 → 定时自动检查 平台提供: ┌─ 统一凭证管理(加密存储) ├─ 设备目录(CMDB 集成) ├─ 任务调度(定时/触发) ├─ 权限控制(RBAC) ├─ 审计日志 └─ 标准化输出(不关心厂商差异) | | | --- | --- | --- |

1.2 平台架构目标

平台架构核心目标:

  1. 统一入口
  ┌─ Web UI 操作(降低使用门槛)
  ├─ REST API 集成(供其他系统调用)
  └─ ChatOps 命令(IM 中执行)

  2. 能力沉淀
  ┌─ 将可复用的运维能力封装为服务
  ├─ 新需求通过编排现有服务实现
  └─ 避免重复造轮子

  3. 安全可控
  ┌─ 用户权限最小化(RBAC)
  ├─ 敏感信息加密(设备凭证)
  ├─ 操作可追溯(完整审计日志)
  └─ 变更审批流程(双人复核)

  4. 可扩展
  ┌─ 插件式架构(新能力即插即用)
  ├─ 标准化接口(降低集成成本)
  └─ 水平扩展(支撑大规模设备)

二、平台架构设计

2.1 整体架构

网络自动化运维平台架构:

用户接入层 ┌─────────┐ ┌─────────┐ ┌─────────┐ Web UI REST API ChatOps
│ │ │
┌───────┴───────────┴───────────┴──────────┐
│ API 网关 / 负载均衡 │
│ ┌─ 认证鉴权(JWT/OAuth) │
│ ├─ 限流熔断 │
│ ├─ 请求路由 │
│ └─ 访问日志 │
└──────────────────────────────────────────┘
│ │ │
业务服务层(微服务) ┌─────────┐ ┌─────────┐ ┌─────────┐ └─────────┘ └─────────┘ └─────────┘ ┌─────────┐ ┌─────────┐ ┌─────────┐ └─────────┘ └─────────┘ └─────────┘ ┌─────────┐ ┌─────────┐ ┌─────────┐ 设备管理 Service 变更服务 Service 任务调度 Service 配置管理 Service 合规检查 Service 报告 Service 巡检服务 Service 备份服务 Service 通知服务 Service
--- --- --- --- --- --- ---
│ │ │
执行引擎层 ┌─────────────────────────────────────┐ 统一设备适配器(多厂商抽象层) ┌─ SSH 执行器 ├─ NETCONF 执行器 ├─ API 执行器 └─ Ansible 执行器
--- --- ---
│ │ │
数据层 ┌─────────┐ ┌─────────┐ ┌─────────┐ └─────────┘ └─────────┘ └─────────┘ ┌─────────┐ ┌─────────┐ ┌─────────┐ MySQL/ PostgreSQL InfluxDB 时序 Redis 缓存 Elastic Search MinIO/ 对象存储 RabbitMQ 消息队列
--- --- --- --- --- --- ---

2.2 核心数据模型

#!/usr/bin/env python3
# platform_models.py — 平台核心数据模型

from dataclasses import dataclass, field
from typing import List, Dict, Optional, Any
from enum import Enum
from datetime import datetime
import json


# =============================================
# 设备管理
# =============================================

@dataclass
class Device:
    """设备信息"""
    id: str                    # 设备 ID(UUID)
    hostname: str              # 主机名
    management_ip: str         # 管理 IP
    device_type: str           # 厂商+型号(huawei_vrp)
    vendor: str                # 厂商
    model: str                 # 型号
    os_version: str            # 系统版本
    role: str                  # 角色(core/access/border)
    location: str = ""         # 物理位置
    status: str = "active"     # 状态
    tags: List[str] = field(default_factory=list)
    created_at: str = field(
        default_factory=lambda: datetime.now().isoformat()
    )


@dataclass
class DeviceCredential:
    """设备凭证(加密存储)"""
    device_id: str
    username: str
    password: str              # 加密存储
    enable_password: str = ""  # 加密存储
    ssh_key: str = ""          # 加密存储
    updated_at: str = field(
        default_factory=lambda: datetime.now().isoformat()
    )


# =============================================
# 任务系统
# =============================================

class TaskStatus(Enum):
    PENDING   = "pending"
    RUNNING   = "running"
    SUCCESS   = "success"
    FAILED    = "failed"
    CANCELLED = "cancelled"


class TaskType(Enum):
    BACKUP      = "backup"
    PATROL      = "patrol"
    DEPLOY      = "deploy"
    COMPLIANCE  = "compliance"
    PING        = "ping"
    CUSTOM      = "custom"


@dataclass
class Task:
    """任务定义"""
    id: str
    type: TaskType
    name: str
    devices: List[str]         # 目标设备 ID 列表
    params: Dict               # 任务参数
    schedule: str = ""         # cron 表达式(定时任务)
    status: TaskStatus = TaskStatus.PENDING
    created_by: str = ""
    created_at: str = field(
        default_factory=lambda: datetime.now().isoformat()
    )
    started_at: Optional[str] = None
    completed_at: Optional[str] = None
    result: Dict = field(default_factory=dict)


@dataclass
class TaskResult:
    """任务执行结果"""
    task_id: str
    device_id: str
    status: TaskStatus
    output: Dict = field(default_factory=dict)
    error: str = ""
    started_at: Optional[str] = None
    completed_at: Optional[str] = None
    duration_ms: int = 0


# =============================================
# 审计日志
# =============================================

@dataclass
class AuditLog:
    """审计日志"""
    id: str
    user: str                  # 操作人
    action: str                # 操作类型
    resource_type: str         # 资源类型
    resource_id: str           # 资源 ID
    detail: str                # 操作详情
    ip_address: str            # 来源 IP
    status: str                # 成功/失败
    timestamp: str = field(
        default_factory=lambda: datetime.now().isoformat()
    )
    extra: Dict = field(default_factory=dict)

三、核心服务实现

3.1 任务调度服务

#!/usr/bin/env python3
# scheduler_service.py — 任务调度服务

from typing import List, Dict, Optional
from datetime import datetime
import asyncio
import json
import logging

logger = logging.getLogger(__name__)


class TaskScheduler:
    """任务调度器"""

    def __init__(self, executor=None):
        self.executor = executor
        self._tasks: Dict[str, Task] = {}
        self._running: Dict[str, bool] = {}

    def create_task(
        self,
        task_type: TaskType,
        name: str,
        devices: List[str],
        params: Dict,
        schedule: str = "",
        created_by: str = "",
    ) -> Task:
        """创建任务"""
        import uuid
        task = Task(
            id=str(uuid.uuid4()),
            type=task_type,
            name=name,
            devices=devices,
            params=params,
            schedule=schedule,
            created_by=created_by,
        )
        self._tasks[task.id] = task
        logger.info(f"创建任务: {task.id} - {name}")
        return task

    def execute_task(self, task_id: str) -> Dict:
        """执行任务"""
        task = self._tasks.get(task_id)
        if not task:
            return {"error": f"任务 {task_id} 不存在"}

        task.status = TaskStatus.RUNNING
        task.started_at = datetime.now().isoformat()
        results = {}

        for device_id in task.devices:
            try:
                device_result = self.executor.execute(
                    device_id, task.type, task.params
                )
                results[device_id] = device_result
                logger.info(
                    f"任务 {task_id} 设备 {device_id} 执行完成"
                )
            except Exception as e:
                results[device_id] = {"error": str(e)}
                logger.error(
                    f"任务 {task_id} 设备 {device_id} 失败: {e}"
                )

        task.result = results
        task.completed_at = datetime.now().isoformat()

        # 判断整体状态
        all_success = all(
            "error" not in r for r in results.values()
        )
        task.status = (
            TaskStatus.SUCCESS if all_success
            else TaskStatus.FAILED
        )

        return results

    def get_task(self, task_id: str) -> Optional[Task]:
        """获取任务信息"""
        return self._tasks.get(task_id)

    def list_tasks(
        self, status: Optional[TaskStatus] = None
    ) -> List[Task]:
        """列出任务"""
        if status:
            return [
                t for t in self._tasks.values()
                if t.status == status
            ]
        return list(self._tasks.values())

    def cancel_task(self, task_id: str) -> bool:
        """取消任务"""
        task = self._tasks.get(task_id)
        if task and task.status in (
            TaskStatus.PENDING, TaskStatus.RUNNING
        ):
            task.status = TaskStatus.CANCELLED
            return True
        return False

3.2 执行引擎服务

class ExecutionEngine:
    """执行引擎"""

    def __init__(self):
        self._adapters = {}

    def register_adapter(
        self, device_type: str, adapter
    ):
        """注册设备适配器"""
        self._adapters[device_type] = adapter

    def execute(
        self, device_id: str,
        task_type: TaskType,
        params: Dict,
    ) -> Dict:
        """在设备上执行任务"""
        # 根据 device_id 获取设备信息和适配器
        device = self._get_device(device_id)
        if not device:
            return {"error": f"设备 {device_id} 不存在"}

        adapter = self._adapters.get(device.device_type)
        if not adapter:
            return {
                "error": f"不支持的设备类型: {device.device_type}"
            }

        # 根据任务类型分发
        handlers = {
            TaskType.BACKUP: self._handle_backup,
            TaskType.PATROL: self._handle_patrol,
            TaskType.DEPLOY: self._handle_deploy,
            TaskType.PING: self._handle_ping,
        }

        handler = handlers.get(task_type)
        if not handler:
            return {"error": f"不支持的任务类型: {task_type}"}

        return handler(adapter, params)

    def _handle_backup(self, adapter, params) -> Dict:
        config = adapter.get_config()
        return {
            "config_length": len(config),
            "timestamp": datetime.now().isoformat(),
            "config": config,
        }

    def _handle_patrol(self, adapter, params) -> Dict:
        facts = adapter.get_facts()
        interfaces = adapter.get_interfaces()
        bgp = adapter.get_bgp_peers()
        return {
            "facts": facts,
            "interfaces": interfaces,
            "bgp": bgp,
            "timestamp": datetime.now().isoformat(),
        }

    def _handle_deploy(self, adapter, params) -> Dict:
        commands = params.get("commands", [])
        results = adapter.send_commands(commands)
        adapter.save_config()
        return {
            "commands_executed": len(commands),
            "results": results,
        }

    def _handle_ping(self, adapter, params) -> Dict:
        target = params.get("target", "")
        count = params.get("count", 3)
        return adapter.ping(target, count)

    def _get_device(self, device_id: str):
        """从数据库获取设备信息"""
        # TODO: 接入 CMDB 或本地数据库
        pass

3.3 Web API 服务

#!/usr/bin/env python3
# api_service.py — REST API 服务

from flask import Flask, request, jsonify
from flask_cors import CORS
import jwt
import hashlib
import hmac
from functools import wraps

app = Flask(__name__)
CORS(app)


# =============================================
# 认证与权限
# =============================================

def token_required(f):
    """JWT 认证装饰器"""
    @wraps(f)
    def decorated(*args, **kwargs):
        token = request.headers.get("Authorization", "")
        try:
            payload = jwt.decode(
                token.split()[-1],
                app.config["SECRET_KEY"],
                algorithms=["HS256"],
            )
            request.user = payload
        except Exception:
            return jsonify({"error": "认证失败"}), 401
        return f(*args, **kwargs)
    return decorated


def role_required(roles: List[str]):
    """角色权限装饰器"""
    def decorator(f):
        @wraps(f)
        def decorated(*args, **kwargs):
            user_role = request.user.get("role", "")
            if user_role not in roles:
                return jsonify({"error": "权限不足"}), 403
            return f(*args, **kwargs)
        return decorated
    return decorator


# =============================================
# API 路由
# =============================================

@app.route("/api/v1/devices", methods=["GET"])
@token_required
def list_devices():
    """获取设备列表"""
    # TODO: 从数据库查询
    return jsonify({"devices": []})


@app.route("/api/v1/devices/<device_id>", methods=["GET"])
@token_required
def get_device(device_id):
    """获取设备详情"""
    # TODO: 从数据库查询
    return jsonify({"device": {"id": device_id}})


@app.route("/api/v1/tasks", methods=["GET"])
@token_required
def list_tasks():
    """获取任务列表"""
    status = request.args.get("status")
    tasks = scheduler.list_tasks(
        TaskStatus(status) if status else None
    )
    return jsonify({
        "tasks": [
            {
                "id": t.id,
                "name": t.name,
                "type": t.type.value,
                "status": t.status.value,
                "created_at": t.created_at,
            }
            for t in tasks
        ]
    })


@app.route("/api/v1/tasks", methods=["POST"])
@token_required
def create_task():
    """创建任务"""
    data = request.get_json()
    task = scheduler.create_task(
        task_type=TaskType(data["type"]),
        name=data["name"],
        devices=data["devices"],
        params=data.get("params", {}),
        schedule=data.get("schedule", ""),
        created_by=request.user.get("username", ""),
    )
    # 立即执行
    if data.get("execute_now", True):
        scheduler.execute_task(task.id)
    return jsonify({"task_id": task.id}), 201


@app.route("/api/v1/tasks/<task_id>", methods=["GET"])
@token_required
def get_task(task_id):
    """获取任务详情"""
    task = scheduler.get_task(task_id)
    if not task:
        return jsonify({"error": "任务不存在"}), 404
    return jsonify({
        "id": task.id,
        "name": task.name,
        "type": task.type.value,
        "status": task.status.value,
        "devices": task.devices,
        "result": task.result,
        "created_at": task.created_at,
    })


# =============================================
# 启动
# =============================================

if __name__ == "__main__":
    scheduler = TaskScheduler()
    app.config["SECRET_KEY"] = "your-secret-key"
    app.run(host="0.0.0.0", port=5000, debug=True)

四、平台关键能力

4.1 凭证安全管理

设备凭证安全管理:

  存储策略:
  ┌──────────────────────────────────────────┐
  │  加密存储(AES-256-GCM)                  │
  │  ┌─ 凭证写入数据库前加密                 │
  │  ├─ 读取时解密                           │
  │  ├─ 密钥定期轮换                         │
  │  └─ 加密密钥与数据分离存储               │
  │                                           │
  │  访问控制(最小权限)                     │
  │  ┌─ 管理员:管理所有凭证                 │
  │  ├─ 操作员:使用凭证(不能查看明文)     │
  │  ├─ 审计员:查看访问日志(不能使用)     │
  │  └─ 只读用户:无权访问凭证               │
  │                                           │
  │  审计追踪                                │
  │  ┌─ 谁在何时使用了哪个凭证               │
  │  ├─ 谁解密查看了凭证明文                 │
  │  └─ 异常访问告警                         │
  │                                           │
  │  自动轮换                                │
  │  ┌─ 定期自动修改设备密码                 │
  │  ├─ 更新后同步到凭证库                   │
  │  └─ 变更审批流程                         │
  └──────────────────────────────────────────┘

4.2 审计与合规

class AuditService:
    """审计服务"""

    def __init__(self):
        self.logs: List[AuditLog] = []

    def log(
        self,
        user: str,
        action: str,
        resource_type: str,
        resource_id: str,
        detail: str,
        status: str = "success",
        ip: str = "",
    ):
        """记录审计日志"""
        import uuid
        log = AuditLog(
            id=str(uuid.uuid4()),
            user=user,
            action=action,
            resource_type=resource_type,
            resource_id=resource_id,
            detail=detail,
            status=status,
            ip_address=ip,
        )
        self.logs.append(log)
        # 异步写入数据库和 ElasticSearch

    def query(
        self,
        user: str = "",
        action: str = "",
        start_time: str = "",
        end_time: str = "",
        limit: int = 100,
    ) -> List[AuditLog]:
        """查询审计日志"""
        results = self.logs

        if user:
            results = [l for l in results if l.user == user]
        if action:
            results = [l for l in results if l.action == action]
        if start_time:
            results = [
                l for l in results if l.timestamp >= start_time
            ]
        if end_time:
            results = [
                l for l in results if l.timestamp <= end_time
            ]

        return results[:limit]

    def generate_audit_report(
        self, start_date: str, end_date: str
    ) -> Dict:
        """生成审计报告"""
        logs = self.query(
            start_time=start_date, end_time=end_date
        )

        return {
            "period": f"{start_date} ~ {end_date}",
            "total_operations": len(logs),
            "by_user": self._group_by("user", logs),
            "by_action": self._group_by("action", logs),
            "failed_operations": len(
                [l for l in logs if l.status == "failed"]
            ),
        }

    def _group_by(
        self, key: str, logs: List[AuditLog]
    ) -> Dict:
        result = {}
        for log in logs:
            val = getattr(log, key, "unknown")
            result[val] = result.get(val, 0) + 1
        return result

五、部署与运维

5.1 容器化部署

# docker-compose.yml — 平台容器化部署

version: '3.8'

services:
  api-gateway:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - web-ui
      - api-service

  web-ui:
    build: ./frontend
    ports:
      - "3000:3000"
    environment:
      - API_URL=http://api-service:5000

  api-service:
    build: ./backend
    ports:
      - "5000:5000"
    environment:
      - DB_HOST=mysql
      - REDIS_HOST=redis
      - RABBITMQ_HOST=rabbitmq
    depends_on:
      - mysql
      - redis
      - rabbitmq

  task-worker:
    build: ./backend
    command: python worker.py
    environment:
      - DB_HOST=mysql
      - REDIS_HOST=redis
    depends_on:
      - mysql
      - redis
      - rabbitmq

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root123
      MYSQL_DATABASE: network_ops
    volumes:
      - mysql_data:/var/lib/mysql

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

  rabbitmq:
    image: rabbitmq:3-management
    ports:
      - "5672:5672"
      - "15672:15672"

  influxdb:
    image: influxdb:2.0
    volumes:
      - influxdb_data:/var/lib/influxdb2

  elasticsearch:
    image: elasticsearch:8.10
    environment:
      - discovery.type=single-node
    volumes:
      - es_data:/usr/share/elasticsearch/data

volumes:
  mysql_data:
  influxdb_data:
  es_data:

5.2 平台运维指标

平台自身运维指标:

  1. 可用性
  ┌─ 平台服务可用率(目标:99.9%)
  ├─ API 响应时间(P99 < 2s)
  └─ 任务执行成功率(目标:> 99%)

  2. 容量
  ┌─ 支持的设备数量
  ├─ 并发任务数
  ├─ 日处理任务量
  └─ 存储使用量

  3. 安全性
  ┌─ 认证失败次数
  ├─ 权限越界尝试
  ├─ 凭证访问审计
  └─ API 异常请求

  4. 用户满意度
  ┌─ 任务完成时间
  ├─ 用户自助完成率
  ├─ 用户反馈评分
  └─ 培训通过率

六、最佳实践总结

自动化运维平台建设建议:

  1. 先有脚本,再有平台
  ┌─ 不要一开始就追求大而全的平台
  ├─ 先把高频、重复的操作脚本化
  ├─ 验证脚本的可靠性和效率
  └─ 再把优秀脚本迁移到平台

  2. 平台要"好用"而不是"强大"
  ┌─ UI 要简单直观(面向运维工程师)
  ├─ 常用操作一键完成
  ├─ 减少点击次数
  └─ 错误提示清晰明了

  3. 开放生态
  ┌─ 提供标准 API 接口
  ├─ 支持自定义插件
  ├─ 与其他系统集成(CMDB/工单/监控)
  └─ 数据开放可导出

  4. 渐进式建设
  ┌─ 第一个版本只做配置备份 + 巡检
  ├─ 第二个版本加入变更部署
  ├─ 第三个版本加入合规检查
  └─ 持续迭代,从不完美开始

  5. 运营度量
  ┌─ 跟踪平台使用率
  ├─ 跟踪自动化覆盖率
  ├─ 收集用户反馈
  └─ 展示自动化价值(节省人天)

下篇预告:第328篇 — 零接触部署与自动上线,讲解零接触部署(ZTP)的实现原理和企业落地方法。


下篇预告:第328篇 — 零接触部署与自动上线,讲解零接触部署(ZTP)的实现原理和企业落地方法。