feat: configure task center archive gc deployments
This commit is contained in:
parent
900a524f9b
commit
16eb283908
@ -91,6 +91,11 @@ CHATAPP_CRON_COMPOSE_FILE=/opt/likei/cron-sg-1/docker-compose.yml
|
||||
CHATAPP_CRON_COMPOSE_SERVICE=week-star-job
|
||||
CHATAPP_CRON_BINARY_PATH=/opt/likei/cron-sg-1/week-star-job/service
|
||||
CHATAPP_CRON_RELEASE_META_PATH=/opt/likei/cron-sg-1/.hy-app-monitor-chatapp-cron-release.json
|
||||
# Task Center 大表 GC 默认关闭。维护窗口必须同时在 Go service.env 和这里显式打开;
|
||||
# cron 发布会把以下三项写入目标机 service.env 并验证容器真实环境变量。
|
||||
CHATAPP_TASK_CENTER_ARCHIVE_GC_ENABLED=false
|
||||
CHATAPP_TASK_CENTER_ARCHIVE_GC_INTERVAL_SECONDS=60
|
||||
CHATAPP_TASK_CENTER_ARCHIVE_GC_TIMEOUT_SECONDS=180
|
||||
MONGO_URI=
|
||||
MONGO_DB_NAME=tarab_all
|
||||
MONGO_INSTANCE_ID=ins-o9mmob66
|
||||
|
||||
@ -140,8 +140,16 @@ CHATAPP_CRON_COMPOSE_FILE=/opt/likei/cron-sg-1/docker-compose.yml
|
||||
CHATAPP_CRON_COMPOSE_SERVICE=week-star-job
|
||||
CHATAPP_CRON_BINARY_PATH=/opt/likei/cron-sg-1/week-star-job/service
|
||||
CHATAPP_CRON_RELEASE_META_PATH=/opt/likei/cron-sg-1/.hy-app-monitor-chatapp-cron-release.json
|
||||
CHATAPP_TASK_CENTER_ARCHIVE_GC_ENABLED=false
|
||||
CHATAPP_TASK_CENTER_ARCHIVE_GC_INTERVAL_SECONDS=60
|
||||
CHATAPP_TASK_CENTER_ARCHIVE_GC_TIMEOUT_SECONDS=180
|
||||
```
|
||||
|
||||
归档 GC 默认关闭。Go 节点的总开关通过“配置 / Go Env”写入各节点
|
||||
`service.env`;cron 的三个调度变量由部署平台根目录 `.env` 提供。Cron 发布会
|
||||
先备份并更新目标机 `service.env`,重建后再核对容器实际环境变量,任何一项未透传
|
||||
都会让发布失败,避免页面显示已开启但调度进程仍使用旧配置。
|
||||
|
||||
可以直接复制模板:
|
||||
|
||||
```bash
|
||||
|
||||
@ -367,6 +367,19 @@ CHATAPP_CRON_RELEASE_META_PATH = (
|
||||
).strip()
|
||||
or f"{CHATAPP_CRON_REMOTE_DIR.rstrip('/')}/.hy-app-monitor-chatapp-cron-release.json"
|
||||
)
|
||||
# 归档 GC 的 Go 总开关和 cron 调度开关使用同名配置;部署平台默认强制关闭,
|
||||
# 只有在维护窗口显式修改 .env 后,cron 发布才会把启用态传给目标容器。
|
||||
CHATAPP_TASK_CENTER_ARCHIVE_GC_ENABLED = "true" if truthy(
|
||||
os.environ.get("CHATAPP_TASK_CENTER_ARCHIVE_GC_ENABLED"), False
|
||||
) else "false"
|
||||
# cron 只负责触发单轮 tick,实际批次、cutoff 和状态机由 Go 服务持久化控制。
|
||||
CHATAPP_TASK_CENTER_ARCHIVE_GC_INTERVAL_SECONDS = max(
|
||||
1, env_int("CHATAPP_TASK_CENTER_ARCHIVE_GC_INTERVAL_SECONDS", 60)
|
||||
)
|
||||
# 超时必须覆盖 Go 单轮的小批删除和 COS 恢复包上传,避免 cron 提前取消请求。
|
||||
CHATAPP_TASK_CENTER_ARCHIVE_GC_TIMEOUT_SECONDS = max(
|
||||
1, env_int("CHATAPP_TASK_CENTER_ARCHIVE_GC_TIMEOUT_SECONDS", 180)
|
||||
)
|
||||
# chatapp-cron 是否启用;未显式指定时,只要目标机和仓库信息齐备就自动开启。
|
||||
CHATAPP_CRON_ENABLED = truthy(
|
||||
os.environ.get("CHATAPP_CRON_ENABLED"),
|
||||
|
||||
@ -33,6 +33,9 @@ from core.config import (
|
||||
CHATAPP_CRON_SSH_PORT,
|
||||
CHATAPP_CRON_SSH_USE_SUDO,
|
||||
CHATAPP_CRON_SSH_USER,
|
||||
CHATAPP_TASK_CENTER_ARCHIVE_GC_ENABLED,
|
||||
CHATAPP_TASK_CENTER_ARCHIVE_GC_INTERVAL_SECONDS,
|
||||
CHATAPP_TASK_CENTER_ARCHIVE_GC_TIMEOUT_SECONDS,
|
||||
ROOT,
|
||||
SERVICE_BUILD_TIMEOUT_SECONDS,
|
||||
SERVICE_PULL_TIMEOUT_SECONDS,
|
||||
@ -148,6 +151,9 @@ def build_chatapp_cron_overview_payload() -> dict[str, Any]:
|
||||
"composeService": CHATAPP_CRON_COMPOSE_SERVICE,
|
||||
"binaryPath": CHATAPP_CRON_BINARY_PATH,
|
||||
"dockerPlatform": BUILD_DOCKER_PLATFORM,
|
||||
"archiveGCEnabled": CHATAPP_TASK_CENTER_ARCHIVE_GC_ENABLED == "true",
|
||||
"archiveGCIntervalSeconds": CHATAPP_TASK_CENTER_ARCHIVE_GC_INTERVAL_SECONDS,
|
||||
"archiveGCTimeoutSeconds": CHATAPP_TASK_CENTER_ARCHIVE_GC_TIMEOUT_SECONDS,
|
||||
}
|
||||
return {
|
||||
"ok": True,
|
||||
@ -262,8 +268,20 @@ def activate_chatapp_cron_binary(build_result: dict[str, Any], *, progress: Prog
|
||||
"binaryPath": CHATAPP_CRON_BINARY_PATH,
|
||||
"composeFile": CHATAPP_CRON_COMPOSE_FILE,
|
||||
"remoteDir": CHATAPP_CRON_REMOTE_DIR,
|
||||
"archiveGCEnabled": CHATAPP_TASK_CENTER_ARCHIVE_GC_ENABLED == "true",
|
||||
"archiveGCIntervalSeconds": CHATAPP_TASK_CENTER_ARCHIVE_GC_INTERVAL_SECONDS,
|
||||
"archiveGCTimeoutSeconds": CHATAPP_TASK_CENTER_ARCHIVE_GC_TIMEOUT_SECONDS,
|
||||
}
|
||||
metadata_blob = base64.b64encode(json.dumps(metadata, ensure_ascii=False, indent=2).encode("utf-8")).decode("ascii")
|
||||
# cron 目标机的 service.env 属于运行时配置,不跟二进制一起进仓库。每次发布只
|
||||
# 覆盖这三个白名单项,并在 compose 重建后核对容器真实 Env,防止文件写了但
|
||||
# compose 没有引用 service.env 的“假成功”。
|
||||
runtime_env_updates = {
|
||||
"CHATAPP_TASK_CENTER_ARCHIVE_GC_ENABLED": CHATAPP_TASK_CENTER_ARCHIVE_GC_ENABLED,
|
||||
"CHATAPP_TASK_CENTER_ARCHIVE_GC_INTERVAL_SECONDS": str(CHATAPP_TASK_CENTER_ARCHIVE_GC_INTERVAL_SECONDS),
|
||||
"CHATAPP_TASK_CENTER_ARCHIVE_GC_TIMEOUT_SECONDS": str(CHATAPP_TASK_CENTER_ARCHIVE_GC_TIMEOUT_SECONDS),
|
||||
}
|
||||
runtime_env_blob = base64.b64encode(json.dumps(runtime_env_updates, sort_keys=True).encode("utf-8")).decode("ascii")
|
||||
|
||||
script = f"""set -euo pipefail
|
||||
REMOTE_DIR={shlex.quote(CHATAPP_CRON_REMOTE_DIR)}
|
||||
@ -273,6 +291,8 @@ BINARY_PATH={shlex.quote(CHATAPP_CRON_BINARY_PATH)}
|
||||
REMOTE_TMP={shlex.quote(remote_tmp)}
|
||||
META_PATH={shlex.quote(CHATAPP_CRON_RELEASE_META_PATH)}
|
||||
META_BLOB={shlex.quote(metadata_blob)}
|
||||
SERVICE_ENV_PATH={shlex.quote(f"{CHATAPP_CRON_REMOTE_DIR.rstrip('/')}/service.env")}
|
||||
RUNTIME_ENV_BLOB={shlex.quote(runtime_env_blob)}
|
||||
|
||||
compose() {{
|
||||
if docker compose version >/dev/null 2>&1; then
|
||||
@ -289,6 +309,45 @@ fi
|
||||
install -m 0755 "$REMOTE_TMP" "$BINARY_PATH"
|
||||
rm -f "$REMOTE_TMP"
|
||||
|
||||
if [ -f "$SERVICE_ENV_PATH" ]; then
|
||||
cp "$SERVICE_ENV_PATH" "$REMOTE_DIR/.backups/service.env.$(date +%Y%m%d-%H%M%S)"
|
||||
fi
|
||||
python3 - "$SERVICE_ENV_PATH" "$RUNTIME_ENV_BLOB" <<'PY'
|
||||
from pathlib import Path
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
target = Path(sys.argv[1])
|
||||
updates = json.loads(base64.b64decode(sys.argv[2]).decode("utf-8"))
|
||||
original_mode = (target.stat().st_mode & 0o777) if target.exists() else 0o600
|
||||
lines = target.read_text(encoding="utf-8").splitlines() if target.exists() else []
|
||||
seen = set()
|
||||
output = []
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
assignment = stripped[7:].strip() if stripped.startswith("export ") else stripped
|
||||
key = assignment.split("=", 1)[0].strip() if "=" in assignment else ""
|
||||
if key not in updates:
|
||||
output.append(line)
|
||||
continue
|
||||
if key in seen:
|
||||
continue
|
||||
output.append(f"{{key}}={{json.dumps(str(updates[key]))}}")
|
||||
seen.add(key)
|
||||
for key, value in updates.items():
|
||||
if key not in seen:
|
||||
output.append(f"{{key}}={{json.dumps(str(value))}}")
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
with tempfile.NamedTemporaryFile("w", encoding="utf-8", dir=target.parent, delete=False) as handle:
|
||||
handle.write("\n".join(output).rstrip() + "\n")
|
||||
temp_name = handle.name
|
||||
os.chmod(temp_name, original_mode)
|
||||
os.replace(temp_name, target)
|
||||
PY
|
||||
|
||||
cd "$REMOTE_DIR"
|
||||
compose -f "$COMPOSE_FILE" up -d --no-deps --force-recreate "$COMPOSE_SERVICE"
|
||||
|
||||
@ -305,6 +364,19 @@ if ! printf '%s\\n' "$STATUS_OUTPUT" | grep -Eiq 'running|up'; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CONTAINER_ID="$(compose -f "$COMPOSE_FILE" ps -q "$COMPOSE_SERVICE")"
|
||||
CONTAINER_ENV="$(docker inspect --format='{{{{range .Config.Env}}}}{{{{println .}}}}{{{{end}}}}' "$CONTAINER_ID")"
|
||||
for EXPECTED_ENV in \
|
||||
"CHATAPP_TASK_CENTER_ARCHIVE_GC_ENABLED={CHATAPP_TASK_CENTER_ARCHIVE_GC_ENABLED}" \
|
||||
"CHATAPP_TASK_CENTER_ARCHIVE_GC_INTERVAL_SECONDS={CHATAPP_TASK_CENTER_ARCHIVE_GC_INTERVAL_SECONDS}" \
|
||||
"CHATAPP_TASK_CENTER_ARCHIVE_GC_TIMEOUT_SECONDS={CHATAPP_TASK_CENTER_ARCHIVE_GC_TIMEOUT_SECONDS}"
|
||||
do
|
||||
if ! printf '%s\\n' "$CONTAINER_ENV" | grep -Fqx -- "$EXPECTED_ENV"; then
|
||||
printf 'missing cron runtime env: %s\\n' "$EXPECTED_ENV" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
mkdir -p "$(dirname "$META_PATH")"
|
||||
printf '%s' "$META_BLOB" | base64 -d > "$META_PATH"
|
||||
"""
|
||||
|
||||
@ -265,6 +265,15 @@ GO_ENV_SPECS: list[dict[str, Any]] = [
|
||||
"aliases": ("TASK_CENTER_ARCHIVE_WORKERS",),
|
||||
"kind": "int",
|
||||
},
|
||||
{
|
||||
"section": "TaskCenter Archive GC",
|
||||
"primary": "CHATAPP_TASK_CENTER_ARCHIVE_GC_ENABLED",
|
||||
"aliases": ("TASK_CENTER_ARCHIVE_GC_ENABLED",),
|
||||
"kind": "bool",
|
||||
# 大表清理必须由运营预检后显式开放;旧 service.env 没有该项时,
|
||||
# 配置页也要展示关闭态,保存后才会把开关写到所有 Go 节点。
|
||||
"default": "false",
|
||||
},
|
||||
{
|
||||
"section": "WeekStar",
|
||||
"primary": "CHATAPP_WEEK_STAR_DEFAULT_SYS_ORIGIN",
|
||||
@ -606,7 +615,13 @@ def extract_go_values(values: dict[str, str]) -> dict[str, str]:
|
||||
extracted: dict[str, str] = {}
|
||||
for spec in GO_ENV_SPECS:
|
||||
keys = (spec["primary"], *spec["aliases"])
|
||||
extracted[spec["primary"]] = first_present_value(values, keys)
|
||||
present = any(key in values for key in keys)
|
||||
current_value = first_present_value(values, keys) if present else ""
|
||||
extracted[spec["primary"]] = (
|
||||
current_value
|
||||
if current_value != ""
|
||||
else str(spec.get("default", ""))
|
||||
)
|
||||
return extracted
|
||||
|
||||
|
||||
|
||||
@ -426,6 +426,9 @@ function createChatappCronDeployState() {
|
||||
composeService: "week-star-job",
|
||||
binaryPath: "",
|
||||
dockerPlatform: "linux/amd64",
|
||||
archiveGCEnabled: false,
|
||||
archiveGCIntervalSeconds: 60,
|
||||
archiveGCTimeoutSeconds: 180,
|
||||
},
|
||||
metadata: {},
|
||||
},
|
||||
|
||||
@ -2939,6 +2939,11 @@
|
||||
<span class="mono">{{ truncateMiddle(chatappCronSettings.repoRoot, 16, 12) || '-' }}</span>
|
||||
<span class="mono">{{ truncateMiddle(chatappCronSettings.composeFile, 16, 12) || '-' }}</span>
|
||||
<span>{{ chatappCronSettings.composeService || '-' }}</span>
|
||||
<span :class="['badge', chatappCronSettings.archiveGCEnabled ? 'warn' : 'neutral']">
|
||||
归档 GC {{ chatappCronSettings.archiveGCEnabled ? '已开启' : '已关闭' }}
|
||||
</span>
|
||||
<span>间隔 {{ chatappCronSettings.archiveGCIntervalSeconds || 60 }}s</span>
|
||||
<span>超时 {{ chatappCronSettings.archiveGCTimeoutSeconds || 180 }}s</span>
|
||||
</div>
|
||||
|
||||
<div class="action-group">
|
||||
|
||||
@ -234,6 +234,10 @@ CHATAPP_ROOM_TURNOVER_REWARD_SETTLE_INTERVAL_SECONDS=60
|
||||
CHATAPP_GAME_KING_SETTLE_INTERVAL_SECONDS=60
|
||||
# Game King 每轮最多派送 4 组(单组硬超时 20 秒);120 秒为 DB 冻结和状态修复保留余量。
|
||||
CHATAPP_GAME_KING_SETTLE_TIMEOUT_SECONDS=120
|
||||
# 大表归档清理默认关闭;测试清理流程时先从管理端 dry-run,再显式打开 Go/cron 共用开关。
|
||||
CHATAPP_TASK_CENTER_ARCHIVE_GC_ENABLED=false
|
||||
CHATAPP_TASK_CENTER_ARCHIVE_GC_INTERVAL_SECONDS=60
|
||||
CHATAPP_TASK_CENTER_ARCHIVE_GC_TIMEOUT_SECONDS=180
|
||||
CHATAPP_WEEK_STAR_ROCKETMQ_ENABLED=false
|
||||
CHATAPP_WEEK_STAR_ROCKETMQ_ENDPOINT=rocketmq-proxy:8081
|
||||
CHATAPP_WEEK_STAR_ROCKETMQ_ACCESS_KEY=test
|
||||
|
||||
@ -12,6 +12,9 @@ class ChatappCronDeployTests(unittest.TestCase):
|
||||
def test_overview_does_not_read_remote_metadata_when_disabled(self) -> None:
|
||||
with (
|
||||
patch.object(cron, "CHATAPP_CRON_ENABLED", False),
|
||||
patch.object(cron, "CHATAPP_TASK_CENTER_ARCHIVE_GC_ENABLED", "false"),
|
||||
patch.object(cron, "CHATAPP_TASK_CENTER_ARCHIVE_GC_INTERVAL_SECONDS", 60),
|
||||
patch.object(cron, "CHATAPP_TASK_CENTER_ARCHIVE_GC_TIMEOUT_SECONDS", 180),
|
||||
patch.object(cron, "read_chatapp_cron_metadata") as read_metadata_mock,
|
||||
):
|
||||
payload = cron.build_chatapp_cron_overview_payload()
|
||||
@ -19,6 +22,9 @@ class ChatappCronDeployTests(unittest.TestCase):
|
||||
self.assertTrue(payload["ok"])
|
||||
self.assertFalse(payload["enabled"])
|
||||
self.assertEqual(payload["metadata"], {})
|
||||
self.assertFalse(payload["settings"]["archiveGCEnabled"])
|
||||
self.assertEqual(payload["settings"]["archiveGCIntervalSeconds"], 60)
|
||||
self.assertEqual(payload["settings"]["archiveGCTimeoutSeconds"], 180)
|
||||
read_metadata_mock.assert_not_called()
|
||||
|
||||
def test_activate_uploads_binary_and_recreates_compose_service(self) -> None:
|
||||
@ -44,6 +50,9 @@ class ChatappCronDeployTests(unittest.TestCase):
|
||||
patch.object(cron, "CHATAPP_CRON_COMPOSE_FILE", "/opt/deploy/runtime/current/docker-compose.yml"),
|
||||
patch.object(cron, "CHATAPP_CRON_COMPOSE_SERVICE", "week-star-job"),
|
||||
patch.object(cron, "CHATAPP_CRON_BINARY_PATH", "/opt/deploy/runtime/current/week-star-job/service"),
|
||||
patch.object(cron, "CHATAPP_TASK_CENTER_ARCHIVE_GC_ENABLED", "false"),
|
||||
patch.object(cron, "CHATAPP_TASK_CENTER_ARCHIVE_GC_INTERVAL_SECONDS", 60),
|
||||
patch.object(cron, "CHATAPP_TASK_CENTER_ARCHIVE_GC_TIMEOUT_SECONDS", 180),
|
||||
patch.object(
|
||||
cron,
|
||||
"CHATAPP_CRON_RELEASE_META_PATH",
|
||||
@ -75,9 +84,13 @@ class ChatappCronDeployTests(unittest.TestCase):
|
||||
self.assertIn("BINARY_PATH=/opt/deploy/runtime/current/week-star-job/service", remote_script)
|
||||
self.assertIn('up -d --no-deps --force-recreate "$COMPOSE_SERVICE"', remote_script)
|
||||
self.assertIn("META_BLOB=", remote_script)
|
||||
self.assertIn("CHATAPP_TASK_CENTER_ARCHIVE_GC_ENABLED=false", remote_script)
|
||||
self.assertIn("CHATAPP_TASK_CENTER_ARCHIVE_GC_INTERVAL_SECONDS=60", remote_script)
|
||||
self.assertIn("CHATAPP_TASK_CENTER_ARCHIVE_GC_TIMEOUT_SECONDS=180", remote_script)
|
||||
|
||||
self.assertEqual(result["metadata"]["commit"], "f0f6205abc1234567890")
|
||||
self.assertEqual(result["metadata"]["component"], "week-star-job")
|
||||
self.assertFalse(result["metadata"]["archiveGCEnabled"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@ -7,6 +7,7 @@ from core.config import HARBOR_PROJECT, HARBOR_REGISTRY, load_hosts
|
||||
from monitors.yumi.runtime_config import (
|
||||
GO_ENV_PRIMARY_BY_KEY,
|
||||
GO_ENV_SPECS_BY_PRIMARY,
|
||||
extract_go_values,
|
||||
expand_host_updates,
|
||||
validate_go_editor_content,
|
||||
)
|
||||
@ -29,6 +30,7 @@ class ServiceReleaseTargetsTests(unittest.TestCase):
|
||||
"CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_CONSUMER_GROUP",
|
||||
"CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_TOPIC",
|
||||
"CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_TAG",
|
||||
"CHATAPP_TASK_CENTER_ARCHIVE_GC_ENABLED",
|
||||
}
|
||||
|
||||
self.assertTrue(expected_keys.issubset(GO_ENV_SPECS_BY_PRIMARY))
|
||||
@ -49,11 +51,17 @@ class ServiceReleaseTargetsTests(unittest.TestCase):
|
||||
'CHATAPP_YUMI_GIFT_CHALLENGE_DELIVERY_LEASE_SECONDS="300"',
|
||||
'CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_ENABLED="true"',
|
||||
'CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_ENDPOINT="rocketmq.example:8081"',
|
||||
'CHATAPP_TASK_CENTER_ARCHIVE_GC_ENABLED="false"',
|
||||
]
|
||||
)
|
||||
)
|
||||
self.assertTrue(valid["ok"])
|
||||
self.assertEqual(valid["values"]["CHATAPP_JAVA_ORDER_BASE_URL"], "http://10.2.1.15:2600")
|
||||
self.assertEqual(valid["values"]["CHATAPP_TASK_CENTER_ARCHIVE_GC_ENABLED"], "false")
|
||||
self.assertEqual(
|
||||
extract_go_values({})["CHATAPP_TASK_CENTER_ARCHIVE_GC_ENABLED"],
|
||||
"false",
|
||||
)
|
||||
|
||||
invalid_lease = validate_go_editor_content(
|
||||
'CHATAPP_YUMI_GIFT_CHALLENGE_DELIVERY_LEASE_SECONDS="0"'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user