跳到主要内容

服务器巡检平台

串联前七阶段能力:资产清单 → 并发 SSH 采集 → 规则判定 → 结果入库 → FastAPI 查询 / 定时调度。目标是替代「每台机器手工登录看磁盘、看进程」的重复劳动。


架构思路

┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Scheduler │────▶│ Executor │────▶│ Reporter │
│ APScheduler │ │ ThreadPool │ │ SQLite/JSON │
│ / Cron │ │ + Paramiko │ │ + 告警推送 │
└─────────────┘ └──────────────┘ └─────────────┘
▲ ▲
│ │
┌──────┴──────┐ ┌──────┴──────┐
│ FastAPI │ │ assets.yaml │
│ 手动触发 │ │ 主机清单 │
└─────────────┘ └─────────────┘
层次职责
配置层assets.yaml:主机、端口、标签、巡检项开关
采集层SSH 执行只读命令,超时与重试
规则层磁盘 >85%、load > CPU 核数、服务非 active
存储层每次巡检一条 run 记录 + 每台主机 detail
接口层触发巡检、查历史、导出 CSV

目录与模块划分

server-inspection/
├── config/ # settings.py, assets.yaml
├── inspection/ # collectors, rules, runner
├── storage/ # repository.py (SQLite)
├── api/main.py
└── scheduler/jobs.py

核心代码骨架

assets.yaml

hosts:
- name: web-01
ip: 10.0.1.11
tags: [web, prod]
checks: [disk, load, nginx]
- name: db-01
ip: 10.0.1.21
tags: [db, prod]
checks: [disk, load, mysql]

collectors.py

"""单主机采集:返回结构化 dict,失败不抛到线程池外。"""
import paramiko
from dataclasses import dataclass

@dataclass
class HostTarget:
name: str
ip: str
checks: list[str]

def collect_one(host: HostTarget, ssh_user: str, key_path: str, timeout: int = 15) -> dict:
result = {"host": host.name, "ip": host.ip, "ok": False, "metrics": {}, "errors": []}
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
client.connect(host.ip, username=ssh_user, key_filename=key_path, timeout=timeout)
if "disk" in host.checks:
_, stdout, _ = client.exec_command("df -h / | tail -1", timeout=10)
result["metrics"]["disk_root"] = stdout.read().decode().strip()
if "load" in host.checks:
_, stdout, _ = client.exec_command("cat /proc/loadavg", timeout=10)
result["metrics"]["loadavg"] = stdout.read().decode().strip()
result["ok"] = True
except Exception as e:
result["errors"].append(str(e))
finally:
client.close()
return result

runner.py

"""线程池批量巡检。"""
from concurrent.futures import ThreadPoolExecutor, as_completed
from inspection.collectors import collect_one, HostTarget
from inspection.rules import evaluate
from storage.repository import save_run

def run_inspection(hosts: list[HostTarget], max_workers: int = 20) -> str:
details = []
with ThreadPoolExecutor(max_workers=max_workers) as pool:
futures = {pool.submit(collect_one, h, "ops", "/home/ops/.ssh/id_rsa"): h for h in hosts}
for fut in as_completed(futures):
row = fut.result()
row["violations"] = evaluate(row) # rules.py:返回违规列表
details.append(row)
run_id = save_run(details)
return run_id

api/main.py

from fastapi import BackgroundTasks, Depends, FastAPI
from scheduler.jobs import load_hosts
from inspection.runner import run_inspection
# from api.auth import verify_api_key # 复用第七阶段认证

app = FastAPI(title="Server Inspection Platform")

@app.post("/api/v1/inspection/run")
def trigger_run(background_tasks: BackgroundTasks): # dependencies=[Depends(verify_api_key)]
hosts = load_hosts()
background_tasks.add_task(run_inspection, hosts)
return {"message": "巡检已提交"}

@app.get("/api/v1/inspection/runs/{run_id}")
def get_run(run_id: str):
# repository.get_run(run_id)
return {"run_id": run_id, "status": "done"}

规则示例(rules.py)

def evaluate(row: dict) -> list[str]:
violations = []
disk = row.get("metrics", {}).get("disk_root", "")
if disk and _parse_use_percent(disk) > 85:
violations.append("disk_root > 85%")
load = row.get("metrics", {}).get("loadavg", "")
if load:
one_min = float(load.split()[0])
if one_min > 8: # 可按 CPU 核数动态化
violations.append(f"load1={one_min} 过高")
return violations

扩展点

扩展做法
新巡检项collectors.py 增加命令块,并在 assets.yamlchecks 注册
新告警通道reporter/notify.py 实现 send(run_id),对接钉钉 / 邮件
多租户assets.yamlteam 分组,API 带 team 参数过滤
大规模主机 >500 时分片 + 消息队列;Executor 改为 Celery worker
可视化导出 CSV 给 Grafana / 自建前端;或写入 Prometheus Pushgateway
落地顺序

先跑通「10 台主机 + SQLite + 手动 API 触发」,再加 Cron 与告警。