# 运维总监审查报告

**项目**: Nexus 6.0 — 服务器运维管理平台
**审查时间**: 2026-05-21（2026-05-22 勘误更新）
**审查范围**: deploy/ 全部配置、docs/project/deploy.md 部署指南、server/config.py 配置项、server/main.py 启动配置、server/background/ 后台任务、server/infrastructure/ 基础设施层

> **勘误说明 (2026-05-22)**: 本报告部分问题在审查后已修复，已在相应位置标注 ✅已修复。
**审查人**: 运维总监

---

## 1. 部署架构总览

```
                         ┌──────────────┐
                         │   Let's Encrypt │  (生产环境 HTTPS)
                         │   (宝塔面板)    │
                         └──────┬───────┘
                                │
                         ┌──────▼───────┐
                         │    Nginx     │  ← 反向代理 + 静态文件 + PHP-FPM
                         │  (80/443)    │
                         └──┬───────┬──┘
                            │       │
              ┌─────────────▼┐  ┌───▼──────────┐
              │  FastAPI     │  │  PHP-FPM     │
              │  :8600       │  │  (旧前端)    │
              │  (4 workers) │  │              │
              └──┬───────┬───┘  └──────────────┘
                 │       │
          ┌──────▼──┐ ┌──▼──────────┐
          │  Redis  │ │  MySQL 8.4  │
          │  (实时)  │ │  (历史)     │
          └─────────┘ └─────────────┘

3层守护机制:
  Layer 1: Supervisor ─── Python崩溃 → 自动重启
  Layer 2: self_monitor ── 30s检查Redis/MySQL/WS → Telegram告警
  Layer 3: health_monitor ── 1min HTTP GET /health → 连续3次失败 → restart + Telegram
```

**整体评价**: 架构设计合理，三层守护覆盖全面，数据流清晰（Agent → Redis → 前端直读 → 10min → MySQL）。核心问题在于**配置一致性偏差**、**资源预留过大**、**安全防护薄弱**。

---

## 2. Supervisor配置评估

**文件**: `deploy/nexus.conf`

### 优点
- `autorestart=true` + `startretries=10` — 崩溃自动重启，最多重试10次
- `stopsignal=INT` — SIGINT发送给uvicorn，可实现优雅关闭
- `stopwaitsecs=10` — 给进程10秒处理完当前请求
- 日志自动轮转：50MB切割，保留5个备份
- 不硬编码密钥（依赖.env文件）— 安全合规

### 问题

| 严重度 | 问题 | 说明 |
|--------|------|------|
| **高** | 单Worker运行 (`--workers` 缺失) | `nexus.conf` 只用1个worker，但文档 `deploy.md` 的示例配置写着 `--workers 4`。两者不一致。实际线上用1个worker处理所有请求（包括心跳、WebSocket、API），单点过载风险。详见第8节。 |
| **中** | `startsecs=3` 偏短 | 如果应用启动耗时超过3秒（如数据库表较多时init_db可能较慢），Supervisor会误判为启动失败。建议改为 `startsecs=10` |
| **中** | `user=root` 运行 | Python进程以root权限运行。如果FastAPI出现RCE漏洞，攻击者可获得服务器完全控制权。建议创建专用系统用户 `nexus` |
| **低** | 缺少 `redirect_stderr=true` | 当前将stderr和stdout分别写入两个文件。设为 `redirect_stderr=true` 可统一日志管理 |
| **低** | 无 `environment` 变量 | 虽然依赖.env文件启动，但指定 `PYTHONPATH` 环境变量可以避免潜在的导入路径问题 |

### 建议修复

```ini
[program:nexus]
command=/www/wwwroot/api.synaglobal.vip/venv/bin/uvicorn server.main:app --host 0.0.0.0 --port 8600 --workers 4
directory=/www/wwwroot/api.synaglobal.vip
user=nexus                          # 改为专用系统用户
autostart=true
autorestart=true
startretries=10
startsecs=10                        # 加长启动验证时间
stopwaitsecs=20                     # 加长优雅关闭等待
stopsignal=INT
environment=
    PATH="/www/wwwroot/api.synaglobal.vip/venv/bin:%(ENV_PATH)s",
    PYTHONPATH="/www/wwwroot/api.synaglobal.vip:%(ENV_PATH)s"
redirect_stderr=true
stdout_logfile=/var/log/nexus/nexus.log
stdout_logfile_maxbytes=50MB
stdout_logfile_backups=5
```

---

## 3. Nginx配置评估

**文件**: `deploy/nginx_172.31.170.47.conf`（WSL环境）

### 优点
- `upstream nexus_api { keepalive 32; }` — 使用连接池，减少TCP握手开销
- 清晰的全域名/API/WebSocket/静态文件分离路由
- 敏感文件保护：`location ~ /\. { deny all; }` + `location ~ /data/config\.php$ { deny all; }`
- 静态资源7天缓存：`expires 7d; add_header Cache-Control "public, immutable";`
- WebSocket代理配置正确：`Upgrade` / `Connection` 头、`proxy_read_timeout 180s`
- `client_max_body_size 100m` — 适合文件管理功能

### 问题

| 严重度 | 问题 | 说明 |
|--------|------|------|
| **高** | 无HTTPS配置 | WSL环境的Nginx配置只有HTTP 80。对于运维管理平台，即使在WSL内网也应启用HTTPS。至少应配置自签名证书。 |
| **高** | 无安全响应头 | 缺少 `X-Content-Type-Options`、`X-Frame-Options`、`Content-Security-Policy`、`Strict-Transport-Security` 等关键安全头 |
| **中** | 无速率限制 | API端点和登录接口缺少 `limit_req` 配置，存在暴力破解和DDoS风险 |
| **中** | 无请求体大小限制 | API代理未设置 `client_max_body_size`，但全局设置了100m，而API代理不应允许这么大的请求体 |
| **中** | `location /health` 缺少转发头 | 健康检查代理未设置 `X-Real-IP` 等头信息（虽然对健康检查影响不大，但不一致） |
| **低** | 无压缩配置 | 未启用gzip/brotli压缩，JSON API响应可能浪费带宽 |
| **低** | 无日志格式自定义 | 使用默认Combined格式，缺少上游响应时间等关键指标 |
| **低** | WSL配置和生产配置分离 | 存在两份Nginx配置（WSL + 生产），但生产配置在deploy.md中而非独立文件 |

### 生产环境安全头建议（补充到Nginx配置）

```nginx
# 在 server block 中添加
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# API速率限制
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;
location /api/ {
    limit_req zone=api burst=50 nodelay;
    # ... 现有配置
}

# 登录接口限制（更严格）
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/s;
location /api/auth/ {
    limit_req zone=login burst=10 nodelay;
    # ...
}
```

---

## 4. 三层守护机制评估

### 4.1 Layer 1: Supervisor

| 维度 | 评估 | 说明 |
|------|------|------|
| 崩溃检测 | ✅ | 进程退出时，Supervisor即时感知 |
| 自动重启 | ✅ | `autorestart=true` + `startretries=10` |
| 优雅关闭 | ✅ | SIGINT + 10秒等待 |
| 启动验证 | ⚠️ | `startsecs=3` 可能偏短，建议10秒 |
| 频繁崩溃防护 | ⚠️ | 10次重试后停止，但未配置 `startretryinterval`（默认0=无间隔），可能瞬间耗尽重试次数 |

### 4.2 Layer 2: Python self_monitor

| 维度 | 评估 | 说明 |
|------|------|------|
| 检查间隔 | ✅ | 每30秒 |
| Redis检测 | ✅ | 返回None时告警 |
| MySQL检测 | ✅ | 执行SELECT 1 |
| WebSocket清理 | ✅ | 移除过期客户端 |
| **自动修复** | ❌ | **仅发送Telegram告警，不做任何修复操作** |
| 连续失败降级 | ❌ | 无降级逻辑，每次失败都发告警（可能造成告警轰炸） |

**严重问题**: Layer 2只有"检测+告警"能力，没有任何自动修复动作。例如Redis连接丢失后，self_monitor只是发一条Telegram消息，不做重连尝试。虽然 `get_redis()` 函数在检测到连接丢失后会置 `_redis_pool = None`，下次调用会尝试重连，但self_monitor自身的Redis检测逻辑并未利用这个机制重连以提高成功率。

### 4.3 Layer 3: Shell health_monitor.sh

| 维度 | 评估 | 说明 |
|------|------|------|
| 独立性 | ✅ | 纯Shell脚本，不依赖Python |
| 连续失败阈值 | ✅ | 3次连续失败才触发重启 |
| 重启后验证 | ✅ | restart后sleep 5秒，再验证 |
| Telegram通知 | ✅ | 失败/恢复/人工介入三类通知 |
| 配置来源 | ✅ | 从config.php或.env读取Telegram凭证 |

**严重问题**: 循环重启风暴风险。当连续3次失败后，脚本调用 `supervisorctl restart`，如果重启后验证仍然失败，**不会重置FAIL计数器**。这意味着每次cron执行都会再次触发 `supervisorctl restart`（因为FAIL计数器保持在3+）。这会造成每分钟一次的循环重启风暴。正确的做法应该是增加"冷却期"机制——例如连续N次重启失败后，停止自动重启并仅发送人类介入通知。

### 4.4 三层联动评估

```
场景: Python进程崩溃
  Layer 1: Supervisor 即时检测 → 秒级重启 ✓
  Layer 2: 如果Python在30s内恢复，self_monitor不会触发告警 ✓
  Layer 3: 如果1分钟内恢复，health_monitor不会触发（FAIL计数器不会达到3）✓

场景: Python进程僵死（不崩溃但无响应）
  Layer 1: 进程未退出，Supervisor不触发 ✗
  Layer 2: self_monitor只要asyncio事件循环还能运行就能检测，但如果整个进程僵死则不行 ✗
  Layer 3: HTTP GET /health超时，3次后触发supervisorctl restart ✓

场景: Redis宕机
  Layer 1: 不触发（Python进程正常运行）✓
  Layer 2: 30s内检测到Redis丢失，发Telegram告警（但不尝试修复）⚠️
  Layer 3: 不触发（/health端点仍能响应，只是标记redis=unavailable）✓（正确）

场景: MySQL连接池耗尽
  Layer 1: 不触发 ✓
  Layer 2: 30s内检测到MySQL异常，发Telegram告警 ⚠️
  Layer 3: 不触发（HTTP健康检查返回python=ok）✓（正确）
```

**评估**: 三层守护的覆盖域合理，各层分工明确。核心缺失是 Layer 2 缺乏自动修复能力。循环重启风暴是需要优先修复的风险。

---

## 5. 运维风险点

### 5.1 高风险

| # | 风险 | 影响 | 建议 |
|---|------|------|------|
| R1 | **单Worker生产运行** | 一个worker处理所有WebSocket连接、心跳接收、API请求。一旦某个请求阻塞（如慢查询），所有其他请求（包括心跳）都会排队等待。后果：Agent心跳超时、WebSocket断连、健康检查失败触发误重启。 | 使用 `--workers 4` 并启用 `--ws max_size=16` 等uvicorn优化参数 |
| R2 | **循环重启风暴** | health_monitor在连续3次失败后，每分钟执行一次 `supervisorctl restart`，无论重启是否成功。如果根本原因（如配置错误、端口冲突）未解决，会造成每分钟的反复重启循环。 | 增加冷却期：连续N次重启失败后，停止自动重启，仅发送"需人工介入"告警，等待操作员处理 |
| R3 | **Root运行所有进程** | Supervisor配置 `user=root`，Python进程以root身份运行。如果FastAPI存在任意代码执行漏洞（如依赖库CVE），攻击者可获得服务器完全控制权。 | 创建 `nexus` 系统用户，最小权限原则 |
| R4 | **CORS全开放** | `allow_origins=["*"]` | ✅**已修复** — 改为 `settings.CORS_ORIGINS.split(",")` 从 `NEXUS_CORS_ORIGINS` 环境变量读取白名单 |

### 5.2 中风险

| # | 风险 | 影响 | 建议 |
|---|------|------|------|
| R5 | **数据库连接池监控** | ~~`DB_POOL_SIZE=100` + `DB_MAX_OVERFLOW=100`~~ → 已修正为 `DB_POOL_SIZE=160` + `DB_MAX_OVERFLOW=120`（基于 MySQL max_connections=400）。install.php 已实现自动计算：`poolSize = max(20, maxConn * 0.4)`。 | ✅ install.php 自动计算已落地。建议增加运行时连接池使用率监控告警 |
| R6 | **健康检查端口固定** | `HEALTH_URL` 硬编码为 `http://127.0.0.1:8600/health`，如果后续端口变更需同时修改脚本。 | 改为从配置文件读取或作为脚本参数传入 |
| R7 | **异常处理宽泛** | `load_settings_from_db` 中 `except Exception` 静默吞掉所有异常，返回0。如果settings表结构变更或数据类型异常，不会被及时发现。 | 区分可忽略异常和需要告警的异常，对关键错误发送运维告警 |
| R8 | **无日志集中管理** | Supervisor日志写在 `/var/log/nexus/`，Nginx日志在 `/www/wwwlogs/`，Python日志可能分散到stdout。无统一日志中心，故障排查需要手动查看多个位置。 | 使用 systemd-journald 或 rsyslog 统一收集，考虑接入 ELK/Loki |
| R9 | **文档与配置不一致** | `deploy.md` 中Nexus Supervisor配置示例使用 `--workers 4`，`startretries`, `startsecs` 等未列出。实际 `nexus.conf` 只有单worker。部署新环境时可能按文档配置4 workers，但本地nexus.conf是1 worker。 | 同步文档和实际配置，确保部署指南与部署文件一致 |
| R10 | **无数据库备份策略** | 项目中未找到自动备份脚本或策略。MySQL存储心跳历史、告警配置、服务器信息，一旦数据丢失将造成严重后果。 | 实现 `mysqldump` 或 `XtraBackup` 每日备份 + 7天轮转 |

### 5.3 低风险

| # | 风险 | 影响 | 建议 |
|---|------|------|------|
| R11 | 无监控面板 | 无Grafana/Prometheus等可视化监控 | 接入node_exporter + Prometheus |
| R12 | 无容量规划 | 心跳数据量增长后MySQL性能未知 | 在1000+服务器规模下进行压测 |
| R13 | WSL无HTTPS | 管理流量明文传输 | 至少配置自签名证书 |
| R14 | 缺少staging环境 | 变更直接上生产 | 保持WSL作为staging环境 |

---

## 6. 改进建议

### P0 — 立即修复（影响生产稳定性）

1. **Supervisor启用多Worker**
   ```bash
   # nexus.conf 中 command 添加 --workers 4
   command=/www/wwwroot/api.synaglobal.vip/venv/bin/uvicorn server.main:app --host 0.0.0.0 --port 8600 --workers 4
   ```
   **注意**: 多Worker下WebSocket连接会分配给不同进程，需要使用Redis Pub/Sub做跨进程WebSocket广播。如果当前WebSocket实现（`connected_clients` 是内存集合）不支持跨进程，则需评估。推荐启用 `--ws ping_interval 20`。

2. **health_monitor.sh 增加重启冷却**
   ```bash
   # 在重启失败后增加冷却文件
   COOLDOWN_FILE="/tmp/nexus_restart_cooldown"
   COOLDOWN_PERIOD=10  # 10分钟内不再自动重启
   
   # 在重启失败分支中
   if [ "$FAIL" -ge "$MAX_FAIL" ]; then
       # 检查冷却期
       COOLDOWN=$(cat "$COOLDOWN_FILE" 2>/dev/null || echo "0")
       NOW=$(date +%s)
       if [ "$COOLDOWN" -gt "$NOW" ]; then
           send_telegram "⚠️ Nexus重启冷却中（跳过自动重启）"
           exit 0
       fi
       # ... 执行重启 ...
       # 如果重启失败，设置10分钟冷却
       echo "$((NOW + COOLDOWN_PERIOD * 60))" > "$COOLDOWN_FILE"
   fi
   ```

3. **创建专用服务用户**
   ```bash
   sudo useradd -r -s /usr/sbin/nologin -M nexus
   sudo chown -R nexus:nexus /www/wwwroot/api.synaglobal.vip
   sudo chmod 640 /www/wwwroot/api.synaglobal.vip/.env  # 限制.env访问
   ```

### P1 — 短期改进（本周内）

4. ~~**调优数据库连接池**~~ → 已修正：config.py 默认值已调整为 `DB_POOL_SIZE=160` + `DB_MAX_OVERFLOW=120`（基于 MySQL max_connections=400）。install.php 自动计算逻辑在生产环境会覆盖 .env 值。

5. **Nginx增加安全头和生产配置文件**
   - 从 `deploy.md` 中提取生产Nginx配置为独立文件 `deploy/nginx_production.conf`
   - 增加安全响应头、速率限制

6. **self_monitor增加自动修复逻辑**
   - Redis丢失时：主动调用 `get_redis()` 触发重连（现有代码已支持，但需确保self_monitor层面触发）
   ```python
   # Redis check — 主动触发重连
   redis = await get_redis()
   if redis is None and settings.REDIS_URL:
       await send_telegram_system_alert("⚠️ Redis连接丢失，正在尝试重连...")
       _redis_pool = None  # 强制下次get_redis重新创建连接
       redis = await get_redis()
       if redis:
           await send_telegram_system_alert("✅ Redis已重新连接")
   ```

### P2 — 中期规划（本月内）

7. **实现数据库自动备份**
   ```bash
   # deploy/backup.sh
   #!/bin/bash
   BACKUP_DIR="/var/backups/nexus/mysql"
   mkdir -p "$BACKUP_DIR"
   mysqldump --single-transaction --routines --events Nexus | gzip > "$BACKUP_DIR/nexus_$(date +%Y%m%d).sql.gz"
   find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete  # 保留30天
   ```
   配置cron：`0 3 * * * /www/wwwroot/api.synaglobal.vip/deploy/backup.sh`

8. **接入运维监控**
   - 安装 `node_exporter` + `Prometheus` + `Grafana`
   - 导入Nexus `/health` 端点到Prometheus
   - 设置关键告警规则：CPU>90%、磁盘>85%、Python进程Down、MySQL连接数>150

9. **Redis Sentinel/Cluster高可用**
   - 当前单点Redis：一旦宕机，所有实时心跳数据和告警状态丢失
   - 建议部署Redis Sentinel实现自动故障切换，或至少配置Redis AOF持久化

### P3 — 长期规划

10. **容器化部署**（Docker Compose/K8s）
    - 将FastAPI/Nginx/Redis/PHP容器化，消除环境不一致问题
    - 便于水平扩展

11. **灰度发布策略**
    - 实现蓝绿部署或canary发布机制，减少变更风险

---

## 7. 总结

Nexus的部署架构在整体设计上是稳健的，三层守护机制覆盖了进程崩溃、服务僵死、依赖服务故障等常见场景。然而，存在几个**必须立即处理**的风险：

1. **单worker瓶颈** — 这是当前最致命的性能隐患，2000+服务器的Agent心跳（60秒间隔）单worker可能无法承受
2. **循环重启风暴** — health_monitor脚本在连续失败后每分钟执行restart，可能造成服务抖动
3. **数据库连接池超配** — 100+100的池大小已经超过MySQL 151的默认上限，多worker后问题更严重
4. **Root运行和服务用户缺失** — 安全基线问题
5. **CORS全开** — ✅已修复：`NEXUS_CORS_ORIGINS` 环境变量白名单

短期改进优先级：
1. 先调低数据库连接池（写死的风险 > 配置不一致的风险）
2. 再修复health_monitor冷却期
3. 然后评估多worker+WebSocket兼容性后启用
4. 最后补充Nginx安全配置

以上改进建议的预计工时：P0项约2小时，P1项约4小时，P2项约2天（含Grafana部署调试）。
