feat: ship blog platform admin and deploy stack
This commit is contained in:
224
deploy/docker/ARCHITECTURE.md
Normal file
224
deploy/docker/ARCHITECTURE.md
Normal file
@@ -0,0 +1,224 @@
|
||||
# Docker / 反代架构说明
|
||||
|
||||
本文记录当前项目在 `tohka` 这类宿主机上的推荐部署结构,以及为什么会同时出现:
|
||||
|
||||
- 宿主机层的 **Caddy**
|
||||
- `admin` 容器内的 **Nginx**
|
||||
|
||||
## 1. 总体分层
|
||||
|
||||
推荐生产结构:
|
||||
|
||||
```text
|
||||
Internet
|
||||
-> Host Caddy (:80 / :443)
|
||||
-> frontend container (Astro SSR / Node :4321)
|
||||
-> admin container (Nginx :80, 静态 SPA)
|
||||
-> backend container (Loco.rs API :5150)
|
||||
-> backend-worker container (Loco.rs worker / Redis queue)
|
||||
```
|
||||
|
||||
职责划分:
|
||||
|
||||
- **Host Caddy**
|
||||
- 统一接收公网流量
|
||||
- 处理域名、HTTPS、证书续签
|
||||
- 反向代理到各个内部容器
|
||||
- **frontend**
|
||||
- Astro SSR(Node) 应用
|
||||
- 不是纯静态站,所以容器内直接运行 Node 服务即可
|
||||
- **admin**
|
||||
- React/Vite 打包后的纯静态 SPA
|
||||
- 容器内使用 Nginx 提供静态文件
|
||||
- 生产推荐前面接 TinyAuth / Pocket ID 做 SSO
|
||||
- **backend**
|
||||
- API、后台鉴权、审计、版本历史、订阅投递
|
||||
- **backend-worker**
|
||||
- 消费 Redis 队列
|
||||
- 负责通知异步投递、失败重试、digest 任务触发后的实际发送
|
||||
|
||||
## 2. 为什么上层已经有 Caddy,admin 容器里还要 Nginx?
|
||||
|
||||
这两层并不冲突,职责不同:
|
||||
|
||||
- **Caddy** 是入口网关
|
||||
- **Nginx** 是 admin 容器内部的静态文件服务器
|
||||
|
||||
也就是说:
|
||||
|
||||
```text
|
||||
Browser
|
||||
-> Caddy
|
||||
-> admin nginx
|
||||
-> /usr/share/nginx/html
|
||||
```
|
||||
|
||||
这样做的好处:
|
||||
|
||||
- admin 镜像本身自带可用的静态文件服务能力
|
||||
- 宿主机层仍然保留统一的域名 / HTTPS / 路由管理
|
||||
- admin 作为独立前端,可以单独构建、单独发布
|
||||
|
||||
## 2.2 为什么现在多了 `backend-worker`?
|
||||
|
||||
因为当前通知系统已经改成:
|
||||
|
||||
```text
|
||||
backend (web)
|
||||
-> 写入 notification_deliveries
|
||||
-> enqueue 到 Redis
|
||||
backend-worker
|
||||
-> 消费队列
|
||||
-> 发送 email / webhook / discord / telegram / ntfy
|
||||
```
|
||||
|
||||
如果只启动 `backend` 而没有 `backend-worker`,通知会入队但没人消费。
|
||||
|
||||
## 2.1 推荐的后台认证链路
|
||||
|
||||
当前最推荐:
|
||||
|
||||
```text
|
||||
Browser
|
||||
-> Caddy (import tinyauth)
|
||||
-> TinyAuth
|
||||
-> Pocket ID (OIDC)
|
||||
-> admin nginx / backend API
|
||||
```
|
||||
|
||||
关键点:
|
||||
|
||||
- admin 页面和它调用的 `/api/*` 建议走 **同一个受保护后台域名**
|
||||
- Caddy 使用 `forward_auth`
|
||||
- backend 开启 `TERMI_ADMIN_TRUST_PROXY_AUTH=true`
|
||||
- 生产推荐再配 `TERMI_ADMIN_PROXY_SHARED_SECRET=<随机长字符串>`
|
||||
- Caddy 在 `/api/*` 反代到 backend 时补 `X-Termi-Proxy-Secret`
|
||||
- backend 读取 TinyAuth 转发的 `Remote-User / Remote-Email / Remote-Groups`
|
||||
- 本地开发可以保留 `TERMI_ADMIN_LOCAL_LOGIN_ENABLED=true`
|
||||
|
||||
## 3. 为什么 frontend 不使用同样的 Nginx 模式?
|
||||
|
||||
因为当前 `frontend` 是 **Astro SSR**:
|
||||
|
||||
- `output: 'server'`
|
||||
- `@astrojs/node` standalone
|
||||
|
||||
它需要在请求期执行服务端逻辑,因此更适合:
|
||||
|
||||
```text
|
||||
Caddy -> frontend Node server
|
||||
```
|
||||
|
||||
而不是先打成纯静态文件再由 Nginx 托管。
|
||||
|
||||
## 4. admin 容器内 Nginx 当前负责什么?
|
||||
|
||||
当前 `admin/nginx.conf` 主要负责:
|
||||
|
||||
- SPA fallback:`try_files ... /index.html`
|
||||
- `assets/` 长缓存(hash 资源可 `immutable`)
|
||||
- `index.html` / `runtime-config.js` 禁缓存,避免配置或入口文件陈旧
|
||||
- `gzip` 压缩
|
||||
- 基础安全响应头
|
||||
- `/healthz` 健康检查入口
|
||||
|
||||
### 为什么 `runtime-config.js` 要禁缓存?
|
||||
|
||||
因为 admin 现在支持运行时环境变量注入,例如:
|
||||
|
||||
- `ADMIN_API_BASE_URL`
|
||||
- `ADMIN_FRONTEND_BASE_URL`
|
||||
|
||||
容器启动时会生成 `runtime-config.js`。
|
||||
如果它被强缓存,改完环境变量重启容器后,浏览器可能仍然读到旧地址。
|
||||
|
||||
## 5. 为什么没有在 admin 容器里启用 Brotli?
|
||||
|
||||
当前基础镜像是官方 `nginx:alpine`。
|
||||
这个镜像默认不一定带 Brotli 模块,所以这里先启用通用的 `gzip`。
|
||||
|
||||
如果后续确实需要 Brotli,有两个常见做法:
|
||||
|
||||
- 让宿主机层的 Caddy 统一负责压缩
|
||||
- 改用带 Brotli 模块的自定义 Nginx 镜像
|
||||
|
||||
对当前项目而言,优先让 **宿主机 Caddy 做统一公网入口**,admin 容器内部只负责稳妥地提供静态文件,是更简单的方案。
|
||||
|
||||
## 6. 推荐的 tohka 思路
|
||||
|
||||
如果 `tohka` 上已经有一个统一的大 Caddyfile,推荐继续保持:
|
||||
|
||||
- Caddy 统一暴露 `80/443`
|
||||
- `frontend/admin/backend` 只走内网端口
|
||||
- 不把数据库 / Redis 直接暴露到公网
|
||||
- backend 如果启用了代理 SSO,不要再把 `:5150` 直接开放给公网
|
||||
|
||||
仓库里已经额外提供:
|
||||
|
||||
- `deploy/docker/compose.tohka.override.yml`
|
||||
|
||||
用它叠加 `compose.package.yml` 后,会把:
|
||||
|
||||
- `frontend:4321`
|
||||
- `admin:4322`
|
||||
- `backend:5150`
|
||||
|
||||
都只绑定到 `127.0.0.1`
|
||||
|
||||
## 7. 当前和配置相关的关键文件
|
||||
|
||||
- 宿主机入口反代:`tohka` 上的大 Caddyfile
|
||||
- admin 静态服务:`admin/nginx.conf`
|
||||
- admin 镜像:`admin/Dockerfile`
|
||||
- Caddy 参考模板:`deploy/caddy/Caddyfile.tohka.example`
|
||||
- compose 示例:`deploy/docker/compose.package.yml`
|
||||
- tohka override:`deploy/docker/compose.tohka.override.yml`
|
||||
- OIDC / Pocket ID 落地:`deploy/docker/TOHKA_POCKET_ID.md`
|
||||
- 运行时环境示例:`deploy/docker/.env.example`
|
||||
|
||||
## 8. Caddy 路由推荐
|
||||
|
||||
默认更推荐:
|
||||
|
||||
- `blog.init.cool` -> frontend
|
||||
- `admin.blog.init.cool` -> admin + backend(`/api/*`)
|
||||
- `api.blog.init.cool` -> backend
|
||||
|
||||
这样最省心,也最不容易碰到路径前缀、资源基路径、Cookie Path 等问题。
|
||||
|
||||
如果一定要用:
|
||||
|
||||
- `init.cool/admin`
|
||||
- `init.cool/api`
|
||||
|
||||
也可以,但 `admin` 需要在构建时设置:
|
||||
|
||||
- `VITE_ADMIN_BASENAME=/admin`
|
||||
|
||||
对应模板见:
|
||||
|
||||
- `deploy/caddy/Caddyfile.tohka.example`
|
||||
|
||||
## 9. 备份 / 恢复入口
|
||||
|
||||
当前仓库内已经补了:
|
||||
|
||||
- `deploy/scripts/backup/backup-postgres.sh`
|
||||
- `deploy/scripts/backup/backup-markdown.sh`
|
||||
- `deploy/scripts/backup/backup-media.sh`
|
||||
- `deploy/scripts/backup/restore-postgres.sh`
|
||||
- `deploy/scripts/backup/restore-markdown.sh`
|
||||
- `deploy/scripts/backup/restore-media.sh`
|
||||
- `deploy/docker/BACKUP_AND_RECOVERY.md`
|
||||
|
||||
建议把这些接进生产上的 cron / systemd timer,而不是只停留在仓库里。
|
||||
|
||||
## 10. 健康检查与启动顺序
|
||||
|
||||
当前推荐闭环:
|
||||
|
||||
- backend 启动时先自动跑 migration
|
||||
- backend 提供 `/healthz`
|
||||
- frontend 提供 `/healthz`
|
||||
- admin 由 Nginx 提供 `/healthz`
|
||||
- compose 中 frontend / admin / backend-worker 都依赖 backend healthy
|
||||
Reference in New Issue
Block a user