feat: 多镜像版本管理与网关化环境编排 (#14)
This commit is contained in:
@@ -2,33 +2,41 @@
|
||||
|
||||
## 技术选型
|
||||
|
||||
- 前端:React + Vite。阶段 A 只有一个运行环境页面,不引入路由、状态库或组件库。
|
||||
- 后端:Go 模块化单体,Fiber v3 提供 HTTP 路由,Viper 读取并校验启动配置,Logrus 输出 JSON 结构化日志,Cobra 保持当前两个服务入口。控制面提供同源 API 和静态文件,受限网关单独封装 Docker Engine API。
|
||||
- 数据:Docker 容器标签仍是运行态事实,Profile 使用命名卷持久化;阶段 A 账号、凭据引用、确认、任务、尝试和审计实体由控制面使用原生 SQL 持久化到 Postgres。
|
||||
- 部署:Docker Compose 启动控制面和受限网关;浏览器容器由网关动态创建。
|
||||
- 前端:React 19 + react-admin(ra-core)+ MUI,包含运行环境、镜像版本、网关管理三个页面。
|
||||
- 后端:Go 模块化单体,Fiber v3 提供 HTTP 路由,Viper 读取并校验启动配置,Logrus 输出 JSON 结构化日志,Cobra 保持当前两个服务入口。控制面提供同源 API 和静态文件,并编排网关;受限网关单独封装 Docker Engine API,是纯执行器。
|
||||
- 数据:环境配置(别名、中文名、网关、镜像版本、指纹参数)持久化在 Postgres,运行态实时查询网关;Profile 使用命名卷持久化;阶段 A 账号、凭据引用、确认、任务、尝试和审计实体同样由控制面持久化到 Postgres。
|
||||
- 部署:Docker Compose 启动控制面和受限网关;浏览器容器由网关按平台下发的镜像引用动态创建,缺失时自动拉取。
|
||||
|
||||
## 调用链与契约
|
||||
|
||||
```text
|
||||
React ──> control-plane ── /api/browsers ──> docker-gateway ──> docker.sock
|
||||
│ │
|
||||
│ └─> browser container
|
||||
└─ /api/phase-a ──> PostgreSQL
|
||||
React ──> control-plane ── /api/browsers ──(Bearer token)──> docker-gateway ──> docker.sock
|
||||
│ │
|
||||
│ └─> browser container
|
||||
└─ /api/phase-a, /api/browser-images, /api/gateways ──> PostgreSQL
|
||||
```
|
||||
|
||||
`POST /api/browsers` 接受 `{name, seed}` 并创建后启动;`GET /api/browsers` 列出受管容器;`POST /api/browsers/{name}/start|stop` 改变状态;`DELETE /api/browsers/{name}` 仅回收容器,保留 Profile 数据卷。名称唯一约束由 Docker 容器名保证,重复创建返回冲突;启停接受 Docker 的幂等响应,不自动重试未知结果。
|
||||
控制面是唯一事实源:网关不持有镜像清单和业务规则,镜像引用、启动命令和卷名均随请求下发。
|
||||
|
||||
- `POST /api/browsers` 接受 `{alias, name, gateway, image_version, fingerprint}`(严格 JSON,未知字段拒绝),校验后先落库,再调网关创建并启动;网关失败时回滚数据库行。`name` 为中文环境名,`alias` 限 `^[a-z0-9][a-z0-9-]{0,31}$`,容器名 `creatorhub-browser-<alias>`,Profile 卷 `creatorhub-profile-<alias>`。
|
||||
- `GET /api/browsers` 合并数据库环境与网关实时状态;环境在网关无容器时显示为未部署。
|
||||
- `POST /api/browsers/{alias}/start|stop` 改变状态;`POST /api/browsers/{alias}/upgrade` 收 `{version}`,由平台编排:停止并删除旧容器(保留 Profile 卷)→ 用新镜像引用与原指纹参数重建 → 启动;失败直接重试,不做自动回滚。
|
||||
- `DELETE /api/browsers/{alias}` 回收容器并删除数据库行,保留 Profile 数据卷。
|
||||
- `GET/POST /api/browser-images` 维护可用镜像版本(版本号不可改,`PUT /{version}` 仅接受 `image_ref/note/enabled`);仅启用版本可用于创建与升级;被环境引用时拒绝删除。
|
||||
- `GET/POST /api/gateways` 注册网关(`POST` 可携带令牌,否则平台生成 48 位十六进制令牌并明文存储),`DELETE /api/gateways/{name}` 删除;仍被环境引用时拒绝删除。
|
||||
- 启停接受幂等响应,不自动重试未知结果;别名唯一约束由数据库保证。
|
||||
|
||||
## docker.sock 安全边界
|
||||
|
||||
将 socket 以只读文件挂载**不会**限制 Docker API 的写操作;拥有 socket 等价于拥有宿主机 root 权限。因此:
|
||||
|
||||
- 只有 `docker-gateway` 挂载 socket,控制面和浏览器容器均不可见;网关只加入 control 网络,浏览器不能连接网关;
|
||||
- 网关只暴露四个面向领域的路由,不提供通用 Docker 代理;
|
||||
- 镜像、命令、网络、挂载和资源限制均由网关固定,外部输入只有受校验的名称和数字 seed;
|
||||
- 网关只暴露面向领域的路由,不提供通用 Docker 代理;`/v1` 全部接口校验 `Authorization: Bearer <GATEWAY_TOKEN>`(常数时间比较),令牌由部署者在网关环境变量与平台注册表中保持一致;
|
||||
- 网关固定命令、网络、挂载和资源限制;外部输入是受校验的别名,以及平台下发的镜像引用、启动参数和卷名——镜像引用来自平台维护的版本表,新增/变更由人工在页面审核启用,不再写死在代码中;
|
||||
- 启停和删除前必须同时匹配固定名称前缀及 `io.creatorhub.managed`、`io.creatorhub.runtime-id` 标签;
|
||||
- 动态容器使用只读根文件系统、非 root 镜像、全部 capability drop、`no-new-privileges`、CPU/内存/PID 限制,且无宿主机端口和目录挂载;
|
||||
- 动态容器使用只读根文件系统、非 root `1000:1000` 与固定镜像入口、全部 capability drop、`no-new-privileges`、CPU/内存/PID 限制,且无宿主机端口和目录挂载;
|
||||
- 控制面发布到宿主机所有网卡;控制网络为固定名称的 Compose 网络;浏览器 bridge 按 ownership、role、driver、Internal 失败关闭校验,且拒绝复用 control 网络;
|
||||
- 浏览器镜像固定为审核过的 immutable digest;运行时创建请求同时固定非 root `1000:1000` 与镜像入口,构建阶段和最终基础镜像也均锁定 digest。
|
||||
- Compose 基础镜像锁定 digest;浏览器镜像推荐使用 `@sha256:` 摘要引用以获得不可变性,tag 引用由部署者自行把控。
|
||||
|
||||
网关自身一旦被攻破,socket 仍允许接管宿主机;应用内校验不能消除这个平台级风险。开发阶段控制面不做认证或访问限制,安全由部署者自行把控。
|
||||
|
||||
@@ -37,13 +45,13 @@ React ──> control-plane ── /api/browsers ──> docker-gateway ──>
|
||||
Docker socket 的 GID 因宿主机而异:
|
||||
|
||||
```bash
|
||||
docker pull git.ipao.vip/rogee/fingerprint-chromium@sha256:b9f23b8e3ac640174db0dfa49e9095fe7eb06f5db55a4e7550d979b35ff3a1b7
|
||||
export GATEWAY_TOKEN="$(openssl rand -hex 24)" # 亦可在 .env 中设置
|
||||
DOCKER_GID=$(stat -c %g /var/run/docker.sock) docker compose up --build
|
||||
```
|
||||
|
||||
打开 <http://127.0.0.1:8080>,局域网内用宿主机 IP 访问同一端口。浏览器镜像固定为 `git.ipao.vip/rogee/fingerprint-chromium@sha256:b9f23b8e3ac640174db0dfa49e9095fe7eb06f5db55a4e7550d979b35ff3a1b7`。浏览器容器可访问外网。
|
||||
打开 <http://127.0.0.1:8080>,局域网内用宿主机 IP 访问同一端口。首次使用:在「网关管理」用 `GATEWAY_TOKEN` 注册 `http://docker-gateway:8081`,在「镜像版本」添加镜像引用(缺失时网关自动拉取,拉取上限 10 分钟)。浏览器容器可访问外网。
|
||||
|
||||
创建成功但启动失败时,网关会立即删除失败容器并保留命名 Profile 卷,允许同名请求安全重试。
|
||||
创建成功但启动失败时,网关会立即删除失败容器并保留命名 Profile 卷,控制面回滚数据库行,允许同名请求安全重试。
|
||||
|
||||
## 阶段 A 离线闭环
|
||||
|
||||
|
||||
+22
-12
@@ -9,7 +9,7 @@
|
||||
- `creator-hub`:Go 1.26 控制面,同时提供 React 19/Vite 8 构建的静态页面;
|
||||
- `docker-gateway`:受限 Docker API 网关,是唯一挂载 `/var/run/docker.sock` 的服务;
|
||||
- `postgres`:PostgreSQL 17,数据保存在 `creatorhub_postgres` 命名卷;
|
||||
- 浏览器容器:由网关按需创建,使用固定摘要的指纹浏览器镜像,Profile 保存在 `creatorhub-profile-<运行时名称>` 命名卷。
|
||||
- 浏览器容器:由网关按需创建,镜像引用由平台「镜像版本」页配置(缺失时网关自动拉取),Profile 保存在 `creatorhub-profile-<别名>` 命名卷。
|
||||
|
||||
控制面发布到宿主机所有网卡,局域网内可直接访问;浏览器容器可访问外网。
|
||||
`creator-hub` 会等待 `docker-gateway` 健康检查通过后再启动。
|
||||
@@ -20,7 +20,7 @@
|
||||
- Docker Engine 26 或兼容版本;
|
||||
- Docker Compose v2;
|
||||
- 当前用户可访问 Docker daemon;
|
||||
- 可访问 `git.ipao.vip`,并已完成私有镜像仓库登录(如仓库要求认证);
|
||||
- 可访问镜像仓库(如 `git.ipao.vip`),并已完成登录(如仓库要求认证);镜像也可不在宿主机预拉取,网关会在缺失时按引用自动拉取;
|
||||
- `curl`,用于部署后检查。
|
||||
|
||||
在仓库根目录执行预检:
|
||||
@@ -39,14 +39,22 @@ docker compose config --quiet
|
||||
```bash
|
||||
export DOCKER_GID="$(stat -c '%g' /var/run/docker.sock)"
|
||||
export CREATORHUB_PORT=8080
|
||||
export GATEWAY_TOKEN="$(openssl rand -hex 24)" # 亦可在 .env 中设置
|
||||
|
||||
docker pull git.ipao.vip/rogee/fingerprint-chromium@sha256:b9f23b8e3ac640174db0dfa49e9095fe7eb06f5db55a4e7550d979b35ff3a1b7
|
||||
docker compose config --quiet
|
||||
docker compose up --detach --build
|
||||
```
|
||||
|
||||
控制面启动时会连接 PostgreSQL,并在事务和 advisory lock 保护下自动执行前向迁移。迁移失败时控制面会退出,由 Compose 按 `restart: unless-stopped` 重启;先检查日志,不要删除数据卷。
|
||||
|
||||
### 首次配置
|
||||
|
||||
服务起来后打开 <http://127.0.0.1:${CREATORHUB_PORT}>:
|
||||
|
||||
1. 「网关管理」页注册网关:名称如 `gw-main`,Endpoint `http://docker-gateway:8081`,令牌填 `GATEWAY_TOKEN` 的值(即 `openssl rand -hex 24` 生成的值)。
|
||||
2. 「镜像版本」页添加可用镜像,如版本 `148.0.7778.215`、引用 `git.ipao.vip/rogee/fingerprint-chromium:148.0.7778.215`(或 `@sha256:` 摘要引用)。
|
||||
3. 「运行环境」页创建环境:中文名 + 小写别名 + 指纹参数,容器名 `creatorhub-browser-<别名>`,Profile 卷 `creatorhub-profile-<别名>`。
|
||||
|
||||
## 部署验证
|
||||
|
||||
```bash
|
||||
@@ -60,13 +68,13 @@ curl --fail --silent --show-error \
|
||||
|
||||
docker compose exec -T postgres \
|
||||
psql -U creatorhub -d creatorhub -tAc \
|
||||
'SELECT 1 FROM schema_migration WHERE version = 1;' \
|
||||
| grep -qx 1
|
||||
'SELECT 1 FROM schema_migration WHERE version = 2;' \
|
||||
| grep -qx 2
|
||||
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
健康检查应成功,浏览器列表接口应返回 JSON,迁移查询当前应输出 `1`,三个 Compose 服务应为运行状态。然后访问 <http://127.0.0.1:8080>;修改过 `CREATORHUB_PORT` 时使用对应端口。
|
||||
健康检查应成功,浏览器列表接口应返回 JSON,迁移查询当前应输出 `2`,三个 Compose 服务应为运行状态。然后访问 <http://127.0.0.1:8080>;修改过 `CREATORHUB_PORT` 时使用对应端口。
|
||||
|
||||
排障时读取结构化服务日志:
|
||||
|
||||
@@ -82,19 +90,20 @@ Compose 部署时通常只需设置以下宿主机变量:
|
||||
| --- | --- | --- |
|
||||
| `CREATORHUB_PORT` | `8080` | 控制面宿主机端口,局域网可访问 |
|
||||
| `DOCKER_GID` | `999` | Docker socket 的宿主机组 ID;必须按实际值设置 |
|
||||
| `GATEWAY_TOKEN` | `dev-creatorhub-gateway-token` | 网关与控制面共享的 Bearer 令牌;生产须改为随机值,并同步填入网关注册表单 |
|
||||
|
||||
服务本身支持并校验以下环境变量;`compose.yaml` 已提供当前部署所需的值:
|
||||
|
||||
| 服务 | 变量 | 当前 Compose 值 |
|
||||
| --- | --- | --- |
|
||||
| `creator-hub` | `LISTEN_ADDR` | 默认 `:8080` |
|
||||
| `creator-hub` | `DOCKER_GATEWAY_URL` | `http://docker-gateway:8081` |
|
||||
| `creator-hub` | `WEB_DIR` | 镜像内固定为 `/app/web` |
|
||||
| `creator-hub` | `DATABASE_URL` | `postgres://creatorhub@postgres/creatorhub?sslmode=disable` |
|
||||
| `creator-hub` | `LOG_LEVEL` | 默认 `info` |
|
||||
| `docker-gateway` | `LISTEN_ADDR` | 默认 `:8081` |
|
||||
| `docker-gateway` | `DOCKER_SOCKET` | 默认值和 Compose 挂载均固定为 `/var/run/docker.sock`;不能只覆盖环境变量 |
|
||||
| `docker-gateway` | `BROWSER_NETWORK` | `creatorhub_browser` |
|
||||
| `docker-gateway` | `GATEWAY_TOKEN` | 与平台注册值一致,长度 ≥16;`/v1` 全部接口校验 Bearer 令牌 |
|
||||
| `docker-gateway` | `LOG_LEVEL` | 默认 `info` |
|
||||
|
||||
不要把凭据写入仓库或 Compose 文件。
|
||||
@@ -123,11 +132,12 @@ set -Eeuo pipefail
|
||||
git pull --ff-only
|
||||
export DOCKER_GID="$(stat -c '%g' /var/run/docker.sock)"
|
||||
export CREATORHUB_PORT=8080
|
||||
docker pull git.ipao.vip/rogee/fingerprint-chromium@sha256:b9f23b8e3ac640174db0dfa49e9095fe7eb06f5db55a4e7550d979b35ff3a1b7
|
||||
docker compose config --quiet
|
||||
docker compose up --detach --build
|
||||
```
|
||||
|
||||
镜像版本升级在页面「运行环境 → 升级」完成:平台会停止并删除旧容器(保留 Profile 卷),用新镜像引用与原指纹参数重建后启动;失败时直接重试即可,无需回滚。
|
||||
|
||||
数据库迁移只支持安全前进,不提供自动破坏性回滚。需要同时恢复旧代码和更新前数据库时,修改下面两个变量后**整块执行一次**;不要逐行或拆块执行。预检、恢复演练、动态容器停止、停服、主库恢复、提交切换和启动都位于同一个 fail-fast subshell 中。
|
||||
|
||||
```bash
|
||||
@@ -154,8 +164,8 @@ docker compose up --detach --build
|
||||
< "$BACKUP_FILE"
|
||||
docker compose exec -T postgres \
|
||||
psql -U creatorhub -d creatorhub_restore_check -v ON_ERROR_STOP=1 -tAc \
|
||||
'SELECT 1 FROM schema_migration WHERE version = 1;' \
|
||||
| grep -qx 1
|
||||
'SELECT 1 FROM schema_migration WHERE version = 2;' \
|
||||
| grep -qx 2
|
||||
docker compose exec -T postgres \
|
||||
dropdb --force -U creatorhub creatorhub_restore_check
|
||||
|
||||
@@ -201,8 +211,8 @@ docker compose up --detach --build
|
||||
< "$BACKUP_FILE"
|
||||
docker compose exec -T postgres \
|
||||
psql -U creatorhub -d creatorhub -v ON_ERROR_STOP=1 -tAc \
|
||||
'SELECT 1 FROM schema_migration WHERE version = 1;' \
|
||||
| grep -qx 1
|
||||
'SELECT 1 FROM schema_migration WHERE version = 2;' \
|
||||
| grep -qx 2
|
||||
|
||||
git switch --detach "$RESTORE_REV"
|
||||
if ! docker compose up --detach --build; then
|
||||
|
||||
Reference in New Issue
Block a user