让一个分析系统「跑一次」不难,难的是它每天 18:00 都准时上班、失败能自愈、账单不爆炸、数据不丢。这一章把部署、调度、安全、备份与排障串成一套可执行的运维清单。
先选「运行位置」,再谈配置
GitHub Actions 适合「零服务器、每天一次」;Docker 适合「长期在线、还要 Web」;本地 Python 适合「开发调试」。三者的配置大体相同,差别在调度入口、数据持久化与网络暴露。
三条部署路径怎么选
| 维度 | GitHub Actions | Docker | 本地 Python |
|---|---|---|---|
| 服务器成本 | 无 | 一台 1–2G 小机器 | 自己的电脑 |
| Web 工作台 | 不支持 | 支持 | 支持 |
| 每天自动 | cron | 容器常驻调度 / 宿主机 cron | --schedule / 系统定时 |
| 数据持久化 | Artifacts(30 天)+ 无数据库持久化 | 挂载 data/ 目录 | 本地 data/ |
| 调试便利 | 看日志 | 较强 | 最强 |
| 适合 | 体验、轻量日常 | 长期稳定使用 | 开发、改代码 |
一个常见组合是:GitHub Actions 跑每日分析 + 本地/服务器只开 Web 看历史。但要注意,Actions 的数据库不持久化,历史报告不会自动同步回本地;如果需要长期历史,请在 Docker/本地跑。
GitHub Actions:零服务器的每日任务
工作流在 .github/workflows/00-daily-analysis.yml,关键事实:
- 触发:cron
0 10 * * 1-5(UTC)= 北京时间周一至周五 18:00;支持手动full / market-only / stocks-only与force_run。 - 并发组
stock-analysis,同一时间只跑一个,不取消正在运行的任务。 - Ubuntu + Python 3.11,依赖缓存;启动前随机 sleep 0–60 秒错峰。
- 默认
MAX_WORKERS=1、REPORT_TYPE=simple,降低云端资源与限流风险。 - 超时由
ANALYSIS_TIMEOUT_MINUTES控制(默认 30 分钟,可配)。 - 产物
reports/、logs/上传为 Artifacts,保留 30 天。
它只执行命令行分析,不会启动 Web,也不会把 SQLite 带回来。把每次运行的报告当作「快照」,而不是完整的历史数据库。
Actions 的“环境变量”有两个位置
工作流里有一个 environment: STOCK_LIST,但这不意味着你必须在 Environments 里建一个同名环境。推荐仍然把 STOCK_LIST 放在 Repository variables 或 Secrets 中;两者都被工作流读取。
Docker:长期运行的标准答案
镜像采用多阶段构建:Node 20 构建前端,Python 3.11 运行后端;以非 root 用户 dsa(UID 1000)运行,健康检查访问 /api/health,默认命令是 python main.py --schedule。
cp .env.example .env
# 填写 STOCK_LIST、模型 Key、通知、API_PORT
# 定时分析
docker compose -f ./docker/docker-compose.yml up -d analyzer
# Web/API
docker compose -f ./docker/docker-compose.yml up -d server
# 两者同时(确保只保留一个调度入口)
docker compose -f ./docker/docker-compose.yml up -d
| 挂载 / 端口 | 作用 | 注意 |
|---|---|---|
./data → /app/data | SQLite、缓存、认证凭据 | 必须持久化,否则重启丢历史 |
./logs、./reports | 日志与本地报告 | 定期清理或归档 |
./strategies → /app/strategies | 自定义策略 | 只读挂载;改完重启生效 |
API_PORT:8000 | Web/API | 公网暴露前先开认证 + HTTPS |
内存建议 1G 以上;512M 仅适合轻量 Web/API、单股或低并发,并建议 MAX_WORKERS=1,不要同时跑 analyzer 与 server。容器内时区默认 Asia/Shanghai,避免 cron 与交易日判断错位。
本地 / 服务器:可控性最高
git clone https://github.com/ZhuLinsen/daily_stock_analysis.git
cd daily_stock_analysis
python3.11 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
vim .env
python main.py --schedule --no-run-immediately # 常驻定时,不立即跑
python main.py --serve-only # 只开 Web/API
在 Linux 服务器上,用 systemd 托管比 nohup 可靠:
[Unit]
Description=DSA daily stock analysis
After=network-online.target
[Service]
WorkingDirectory=/opt/dsa
Environment=TZ=Asia/Shanghai
ExecStart=/opt/dsa/.venv/bin/python main.py --schedule
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
若要让 Web 对外访问,推荐 Nginx/Caddy 反向代理到 127.0.0.1:8000,并关闭 SSE 路径的代理缓冲:
location /api/v1/analysis/tasks/stream {
proxy_pass http://127.0.0.1:8000;
proxy_buffering off;
proxy_read_timeout 3600s;
}
调度:只能有一个「发令枪」
项目有三类调度入口:CLI --schedule、Web 内置 RuntimeScheduler、GitHub Actions cron。它们都最终调用同一条分析流水线,但不要同时开多个入口去跑同一批股票。
- Actions 用 concurrency group 防并发。
- 本地进程用文件锁。
- 定时任务通过 SQLite 做原子认领,尽量保证「至多一次」。
- 多机部署且数据库不共享时,认领失效,可能重复推送。
如果你在 Docker 里同时起了 analyzer 和 server,而 server 又启用了 Web 调度,就等于两把发令枪。正确做法是只保留一个:要么让 analyzer 负责定时、server 只提供 Web;要么反过来。
安全:公网部署的底线
认证是单管理员模式,密码用 PBKDF2-SHA256(10 万次迭代)存哈希,Cookie 为 HttpOnly + SameSite=Lax,默认 24 小时;登录失败 5 次/300 秒限流;登出会轮换密钥使旧会话全部失效。
不要把「本机方便」变成「公网裸奔」
--host 0.0.0.0 会把服务暴露到所有网卡。没有认证时,任何人访问你的 IP 就能看到持仓、配置与报告,甚至触发分析消耗你的模型额度。公网部署前,先完成上面的清单。
日常运维:备份、升级与排障
备份
核心资产是 data/stock_analysis.db(历史报告、决策信号、持仓、会话)、data/.session_secret、data/.admin_password_hash 与 reports/。SQLite 使用 WAL 模式,最稳妥的备份方式是停服务后复制,或用 SQLite 的在线备份命令;直接 cp 正在写入的库可能拿到不一致快照。
升级
git pull --ff-only
pip install -r requirements.txt
# 前端有改动时重新构建
cd apps/dsa-web && npm ci && npm run build
# 重启服务;数据库迁移会在启动时自动执行
迁移是幂等的「自愈式」检查:缺列补列、缺索引补索引。升级前仍然建议备份数据库,尤其是跨多个版本时。
成本大概是多少
| 项目 | 量级 | 说明 |
|---|---|---|
| GitHub Actions | 0 元起 | 公开仓库通常免费;私有仓库计入账号额度 |
| LLM | 与模型单价 × token 成正比 | 单股一次约几千到数万 token;批量与 Agent 更高。看「用量」页而非猜 |
| 服务器 | 一台 1C1G/2G 小机器 | 个人使用足够;本地 NAS 也可 |
| 数据源 | 0 元起 | 免费源可用;Tushare/Longbridge 等按各自规则 |
控制成本最有效的手段不是「少分析」,而是选对模型、控制上下文、限制并发、善用回退。一次分析的输入里,新闻与 Prompt 往往比行情更占 token。
排障顺序:先看哪一层
| 现象 | 先查 | 常见解法 |
|---|---|---|
| 任务没触发 | 交易日 / cron / Actions 是否启用 | --force-run、检查时区、手动触发 |
| 触发但没结果 | 日志中 empty_stock_list / 数据源异常 | 检查 STOCK_LIST、换数据源、看数据中心 |
| 有结果但没推送 | 通知渠道配置 / 降噪 / 路由 | --check-notify、看降噪日志、检查 NOTIFICATION_* |
| LLM 超时/报错 | 模型额度、网络、并发 | 降并发、换回退模型、缩短上下文 |
| Web 空白 / Not Built | 前端静态产物 | 构建前端或用 Docker 镜像 |
| 重复推送 | 是否多个调度入口 / 多实例数据库不共享 | 只保留一个发令枪,共享 SQLite |
| 数据库锁/写失败 | 磁盘、并发写、WAL | 降并发、检查磁盘、避免多实例 |
你在 Docker 里同时启动了 analyzer(默认 --schedule)和 server,并在 Web 设置里也开启了定时任务。最可能发生什么?
--schedule 运行,server 的 Web 调度也是独立入口。两者共享同一个 SQLite 时,原子认领能降低重复概率,但并不能把所有竞态消灭;更稳妥的做法是明确只保留一个调度入口。不要依赖「系统应该会处理」。