From f690b95d23fd1eb9e76aedd2cd08d1a48a0864c9 Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 25 May 2026 16:30:45 +0800 Subject: [PATCH] ci: reuse G14 tools image for GitOps jobs --- docs/reference/g14-gitops-cicd.md | 1 + scripts/g14-gitops-render.mjs | 22 +++++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/docs/reference/g14-gitops-cicd.md b/docs/reference/g14-gitops-cicd.md index f9748bf9..85071a4a 100644 --- a/docs/reference/g14-gitops-cicd.md +++ b/docs/reference/g14-gitops-cicd.md @@ -24,6 +24,7 @@ G14 是 HWLAB 的旁路 DEV 集群目标。G14 CI/CD 必须只作用于 G14 k3s - 默认 registry prefix 是 `127.0.0.1:5000/hwlab`,用于 G14 单节点 k3s 的 node-local registry。 - 默认 CI/CD proxy 是 G14 本机 `http://127.0.0.1:10808` / `socks5h://127.0.0.1:10808`。Tekton CI step、Docker-in-Docker sidecar 和 publish step 都注入 proxy/no_proxy;publish step 必须先在 DIND 内 `docker pull $HWLAB_DEV_BASE_IMAGE`,再进入 DEV base-image preflight、build 和 registry push,保证 npm、base image 拉取和 registry push 走 G14 本机代理边界。 - `ci-json` 不允许每次运行时重新 `apt-get install` 或 `npx playwright install --with-deps`。它必须使用已经预热的 CI 基础镜像,当前默认由 `scripts/g14-gitops-render.mjs` 固定为 `mcr.microsoft.com/playwright@sha256:b0ab6f3cb99aa7803adbc14d9027ec1785fc6e433b97e134e0f8fe61683b6b53`;该镜像在 G14 k3s/containerd 中已缓存,包含 Node、npm、Git、Python3、OpenSSH、curl、Playwright 系统依赖和 `/ms-playwright/chromium-1217`。脚本启动时必须输出 `ci-base-image` 和 `playwright-cache-check` 结构化日志;缺少工具或 Chromium cache 时直接失败并修基础镜像,不能回退到 runtime apt 下载。若以后需要 Node 22 固定版本,先构建并推送 G14 registry 内的 Node22+Playwright runner 镜像,再修改 `HWLAB_G14_CI_JSON_IMAGE`/render 默认值。 +- Poller、control-plane reconciler 和 image publish step 也不允许每次运行时 `apk add`。当前 G14 registry 固定工具镜像是 `127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-v1`,包含 Node 22、npm、Git、OpenSSH、curl、Python3 和 Docker CLI;生成的脚本只做 proxy preflight 与工具存在性检查。若需要升级工具,先在 G14 构建/推送新的工具镜像,再修改 `HWLAB_G14_CI_TOOLS_IMAGE`/render 默认值并由 reconciler apply。 - 任何依赖下载阶段都必须有可观测诊断。生成的 Tekton 脚本在 apt/apk、npm、Playwright browser、Docker base image pull 和 GitOps promote 之前输出结构化 `dependency-proxy-probe`、`dependency-curl-probe` 或 `dependency-download-*` 日志,至少包含 phase、目标 URL/镜像、脱敏 proxy、首包耗时、总耗时、下载字节数和速度。CI/CD 卡在下载时,先用这些日志判断是 proxy 不可达、目标源慢、DNS/首包慢还是下载吞吐低,再决定是否切换 G14 代理节点或预热镜像;不能只凭 PipelineRun Running 时长判断业务测试失败。 常用命令: diff --git a/scripts/g14-gitops-render.mjs b/scripts/g14-gitops-render.mjs index 0343b8e7..d72a80ea 100644 --- a/scripts/g14-gitops-render.mjs +++ b/scripts/g14-gitops-render.mjs @@ -19,6 +19,7 @@ const defaultProxyUrl = process.env.HWLAB_G14_PROXY_URL || "http://127.0.0.1:108 const defaultAllProxyUrl = process.env.HWLAB_G14_ALL_PROXY_URL || "socks5h://127.0.0.1:10808"; const defaultNoProxy = process.env.HWLAB_G14_NO_PROXY || "127.0.0.1,localhost,::1,10.0.0.0/8,10.42.0.0/16,10.43.0.0/16,192.168.0.0/16,.svc,.cluster.local,kubernetes.default.svc,registry.hwlab-ci.svc,hwlab-registry.hwlab-ci.svc"; const ciJsonRunnerImage = process.env.HWLAB_G14_CI_JSON_IMAGE || "mcr.microsoft.com/playwright@sha256:b0ab6f3cb99aa7803adbc14d9027ec1785fc6e433b97e134e0f8fe61683b6b53"; +const ciToolsRunnerImage = process.env.HWLAB_G14_CI_TOOLS_IMAGE || "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-v1"; const sourceCommitPattern = /^[a-f0-9]{7,40}$/u; function proxyEnv() { @@ -455,8 +456,9 @@ function ciJsonScript(ci) { function publishScript() { return `#!/bin/sh set -eu -${dependencyProxyProbeScript("image-publish-pre-apk", "https://dl-cdn.alpinelinux.org/alpine/latest-stable/main/x86_64/APKINDEX.tar.gz")} -apk add --no-cache docker-cli git openssh-client python3 +${dependencyProxyProbeScript("image-publish-proxy-preflight", "http://deb.debian.org/debian/dists/bookworm/InRelease")} +echo '{"event":"ci-base-image","phase":"image-publish","image":"${ciToolsRunnerImage}","policy":"no-runtime-apk"}' +for tool in node npm git python3 ssh docker; do command -v "$tool" >/dev/null 2>&1 || { echo '{"event":"ci-base-image","phase":"image-publish","ok":false,"reason":"missing-tool","tool":"'"$tool"'"}'; exit 31; }; done export DOCKER_HOST=unix:///var/run/docker.sock export HWLAB_CI_ARTIFACT_RUN_ID="$(context.pipelineRun.name)" export HWLAB_CI_ARTIFACT_RUN_OWNER="tekton" @@ -545,8 +547,9 @@ echo '{"status":"pushed","gitopsBranch":"'"$(params.gitops-branch)"'","sourceRev function controlPlaneReconcileScript() { return `#!/bin/sh set -eu -${dependencyProxyProbeScript("control-plane-pre-apk", "https://dl-cdn.alpinelinux.org/alpine/latest-stable/main/x86_64/APKINDEX.tar.gz")} -apk add --no-cache git openssh-client ca-certificates +${dependencyProxyProbeScript("control-plane-proxy-preflight", "http://deb.debian.org/debian/dists/bookworm/InRelease")} +echo '{"event":"ci-base-image","phase":"control-plane-reconciler","image":"${ciToolsRunnerImage}","policy":"no-runtime-apk"}' +for tool in node npm git python3 ssh curl; do command -v "$tool" >/dev/null 2>&1 || { echo '{"event":"ci-base-image","phase":"control-plane-reconciler","ok":false,"reason":"missing-tool","tool":"'"$tool"'"}'; exit 31; }; done mkdir -p /root/.ssh cp /workspace/git-ssh/ssh-privatekey /root/.ssh/id_rsa chmod 600 /root/.ssh/id_rsa @@ -653,8 +656,9 @@ NODE function pollerScript() { return `#!/bin/sh set -eu -${dependencyProxyProbeScript("poller-pre-apk", "https://dl-cdn.alpinelinux.org/alpine/latest-stable/main/x86_64/APKINDEX.tar.gz")} -apk add --no-cache git openssh-client ca-certificates +${dependencyProxyProbeScript("poller-proxy-preflight", "http://deb.debian.org/debian/dists/bookworm/InRelease")} +echo '{"event":"ci-base-image","phase":"poller","image":"${ciToolsRunnerImage}","policy":"no-runtime-apk"}' +for tool in node git ssh curl; do command -v "$tool" >/dev/null 2>&1 || { echo '{"event":"ci-base-image","phase":"poller","ok":false,"reason":"missing-tool","tool":"'"$tool"'"}'; exit 31; }; done mkdir -p /root/.ssh cp /workspace/git-ssh/ssh-privatekey /root/.ssh/id_rsa chmod 600 /root/.ssh/id_rsa @@ -923,7 +927,7 @@ function tektonPipeline(ci) { }], steps: [{ name: "publish", - image: "node:22-alpine", + image: ciToolsRunnerImage, env: [{ name: "DOCKER_HOST", value: "unix:///var/run/docker.sock" }, ...proxyEnv()], volumeMounts: [{ name: "docker-run", mountPath: "/var/run" }], script: publishScript() @@ -1045,7 +1049,7 @@ function tektonPollerCronJob(args) { dnsPolicy: "ClusterFirstWithHostNet", containers: [{ name: "poll", - image: "node:22-alpine", + image: ciToolsRunnerImage, imagePullPolicy: "IfNotPresent", env: [ ...proxyEnv(), @@ -1111,7 +1115,7 @@ function tektonControlPlaneReconcilerCronJob(args) { dnsPolicy: "ClusterFirstWithHostNet", containers: [{ name: "reconcile", - image: "node:22-alpine", + image: ciToolsRunnerImage, imagePullPolicy: "IfNotPresent", env: [ ...proxyEnv(),