mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
138 lines
6.0 KiB
Markdown
138 lines
6.0 KiB
Markdown
# ADR-0198:绑定 Digest 的本机 Compose Rollout Preflight
|
||
|
||
- 状态:Accepted(只读 preflight 已实现;真实 apply/健康处置见 ADR-0199)
|
||
- 日期:2026-07-29
|
||
- 关联 RFC:QL-RFC-0001 D-184、D-185、D-186、D-187、D-188
|
||
- 关联 ADR:ADR-0194、ADR-0195、ADR-0196、ADR-0197
|
||
|
||
## 背景
|
||
|
||
ADR-0197 已把 desired image 切换收敛为 generation CAS,但“某个 digest 已签名”和
|
||
“该 image 能安全打开当前 deployment”是两种不同事实。旧 image 可能只支持较早
|
||
SQLite capability;本机 tag/image ID 可能没有 registry identity;Compose 两个文件
|
||
也可能被错误顺序、错误 project 或错误 daemon 解释。
|
||
|
||
原 Compose descriptor 未声明 project name。Compose 默认从配置目录名派生 project,
|
||
所有标准 deployment 都位于名为 `service` 的目录,因此同一 Docker daemon 上的多个
|
||
QingLong 实例可能共享 project identity。
|
||
|
||
在没有这些事实时直接实现自动 `up` 或自动 rollback,会把镜像供应链、Docker
|
||
副作用、SQLite migration 和健康判断错误地包装成一个不存在的事务。
|
||
|
||
## 决策
|
||
|
||
### 1. Image config 声明可审计兼容窗口
|
||
|
||
AI-excluded local image 固定增加:
|
||
|
||
```text
|
||
io.qinglong.local.application-config=2
|
||
io.qinglong.local.compose-selection=1
|
||
io.qinglong.local.sqlite-contract-min=35
|
||
io.qinglong.local.sqlite-contract-max=35
|
||
```
|
||
|
||
同时保留并复核 `io.qinglong.profile=edge,standalone`、
|
||
`io.qinglong.ai=excluded`、OCI source/revision/version、numeric
|
||
`65532:65532` 和唯一 entrypoint。min/max 是该 image 可打开的 SQLite contract
|
||
闭区间,不是“最新版本”提示;当前 image 只接受 exact v35。
|
||
|
||
这些 label 同时进入:
|
||
|
||
- Dockerfile 静态 mutation audit;
|
||
- native amd64/arm64 image inspect;
|
||
- OCI layout 双平台 config exact audit;
|
||
- pushed digest 的 release preflight。
|
||
|
||
调用方 command 中不得复制或声明这些 label。preflight 只信 Docker daemon 对 exact
|
||
RepoDigest 返回的 image config。
|
||
|
||
### 2. Project 与容器 revision 身份稳定
|
||
|
||
基础 `compose.yaml` 的 top-level `name` 从 instance ID 生成:
|
||
|
||
```text
|
||
ql3-<bounded-slug>-<domain-separated-sha256-prefix>
|
||
```
|
||
|
||
`.` 会转为 `-`,摘要关闭 slug 碰撞。名称不依赖 deployment root,目录移动不会静默
|
||
创建第二个 project;重复 instance ID 也不会在同一 daemon 上被当作两个实例。
|
||
|
||
`compose.image.yaml` 除 exact image 外,向 `qinglong3` service 写入:
|
||
|
||
```text
|
||
io.qinglong.deployment.generation
|
||
io.qinglong.deployment.mutation
|
||
```
|
||
|
||
两者由 canonical selection 重复派生并在解析时交叉复核,不能由 operator 单独填写。
|
||
后续 apply/recovery 可据此识别 container 对应的 durable desired revision。
|
||
|
||
### 3. Preflight 是现有短生命周期 CLI 的只读操作
|
||
|
||
入口继续复用现有 package 和 binary:
|
||
|
||
```text
|
||
ql3-local-deploy compose-preflight \
|
||
--command-file /absolute/private-command.json
|
||
```
|
||
|
||
command 只含 deployment root、canonical Docker executable、canonical Unix socket、
|
||
root acknowledgement 和 expected generation。Docker executable 必须是 root 或当前
|
||
UID 拥有、不可 group/world write 的 canonical regular executable。socket 必须是
|
||
root 或当前 UID 拥有、非 symlink、非 world-writable 的 canonical Unix socket。
|
||
CLI 不使用 ambient Docker context。
|
||
|
||
检查顺序:
|
||
|
||
1. 复核 deployment/service/revision 目录与 active/archive exact;
|
||
2. 从 application v2 读取 instance/Profile/busy timeout,再用正式 renderer 重建并
|
||
byte-exact 复核 application config 与 Compose descriptor;
|
||
3. 对 host SQLite 执行完整 read-only readiness:quick/foreign-key check、70 条
|
||
migration history、schema、Plugin Package evidence 与 contract v35;
|
||
4. 以显式 `--host unix://...` 和一次性 `0700` 空 Docker config 执行
|
||
`image inspect`;要求本地 RepoDigests 包含 active exact reference,并复核
|
||
OS/arch/user/entrypoint/source/revision/version/Profile/AI 与 compatibility labels;
|
||
5. 对 base + selection 执行真实 `docker compose config --format json`,精确复核
|
||
project、image、generation/mutation label、UID:GID、read-only root、network
|
||
none、drop ALL、no-new-privileges、memory/PID、command、唯一 bind 和 tmpfs。
|
||
|
||
成功输出仅含 ready、generation、Profile、SQLite contract、architecture 和 service
|
||
kind,不输出 root、socket、executable、image/digest 或 mutation。
|
||
|
||
### 4. Preflight 不执行 rollout
|
||
|
||
本命令不得:
|
||
|
||
- pull/build/push image;
|
||
- create/start/stop/restart/remove container;
|
||
- 打开可写 SQLite、执行 migration 或创建 backup;
|
||
- 写 deployment receipt、修改 selection 或自动 rollback;
|
||
- 把本地 registry evidence 宣称为正式 release。
|
||
|
||
正式 operator 仍须先完成 D-186 signature/attestation verify 和显式 pull,再运行本
|
||
门。共享 release workflow 对 local pushed digest 的只读检查已由 ADR-0199 的 Edge
|
||
与 Standalone 真实 rollout gate 包含并加强。
|
||
|
||
## 验收
|
||
|
||
- application config v2、selection v1、SQLite `35..35` 四项 label 在
|
||
Dockerfile/native image/OCI layout/release workflow 中锁定;
|
||
- Compose project name 对 instance 稳定,selection label 与 generation/mutation
|
||
exact;
|
||
- stale generation、active/archive/config/descriptor drift、非 socket、错误
|
||
RepoDigest、标签窗口或 Compose 合并均拒绝;
|
||
- 完整真实 SQLite v35 readiness 在 Docker 之前执行;
|
||
- CLI 输出低敏且不产生 Docker container 副作用;
|
||
- `local-owner-cli` 36/36、镜像/OCI/release 专项 36/36;
|
||
- arm64 Docker Compose v5.3.1 对 Edge/Standalone 的 localhost exact manifest
|
||
live preflight 均 compatible;
|
||
- workspace 保持 22 packages,无新增生产依赖。
|
||
|
||
## 未包含
|
||
|
||
- `docker compose up`、active event、receipt 与失败 rollback(由 ADR-0199 实现);
|
||
- SQLite migration 前 backup、写后 capability 识别和旧 image compatibility;
|
||
- systemd/OpenRC 制品 compatibility/preflight;
|
||
- 正式 GHCR 双架构 workflow 成功记录和物理路由设备门。
|