fix: refresh provider gateway build context
This commit is contained in:
@@ -124,6 +124,8 @@ provider-gateway 连接成功后必须周期性上报节点 CPU、内存、硬
|
||||
|
||||
backend-core 可以通过真实 WebSocket 调度向在线 provider 下发 `provider.upgrade`。`mode: "plan"` 只返回升级计划,用于 E2E 和人工预检;`mode: "schedule"` 会要求 provider-gateway 通过本地 Docker socket 启动一个 detached updater 容器。updater 的固定策略是先执行 `docker compose build provider-gateway`,构建成功后只按 `com.docker.compose.project` 与 `com.docker.compose.service=provider-gateway` label 删除旧 provider-gateway 容器,最后执行 `docker compose up -d --no-deps --force-recreate provider-gateway`。`--no-deps` 是强制要求,`--force-recreate` 用于保证 provider 重新注册能力标签并避免 compose no-op;升级 provider-gateway 时不得重建或停止 database、backend-core、frontend。对 D518、D601 这类计算节点,`mode: "schedule"` 是正式重建/升级 `provider-gateway` 容器的唯一标准路径;升级执行路径使用 Docker socket 和只读仓库挂载,不使用 Host SSH 维护桥作为自动调度通道。
|
||||
|
||||
使用 compact build context 的节点必须把 context 视为派生缓存,不得让它成为版本真相。D601 这类节点可能让 Compose 从 `.state/provider-<ID>-build-context` 构建兜底镜像,以便复用本地基础镜像和缩小构建输入;`provider.upgrade mode=schedule` 在 `docker compose build` 前必须从只读 `/workspace/src/components/provider-gateway` 与 `/workspace/src/components/shared` 刷新该 context,并把宿主 `.state` 以可写方式只挂给 detached updater。验收不能只看 updater 日志中的 `Built` 或 `candidate provider-gateway validated and promoted`,必须以主 server 可观测的 `providerGatewayVersion`、`unideskCapabilities` 和目标能力字段为准;如果线上仍上报旧版本,说明运行镜像没有真正更新,必须先修复构建 context 再重跑标准升级。
|
||||
|
||||
远程升级策略固定为 always-enabled:只要 provider-gateway 在线并声明 `provider.upgrade`,`mode: "schedule"` 就必须真正调度升级容器,不允许被 `PROVIDER_UPGRADE_ENABLED=false`、前端隐藏按钮或服务端特殊名单禁用。升级能力的安全边界不是开关,而是显式 `PROVIDER_UPGRADE_*` 配置、Docker socket 权限、只读仓库挂载、固定 Compose service 和 `--no-deps` 约束。升级计划中必须展示 `policy: "always-enabled"`、updater 容器名、runner image、workspace、Compose project/service、env file、compose file 和实际 `docker run` 命令,方便前端任务历史与 CLI debug 直接诊断。
|
||||
|
||||
`mode: "schedule"` 的成功返回只代表 updater 已被调度,最终升级成败由候选 gateway 自验证决定。updater 必须先按 Compose 构建新镜像,再用旧容器的 `Config.Env` 生成候选 env-file,并复用旧容器的 Docker socket、日志目录、SSH 私钥只读挂载、Compose 网络和 `extra_hosts`;候选容器启动时 restart policy 必须先是 `no`,并显式使用 `--pid host` 保持节点级进程资源采集,验证通过后才能改成 `always` 并删除旧容器。升级计划的 `replacementStrategy` 必须包含 `oldGatewaySleepMs`、`validationTimeoutMs`、`promoteOnlyAfterCandidateValidation`、`candidateRestartPolicyAfterPromotion: "always"`、`candidateUsesOldContainerEnvironment`、`candidateUsesOldContainerMounts`、`candidateUsesOldContainerNetworks`、`candidateUsesOldContainerExtraHosts` 和 `candidateUsesHostPidNamespace`,并且必须在 plan 中显示指定 Provider 的当前/目标 gateway 版本号,便于前端和 CLI 判断这不是旧的先删旧容器再 up 的危险流程。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@unidesk/provider-gateway",
|
||||
"version": "0.2.20",
|
||||
"version": "0.2.21",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1552,8 +1552,13 @@ function candidateGatewayName(taskId: string): string {
|
||||
|
||||
function upgradePlan(taskId: string): Record<string, JsonValue> {
|
||||
const workspace = config.upgradeWorkspacePath;
|
||||
const workspaceRoot = workspace.replace(/\/+$/, "");
|
||||
const sleepMs = 300_000;
|
||||
const validationTimeoutMs = 180_000;
|
||||
const compactBuildContextCandidates = Array.from(new Set([
|
||||
`${workspaceRoot}/.state/provider-${config.providerId}-build-context`,
|
||||
`${workspaceRoot}/.state/provider-${safeProviderSlug(config.providerId)}-build-context`,
|
||||
]));
|
||||
const composeBaseCommand = [
|
||||
"docker",
|
||||
"compose",
|
||||
@@ -1595,9 +1600,31 @@ function upgradePlan(taskId: string): Record<string, JsonValue> {
|
||||
const validationNeedleOk = `"ok":true`;
|
||||
const validationAttempts = Math.max(1, Math.ceil(validationTimeoutMs / 2000));
|
||||
const targetGatewayMetadata = readTargetGatewayMetadata(workspace);
|
||||
const compactBuildContextRefreshScript = [
|
||||
`provider_src=${shellQuote(`${workspaceRoot}/src/components/provider-gateway`)}`,
|
||||
`shared_src=${shellQuote(`${workspaceRoot}/src/components/shared`)}`,
|
||||
`for compact_context in ${compactBuildContextCandidates.map(shellQuote).join(" ")}; do`,
|
||||
` if [ -d "$compact_context" ]; then`,
|
||||
` echo "refreshing provider-gateway build context: $compact_context"`,
|
||||
` mkdir -p "$compact_context/src/components/provider-gateway" "$compact_context/src/components/shared"`,
|
||||
` rm -rf "$compact_context/src/components/provider-gateway/src" "$compact_context/src/components/provider-gateway/scripts" "$compact_context/src/components/shared/src"`,
|
||||
` cp -a "$provider_src/package.json" "$compact_context/src/components/provider-gateway/package.json"`,
|
||||
` [ ! -f "$provider_src/tsconfig.json" ] || cp -a "$provider_src/tsconfig.json" "$compact_context/src/components/provider-gateway/tsconfig.json"`,
|
||||
` [ ! -f "$provider_src/Dockerfile" ] || cp -a "$provider_src/Dockerfile" "$compact_context/src/components/provider-gateway/Dockerfile"`,
|
||||
` [ -f "$compact_context/Dockerfile" ] || cp -a "$provider_src/Dockerfile" "$compact_context/Dockerfile"`,
|
||||
` cp -a "$provider_src/src" "$compact_context/src/components/provider-gateway/src"`,
|
||||
` cp -a "$provider_src/scripts" "$compact_context/src/components/provider-gateway/scripts"`,
|
||||
` cp -a "$shared_src/package.json" "$compact_context/src/components/shared/package.json"`,
|
||||
` [ ! -f "$shared_src/tsconfig.json" ] || cp -a "$shared_src/tsconfig.json" "$compact_context/src/components/shared/tsconfig.json"`,
|
||||
` cp -a "$shared_src/src" "$compact_context/src/components/shared/src"`,
|
||||
` date -u +%Y-%m-%dT%H:%M:%SZ > "$compact_context/.unidesk-build-context-refreshed-at"`,
|
||||
` fi`,
|
||||
`done`,
|
||||
].join("\n");
|
||||
const script = [
|
||||
"set -eu",
|
||||
`cd ${shellQuote(workspace)}`,
|
||||
compactBuildContextRefreshScript,
|
||||
`old_ids=$(${listServiceContainersCommand.map(shellQuote).join(" ")})`,
|
||||
`first_old=""`,
|
||||
`for old_id in $old_ids; do first_old="$old_id"; break; done`,
|
||||
@@ -1669,6 +1696,8 @@ function upgradePlan(taskId: string): Record<string, JsonValue> {
|
||||
"/var/run/docker.sock:/var/run/docker.sock",
|
||||
"-v",
|
||||
`${config.upgradeHostProjectRoot}:${workspace}:ro`,
|
||||
"-v",
|
||||
`${config.upgradeHostProjectRoot.replace(/\/+$/, "")}/.state:${workspaceRoot}/.state`,
|
||||
"-w",
|
||||
workspace,
|
||||
config.upgradeRunnerImage,
|
||||
@@ -1714,7 +1743,8 @@ function upgradePlan(taskId: string): Record<string, JsonValue> {
|
||||
candidateUsesOldContainerEnvironment: true,
|
||||
candidateUsesHostPidNamespace: true,
|
||||
startupSelfHealsRestartPolicy: true,
|
||||
dockerStatusReportsRestartPolicyAndPidMode: true,
|
||||
dockerStatusReportsRestartPolicyAndPidMode: true,
|
||||
refreshesCompactBuildContextBeforeBuild: true,
|
||||
removeScope: {
|
||||
projectLabel: config.upgradeComposeProject,
|
||||
serviceLabel: config.upgradeService,
|
||||
@@ -1723,6 +1753,8 @@ function upgradePlan(taskId: string): Record<string, JsonValue> {
|
||||
namedVolumesPreserved: true,
|
||||
},
|
||||
dockerRunCommand,
|
||||
compactBuildContextCandidates,
|
||||
workspaceStateMount: `${config.upgradeHostProjectRoot.replace(/\/+$/, "")}/.state:${workspaceRoot}/.state`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user