Files
qinglong/docs/adr/ADR-0197-generation-cas-local-compose-image-selection.md
T

134 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-0197:基于 Generation CAS 的本机 Compose 镜像选择
- 状态:Accepted(选择事务与产品 CLI 已实现;真实切流和设备门仍待完成)
- 日期:2026-07-29
- 关联 RFCQL-RFC-0001 D-184、D-185、D-186、D-187
- 关联 ADRADR-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 nameparser
要求 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、闪存寿命和镜像垃圾回收策略。