mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
134 lines
6.0 KiB
Markdown
134 lines
6.0 KiB
Markdown
# ADR-0197:基于 Generation CAS 的本机 Compose 镜像选择
|
||
|
||
- 状态:Accepted(选择事务与产品 CLI 已实现;真实切流和设备门仍待完成)
|
||
- 日期:2026-07-29
|
||
- 关联 RFC:QL-RFC-0001 D-184、D-185、D-186、D-187
|
||
- 关联 ADR:ADR-0194、ADR-0195、ADR-0196
|
||
|
||
## 背景
|
||
|
||
D-184 生成包含不可变镜像 digest 的 `compose.yaml`,适合首次部署,却把长期不应
|
||
变化的 sandbox、资源限制、volume 和网络策略与每次发布都变化的镜像选择放在同一
|
||
文件。直接允许覆盖该文件会同时扩大升级 authority,并且在进程中断、两个 operator
|
||
竞争或回退多代版本时无法区分安全模板漂移和合法镜像变化。
|
||
|
||
控制器若直接调用 `docker compose pull/up/down`,又会把本地文件提交、远端 registry、
|
||
Docker daemon、进程健康和 SQLite capability 合并成一个实际上不具备 ACID 的操作。
|
||
在路由设备上,这还会扩大短生命周期 CLI 的依赖和常驻成本。
|
||
|
||
## 决策
|
||
|
||
### 1. 稳定模板与可变选择分离
|
||
|
||
Compose 基础 descriptor 固定不携带 image;私有 selection 是第二个 override 文件:
|
||
|
||
```yaml
|
||
x-qinglong-image-selection:
|
||
schema: qinglong/local-compose-image-selection@v1
|
||
generation: 1
|
||
services:
|
||
qinglong3:
|
||
image: registry.example/qinglong3-local@sha256:...
|
||
```
|
||
|
||
operator 必须按固定顺序同时传入 `service/compose.yaml` 和私有
|
||
`service/compose.image.yaml`:
|
||
|
||
```text
|
||
docker compose \
|
||
-f /opt/qinglong3/service/compose.yaml \
|
||
-f /opt/qinglong3/service/compose.image.yaml ...
|
||
```
|
||
|
||
`compose.image.yaml` 是唯一活动镜像选择,权限为当前 UID `0600`。它同时记录 schema、
|
||
generation、previous generation、rollback target、mutation、变更时间和完整 OCI
|
||
digest。字段采用固定顺序和安全 YAML 子集;未知、重复、非 canonical 或 mutable
|
||
image 均拒绝。它不使用 Compose interpolation,因此 shell、`.env` 或 ambient
|
||
environment 不能覆盖 image authority。
|
||
|
||
ADR-0198 在不改变 selection authority 的前提下,为 service 重复派生
|
||
generation/mutation label,并为基础模板加入 instance-scoped project name;parser
|
||
要求 metadata 与 label exact 相等。
|
||
|
||
D-184 fresh prepare 为 Compose 创建 generation 1,并在
|
||
`service/revisions/1.yaml` 保存完全相同的不可变记录。revision 目录为当前 UID
|
||
`0700`。
|
||
|
||
### 2. Upgrade 和 rollback 都创建新 generation
|
||
|
||
入口继续属于既有 `@qinglong/local-owner-cli/local-deployment`:
|
||
|
||
```text
|
||
ql3-local-deploy compose-revision \
|
||
--command-file /absolute/private-command.json
|
||
```
|
||
|
||
upgrade command 固定当前 `expectedGeneration`、新的 `@sha256` image、
|
||
UUID v4 mutation 和时间。rollback 不接受调用方重新提交旧 image,只接受小于当前
|
||
generation 的 `targetGeneration`;控制器从同一 revision 目录读取、复核并复制其
|
||
image。回退也创建新的 generation,而不是把 head 数字倒退,因此历史和 CAS 始终
|
||
单调。
|
||
|
||
每次操作最多读取当前 selection、一个 rollback target 和本次 revision,使用常数级
|
||
内存。generation 上限为 100,000;每代只增加一个不足 64 KiB、实际为数百字节的
|
||
私有文件,不引入数据库表、timer、watcher、listener、第三方依赖或第 23 个
|
||
workspace package。
|
||
|
||
### 3. 单文件原子切换和结果未知恢复
|
||
|
||
控制器按以下顺序执行:
|
||
|
||
1. 复核 deployment、service、revision 目录的 canonical/current-UID/`0700`
|
||
identity;
|
||
2. 严格读取 active selection,要求当前 generation 等于 expected,或已经是同一
|
||
mutation 的 exact next generation;
|
||
3. 以完整 normalized command 创建 deterministic no-replace lock;不同 intent
|
||
冲突失败关闭;
|
||
4. 再读 active selection,关闭 lock 获取窗口中的变更;
|
||
5. no-replace 发布新的 immutable revision;
|
||
6. 在同一目录完整写入 stage、`fsync`,再次复核 expected active bytes,再以
|
||
`rename` 原子替换 `compose.image.yaml` 并 `fsync` service 目录;
|
||
7. 精确复核并移除 command lock。
|
||
|
||
崩溃发生在 stage、revision publication、active rename 或 lock cleanup 窗口时,
|
||
operator 必须原样重放同一 command。若 active 已是 exact next selection,重放只补齐
|
||
缺失 revision/清理 lock 并返回 `existing`;不得换 mutation 或时间来猜测结果。
|
||
stale generation、active/revision/stage/lock 的 identity 或内容漂移均拒绝。
|
||
|
||
### 4. Selection activation 不等于运行中容器切流
|
||
|
||
本控制器明确不:
|
||
|
||
- 拉取、构建或验证镜像;
|
||
- 调用 Docker daemon 或 Compose;
|
||
- start、stop、restart 服务;
|
||
- 打开、迁移、备份或降级 SQLite;
|
||
- 删除历史 revision;
|
||
- 宣称旧 image 一定兼容当前数据库 capability。
|
||
|
||
operator 必须先按 D-186 验证 exact release digest 并预取镜像,再提交 selection,
|
||
检查 `docker compose ... config --images`,最后显式执行 `up -d` 并观察应用
|
||
`active`/`stopped` 事件。镜像选择回退只恢复 desired image,不等于数据回退;目标
|
||
image 与当前 SQLite capability 的兼容证据属于独立 release gate。
|
||
|
||
## 验收
|
||
|
||
- Compose fresh prepare 生成稳定模板、generation 1 active selection 和 immutable
|
||
revision;
|
||
- upgrade `1 → 2`、exact replay 和 rollback `2 → 3(target=1)` 收敛;
|
||
- rollback 只读取历史 revision,历史 drift 拒绝;
|
||
- stale expected generation 和不同 command lock 拒绝;
|
||
- stage 与 active-switch response-loss 窗口可由原命令恢复;
|
||
- CLI 只接收私有 command file,输出仅含 operation/status/generation/kind;
|
||
- 本机 Docker Compose v5.3.1 双 `-f` `config --images` 精确返回 selection 中的
|
||
digest;
|
||
- `local-owner-cli` 全量测试通过,workspace package 和生产依赖数量不变。
|
||
|
||
## 未包含
|
||
|
||
- 自动 Docker service activation 和健康回退;
|
||
- systemd/OpenRC 二进制或系统包 revision;
|
||
- SQLite schema/capability downgrade;
|
||
- remote signature verification 与真实 GHCR release;
|
||
- 路由器断电、ENOSPC、闪存寿命和镜像垃圾回收策略。
|