Files
qinglong/docs/adr/ADR-0433-catalog-bound-kubernetes-target-deployment-ceremony.md
T

87 lines
7.3 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-0433Catalog-bound Kubernetes 目标部署 Ceremony
- 状态:Accepted
- 日期:2026-08-16
- 关联 RFCQL-RFC-0001 D-03、D-14、D-337、D-339、D-341
- 关联 ADRADR-0429、ADR-0431、ADR-0432
## 上下文
ADR-0431 已把 verified release catalog 物化为 Kubernetes v2 locked manifest/report,但最后一步仍由运维者直接执行
`kubectl apply -f locked.yaml`。该命令没有再次绑定 report self-digest、目标 cluster、kubeconfig、kubectl executable 或
field manager;检查过的文件、实际输入和实际 API target 之间仍存在复制、context 漂移与 TOCTOU 窗口。成功输出也没有 durable
receipt,响应丢失后只能凭人工判断是否重放。
Cluster 需要解决这个缺口,但不能把 Kubernetes、YAML 或 registry 工具带到 Local/Edge,也不能为一次发布新增常驻 controller、
webhook 或长期 ServiceAccount authority。
## 决策
1. 在现有维护工作站脚本边界增加 `cluster.deployment.preflight``cluster.deployment.apply`
`cluster.deployment.receipt.audit` 三种 canonical command;只提供一个
`pnpm cluster-deployment:ql3 -- --command-file=/absolute/private/command.json` 入口,不新增 workspace package 或生产依赖。
2. command、locked manifest、lock report、kubeconfig、preflight 与 receipt 必须位于 current-UID 的 canonical `0700` 目录,文件为
canonical、current-UID、单链接 `0600` regular file。所有输入使用 `O_NOFOLLOW|O_CLOEXEC` stable descriptor 有界读取,调用前后
复验 dev/inode/size/mtime/ctime 与 SHA-256。kubectl 必须是 absolute canonical、current-UID 或 root owner、不可 group/other write
的单链接 executable,并由 command 固定其 SHA-256。
3. 目标 consumer 独立验证 `qinglong/kubernetes-deployment-lock@v2` exact shape/self-digest、3.x release identity、catalog workflow/
immutable reference/release-set 闭包、required role 顺序、全部 GHCR digest reference、manifest byte digest、资源/authority 数量、
五项 release annotation,以及未知或畸形 QingLong image authority 为零。不能只相信 materializer 的 success stdout。
4. kubeconfig 必须由 command 固定 SHA-256 和 explicit context;禁止 `exec` 与 legacy `auth-provider`,避免稳定文件读取后再隐式执行
ambient credential plugin。每次网络动作前显式读取 `kube-system` Namespace UID,并与人工审核的 `expectedClusterUid` 精确匹配。
5. preflight 只执行固定 manager `qinglong3-catalog-lock``kubectl apply --server-side --dry-run=server --validate=strict -f=-`,通过
stdin 发送已验证的内存字节,不让 kubectl 按路径二次读取 manifest。所有承载 QingLong image authority 的资源必须显式携带
namespace,禁止依赖 context 的 ambient default namespace。成功后才 no-replace 发布 self-digest preflight report;它明确
`networkAccess=true``kubernetesMutation=false`。目标 namespace 必须预先存在,否则包含 namespaced resources 的 dry-run 自然失败关闭。
6. apply 必须消费 exact preflight digest,并重新执行全部离线检查、cluster UID 与 server-side dry-run;不使用
`--force-conflicts`。实际 apply 后以 `kubectl get -f=- -o=json --show-managed-fields=true` 读取同一资源集合:每个对象必须有
UID/resourceVersion,全部期望字段递归匹配;承载 QingLong image authority 的资源必须由固定 field manager 以 `Apply` 持有,所有
image/catalog annotation 再次精确验证。结束时再次检查 cluster UID 和全部稳定文件。
7. 只有所有步骤成功后才以 `0600` no-replace 发布 self-digest receipt。receipt 绑定 mutation、command、preflight、lock/manifest/
catalog digest、cluster UID、kubeconfig/kubectl digest、各 argv/stdout/stderr digest 与 byte count。已有 exact receipt 的相同 command
离线返回;apply 成功但 receipt 丢失时,使用同一 command/field manager 重放 server-side apply,再以 live convergence 收敛。
8. Kubernetes 多资源 apply 不是事务,receipt 必须固定 `crossResourceAtomicity=false`。失败时不自动删除或回滚资源;先审计 live state,
再用上一份 catalog-bound lock 执行新的显式 roll-forward。offline receipt audit 只重建 canonical/self/command binding,明确
`externalResultsReplayed=false`,不伪称离线重放 API Server 结果。
## 部署与资源影响
- Local/Edge/Standalone 零导入、零制品增量、零常驻 CPU/RSS/网络/写放大;低配路由器继续只消费 Local v2 selection。
- Cluster 不新增 controller、webhook、CRD、RBAC、ServiceAccount、Pod、listener、timer、watcher、数据库、migration、SQL 或 Pool。
ceremony 使用部署者原有 kubeconfig 权限并在命令结束后退出。每个 kubectl 子进程使用独立 `0700` 临时 HOME/XDG cache/TMPDIR
结束即清理,不读取 ambient HOME,也不在仓库或 operator home 留下 discovery cache。
- 实现内聚在 `scripts/lib/ql3-kubernetes-deployment-ceremony.cjs` 与薄 CLI;没有为单一工作站流程拆出浅 workspace package。
- 每次 preflight 为一次 cluster identity read 和一次 server-side dry-runapply 为 identity read、dry-run、apply、convergence read、末次
identity read。manifest/report/kubeconfig 各有明确 byte ceilingprocess stdout/stderr 各最多 4 MiB。
## 被拒绝的替代方案
### 继续文档化裸 `kubectl apply`
拒绝。它无法证明 apply 的文件、report、context、cluster 和 executable 与人工审核对象相同,也没有 response-loss recovery receipt。
### 使用 `kubectl diff` 证明收敛
拒绝。真实 K3s 门证明 kubectl 会隐式从 `$PATH` 启动外部 `diff`,扩大未固定的 executable authority。受审 convergence read 只调用
同一 pinned kubectl,并直接验证 live object identity、managed field ownership 和完整期望字段。
### 自动 `--force-conflicts` 或失败后删除资源
拒绝。强夺其他 field manager 或跨资源猜测回滚会扩大故障;冲突必须失败关闭,多资源非原子事实必须进入 receipt 与人工恢复流程。
### 在集群内新增持续部署 controller
拒绝。当前缺口是显式 release rollout 的最后一跳,不值得新增常驻 availability、credential、certificate 与升级故障域。
## 验证
- 定向契约 10/10,覆盖 lock/report/manifest/annotation、cluster UID、文件权限、kubeconfig executable auth、server dry-run、apply、live
convergence、field manager、receipt 重签、response replay、closed CLI 与低敏失败;
- 隔离三节点 K3s `v1.34.3+k3s1`/Linux arm64 真实运行 6 个资源、4 个零副本 Deploymentpreflight、server-side apply、live
convergence 与 offline receipt audit 全部通过,固定 manager 为 `qinglong3-catalog-lock`,临时 Docker container/network 零残留;
- 最终 live cluster UID 为 `7b2a5391-41a3-4905-90cc-3b831bef0058`preflight digest 为
`sha256:7c6db6236a704cd7791f94268b3883d6385a75a993a14d1611791f885fb65404`receipt digest 为
`sha256:58817a667bccf28c068fff40619cf13d2d0a1be84153de66a137b005feb536ba`。该 synthetic lock 只验证目标 apply 语义,不冒充公开
GHCR catalog ceremony
- 完整 backend、18-package、边界审计与制品结果记录在 QL-RFC-0001 D-341。