From 84a6c1de951e59be342c446db742d767551ed48e Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 26 May 2026 16:16:33 +0800 Subject: [PATCH] fix: clarify G14 Argo template health --- docs/reference/g14-gitops-cicd.md | 3 ++- scripts/g14-gitops-render.mjs | 24 +++++++++++++++++------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/docs/reference/g14-gitops-cicd.md b/docs/reference/g14-gitops-cicd.md index 5fa030e1..35d31f45 100644 --- a/docs/reference/g14-gitops-cicd.md +++ b/docs/reference/g14-gitops-cicd.md @@ -60,13 +60,14 @@ G14 CI/CD 加速的第一判定标准不是 PipelineRun 总耗时单点变短, - 并发 fan-out:per-service TaskRun 应由 Tekton/k8s scheduler 同时调度,多个 service 的 build/reuse 窗口应接近“最慢单个服务任务耗时”,而不是所有服务串行相加。reuse-only 场景的 fan-out 窗口当前基线约为十几秒;真实组件构建场景仍需按 changed service 单独记录 BuildKit 耗时和 cache hit 情况。 - CD rollout:unchanged service 必须复用 `deploy.services[]` 的 image/digest/env,pod template 不应因全局 source commit 改变而无意义滚动。手写 manifest(例如 `deepseek-proxy`、device-agent 类辅助 workload)只要复用某个已发布服务镜像,也必须走同一个 deploy catalog image 选择逻辑,不能直接用当前 source commit tag。 - 运行态验证:GitOps promote 成功只说明 `G14-gitops` 分支更新;最终通过必须看 Argo Application revision、sync 状态、目标 namespace Deployment/StatefulSet ready、公网 health。Argo 还停在旧 revision 时,优先做 `argocd.argoproj.io/refresh=hard` 刷新;不要把已经推送的 `G14-gitops` 分支误判成运行面已滚动。 +- Argo health 判定:`spec.suspend=true` 的模板型 Job 会让 Application 总 health 显示 `Suspended`。`hwlab-agent-worker-template`、`hwlab-cli-template` 这类只作为手动启动模板的 Job 必须带 `argocd.argoproj.io/ignore-healthcheck: "true"`,避免 DEV 实际 workload 全部 ready 时仍被误判为 Suspended;真正发布验收仍以长驻 workload ready、公网 health 和失败 Pod 清单为准。 当前仍然慢的主要位置: - `ci-json` 仍是大头,当前量级约 90 秒。它包含 Git clone、checkout、`npm ci`、Playwright cache probe 和 `CI.json` 命令执行。继续加速应优先减少每轮 `npm ci` 成本,例如使用预热 workspace/cache、依赖层镜像或把源码校验拆成更细粒度的 Tekton Task。 - `gitops-promote` 仍是大头,当前量级约 100 秒。它包含 source branch freshness check、artifact catalog refresh、render/check、clone `G14-gitops`、复制 generated desired state、commit 和 push。继续加速应优先减少 git clone/push 成本,例如使用浅 clone、持久 workspace、server-side patch 或把 GitOps repo 操作单独缓存。 - per-service TaskRun 数量增加后会有 k8s 调度和 sidecar 生命周期开销。reuse-only 任务很快,但如果每个 unchanged service 都启动完整 buildkit sidecar,调度开销会抵消部分收益;长期目标是让 unchanged service 走更轻量的 reuse Task,changed service 才启动 BuildKit。 -- Argo sync/health 不是 instant。`Sync=Synced` 后 health 仍可能因为 replicas=0 的模板 Deployment、Job 模板或 StatefulSet 旧 revision 显示 `Suspended`/`Progressing`。这时应先看实际长驻 workload ready 与具体 unhealthy resource,不要只看 Application 总 health。 +- Argo sync/health 不是 instant。`Sync=Synced` 后 health 仍可能因为 replicas=0 的模板 Deployment、未排除 health 的模板 Job 或 StatefulSet 旧 revision 显示 `Suspended`/`Progressing`。这时应先看实际长驻 workload ready 与具体 unhealthy resource,不要只看 Application 总 health;如果 suspended Job 只是模板资源,应修 render 注解而不是把 DEV rollout 判成失败。 - StatefulSet 旧 ordinal pod 可能长期卡在历史缺失镜像上,即使 StatefulSet 当前 template 已经修正。DEV 可按运行面热修流程删除旧 Pending pod 让 StatefulSet 按当前 template 重建;PROD namespace 需要明确 maintenance 授权后再做同类删除。 每次优化 CI/CD 都必须保留可比测量:记录 PipelineRun 总耗时、`ci-json`、`plan-artifacts`、per-service fan-out 起止窗口、`collect-artifacts`、`gitops-promote`、Argo sync 到 workload ready 的时间,并标明本轮是 reuse-only、单组件 build 还是多组件 build。没有这些分段数据,不要只用“感觉变快/变慢”判断优化成败。 diff --git a/scripts/g14-gitops-render.mjs b/scripts/g14-gitops-render.mjs index 2871c259..0f332522 100644 --- a/scripts/g14-gitops-render.mjs +++ b/scripts/g14-gitops-render.mjs @@ -128,10 +128,15 @@ function usage() { ` --prod-web-endpoint URL default: ${defaultProdWebEndpoint}`, " --use-deploy-images default: render workload images/env from deploy/deploy.json per service", " --legacy-source-images compatibility fallback: render every workload with the source revision tag", - " --check verify generated files are current without writing" + " --check verify generated files are current without writing", + " --no-write plan and print generated file list without writing" ].join("\n"); } +function generatedPath(filePath) { + return path.isAbsolute(filePath) ? filePath : path.join(repoRoot, filePath); +} + async function readJson(relativePath) { return JSON.parse(await readFile(path.join(repoRoot, relativePath), "utf8")); } @@ -153,7 +158,7 @@ async function resolveSourceRevision(value) { async function defaultCheckSourceRevision(args) { if (!args.check || args.sourceRevision) return args; try { - const raw = await readFile(path.join(repoRoot, args.outDir, "source.json"), "utf8"); + const raw = await readFile(generatedPath(path.join(args.outDir, "source.json")), "utf8"); const parsed = JSON.parse(raw); if (typeof parsed.sourceCommit === "string" && sourceCommitPattern.test(parsed.sourceCommit)) { return { ...args, sourceRevision: parsed.sourceCommit }; @@ -312,6 +317,11 @@ function transformWorkloads({ workloads, deploy, source, registryPrefix, runtime annotate(item.metadata, { "argocd.argoproj.io/sync-options": "Force=true,Replace=true" }); + if (item.spec?.suspend === true) { + annotate(item.metadata, { + "argocd.argoproj.io/ignore-healthcheck": "true" + }); + } } if (serviceIdForWorkload(item, null) === "hwlab-tunnel-client" && typeof item.spec?.replicas === "number") { item.spec.replicas = 0; @@ -2254,8 +2264,8 @@ async function plannedFiles(args) { } async function writeFiles(files) { - for (const [relativePath, content] of files) { - const absolutePath = path.join(repoRoot, relativePath); + for (const [filePath, content] of files) { + const absolutePath = generatedPath(filePath); await mkdir(path.dirname(absolutePath), { recursive: true }); await writeFile(absolutePath, content); } @@ -2263,14 +2273,14 @@ async function writeFiles(files) { async function checkFiles(files) { const mismatches = []; - for (const [relativePath, expected] of files) { + for (const [filePath, expected] of files) { let actual = null; try { - actual = await readFile(path.join(repoRoot, relativePath), "utf8"); + actual = await readFile(generatedPath(filePath), "utf8"); } catch (error) { if (error?.code !== "ENOENT") throw error; } - if (actual !== expected) mismatches.push(relativePath); + if (actual !== expected) mismatches.push(filePath); } return mismatches; }