c9a99f4fb3
代码修复: - web/agent/agent.py: datetime.utcnow() → datetime.now(timezone.utc) - requirements.txt: 移除 paramiko==3.5.0 (已无活跃引用) - 删除 server/infrastructure/ssh/pool.py (DEPRECATED, 无引用) 连接池三层对齐 (config.py/.env.example/session.py): - DB_POOL_SIZE 100→160, DB_MAX_OVERFLOW 100→120 - 基于 MySQL max_connections=400, install.php 公式 文档修复 (21项): - P0: 硬编码域名/IP替换为配置变量占位符 - P1: 10个过时设计文档加归档标注 (引用旧文件结构) - P2: step-3-webssh报告 paramiko引用修正 - 6份审查报告连接池参数勘误 100/100→160/120 - ECC安全报告 EC5 datetime.utcnow 标记已修复 - docs/README.md 文档索引重写 - docs/memory/mem_nexus_overview.md 移除硬编码凭证 - docs/project/tech-stack-inventory.md paramiko标记已移除 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
352 lines
18 KiB
Markdown
352 lines
18 KiB
Markdown
# 运维总监审查报告
|
||
|
||
**项目**: 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部署调试)。
|