mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
refactor: 拆分 gateway 单体为独立 crate,新增 systemd 部署方案
将 gateway 内部的 model-fetch、provider-transport、scheduler-core、 usage-runtime、video-tasks-core 模块提取为独立 crate;重构 gateway 内部模块结构(state/router/cache/data/query 等);移除大量遗留模块 文件;新增 systemd 二进制部署骨架及相关文档;更新前端 usage 相关 API 和组件。
This commit is contained in:
241
docs/deploy/logging.md
Normal file
241
docs/deploy/logging.md
Normal file
@@ -0,0 +1,241 @@
|
||||
# Aether Logging Guide
|
||||
|
||||
目标:
|
||||
|
||||
- 明确 `stdout`、文件日志和 `journald` 的职责边界
|
||||
- 给 `aether-gateway` / `aether-proxy` 提供统一的查看与排障手册
|
||||
- 避免重复轮转、重复采集和“日志写了但不知道去哪找”的问题
|
||||
|
||||
## 1. 组合策略
|
||||
|
||||
推荐组合不是“二选一”,而是按运行方式选一套固定策略:
|
||||
|
||||
| 场景 | 推荐配置 | 说明 |
|
||||
|------|----------|------|
|
||||
| 本地开发 | `stdout` | 直接看终端输出,最简单 |
|
||||
| Docker Compose 开发 | `stdout` | 交给容器日志驱动,避免和文件日志重复 |
|
||||
| systemd 单机生产 | `both` | `journald` 负责最近事件,文件日志负责留痕和 grep |
|
||||
| 裸机临时排障 | `file` 或 `both` | 需要长时间保留时用文件日志 |
|
||||
|
||||
规则:
|
||||
|
||||
- `stdout` 进入容器日志驱动或 `journald`
|
||||
- `file` 由应用自己轮转和清理
|
||||
- `both` 适合宿主机生产
|
||||
- 不要再给同一目录额外叠 `logrotate`
|
||||
|
||||
## 2. 关键配置
|
||||
|
||||
### aether-gateway
|
||||
|
||||
- `AETHER_LOG_DESTINATION=stdout|file|both`
|
||||
- `AETHER_LOG_FORMAT=pretty|json`
|
||||
- `AETHER_LOG_DIR=/var/log/aether`
|
||||
- `AETHER_LOG_ROTATION=hourly|daily`
|
||||
- `AETHER_LOG_RETENTION_DAYS=7`
|
||||
- `AETHER_LOG_MAX_FILES=30`
|
||||
|
||||
### aether-proxy
|
||||
|
||||
- `AETHER_PROXY_LOG_DESTINATION=stdout|file|both`
|
||||
- `AETHER_PROXY_LOG_JSON=false|true`
|
||||
- `AETHER_PROXY_LOG_DIR=/var/log/aether-proxy`
|
||||
- `AETHER_PROXY_LOG_ROTATION=hourly|daily`
|
||||
- `AETHER_PROXY_LOG_RETENTION_DAYS=7`
|
||||
- `AETHER_PROXY_LOG_MAX_FILES=30`
|
||||
|
||||
注意:
|
||||
|
||||
- `file` / `both` 必须同时给出 `*_LOG_DIR`
|
||||
- Docker Compose 默认建议 `stdout`
|
||||
- systemd 默认建议 `both`
|
||||
|
||||
## 3. 日志落点
|
||||
|
||||
### systemd
|
||||
|
||||
`aether-gateway`
|
||||
|
||||
- `journalctl -u aether-gateway`
|
||||
- 文件日志目录默认建议 `/var/log/aether`
|
||||
|
||||
`aether-proxy`
|
||||
|
||||
- `journalctl -u aether-proxy`
|
||||
- 文件日志目录默认建议 `/var/log/aether-proxy`
|
||||
|
||||
### Docker Compose
|
||||
|
||||
- `docker compose logs -f gateway`
|
||||
- `docker compose logs -f aether-proxy`
|
||||
- 如果显式启用了 `file/both`,再去看容器内挂载出来的日志目录
|
||||
|
||||
## 4. 常用排障命令
|
||||
|
||||
### gateway
|
||||
|
||||
```bash
|
||||
sudo systemctl status aether-gateway --no-pager
|
||||
sudo journalctl -u aether-gateway -n 200 --no-pager
|
||||
sudo tail -n 200 /var/log/aether/aether-gateway.*.log
|
||||
sudo rg "trace_id|request_id|error" /var/log/aether/aether-gateway.*.log
|
||||
```
|
||||
|
||||
### proxy
|
||||
|
||||
```bash
|
||||
sudo systemctl status aether-proxy --no-pager
|
||||
sudo journalctl -u aether-proxy -n 200 --no-pager
|
||||
sudo tail -n 200 /var/log/aether-proxy/aether-proxy.*.log
|
||||
sudo rg "node_id|server|error" /var/log/aether-proxy/aether-proxy.*.log
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
docker compose logs -f gateway
|
||||
docker compose logs -f aether-proxy
|
||||
```
|
||||
|
||||
## 5. 故障定位顺序
|
||||
|
||||
1. 先看 `systemctl status`,判断是不是进程本身没起来
|
||||
2. 再看 `journalctl`,确认启动报错、权限报错、配置报错
|
||||
3. 如果启用了 `both`,再查文件日志做结构化检索
|
||||
4. 按 `trace_id` / `request_id` 串联同一请求
|
||||
5. 对长时间问题看文件日志,对最近故障先看 `journald`
|
||||
|
||||
## 6. 常见问题
|
||||
|
||||
### 没有生成文件日志
|
||||
|
||||
优先检查:
|
||||
|
||||
- `*_LOG_DESTINATION` 是否是 `file` 或 `both`
|
||||
- `*_LOG_DIR` 是否已设置
|
||||
- systemd 的 `LogsDirectory=` 是否生效
|
||||
- 进程用户对日志目录是否有写权限
|
||||
|
||||
### 日志重复
|
||||
|
||||
通常是以下原因之一:
|
||||
|
||||
- 容器里开了 `both`,同时又在看 `docker logs`
|
||||
- 宿主机上既开了应用文件日志,又配了外部 `logrotate` / 采集器重复收集同一路径
|
||||
|
||||
处理方式:
|
||||
|
||||
- Docker 开发环境保持 `stdout`
|
||||
- systemd 生产环境用 `both`
|
||||
- 同一文件目录不要再叠第二套轮转器
|
||||
|
||||
### 磁盘持续增长
|
||||
|
||||
先看:
|
||||
|
||||
- `*_LOG_RETENTION_DAYS`
|
||||
- `*_LOG_MAX_FILES`
|
||||
- 是否误开 `hourly` 且保留天数过大
|
||||
- 是否有旧目录不再被应用清理
|
||||
|
||||
### grep 不到请求
|
||||
|
||||
优先用这些字段:
|
||||
|
||||
- `trace_id`
|
||||
- `request_id`
|
||||
- `node_id`
|
||||
- `route_class`
|
||||
- `execution_path`
|
||||
|
||||
如果 `stdout` 噪声太大,就切到文件日志查。
|
||||
|
||||
## 7. 脱敏边界
|
||||
|
||||
常规日志不应该直接出现:
|
||||
|
||||
- `Authorization`
|
||||
- `x-api-key`
|
||||
- `x-goog-api-key`
|
||||
- 完整请求体
|
||||
- 完整响应体
|
||||
- 完整 `report_context`
|
||||
|
||||
当前约定是:
|
||||
|
||||
- body 只记录摘要,例如字节数和 hash
|
||||
- 真正的原文只允许进入显式调试通道,且必须先过 redaction
|
||||
|
||||
## 8. 关键事件名
|
||||
|
||||
当前先固定这批事件名,后续新增事件优先复用同一命名风格:
|
||||
|
||||
| event_name | log_type | 用途 |
|
||||
|------------|----------|------|
|
||||
| `http_request_started` | `access` | 请求进入 gateway |
|
||||
| `http_request_completed` | `access` | 请求正常完成 |
|
||||
| `http_request_failed` | `access` | 请求以 5xx 结束 |
|
||||
| `local_sync_candidate_retry_scheduled` | `event` | 本地 sync 候选命中可重试结果,调度下一个候选 |
|
||||
| `local_stream_candidate_retry_scheduled` | `event` | 本地 stream 候选命中可重试状态,调度下一个候选 |
|
||||
| `local_openai_chat_candidates_exhausted` | `event` | 本地 OpenAI Chat 路径耗尽全部候选 |
|
||||
| `local_core_finalize_fallback_raw_response_body` | `event` | 本地核心 finalize 无法映射成结构化响应,退回原始 body |
|
||||
| `local_core_finalize_missing_error_report_mapping` | `event` | 本地核心 finalize 缺少错误上报映射 |
|
||||
| `local_core_finalize_missing_success_report_mapping` | `event` | 本地核心 finalize 缺少成功上报映射 |
|
||||
| `usage_terminal_settlement_failed` | `event` | usage 终态直写后结算失败 |
|
||||
| `admin_billing_preset_applied` | `audit` | 管理员应用计费预设 |
|
||||
| `admin_system_settings_updated` | `audit` | 管理员更新系统设置 |
|
||||
| `admin_system_config_updated` | `audit` | 管理员更新系统配置项 |
|
||||
| `admin_system_config_deleted` | `audit` | 管理员删除系统配置项 |
|
||||
| `admin_wallet_balance_adjusted` | `audit` | 管理员手动调整钱包余额 |
|
||||
| `admin_wallet_manual_recharge_created` | `audit` | 管理员创建手动充值 |
|
||||
| `admin_wallet_refund_processed` | `audit` | 管理员开始处理退款 |
|
||||
| `admin_wallet_refund_completed` | `audit` | 管理员完成退款 |
|
||||
| `admin_wallet_refund_failed` | `audit` | 管理员标记退款失败 |
|
||||
| `video_task_status_updated` | `event` | 异步视频任务轮询后状态发生更新 |
|
||||
| `video_task_finalize_settlement_failed` | `event` | 异步视频任务终态结算失败 |
|
||||
| `maintenance_worker_failed` | `ops` | 定时任务启动、tick 或调度查找失败 |
|
||||
| `usage_cleanup_completed` | `ops` | usage 清理批次完成 |
|
||||
| `provider_checkin_completed` | `ops` | provider checkin 批次完成 |
|
||||
| `log_retention_cleanup_failed` | `ops` | 日志保留清理失败,但不阻断服务启动 |
|
||||
| `execution_runtime_stream_flush_skipped` | `debug` | 下游断开,跳过 stream flush |
|
||||
| `execution_runtime_stream_report_skipped` | `debug` | 下游断开,跳过 stream report |
|
||||
|
||||
命名规则:
|
||||
|
||||
- 统一小写蛇形
|
||||
- 前缀先写模块,再写动作,再写结果
|
||||
- `access`、`event`、`debug`、`ops`、`audit` 分层不要混用
|
||||
- 管理员高风险写操作优先落 `audit`
|
||||
- 定时任务批次结果和失败统一落 `ops`
|
||||
|
||||
## 9. 字段字典
|
||||
|
||||
这些字段应该优先保持稳定,方便 grep、告警和日志采集:
|
||||
|
||||
| 字段 | 含义 |
|
||||
|------|------|
|
||||
| `event_name` | 机器可依赖的稳定事件名 |
|
||||
| `log_type` | 日志类别,例如 `access` / `event` / `debug` / `ops` / `audit` |
|
||||
| `service` | 服务名,例如 `aether-gateway` / `aether-proxy` |
|
||||
| `node_role` | 节点角色,例如 `gateway` / `proxy` |
|
||||
| `instance_id` | 进程或节点实例标识 |
|
||||
| `trace_id` | 跨链路关联键 |
|
||||
| `request_id` | 业务请求标识 |
|
||||
| `route_class` | 路由大类,例如 `passthrough` / `control` |
|
||||
| `execution_path` | 实际执行路径,例如 `local` / `proxy` / `passthrough` |
|
||||
| `status` | 事件状态,例如 `started` / `completed` / `failed` |
|
||||
| `status_code` | HTTP 或上游状态码 |
|
||||
| `elapsed_ms` | 整体耗时 |
|
||||
| `error` | 错误摘要,不放原始敏感内容 |
|
||||
| `candidate_id` | 候选执行槽位标识 |
|
||||
| `candidate_count` | 候选数量或耗尽数量 |
|
||||
| `provider_id` | 供应商标识 |
|
||||
| `endpoint_id` | endpoint 标识 |
|
||||
| `key_id` | provider key 标识 |
|
||||
| `task_id` | 异步任务标识 |
|
||||
|
||||
约束:
|
||||
|
||||
- 没有稳定含义的临时字段不要进入长期事件模板
|
||||
- 敏感原文不进入 `error`
|
||||
- body 相关字段默认只允许摘要,不允许原文
|
||||
198
docs/deploy/systemd.md
Normal file
198
docs/deploy/systemd.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# Aether Gateway Systemd 部署
|
||||
|
||||
目标形态:
|
||||
|
||||
- `aether-gateway` 作为宿主机上的 `systemd` 服务运行
|
||||
- Docker 只保留 `Postgres` 和 `Redis`
|
||||
- 前端静态资源由 `aether-gateway` 直接服务
|
||||
|
||||
适用场景:
|
||||
|
||||
- 单机/单节点部署
|
||||
- 允许短暂停机更新
|
||||
- 未来需要网页一键更新
|
||||
|
||||
## 目录约定
|
||||
|
||||
安装脚本默认使用以下路径:
|
||||
|
||||
- Gateway release: `/opt/aether/releases/<release-id>`
|
||||
- 当前版本软链接: `/opt/aether/current`
|
||||
- systemd env 文件: `/etc/aether/aether-gateway.env`
|
||||
- 数据栈 compose: `/opt/aether/shared/docker-compose.data.yml`
|
||||
|
||||
## 1. 构建二进制与前端
|
||||
|
||||
在目标机器或 CI 产物目录中准备:
|
||||
|
||||
```bash
|
||||
cargo build --release -p aether-gateway
|
||||
(cd frontend && npm ci && npm run build)
|
||||
```
|
||||
|
||||
构建完成后需要存在:
|
||||
|
||||
- `target/release/aether-gateway`
|
||||
- `frontend/dist`
|
||||
|
||||
## 2. 准备环境变量
|
||||
|
||||
复制示例文件并修改:
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /etc/aether
|
||||
sudo cp deploy/systemd/aether-gateway.env.example /etc/aether/aether-gateway.env
|
||||
sudo chmod 600 /etc/aether/aether-gateway.env
|
||||
sudo vim /etc/aether/aether-gateway.env
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
- `systemd` 的 `EnvironmentFile` 只适合简单 `KEY=VALUE`
|
||||
- 不要写 `export`
|
||||
- 不要写 `${VAR}`、命令替换、shell 函数等复杂语法
|
||||
- `DATABASE_URL` / `REDIS_URL` 请直接写完整值
|
||||
- 文件日志建议直接配 `AETHER_LOG_DESTINATION=both` 和 `AETHER_LOG_DIR=/var/log/aether`
|
||||
- 现在支持 `AETHER_GATEWAY_DEPLOYMENT_TOPOLOGY=single-node|multi-node`
|
||||
- 现在支持 `AETHER_GATEWAY_NODE_ROLE=all|frontdoor|background`
|
||||
- 如果未来准备跑多实例,先把它切成 `multi-node`,让启动期校验替你拦掉单机残留配置
|
||||
- 安装脚本会拒绝示例占位值,例如 `change-me-*`、`change-this-*`
|
||||
|
||||
三种推荐运行档位:
|
||||
|
||||
- `single-node + all + Postgres/Redis`:完整单机,功能最全
|
||||
- `single-node + all + 无 Postgres/Redis`:轻量单机,本地文件/内存兜底,适合最小化运行
|
||||
- `multi-node + frontdoor/background + Postgres/Redis`:集群预备形态
|
||||
|
||||
## 3. 启动数据服务
|
||||
|
||||
这套 compose 只负责 `Postgres` 和 `Redis`:
|
||||
|
||||
```bash
|
||||
docker compose \
|
||||
--env-file /etc/aether/aether-gateway.env \
|
||||
-f deploy/docker-compose.data.yml \
|
||||
up -d
|
||||
```
|
||||
|
||||
其中:
|
||||
|
||||
- `Postgres` 数据目录走 Docker volume
|
||||
- `Redis` 默认开启 `AOF + everysec`,避免 usage stream、分布式限流和锁状态在容器重启时全部丢失
|
||||
- 端口默认只绑定到 `127.0.0.1`
|
||||
- 如果给 gateway 打开 `AETHER_LOG_DESTINATION=file|both`,建议把 `AETHER_LOG_DIR` 指到持久目录
|
||||
|
||||
如果你已经执行过安装脚本,也可以使用安装后的固定路径:
|
||||
|
||||
```bash
|
||||
docker compose \
|
||||
--env-file /etc/aether/aether-gateway.env \
|
||||
-f /opt/aether/shared/docker-compose.data.yml \
|
||||
up -d
|
||||
```
|
||||
|
||||
## 4. 安装 systemd 服务
|
||||
|
||||
```bash
|
||||
sudo deploy/systemd/install-systemd.sh --env-file /etc/aether/aether-gateway.env
|
||||
```
|
||||
|
||||
这个脚本会完成:
|
||||
|
||||
- 创建 `aether` 系统用户/组
|
||||
- 复制 release 到 `/opt/aether/releases/<release-id>`
|
||||
- 更新 `/opt/aether/current` 软链接
|
||||
- 安装 `aether-gateway.service`
|
||||
- 安装数据栈 compose 到 `/opt/aether/shared/docker-compose.data.yml`
|
||||
- 校验 env 文件语法、拓扑模式和关键密钥占位值
|
||||
- 重载并启动 `aether-gateway`
|
||||
|
||||
日志约定:
|
||||
|
||||
- 默认 stdout 仍进入 `journald`
|
||||
- 如果设置 `AETHER_LOG_DESTINATION=both`,gateway 会同时把日志写到 `AETHER_LOG_DIR`
|
||||
- 文件日志目前支持 `daily/hourly` 轮转
|
||||
- 文件日志会按 `AETHER_LOG_RETENTION_DAYS` 和 `AETHER_LOG_MAX_FILES` 自动清理
|
||||
- 不建议再叠加外部 `logrotate` 对同一目录做二次轮转
|
||||
- 更完整的组合策略和排障命令见 `docs/deploy/logging.md`
|
||||
|
||||
## 5. 验证
|
||||
|
||||
```bash
|
||||
sudo systemctl status aether-gateway --no-pager
|
||||
sudo journalctl -u aether-gateway -n 100 --no-pager
|
||||
sudo ls -lah /var/log/aether
|
||||
sudo tail -n 100 /var/log/aether/aether-gateway.*.log
|
||||
|
||||
curl -fsS http://127.0.0.1:8084/_gateway/health
|
||||
curl -fsS http://127.0.0.1:8084/readyz
|
||||
curl -fsS http://127.0.0.1:8084/.well-known/aether/frontdoor.json
|
||||
```
|
||||
|
||||
## 6. 升级前备份
|
||||
|
||||
如果你使用仓库内置的数据栈 compose,升级前至少备份 `Postgres`:
|
||||
|
||||
```bash
|
||||
docker compose \
|
||||
--env-file /etc/aether/aether-gateway.env \
|
||||
-f /opt/aether/shared/docker-compose.data.yml \
|
||||
exec -T postgres pg_dump -U postgres aether | gzip > backup_$(date +%Y%m%d_%H%M%S).sql.gz
|
||||
```
|
||||
|
||||
如果你改过 `DB_USER` / `DB_NAME`,把命令里的 `postgres` / `aether` 替换成实际值。
|
||||
|
||||
## 7. 更新流程
|
||||
|
||||
重新构建新版本后,重复执行安装脚本即可:
|
||||
|
||||
```bash
|
||||
cargo build --release -p aether-gateway
|
||||
(cd frontend && npm ci && npm run build)
|
||||
sudo deploy/systemd/install-systemd.sh \
|
||||
--env-file /etc/aether/aether-gateway.env \
|
||||
--release-id "$(date +%Y%m%d%H%M%S)"
|
||||
```
|
||||
|
||||
这会:
|
||||
|
||||
- 安装新 release
|
||||
- 切换 `/opt/aether/current`
|
||||
- 重启 `aether-gateway`
|
||||
|
||||
## 8. 回滚
|
||||
|
||||
列出历史 release:
|
||||
|
||||
```bash
|
||||
ls -1 /opt/aether/releases
|
||||
```
|
||||
|
||||
把软链接切回旧版本并重启:
|
||||
|
||||
```bash
|
||||
sudo ln -sfn /opt/aether/releases/<old-release-id> /opt/aether/current
|
||||
sudo systemctl restart aether-gateway
|
||||
```
|
||||
|
||||
如果这次更新包含数据库结构变更,单纯切回旧 release 不一定够,还需要把 `Postgres` 恢复到升级前备份。当前这套 Rust 迁移链路不应该再按老的 `alembic downgrade` 方式处理。
|
||||
|
||||
## 9. 集群演进注意点
|
||||
|
||||
这套方式适合单机,但不会阻塞以后做集群。要提前避免的坑:
|
||||
|
||||
- 不要把长期业务状态继续写进本地文件
|
||||
- `AETHER_GATEWAY_VIDEO_TASK_STORE_PATH` 只适合单机临时方案
|
||||
- 共享状态优先放 `Postgres` / `Redis`
|
||||
- 以后扩成多实例时,替换的是上层托管者,不是 `aether-gateway` 二进制形态本身
|
||||
|
||||
建议先把单机 env 收敛成未来可迁移的基线:
|
||||
|
||||
- 保留 `DATABASE_URL` 和 `REDIS_URL`,不要跑“无 Redis 的单机特例”
|
||||
- 默认就使用 `AETHER_GATEWAY_VIDEO_TASK_TRUTH_SOURCE_MODE=rust-authoritative`
|
||||
- 只有单机临时排障时才启用 `AETHER_GATEWAY_VIDEO_TASK_STORE_PATH`
|
||||
- 如果未来要接 `aether-proxy` / tunnel owner relay,多实例下要给每个节点单独设置 `AETHER_GATEWAY_INSTANCE_ID`
|
||||
- 如果未来要跨节点转发 tunnel owner 请求,还要给每个节点设置外部可达的 `AETHER_TUNNEL_RELAY_BASE_URL`
|
||||
- `AETHER_GATEWAY_DISTRIBUTED_REQUEST_LIMIT` 是可选项,但要开的话现在已经可以直接复用 `REDIS_URL`,不必再维护第二份 Redis 地址
|
||||
- 多实例下不要再用 `AETHER_GATEWAY_NODE_ROLE=all`;前台节点用 `frontdoor`,后台节点用 `background`
|
||||
- 多实例下前台限流不会再退回本地内存计数;单机模式才允许这种兜底
|
||||
Reference in New Issue
Block a user