feat: add git mirror proxy benchmark stage

This commit is contained in:
Codex
2026-06-27 02:23:46 +00:00
parent 1114517e98
commit cb99ad969b
5 changed files with 94 additions and 24 deletions
+7 -7
View File
@@ -1,6 +1,6 @@
---
name: unidesk-ops
description: UniDesk 手动运维 CLI — `server``gc`、PK01 `platform-db postgres`、platform-infra egress proxy 和 k3s dependency proxy benchmark 运维。覆盖主 server 启停、健康检查、swap、日志、Docker 镜像清理、磁盘 GC、服务重建/重启、PK01 host PostgreSQL、D601/D518 proxyserver 视角流量测速和 k3s 真实依赖拉取 benchmark。用户提到 server start、server status、server swap、server rebuild、server restart、gc、磁盘清理、platform-db、PK01 PostgreSQL、egress-proxy traffic、proxy 测速、k3s benchmark、apk/npm/go 拉取测速时使用。
description: UniDesk 手动运维 CLI — `server``gc`、PK01 `platform-db postgres`、platform-infra egress proxy 和 k3s dependency proxy benchmark 运维。覆盖主 server 启停、健康检查、swap、日志、Docker 镜像清理、磁盘 GC、服务重建/重启、PK01 host PostgreSQL、D601/D518 proxyserver 视角流量测速和 k3s 真实依赖拉取 benchmark。用户提到 server start、server status、server swap、server rebuild、server restart、gc、磁盘清理、platform-db、PK01 PostgreSQL、egress-proxy traffic、proxy 测速、k3s benchmark、apk/npm/go/git mirror 拉取测速时使用。
---
# UniDesk Ops
@@ -24,7 +24,7 @@ bun scripts/cli.ts platform-infra egress-proxy k3s-build-benchmark --targets D60
## K3s Dependency Proxy Benchmark
用于验证 k3s CI/CD 构建出网性能时,必须用真实远程依赖,不用 Cloudflare synthetic。标准 profile 是 `real-deps-500m`k3s 远程拉 `alpine:3.20``node:22-bookworm``golang:1.24-bookworm`,然后在 Pod 内跑 `apk add``npm install``go mod download`proxyserver 视角累计/窗口流量至少要能支撑 500MiB+ 验收。
用于验证 k3s CI/CD 构建出网性能时,必须用真实远程依赖,不用 Cloudflare synthetic。标准 profile 是 `real-deps-500m`k3s 远程拉 `alpine:3.20``node:22-bookworm``golang:1.24-bookworm`,然后在 Pod 内跑 `apk add``npm install``go mod download``git clone --mirror``remote update --prune`proxyserver 视角累计/窗口流量至少要能支撑 500MiB+ 验收。
```bash
# 计划预览:确认 D601/D518、镜像、pull policy、最小 proxy 流量和依赖集合。
@@ -35,11 +35,11 @@ bun scripts/cli.ts platform-infra egress-proxy k3s-build-benchmark \
bun scripts/cli.ts platform-infra egress-proxy k3s-build-benchmark \
--targets D601,D518 --profile real-deps-500m --confirm
# 状态表:看 APK/NPM/GO/REAL_DEPS、failure family 和 proxyserver 采样。
# 状态表:看 APK/NPM/GO/GIT_MIRROR/REAL_DEPS、failure family 和 proxyserver 采样。
bun scripts/cli.ts platform-infra egress-proxy k3s-build-benchmark status \
--targets D601,D518 --profile real-deps-500m --traffic-sample-seconds 15
# 日志 drill-down:按 init container 展开 image pull、apk、npm、go 阶段尾部。
# 日志 drill-down:按 init container 展开 image pull、apk、npm、go、git mirror 阶段尾部。
bun scripts/cli.ts platform-infra egress-proxy k3s-build-benchmark logs \
--targets D601,D518 --profile real-deps-500m --tail-lines 160
@@ -52,7 +52,7 @@ bun scripts/cli.ts platform-infra egress-proxy k3s-build-benchmark cleanup \
--targets D601,D518 --profile real-deps-500m --confirm
```
D601/D518 结果必须分表记录:`STATE`、Job/run、duration、`APK/NPM/GO/REAL_DEPS``TRAFFIC_WINDOW``TRAFFIC_RATE``PROXY_CUM``TOP_CLIENT``TOP_DEST``FAILURE`。D518 通过不代表 D601 通过;D601 只证明 k3s/containerd 走到 proxy 也不等于性能达标。未完成 500MiB+ 的真实 k3s image pull + apk/npm/go 测试前,不关闭对应 issue,不合并标记为等待运行面验收的 PR。
D601/D518 结果必须分表记录:`STATE`、Job/run、duration、`APK/NPM/GO/GIT_MIRROR/REAL_DEPS``TRAFFIC_WINDOW``TRAFFIC_RATE``PROXY_CUM``TOP_CLIENT``TOP_DEST``FAILURE`。D518 通过不代表 D601 通过;D601 只证明 k3s/containerd 走到 proxy 也不等于性能达标。未完成 500MiB+ 的真实 k3s image pull + apk/npm/go/git mirror 测试前,不关闭对应 issue,不合并标记为等待运行面验收的 PR。
## Egress Proxy 运行面修复入口
@@ -69,8 +69,8 @@ D601 若需要让 `sub2api-egress-proxy` 绕开 pod overlay,可在 YAML 中显
常见判读:
- `TOP_CLIENT=10.42.0.1``TOP_DEST=registry-1.docker.io:443`k3s/containerd image pull 已从 proxyserver 视角可见。
- `TOP_DEST=dl-cdn.alpinelinux.org:443`Pod 内 `apk` 阶段已走 proxy。
- `registry.npmjs.org:443``proxy.golang.org:443`:分别对应 `npm install`Go module 拉取。
- `image-pull` failure 表示还卡在 kubelet/containerd 拉镜像;`apk-download``npm-download``go-download` 分别表示 Pod 内依赖阶段失败。
- `registry.npmjs.org:443``proxy.golang.org:443``github.com:443`:分别对应 `npm install`Go module 拉取和 Git mirror clone/sync
- `image-pull` failure 表示还卡在 kubelet/containerd 拉镜像;`apk-download``npm-download``go-download``git-mirror` 分别表示 Pod 内依赖阶段失败。
- proxy 窗口 `0 B/s` 但 active cumulative 增长很慢时,按性能不达标处理;先清理 Job,再继续查上游,不要让慢速 benchmark 长时间占用资源。
## P0 边界
@@ -6,12 +6,13 @@ metadata:
relatedIssues:
- 1032
- 1048
- 1077
profiles:
real-deps-500m:
enabled: true
workload: k3s-real-deps
description: Real k3s dependency egress benchmark; kubelet pulls remote alpine/node/golang images, then pod stages run apk, npm and go downloads through the YAML-declared proxy.
description: Real k3s dependency egress benchmark; kubelet pulls remote alpine/node/golang images, then pod stages run apk, npm, go and git mirror clone/sync through the YAML-declared proxy.
image: docker.io/library/alpine:3.20
imagePullPolicy: Always
targetOverrides: {}
@@ -72,6 +73,10 @@ profiles:
- google.golang.org/grpc@v1.69.4
- k8s.io/client-go@v0.32.4
expectedMiB: 180
gitMirror:
image: docker.io/library/alpine:3.20
remote: https://github.com/git/git.git
expectedMiB: 30
no-mirror-600m:
enabled: true
+1 -1
View File
@@ -169,7 +169,7 @@ When target-level `egressProxy.enabled=true`, the D601 target renders an in-clus
The egress proxy Deployment may opt into `hostNetwork: true` per target via `config/platform-infra/sub2api.yaml` `targets[].egressProxy.hostNetwork`. When enabled, the manifest renders `hostNetwork: true`, `dnsPolicy: ClusterFirstWithHostNet`, and a RollingUpdate strategy of `maxSurge=0`/`maxUnavailable=1` so the sing-box client bypasses the pod overlay and connects the master upstream directly from the node network; this is the durable fix for a target whose pod-overlay path to the upstream is the throughput bottleneck. It is a per-target YAML decision, not a D601-only default: a target whose pod overlay is already fast enough must keep `hostNetwork: false`, and the `no-host-network` policy check only permits `hostNetwork: true` on the single YAML-declared egress proxy Deployment for a target whose `egressProxy.hostNetwork=true`. Do not generalize one target's hostNetwork experiment to other nodes, and do not leave a one-off `kubectl patch` as the final state; promote or demote hostNetwork only by editing the target YAML and running `platform-infra sub2api apply --target <id>`.
`platform-infra egress-proxy k3s-build-benchmark --targets <ids> --profile real-deps-500m` is the production-ready egress proxy throughput acceptance entry. The `real-deps-500m` profile in `config/platform-infra/egress-proxy-benchmarks.yaml` is the only acceptance profile: it renders one Job per target whose kubelet/containerd pulls remote `alpine`, `node` and `golang` images with `imagePullPolicy: Always`, then runs Pod-internal `apk add`, `npm install` and `go mod download` stages through the YAML-declared proxy env. Acceptance requires `STATE=succeeded`, `REAL_DEPS >= 500 MiB` (the profile's `realDeps.minProxyMiB`), image-pull plus apk/npm/go evidence, and a proxyserver-observed cumulative traffic above the profile minimum. Cloudflare synthetic downloads and curl-only probes are bypass diagnostics, never acceptance evidence. Status/logs/traffic are short-polled; a started benchmark is fire-and-forget and must be `cleanup`-ed when it stalls or after acceptance to release k3s resources. D601 and D518 must both pass the same profile: a single node passing does not close a cross-node proxy issue, and an optimization on one target must not regress the other.
`platform-infra egress-proxy k3s-build-benchmark --targets <ids> --profile real-deps-500m` is the production-ready egress proxy throughput acceptance entry. The `real-deps-500m` profile in `config/platform-infra/egress-proxy-benchmarks.yaml` is the only acceptance profile: it renders one Job per target whose kubelet/containerd pulls remote `alpine`, `node` and `golang` images with `imagePullPolicy: Always`, then runs Pod-internal `apk add`, `npm install`, `go mod download` and `git clone --mirror` plus `remote update --prune` stages through the YAML-declared proxy env. Acceptance requires `STATE=succeeded`, `REAL_DEPS >= 500 MiB` (the profile's `realDeps.minProxyMiB`), image-pull plus apk/npm/go/git-mirror evidence, and a proxyserver-observed cumulative traffic above the profile minimum. Cloudflare synthetic downloads and curl-only probes are bypass diagnostics, never acceptance evidence. Status/logs/traffic are short-polled; a started benchmark is fire-and-forget and must be `cleanup`-ed when it stalls or after acceptance to release k3s resources. D601 and D518 must both pass the same profile: a single node passing does not close a cross-node proxy issue, and an optimization on one target must not regress the other.
`platform-infra sub2api validate --target D601 --full` must prove the proxy Deployment/Service is ready and that an app pod can complete the YAML-declared health probe through the proxy. This target-level injection does not by itself bind manually created Sub2API accounts to that proxy; account tests and account-specific upstream transports still need a YAML-declared `manualAccounts.protected[].proxyBinding` when the account must avoid direct egress. Proxy credentials, subscription contents, and generated proxy configs are Secret material and must not be printed. If a direct D601-to-upstream TLS/SNI path is reset, do not leave a one-off plain HTTP CONNECT or JS proxy as the durable fix; use a mature encrypted proxy source, currently master `shadowsocks-rust` plus D601 `sing-box`, through YAML/compose.
@@ -4,12 +4,13 @@
This SPEC covers pikasTech/unidesk#1048. It supersedes synthetic Cloudflare download evidence for proxy acceleration decisions and adds a real k3s dependency benchmark profile named `real-deps-500m`.
The benchmark must prove the target k3s cluster can use the platform-infra egress proxy for real dependency acquisition. It has four required stages:
The benchmark must prove the target k3s cluster can use the platform-infra egress proxy for real dependency acquisition. It has five required stages:
- Kubernetes image pull: kubelet/containerd must directly pull remote `alpine`, `node`, and `golang` images with `imagePullPolicy: Always`.
- Pod `apk add`: the Alpine stage must fetch packages from upstream apk repositories through proxy environment variables.
- Pod `npm install`: the Node stage must install packages from `https://registry.npmjs.org/` through the proxy.
- Pod `go mod download`: the Go stage must download modules through `GOPROXY=https://proxy.golang.org,direct` and the proxy.
- Pod Git mirror sync/clone: the Git stage must run `git clone --mirror` against the configured public remote, then `remote update --prune`, with Git HTTP(S) proxy settings bound to the YAML-declared proxy.
If the Kubernetes image pull stage fails, the benchmark result is not an application dependency failure; it is an image-pull proxy failure in the k3s/containerd path and must be fixed there.
@@ -22,15 +23,16 @@ The `real-deps-500m` profile renders a multi-stage Kubernetes Job:
- `initContainer/apk-add`: image `docker.io/library/alpine:3.20`.
- `initContainer/npm-install`: image `docker.io/library/node:22-bookworm`.
- `initContainer/go-download`: image `docker.io/library/golang:1.24-bookworm`.
- `initContainer/git-mirror`: image and remote declared by `realDeps.gitMirror`.
- `container/summary`: emits a bounded result marker after all init containers finish.
All three init containers receive the YAML-declared `sub2api-egress-proxy` service URL through `HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`, and lowercase variants. The image pull itself happens before Pod process execution; therefore image pull proxy evidence must come from the k3s/containerd path and proxyserver-side traffic sampling, not from in-container env alone.
All dependency init containers receive the YAML-declared `sub2api-egress-proxy` service URL through `HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`, and lowercase variants. The image pull itself happens before Pod process execution; therefore image pull proxy evidence must come from the k3s/containerd path and proxyserver-side traffic sampling, not from in-container env alone.
## Observability
The source of truth for traffic is `platform-infra egress-proxy traffic --target <node> --sample-seconds N`. Benchmark status may include this traffic sample. The final evidence table must include proxyserver window bytes/rate/cumulative bytes, top client, and top destination.
For image pull traffic, the observed proxy client may be the node/k3s/containerd path rather than the benchmark Pod IP. For `apk`, `npm`, and `go` stages, the observed proxy client should correspond to the benchmark Pod network path. This distinction must be preserved in issue evidence.
For image pull traffic, the observed proxy client may be the node/k3s/containerd path rather than the benchmark Pod IP. For `apk`, `npm`, `go`, and `git-mirror` stages, the observed proxy client should correspond to the benchmark Pod network path. This distinction must be preserved in issue evidence.
Status output must classify failures into at least:
@@ -38,6 +40,7 @@ Status output must classify failures into at least:
- `apk-download`: Pod started but apk fetch/install failed.
- `npm-download`: Pod started but npm install failed.
- `go-download`: Pod started but Go module download failed.
- `git-mirror`: Pod started but Git mirror clone/sync failed.
- `none`: all stages succeeded.
## Boundaries
@@ -47,15 +50,15 @@ This benchmark must not:
- use Cloudflare speed-test downloads as acceptance evidence;
- install Node or Go only as a substitute for Kubernetes pulling `node`/`golang` images;
- rewrite apk/npm/go sources to regional mirrors;
- use HWLAB source repositories, Tekton, Argo, git-mirror, or previous build caches;
- use HWLAB source repositories, Tekton, Argo, the production git-mirror service, or previous build caches;
- hide image pull failures behind local image overrides.
`payloadMiB: 500` in the `real-deps-500m` profile means the minimum proxyserver-observed traffic required for acceptance. The Pod result marker may report apk/npm/go workspace sizes, but those sizes do not replace proxyserver traffic evidence because image pull bytes are outside the Pod filesystem.
`payloadMiB: 500` in the `real-deps-500m` profile means the minimum proxyserver-observed traffic required for acceptance. The Pod result marker may report apk/npm/go/git-mirror workspace sizes, but those sizes do not replace proxyserver traffic evidence because image pull bytes are outside the Pod filesystem.
## Acceptance
- `bun scripts/cli.ts platform-infra egress-proxy k3s-build-benchmark --targets D601,D518 --profile real-deps-500m --dry-run` prints both node plans and the remote image set.
- `--confirm` creates one unique Job per node and returns immediately.
- `status --traffic-sample-seconds 15` reports Job state, `image-pull`/`apk`/`npm`/`go` failure family when applicable, and proxyserver traffic columns.
- D601 and D518 both have final rows with target, profile, job, state, duration, apk MiB, npm MiB, go MiB, proxy traffic window/rate/cumulative, top client, top destination, and failure family.
- `status --traffic-sample-seconds 15` reports Job state, `image-pull`/`apk`/`npm`/`go`/`git-mirror` failure family when applicable, and proxyserver traffic columns.
- D601 and D518 both have final rows with target, profile, job, state, duration, apk MiB, npm MiB, go MiB, git mirror MiB, proxy traffic window/rate/cumulative, top client, top destination, and failure family.
- Acceptance requires at least 500 MiB of proxyserver-observed traffic per successful node run. If a node cannot reach that point because image pull fails, the issue remains open until the k3s/containerd image pull proxy path is fixed or a blocker is explicitly documented.
@@ -74,6 +74,11 @@ interface K3sRealDepsSpec {
modules: readonly string[];
expectedMiB: number;
};
gitMirror: {
image: string;
remote: string;
expectedMiB: number;
};
}
interface K3sBuildBenchmarkTargetOverride {
@@ -113,6 +118,7 @@ interface TargetStatus {
apkMiB: number | null;
npmMiB: number | null;
goMiB: number | null;
gitMirrorMiB: number | null;
realDepsMiB: number | null;
failureFamily: string;
logTail: string;
@@ -276,6 +282,7 @@ function statusBenchmarks(plans: readonly TargetPlan[], options: K3sBuildBenchma
status.apkMiB === null ? "-" : `${status.apkMiB}MiB`,
status.npmMiB === null ? "-" : `${status.npmMiB}MiB`,
status.goMiB === null ? "-" : `${status.goMiB}MiB`,
status.gitMirrorMiB === null ? "-" : `${status.gitMirrorMiB}MiB`,
status.realDepsMiB === null ? "-" : `${status.realDepsMiB}MiB`,
status.traffic === undefined ? "-" : bytes(status.traffic.windowBytes),
status.traffic === undefined ? "-" : rate(status.traffic.rateBps),
@@ -294,7 +301,7 @@ function statusBenchmarks(plans: readonly TargetPlan[], options: K3sBuildBenchma
renderedText: [
"PLATFORM-INFRA K3S BUILD BENCHMARK STATUS",
"",
...table(["TARGET", "PROFILE", "STATE", "JOB", "DURATION", "OUTPUT", "DOWNLOAD", "APK", "NPM", "GO", "REAL_DEPS", "TRAFFIC_WINDOW", "TRAFFIC_RATE", "PROXY_CUM", "TOP_CLIENT", "TOP_DEST", "FAILURE"], rows),
...table(["TARGET", "PROFILE", "STATE", "JOB", "DURATION", "OUTPUT", "DOWNLOAD", "APK", "NPM", "GO", "GIT_MIRROR", "REAL_DEPS", "TRAFFIC_WINDOW", "TRAFFIC_RATE", "PROXY_CUM", "TOP_CLIENT", "TOP_DEST", "FAILURE"], rows),
...logSections,
"",
"NEXT",
@@ -371,7 +378,7 @@ function renderDryRun(plans: readonly TargetPlan[], options: K3sBuildBenchmarkOp
function dryRunImages(profile: K3sBuildBenchmarkProfile): string {
if (profile.workload === "k3s-real-deps" && profile.realDeps !== undefined) {
return [profile.realDeps.apk.image, profile.realDeps.npm.image, profile.realDeps.go.image].join(",");
return [profile.realDeps.apk.image, profile.realDeps.npm.image, profile.realDeps.go.image, profile.realDeps.gitMirror.image].join(",");
}
return profile.image;
}
@@ -383,13 +390,13 @@ function dryRunPullPolicy(profile: K3sBuildBenchmarkProfile): string {
function dependencySummary(profile: K3sBuildBenchmarkProfile): string {
if (profile.workload === "k3s-real-deps" && profile.realDeps !== undefined) {
return `apk~${profile.realDeps.apk.expectedMiB} npm~${profile.realDeps.npm.expectedMiB} go~${profile.realDeps.go.expectedMiB}`;
return `apk~${profile.realDeps.apk.expectedMiB} npm~${profile.realDeps.npm.expectedMiB} go~${profile.realDeps.go.expectedMiB} gitMirror~${profile.realDeps.gitMirror.expectedMiB}`;
}
return `${profile.dependencyDownload.expectedMiB}MiB`;
}
function dryRunDetail(profile: K3sBuildBenchmarkProfile | undefined): string {
if (profile?.workload === "k3s-real-deps") return "kubelet-image-pull + apk/npm/go through proxy";
if (profile?.workload === "k3s-real-deps") return "kubelet-image-pull + apk/npm/go/git-mirror through proxy";
return "no-mirror emptyDir unique-job";
}
@@ -491,7 +498,8 @@ function realDepsJobManifest(target: Sub2ApiTargetConfig, profile: K3sBuildBench
"unidesk.ai/workload": profile.workload,
"unidesk.ai/min-proxy-mib": String(realDeps.minProxyMiB),
"unidesk.ai/image-pull-mode": "kubelet-containerd",
"unidesk.ai/remote-images": [realDeps.apk.image, realDeps.npm.image, realDeps.go.image].join(","),
"unidesk.ai/remote-images": [realDeps.apk.image, realDeps.npm.image, realDeps.go.image, realDeps.gitMirror.image].join(","),
"unidesk.ai/git-mirror-remote": realDeps.gitMirror.remote,
},
},
spec: {
@@ -543,6 +551,19 @@ function realDepsJobManifest(target: Sub2ApiTargetConfig, profile: K3sBuildBench
],
volumeMounts: [{ name: "work", mountPath: "/work" }],
},
{
name: "git-mirror",
image: realDeps.gitMirror.image,
imagePullPolicy: realDeps.imagePullPolicy,
command: ["/bin/sh", "-lc"],
args: [realDepsGitMirrorScript()],
env: [
...commonEnv,
{ name: "GIT_MIRROR_IMAGE", value: realDeps.gitMirror.image },
{ name: "GIT_MIRROR_REMOTE", value: realDeps.gitMirror.remote },
],
volumeMounts: [{ name: "work", mountPath: "/work" }],
},
],
containers: [{
name: "summary",
@@ -630,23 +651,53 @@ printf 'UNIDESK_K3S_REAL_DEPS_STAGE {"stage":"go","ok":true,"image":"%s","downlo
`;
}
function realDepsGitMirrorScript(): string {
return `set -eu
mkdir -p /work/stages /work/git-mirror
printf 'UNIDESK_K3S_REAL_DEPS_EVENT stage=git-mirror target=%s profile=%s run=%s image=%s remote=%s\\n' "$BENCHMARK_TARGET" "$BENCHMARK_PROFILE" "$BENCHMARK_RUN_ID" "$GIT_MIRROR_IMAGE" "$GIT_MIRROR_REMOTE"
if ! command -v git >/dev/null 2>&1; then
if ! command -v apk >/dev/null 2>&1; then
echo "git-runtime-missing image=$GIT_MIRROR_IMAGE" >&2
exit 127
fi
if grep -R -E 'npmmirror|daocloud|aliyun|tuna|ustc|huaweicloud' /etc/apk/repositories >/tmp/git-apk-mirror-check.out 2>/dev/null; then
cat /tmp/git-apk-mirror-check.out >&2
echo "unexpected apk mirror in git mirror base image" >&2
exit 42
fi
apk update
apk add --no-cache git ca-certificates
fi
git --version
git -c "http.proxy=\${HTTP_PROXY:-}" -c "https.proxy=\${HTTPS_PROXY:-}" -c http.lowSpeedLimit=1024 -c http.lowSpeedTime=120 clone --mirror --filter=blob:none "$GIT_MIRROR_REMOTE" /work/git-mirror/repo.git
git -c "http.proxy=\${HTTP_PROXY:-}" -c "https.proxy=\${HTTPS_PROXY:-}" --git-dir=/work/git-mirror/repo.git remote update --prune
git -c "http.proxy=\${HTTP_PROXY:-}" -c "https.proxy=\${HTTPS_PROXY:-}" --git-dir=/work/git-mirror/repo.git remote -v
git -c "http.proxy=\${HTTP_PROXY:-}" -c "https.proxy=\${HTTPS_PROXY:-}" ls-remote --heads "$GIT_MIRROR_REMOTE" >/work/git-mirror/ls-remote-heads.txt
git_mib="$(du -sk /work/git-mirror/repo.git /work/git-mirror/ls-remote-heads.txt 2>/dev/null | awk '{s+=$1} END {printf "%d", int((s+1023)/1024)}')"
printf 'gitMirrorMiB=%s\\n' "$git_mib" > /work/stages/git-mirror.env
printf 'UNIDESK_K3S_REAL_DEPS_STAGE {"stage":"git-mirror","ok":true,"image":"%s","remote":"%s","mirrorMiB":%s}\\n' "$GIT_MIRROR_IMAGE" "$GIT_MIRROR_REMOTE" "$git_mib"
`;
}
function realDepsSummaryScript(realDeps: K3sRealDepsSpec): string {
return `set -eu
apkMiB=0
npmMiB=0
goMiB=0
gitMirrorMiB=0
realDepsStartedEpoch="$(date +%s)"
realDepsStartedAt="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
[ -f /work/stages/apk.env ] && . /work/stages/apk.env
[ -f /work/stages/npm.env ] && . /work/stages/npm.env
[ -f /work/stages/go.env ] && . /work/stages/go.env
[ -f /work/stages/git-mirror.env ] && . /work/stages/git-mirror.env
completed_epoch="$(date +%s)"
completed_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
real_deps_mib=$((apkMiB + npmMiB + goMiB))
real_deps_mib=$((apkMiB + npmMiB + goMiB + gitMirrorMiB))
duration=$((completed_epoch - realDepsStartedEpoch))
pod_ip="$(hostname -i 2>/dev/null | awk '{print $1}')"
printf 'UNIDESK_K3S_REAL_DEPS_RESULT {"ok":true,"target":"%s","profile":"%s","runId":"%s","startedAt":"%s","completedAt":"%s","durationSeconds":%s,"podIp":"%s","apkMiB":%s,"npmMiB":%s,"goMiB":%s,"realDepsMiB":%s,"minProxyMiB":%s,"imagePullMode":"kubelet-containerd","apkImage":"%s","npmImage":"%s","goImage":"%s"}\\n' \\
"$BENCHMARK_TARGET" "$BENCHMARK_PROFILE" "$BENCHMARK_RUN_ID" "$realDepsStartedAt" "$completed_at" "$duration" "$pod_ip" "$apkMiB" "$npmMiB" "$goMiB" "$real_deps_mib" "$MIN_PROXY_MIB" ${shQuote(realDeps.apk.image)} ${shQuote(realDeps.npm.image)} ${shQuote(realDeps.go.image)}
printf 'UNIDESK_K3S_REAL_DEPS_RESULT {"ok":true,"target":"%s","profile":"%s","runId":"%s","startedAt":"%s","completedAt":"%s","durationSeconds":%s,"podIp":"%s","apkMiB":%s,"npmMiB":%s,"goMiB":%s,"gitMirrorMiB":%s,"realDepsMiB":%s,"minProxyMiB":%s,"imagePullMode":"kubelet-containerd","apkImage":"%s","npmImage":"%s","goImage":"%s","gitMirrorImage":"%s","gitMirrorRemote":"%s"}\\n' \\
"$BENCHMARK_TARGET" "$BENCHMARK_PROFILE" "$BENCHMARK_RUN_ID" "$realDepsStartedAt" "$completed_at" "$duration" "$pod_ip" "$apkMiB" "$npmMiB" "$goMiB" "$gitMirrorMiB" "$real_deps_mib" "$MIN_PROXY_MIB" ${shQuote(realDeps.apk.image)} ${shQuote(realDeps.npm.image)} ${shQuote(realDeps.go.image)} ${shQuote(realDeps.gitMirror.image)} ${shQuote(realDeps.gitMirror.remote)}
`;
}
@@ -952,6 +1003,8 @@ elif "npm-install" in tail_text and ("npm ERR!" in tail_text or "EAI_AGAIN" in t
failure_family = "npm-download"
elif "go-download" in tail_text and ("terminated exit=" in tail_text or "i/o timeout" in tail_text or "connection refused" in tail_text or "no such host" in tail_text or "proxyconnect tcp" in tail_text or "TLS handshake timeout" in tail_text or "go-runtime-missing" in tail_text):
failure_family = "go-download"
elif "git-mirror" in tail_text and ("terminated exit=" in tail_text or "fatal:" in tail_text or "Could not resolve host" in tail_text or "Failed to connect" in tail_text or "connection refused" in tail_text or "proxyconnect tcp" in tail_text or "TLS handshake timeout" in tail_text or "git-runtime-missing" in tail_text):
failure_family = "git-mirror"
elif "curl:" in tail_text or "urllib.error.HTTPError" in tail_text or "urllib.error.URLError" in tail_text:
failure_family = "dependency-download"
elif "payload-too-small" in tail_text:
@@ -1032,6 +1085,7 @@ function normalizeStatus(plan: TargetPlan, parsed: unknown, result: CommandResul
apkMiB: null,
npmMiB: null,
goMiB: null,
gitMirrorMiB: null,
realDepsMiB: null,
failureFamily: result.timedOut ? "transport-timeout" : state,
logTail: (result.stderr || result.stdout).slice(-4000),
@@ -1057,6 +1111,7 @@ function normalizeStatus(plan: TargetPlan, parsed: unknown, result: CommandResul
apkMiB: nullableNumber(jobResult.apkMiB),
npmMiB: nullableNumber(jobResult.npmMiB),
goMiB: nullableNumber(jobResult.goMiB),
gitMirrorMiB: nullableNumber(jobResult.gitMirrorMiB),
realDepsMiB: nullableNumber(jobResult.realDepsMiB),
failureFamily: text(data.failureFamily, data.ok === true ? "none" : state === "running" || state === "pending" ? "in-progress" : text(data.reason, "unknown")),
logTail: text(data.logTail, result.stderr.slice(-2000)),
@@ -1081,6 +1136,7 @@ function blockedStatus(plan: TargetPlan, profile: string): TargetStatus {
apkMiB: null,
npmMiB: null,
goMiB: null,
gitMirrorMiB: null,
realDepsMiB: null,
failureFamily: plan.blocker ?? "blocked",
logTail: plan.detail ?? "",
@@ -1151,6 +1207,7 @@ function realDepsSpec(raw: Record<string, unknown>, path: string): K3sRealDepsSp
const apk = asRecord(raw.apk, `${path}.apk`);
const npm = asRecord(raw.npm, `${path}.npm`);
const go = asRecord(raw.go, `${path}.go`);
const gitMirror = asRecord(raw.gitMirror, `${path}.gitMirror`);
return {
minProxyMiB: integerField(raw, "minProxyMiB", path),
imagePullPolicy: imagePullPolicyField(raw, "imagePullPolicy", path),
@@ -1171,6 +1228,11 @@ function realDepsSpec(raw: Record<string, unknown>, path: string): K3sRealDepsSp
modules: stringArrayField(go, "modules", `${path}.go`),
expectedMiB: integerField(go, "expectedMiB", `${path}.go`),
},
gitMirror: {
image: stringField(gitMirror, "image", `${path}.gitMirror`),
remote: stringField(gitMirror, "remote", `${path}.gitMirror`),
expectedMiB: integerField(gitMirror, "expectedMiB", `${path}.gitMirror`),
},
};
}