Files
knowledge-governance/deploy/cloud-test/run-release-executor.sh
T
姚章浩 e6472b0ec6 feat(library): 云测试发布目标接入的角色分离、快照搬运与图谱门禁
为什么改:发布执行链是"与目标同机"假设写死的(本机 docker.sock、本机 drain.json、
本机 Redis/PG),把目标换成云测试不能只改展示 URL;直接搬整套 worker 又会抢走
本机封存/QA/周报队列并按本机配置写错目标。所以先把"谁消费哪段队列"变成显式角色,
再补齐跨主机搬运与目标侧门禁。

角色(LIBRARY_WORKER_ROLE=full/release-executor/capture)不只影响 handler 列表,
还决定是否拥有 DDL(release-executor 只读校验迁移版本,落后即拒绝启动)、
收拾哪些 library_jobs.kind、是否跑 release 接管扫描、是否起 scheduler/QA/周报/探测。
capture 保留本机原有全部业务;release-executor 只挂 sync/restore/recover/baseline。

快照跨主机:新增内容寻址的 bundle(版本清单先落、blob 按 sha256 命名、清单最后
Link 作提交点),pack/unpack 幂等且拒绝覆盖与同名不同内容;执行器使用前仍逐 blob
重算 sha256,因此传输不可变有两道证据。

目标侧门禁:目标知识库 graph_enabled=true 时拒绝发布(发布只写 PG/ES/OSS,不写
Neo4j;未知也拒绝),只读检测、不改目标配置、不做图谱迁移。

云端入口:新增 drain 感知的 ingress 闸门(建连超时与读超时分离,半关闭单向传播,
SIGTERM 排空后写 drain.json 并 exit 0/2)、compose 叠加片段(app 不再发布端口、
frontend 与 adapter 的入口收敛到闸门、指向真实网络 weknora-test-v071-network)、
执行器启动脚本与治理 PG 反向隧道脚本(默认 unix socket,不占 TCP)。

验证:deploy/cloud-test/test_gate.py 13 项全 PASS(含 12 秒静默流、半关闭、2MiB 完整、
排空 exit 0/active_at_close=1、排空超时 exit 2、上游不可达不挂住);go test 相关包全绿;
scripts/ci_checks.py 16 项全部 success(EXIT=0,1241 用例通过/0 失败/191 已声明可选集成跳过);
云测试只读预检 17 项 / 6 FAIL,FAIL 项即当前部署缺口(无闸门、app 仍发布端口、
adapter 与 frontend 仍直连 app、云端归档根未建),未部署、未改云端任何状态。
2026-10-30 18:38:38 +08:00

48 lines
2.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# 在云测试主机上启动/更新"发布执行器"容器(由父在目标机执行,不在本机远程包装)。
#
# 为什么单独 docker run 而不是塞进 weknora-test-v071 这个 compose 项目:
# 维护协调器要求项目里每个服务都被声明为写入者或"非写入者",执行器不是目标的一部分,
# 放进同一个项目只会让拓扑清单多一条无关服务。它只需要挂上目标项目的网络即可。
set -euo pipefail
IMAGE="${RELEASE_EXECUTOR_IMAGE:-}"
NETWORK="${TARGET_NETWORK:-weknora-test-v071_default}"
ENV_FILE="${RELEASE_EXECUTOR_ENV:-/srv/knowledge-governance/release-executor.env}"
ARCHIVE="${RELEASE_ARCHIVE_ROOT:-/srv/knowledge-governance/library}"
JOURNAL="${RELEASE_JOURNAL_ROOT:-/srv/knowledge-governance/target-journal}"
GATE_STATE="${RELEASE_GATE_STATE:-/srv/kgtest-gate/state}"
NAME="${RELEASE_EXECUTOR_NAME:-kgtest-release-executor}"
die() { echo "release-executor: $*" >&2; exit 1; }
[ -n "$IMAGE" ] || die "RELEASE_EXECUTOR_IMAGE 未设置(必须是与已验收提交同 sha 的 worker 镜像)"
[ "$(id -u)" -eq 0 ] || die "需要 root:要读 docker.sock 并读写 0700 归档目录"
docker network inspect "$NETWORK" >/dev/null 2>&1 || die "网络 $NETWORK 不存在(先部署目标项目)"
[ -f "$ENV_FILE" ] || die "缺少 env 文件 $ENV_FILE"
[ "$(stat -c '%a' "$ENV_FILE")" = "600" ] || die "$ENV_FILE 必须是 0600(里面有目标凭据)"
grep -q '^LIBRARY_WORKER_ROLE=release-executor$' "$ENV_FILE" || die "$ENV_FILE 里 LIBRARY_WORKER_ROLE 必须是 release-executor"
[ -d "$GATE_STATE" ] || die "闸门状态目录 $GATE_STATE 不存在(先部署 ingress 闸门)"
install -d -m 0700 "$ARCHIVE" "$JOURNAL"
install -d -m 0700 "$ARCHIVE/blobs" "$ARCHIVE/versions"
if docker inspect "$NAME" >/dev/null 2>&1; then
echo "release-executor: 已存在,按同一 env 重建(先停,便于看清失败原因)"
docker rm -f "$NAME" >/dev/null
fi
docker run -d --name "$NAME" \
--restart unless-stopped \
--network "$NETWORK" \
--label com.nous.role=release-executor \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$ARCHIVE:/var/lib/knowledge-governance/library" \
-v "$JOURNAL:/var/lib/knowledge-governance/target-journal" \
-v "$GATE_STATE:/gate-state" \
--env-file "$ENV_FILE" \
"$IMAGE" >/dev/null
sleep 2
docker ps --filter "name=$NAME" --format '{{.Names}} {{.Status}} {{.Image}}'
echo "release-executor: 启动完成;日志用 docker logs -f $NAME(准入失败会在第一屏写明原因)"