From 4d6210fef27ffdab57b0e6ab79f9bdc1e5a7cc52 Mon Sep 17 00:00:00 2001 From: pikastech Date: Mon, 20 Jul 2026 10:38:24 +0200 Subject: [PATCH] fix(platform-infra): fast-path converged k3s apply --- docs/reference/platform-infra.md | 6 ++ scripts/src/platform-infra-host-proxy.ts | 4 ++ scripts/src/platform-infra-k3s-cluster.ts | 88 ++++++++++++++++++++++- 3 files changed, 95 insertions(+), 3 deletions(-) diff --git a/docs/reference/platform-infra.md b/docs/reference/platform-infra.md index 13c52a14..39b90306 100644 --- a/docs/reference/platform-infra.md +++ b/docs/reference/platform-infra.md @@ -29,6 +29,12 @@ `platform-infra k3s-cluster plan|apply|status|smoke|registry-smoke --target d601`。`apply` 先收敛 host proxy,再比较 server 声明摘要; 内容一致时不得重装或重启。 +- `apply` 必须先用一次目标侧短查询比较: + - server 与 host proxy service 状态; + - 独立集群声明摘要; + - Node、系统 Deployment 和业务 Pod 状态。 + - 全部一致时直接返回 `mode=already-converged` 与 `mutation=false`, + 不分发 proxy/k3s 工件,也不进入长控制通道。 - `status` 和 `smoke` 必须证明: - 只有一个 Ready 节点; - server active 且 agent inactive; diff --git a/scripts/src/platform-infra-host-proxy.ts b/scripts/src/platform-infra-host-proxy.ts index acaaaece..ad0d09c0 100644 --- a/scripts/src/platform-infra-host-proxy.ts +++ b/scripts/src/platform-infra-host-proxy.ts @@ -153,8 +153,10 @@ export interface HostProxyGuestBundle { prepareScript: string; applyScript: string; statusScript: string; + serviceName: string; sourceFingerprint: string; materialFingerprint: string; + desiredFingerprint: string; valuesPrinted: false; } @@ -203,8 +205,10 @@ export function prepareHostProxyGuestBundle(targetId: string): HostProxyGuestBun prepareScript: remotePrepareScript(target.source.client), applyScript: remoteApplyScript(target, material.clientConfigJson), statusScript: remoteStatusScript(target), + serviceName: target.source.client.serviceName, sourceFingerprint: target.source.fingerprint, materialFingerprint: material.fingerprint, + desiredFingerprint: fingerprint({ target, materialFingerprint: material.fingerprint }), valuesPrinted: false, }; } diff --git a/scripts/src/platform-infra-k3s-cluster.ts b/scripts/src/platform-infra-k3s-cluster.ts index 324f83ec..436f5699 100644 --- a/scripts/src/platform-infra-k3s-cluster.ts +++ b/scripts/src/platform-infra-k3s-cluster.ts @@ -149,9 +149,29 @@ function registryCredentialInit(target: K3sClusterTarget, confirm: boolean): Rec function apply(target: K3sClusterTarget): Record { const secret = readRegistrySecret(target); const proxyBundle = prepareHostProxyGuestBundle(target.node.hostProxyTargetId); + const desired = standaloneDesired(target, secret, proxyBundle.desiredFingerprint); + const before = runNode( + target, + standaloneConvergenceScript(target, desired.desiredHash, proxyBundle.serviceName), + false, + 60_000, + ); + const beforeData = parseFirstJson(before.stdout); + if (before.exitCode === 0 && beforeData.ok === true) { + return { + ok: true, + mutation: false, + mode: "already-converged", + targetId: target.id, + topology: "standalone-single-node", + status: beforeData, + result: { convergence: compact(before) }, + valuesPrinted: false, + }; + } const proxy = runNodeHostProxy(target, proxyBundle, 240_000); requireSuccess(proxy, "standalone-host-proxy-apply"); - const server = runNode(target, standaloneApplyScript(target, secret, proxyBundle.materialFingerprint), true, 300_000); + const server = runNode(target, standaloneApplyScript(target, secret, desired), true, 300_000); requireSuccess(server, "standalone-k3s-apply"); const converged = waitForStatus(target, 180_000); requireSuccess(converged, "standalone-k3s-status"); @@ -163,7 +183,12 @@ function apply(target: K3sClusterTarget): Record { targetId: target.id, topology: "standalone-single-node", status: data, - result: { hostProxy: compact(proxy), server: compact(server), status: compact(converged) }, + result: { + convergenceBefore: compact(before), + hostProxy: compact(proxy), + server: compact(server), + status: compact(converged), + }, valuesPrinted: false, }; } @@ -231,7 +256,11 @@ function registrySmoke(target: K3sClusterTarget, confirm: boolean): Record ["--disable", component]); const labels = Object.entries(target.node.labels).flatMap(([key, value]) => ["--node-label", `${key}=${value}`]); const exec = [ @@ -251,6 +280,15 @@ function standaloneApplyScript(target: K3sClusterTarget, secret: RegistrySecret, registryPassword: secret.password, proxyFingerprint, })); + return { exec, desiredHash }; +} + +function standaloneApplyScript( + target: K3sClusterTarget, + secret: RegistrySecret, + desired: { exec: string; desiredHash: string }, +): string { + const { exec, desiredHash } = desired; return `set -eu desired_hash=${shQuote(desiredHash)} desired_hash_file=/etc/unidesk/k3s-standalone-desired.sha256 @@ -290,6 +328,50 @@ systemctl start --no-block k3s `; } +function standaloneConvergenceScript( + target: K3sClusterTarget, + desiredHash: string, + proxyServiceName: string, +): string { + return `set -eu +active=false; systemctl is-active --quiet k3s && active=true +agent_active=false; systemctl is-active --quiet k3s-agent && agent_active=true +proxy_active=false; systemctl is-active --quiet ${shellToken(proxyServiceName)} && proxy_active=true +desired_current=false; [ "$(cat /etc/unidesk/k3s-standalone-desired.sha256 2>/dev/null || true)" = ${shQuote(desiredHash)} ] && desired_current=true +ready="$(k3s kubectl get node ${shQuote(target.node.nodeName)} -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || true)" +node_count="$(k3s kubectl get nodes --no-headers 2>/dev/null | wc -l | tr -d ' ')" +business_pods="$(k3s kubectl get pods -A --no-headers 2>/dev/null | awk '$1 != "kube-system" {count++} END {print count+0}')" +deployments="$(k3s kubectl get deployments -A --no-headers 2>/dev/null | awk '$1 == "kube-system" {count++} END {print count+0}')" +ready_deployments="$(k3s kubectl get deployments -A --no-headers 2>/dev/null | awk '$1 == "kube-system" {split($3, ready, "/"); if (ready[1] == ready[2] && ready[2] != "0" && $5 == ready[2]) count++} END {print count+0}')" +python3 - "$active" "$agent_active" "$proxy_active" "$desired_current" "$ready" "$node_count" "$business_pods" "$deployments" "$ready_deployments" <<'PY' +import json, sys +active, agent, proxy, desired, ready, nodes, business, deployments, ready_deployments = sys.argv[1:] +ok = ( + active == "true" + and agent == "false" + and proxy == "true" + and desired == "true" + and ready == "True" + and nodes == "1" + and business == "0" + and deployments == ready_deployments +) +print(json.dumps({ + "ok": ok, + "serverActive": active == "true", + "oldAgentActive": agent == "true", + "hostProxyActive": proxy == "true", + "desiredHashCurrent": desired == "true", + "nodeReady": ready == "True", + "nodeCount": int(nodes or 0), + "businessPodCount": int(business or 0), + "systemDeploymentCount": int(deployments or 0), + "systemReadyDeploymentCount": int(ready_deployments or 0), +}, separators=(",", ":"))) +PY +`; +} + function standaloneStatusScript(target: K3sClusterTarget): string { return `set -eu active=false; systemctl is-active --quiet k3s && active=true