diff --git a/docs/reference/spec-v02-cicd.md b/docs/reference/spec-v02-cicd.md index e8f92775..8c1e4aa8 100644 --- a/docs/reference/spec-v02-cicd.md +++ b/docs/reference/spec-v02-cicd.md @@ -103,6 +103,8 @@ CI/CD 内部由 UniDesk 手动触发入口、PipelineRun、component planner、B 3. CI/CD 校验只保留最小构建、语法、打包和冒烟检查;旧 DEV/D601/main gate、运行时内部证明型校验、健康诊断重断言和历史预检不进入 lane。 4. planner 根据 component input 判断 affected/reused services。 5. affected service 通过 BuildKit 发布到 G14 本地 registry;reused service 复用 catalog digest。 + 所有 selected service 的 build TaskRun 都只依赖 `plan-artifacts`,不按 service 串行排队,也不设置 8 并发或其他 Pipeline 级限流。 + 实际并发由 Tekton controller、G14 节点资源、PVC I/O、BuildKit sidecar 和本地 registry 承载能力决定。 6. promotion 刷新 `deploy/artifact-catalog.v02.json`,render `deploy/gitops/g14/runtime-v02/**`,只推送到 `devops-infra` mirror/relay 的 `v0.2-gitops`。 7. `hwlab-g14-v02` 从本地 mirror/relay 的 `v0.2-gitops:deploy/gitops/g14/runtime-v02` 同步到 `hwlab-v02`。 8. UniDesk CLI 或 mirror/relay flush 操作把本地 `v0.2-gitops` 推送到 GitHub canonical remote;flush 不在 CI runtime-ready 的关键路径内,但必须可查询 pending、lastFlushed 和 failure。 @@ -135,6 +137,13 @@ CI/CD 内部由 UniDesk 手动触发入口、PipelineRun、component planner、B 当前预算判定:source-only、所有 service 都复用 artifact、GitOps runtime 只发生 source identity 变化时,总耗时应接近 40s;超过 50s 需要先查是否误触发 `runtime-ready`、是否发生 GitHub 直连、是否恢复了 `npm ci` 或无效 preflight。真正需要 rollout 的 code-only 变更允许约 50s,因为 `runtime-ready` 必须等待 Argo 与 workload 收敛。涉及 BuildKit publish、env image rebuild、registry push 或真实 runtime 滚动时,不适用 40s 预算,应按 affected service 的 build 耗时单独测量。混合变更必须按 service 作用域裁剪:只改 `package.json` 的 `scripts`、旧门禁入口、短连接 CLI、CLI 测试、非 runtime 文档或 device-pod host asset 时,不得触发无关 runtime service 全量 build;若同一变更确实同时改了 `internal/cloud/**`、device-pod code 和 skill bundle,则只允许对应 service build/rollout,其他 service 必须复用 catalog digest。 +真实 rebuild 场景必须按全并行 fan-out 判定性能。 +`plan-artifacts` 之后所有 affected service build task 应同时进入 Tekton 调度队列,`collect-artifacts` 只做 fan-in 等待全部 build task 写入 service report。 +不得为了稳定表面耗时在 Pipeline 拓扑上重新串行化,也不得引入固定 8 并发上限。 +全量 rebuild 的关键路径应接近最慢 service build 耗时加上 source、planning、collect、promotion 和 runtime-ready 固定开销,而不是所有 service build 耗时求和。 +若全并行导致节点 CPU、内存、PVC I/O、BuildKit sidecar 或 registry 竞争,先通过 TaskRun duration、Pod scheduling、node pressure、registry latency 和 BuildKit 日志定位容量瓶颈。 +除非已经证明控制面无法承载,否则不要把容量问题回退成 service 串行依赖。 + 关键阶段预算如下: | 阶段 | 正常预算 | 退化信号 | @@ -155,6 +164,11 @@ Git 读写都走 `devops-infra` 本地 mirror/relay。读路径用 HTTP mirror c env image 复用把系统依赖和业务代码身份分离。`environmentDigest` 表示可复用运行环境,`HWLAB_BOOT_REPO`、`HWLAB_BOOT_COMMIT` 和 `HWLAB_BOOT_SH` 表示本次代码启动身份。code-only 变更只更新 boot metadata 和 runtime identity,不发布新 env image;只有真实 env 输入变化或缺少可复用 env digest 时才进入 env rebuild。`package.json` 只按 dependency/runtime 字段参与 env/runtime hash,`scripts` 只属于开发入口,不得因为清理旧门禁脚本而重建所有 service 或重建 env image。 +BuildKit publish 采用 service 级全并行 fan-out。 +每个 service build task 拥有独立 BuildKit sidecar、独立 service workdir 和独立 report 文件,均从同一 `plan-artifacts` 结果判断是否需要执行。 +跳过的 task 由 Tekton `when` 表达式裁剪,执行的 task 并行写入 `/workspace/source/service-results/.json`。 +`collect-artifacts` 只从 report 目录收敛结果,不再通过 `tasks.build-*.results` 直接依赖某个前序 task 输出,因此全并行不会改变 artifact 身份语义。 + planner 必须按 service component model 裁剪 build。`tools/` 下的短连接 CLI 源码不是 runtime service 输入;`skills/` 只有 agent runtime 或 skills bundle 真正消费的子目录才影响对应 runtime;device-pod host CLI asset 属于 skills bundle 或 host 侧分发输入,不得让 cloud-api、agent-worker、gateway、edge-proxy 等服务重建。CI 读取 artifact catalog 时支持 repo 相对路径和绝对路径,便于用真实 catalog 做本地热探测;catalog 缺失才允许 fail closed 到 rebuild,不得把“诊断命令没读到 catalog”误判为生产路径必须全量构建。 P1 no-op runtime skip 只跳过无实际 runtime 变化的等待。planner 输出 `buildServices=[]` 且 `rolloutServices=[]` 后,`gitops-promote` 会在写入前对旧 `runtime-v02` 与新 render 结果做归一化比较:忽略 source commit、artifact source commit、boot commit 和等价 commit env/annotation 这类 identity-only 字段。如果归一化结果相同,promotion 输出 `skipped-runtime-unchanged`,把 Tekton result `runtime-ready-required=false` 写出,并跳过 GitOps commit、push、Argo hard refresh 和 `runtime-ready`。如果 workload spec、image digest、env image、SecretRef、Service、Ingress、FRP、ConfigMap 或 rollout service 有实际变化,必须保持 `runtime-ready-required=true` 并等待 runtime 收敛。 @@ -208,6 +222,8 @@ done | `source-clone` 超过 5s | PipelineRun 参数 `git-read-url`、mirror service、PVC I/O、`git-mirror status` | 不要改回 GitHub 直连;先修 mirror/read service。 | | catalog 缺失导致全量 build | `prepare-source` 是否从 `v0.2-gitops` 取到 `deploy/artifact-catalog.v02.json` | 先 `git-mirror sync --confirm`;必要时查 mirror refs/object closure。 | | `buildSkippedCount` 偏低或突然变成 0 | `g14-ci-plan` 的 `affectedServices`、`buildServices`、`rolloutServices`、catalog 加载状态、`package.json` 字段 diff 和 service component path | 真实业务变更可以 build;脚本清理、CLI/测试/文档、host asset 或 code-only env-reuse 变更不应误触发全量 build。 | +| 多 service rebuild 仍串行执行 | `tekton-v02/pipeline.yaml` 中所有 `build-*` task 的 `runAfter`,以及 TaskRun startTime 是否都紧跟 `plan-artifacts` | build task 只能依赖 `plan-artifacts`;发现 `build-B` 依赖 `build-A` 时直接修 render 脚本和生成物,不保留串行兼容路径。 | +| 全并行 rebuild 变慢或不稳定 | TaskRun duration、Pod Pending/Unschedulable、node pressure、PVC I/O、BuildKit sidecar 日志、本地 registry push latency | 优先定位资源容量或 registry/BuildKit 瓶颈;不要先把 Pipeline 拓扑改回串行或固定 8 并发。 | | no-op 仍执行 `runtime-ready` | `gitops-promote` 是否输出 `runtime-ready-required=false` 和 `skipped-runtime-unchanged` | 查 runtime 归一化比较;不要直接删除 `runtime-ready`,只修 no-op 判定。 | | mirror `pendingFlush=true` 长期存在 | `bun scripts/cli.ts hwlab g14 git-mirror status` 和最近一次 flush 错误 | 手动 `git-mirror flush --confirm`;flush 不影响已 rollout 本地 revision,但不能静默积压。 | | Argo `Synced/Healthy` 但公网不通 | `hwlab-v02-frpc` 日志、master frps allowlist、`19666/19667` | 修 FRP allowlist 或 frpc;不要把 DEV/PROD 入口当作 v02 证据。 | @@ -356,6 +372,7 @@ GitOps branch 已更新、source branch render 通过、PipelineRun 名称存在 | `devops-infra` git mirror/relay 加速 | 已实现/持续约束 | source/catalog/runtime checkout 读路径和 GitOps promotion 写路径均使用独立基础设施集群 mirror/relay;Argo source 指向本地 mirror,GitHub flush 由 UniDesk CLI 手动触发,不设置 CronJob。runtime namespace 不持有 GitHub deploy key,registry 保持 G14 `hwlab-ci/hwlab-registry`。 | | `hwlab-cli` 不进 CI/CD service matrix | 已实现/持续约束 | CLI 是固定 repo 短连接 client,不发布镜像、不生成 artifact、不创建 `build-hwlab-cli` TaskRun;相关旧入口出现时直接删除。 | | CI/CD fast lane 性能预算 | 已实现/持续约束 | env-reuse no-op 目标约 40s;真实 runtime rollout 目标约 50s;不得恢复 `prepare-source` 依赖安装、GitHub 关键路径写入或 no-op runtime 等待。 | +| Service build 全并行 fan-out | 已实现/持续约束 | selected services 的 build TaskRun 全部只依赖 `plan-artifacts`,不设置 8 并发或其他 Pipeline 级限流;`collect-artifacts` 通过 service report 目录 fan-in。 | | 自动 registry GC | 未实现 | 初期不启用自动 GC,后续需 lane/profile 保护集。 | ## 平行 lane 运维边界 diff --git a/scripts/g14-gitops-render.mjs b/scripts/g14-gitops-render.mjs index 85fb1224..db994b6f 100644 --- a/scripts/g14-gitops-render.mjs +++ b/scripts/g14-gitops-render.mjs @@ -2543,8 +2543,8 @@ function runtimeReadyTask({ profile = "dev" } = {}) { function imagePublishTaskSet(args = { lane: "g14" }) { const serviceIds = serviceIdsForLane(args.lane); - const buildTasks = serviceIds.map((serviceId, index) => perServiceBuildTask(serviceId, { - runAfter: args.lane === "v02" && index > 0 ? [buildTaskName(serviceIds[index - 1])] : ["plan-artifacts"] + const buildTasks = serviceIds.map((serviceId) => perServiceBuildTask(serviceId, { + runAfter: ["plan-artifacts"] })); return [ planArtifactsTask({ serviceIds }), diff --git a/scripts/g14-gitops-render.test.ts b/scripts/g14-gitops-render.test.ts index 9cb9cb9a..7f48adbe 100644 --- a/scripts/g14-gitops-render.test.ts +++ b/scripts/g14-gitops-render.test.ts @@ -171,8 +171,7 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots for (let index = 0; index < retainedServiceIds.length; index += 1) { const serviceId = retainedServiceIds[index]; const task = taskByName(pipelineJson, `build-${serviceId}`); - const expectedRunAfter = index === 0 ? ["plan-artifacts"] : [`build-${retainedServiceIds[index - 1]}`]; - assert.deepEqual(task.runAfter, expectedRunAfter); + assert.deepEqual(task.runAfter, ["plan-artifacts"]); assert.deepEqual(task.when, [{ input: `$(tasks.plan-artifacts.results.affected-${serviceId})`, operator: "in", values: ["true"] }]); }