diff --git a/.agents/skills/unidesk-ymalops/SKILL.md b/.agents/skills/unidesk-ymalops/SKILL.md index a4583fbc..fd400110 100644 --- a/.agents/skills/unidesk-ymalops/SKILL.md +++ b/.agents/skills/unidesk-ymalops/SKILL.md @@ -91,6 +91,7 @@ description: UniDesk YAML-first 运维正规化技能。用户提到 ymal-first/ - 多主机 k3s 集群: - 跨主机拓扑、WireGuard、agent token、worker host proxy 引用、集群内 artifact registry 映射、ServiceLB 节点和调度 smoke 归独立集群 owning YAML。 - worker host proxy 归 `config/platform-infra/host-proxy.yaml`;集群 apply 必须先确认 proxy client 连接声明的 `vpn-server` 并通过外网 probe,再启动 agent。 + - worker proxy 前必须先收敛 WireGuard;配置一致时不重启,只有 unit 未运行或握手过期且隧道探针失败时才允许恢复 worker 接口。 - containerd 通过 host proxy 直接访问公共 registry,禁止为公共依赖新增 registry mirror;集群内 artifact registry 映射不属于公共镜像代理。 - 既有 control-plane 的 k3s renderer 通过 `clusterConfigRef` 消费集群网络参数,不得由集群 CLI 再写第二个 systemd drop-in。 - 无固定公网地址 worker 使用宿主内网 SSH 作为部署控制通道并主动连接 WireGuard endpoint,不使用 FRP 代替三层集群网络。 diff --git a/docs/reference/platform-infra.md b/docs/reference/platform-infra.md index 31928842..1891a8c1 100644 --- a/docs/reference/platform-infra.md +++ b/docs/reference/platform-infra.md @@ -22,6 +22,7 @@ - `platform-infra k3s-cluster plan|apply|status|smoke --target ` 是部署和验收入口。 - 无固定公网地址的 worker 由宿主内网 SSH 控制,主动连接 control-plane WireGuard endpoint;worker provider route 只作为部署后补充验收入口。 - 集群 apply 先经宿主内网 SSH 分发零依赖 proxy client,确认它连接声明的 `vpn-server` 且外网 probe 成功,再启动或恢复 `k3s-agent`。 + - 集群 apply 在 proxy 前先收敛 worker WireGuard;内容一致时不强写,只有 unit 未运行或握手过期且隧道探针失败时才恢复 worker 接口。 - 大型 proxy 工件由 Windows 宿主按 YAML `upstreamUrl` 获取并校验 SHA-256,Provider/trans 只承载控制命令,不作为大二进制数据面。 - WireGuard 已建立的 worker 应通过隧道地址连接 control-plane 上的 `vpn-server`,不要求为 proxy 单独开放公网端口。 - proxy client 协议实现版本由 host-proxy YAML 声明,并与所连接的 `vpn-server` 协议实现版本保持一致。 diff --git a/scripts/src/platform-infra-k3s-cluster.ts b/scripts/src/platform-infra-k3s-cluster.ts index 53511650..de1bf977 100644 --- a/scripts/src/platform-infra-k3s-cluster.ts +++ b/scripts/src/platform-infra-k3s-cluster.ts @@ -103,11 +103,14 @@ function secretInit(target: K3sClusterTarget, confirm: boolean): Record { + const secret = readSecrets(target); + const workerNetwork = runWorker(target, workerWireguardApplyScript(target, secret), false, 120_000); + requireSuccess(workerNetwork, "worker-wireguard-apply"); + const hostProxy = prepareHostProxyGuestBundle(target.worker.hostProxyTargetId); const workerHostProxy = runWorkerHostProxy(target, hostProxy, 240_000); requireSuccess(workerHostProxy, "worker-host-proxy-apply"); - const secret = readSecrets(target); const controlWireguard = runControlPlane(target, controlPlaneApplyScript(target, secret), 180_000); requireSuccess(controlWireguard, "control-plane-wireguard-apply"); @@ -133,6 +136,7 @@ function apply(target: K3sClusterTarget): Record { mutation: true, targetId: target.id, stages: { + workerWireguard: compact(workerNetwork), workerHostProxy: { ...compact(workerHostProxy), configRef: hostProxy.configRef, @@ -223,6 +227,49 @@ function smoke(target: K3sClusterTarget, confirm: boolean): Record/dev/null 2>&1; then + DEBIAN_FRONTEND=noninteractive apt-get update -qq + DEBIAN_FRONTEND=noninteractive apt-get install -y -qq ${shellToken(wg.packageName)} +fi +install -d -m 700 /etc/wireguard +candidate="$(mktemp)" +trap 'rm -f "$candidate"' EXIT +cat >"$candidate" <<'EOF' +[Interface] +Address = ${wg.workerAddress} +PrivateKey = ${secret.workerPrivate} +MTU = ${wg.mtu} + +[Peer] +PublicKey = ${secret.controlPublic} +AllowedIPs = ${address(wg.controlPlaneAddress)}/32 +Endpoint = ${wg.endpoint} +PersistentKeepalive = ${wg.persistentKeepaliveSeconds} +EOF +changed=false +if ! cmp -s "$candidate" /etc/wireguard/${wg.interfaceName}.conf 2>/dev/null; then + install -m 0600 "$candidate" /etc/wireguard/${wg.interfaceName}.conf + changed=true +fi +systemctl enable wg-quick@${wg.interfaceName} >/dev/null 2>&1 || true +if [ "$changed" = true ]; then + systemctl restart wg-quick@${wg.interfaceName} +elif ! systemctl is-active --quiet wg-quick@${wg.interfaceName}; then + systemctl start wg-quick@${wg.interfaceName} +elif ! ping -c 1 -W 3 ${address(wg.controlPlaneAddress)} >/dev/null 2>&1; then + handshake="$(wg show ${wg.interfaceName} latest-handshakes 2>/dev/null | awk '{print $2}' | head -n1)" + now="$(date +%s)" + if [ -z "$handshake" ] || [ $((now - handshake)) -ge 180 ]; then + systemctl restart wg-quick@${wg.interfaceName} + fi +fi +ping -c 3 -W 5 ${address(wg.controlPlaneAddress)} >/dev/null +`; +} + function controlPlaneApplyScript(target: K3sClusterTarget, secret: SecretMaterial): string { const wg = target.wireguard; return `set -eu