deploy: sync autoscaler unit with production host (seed-skills, imagegen-mcp, playwright-mcp)

仓库版 ExecStart 只带 --websearch-mcp,生产主机现行 unit 在此之外
还带 --seed-skills --imagegen-mcp --playwright-mcp;按仓库重装会静默
丢掉技能播种与两路 MCP。同步后仓库 ExecStart 与主机现行 ExecStart
逐 token 一致,并补 deploy/tests/test_autoscaler_unit.py(5 项)拦
后续漂移:四项齐全、无重复、均在 cloud/worker.py 有定义、不在
scaler 侧、--docker-runtime 仍为 runsc。

(cherry picked from commit bc3a23fc068cac189c0173b8e1c2befa7b832ed2)
This commit is contained in:
host committed 2026-11-01 09:06:28 +00:00
1 parent 2a569ac6e3
commit 1141d0d0f8
2 files changed
+98 -1

No files matched your search

+4 -1
View File
@@ -14,7 +14,10 @@ EnvironmentFile=/etc/codex-app-server/env
# scaler 会按实例强制追加;--db 只给 scaler,scaler 会补给 worker。
# 注意:启用 autoscaler 后必须停掉静态 codex-cloud-worker,
# 否则两边同时派任务(scaler 只管理 autoscale-* 实例)。
ExecStart=/usr/bin/python3 -m cloud.autoscaler --db /var/lib/codex-app-server/control.db --interval 30 --min-workers 1 --max-workers 2 --per-worker-capacity 2 --scale-down-after 3 --drain-timeout 60 --state-file /var/lib/codex-app-server/autoscaler-state.json --log-dir /var/lib/codex-app-server/autoscaler-logs -- --interval 10 --config /opt/codex-app-server/host-config-docker.json --executor docker --docker-image codex-app-server:latest --docker-user 1003:1004 --docker-cpus 2 --docker-memory 2g --docker-extra-mounts /opt/codex-app-server/work:/app/work,/var/lib/codex-app-server/codex-home:/var/lib/codex-app-server/codex-home,/var/lib/codex-app-server/repos:/var/lib/codex-app-server/repos,/var/lib/codex-app-server/test-repo:/var/lib/codex-app-server/test-repo --repos-dir /var/lib/codex-app-server/repos --tenant-isolation --tenants-root /var/lib/codex-app-server/tenants --tenant-template-home /var/lib/codex-app-server/codex-home --tenant-template-repo /var/lib/codex-app-server/test-repo --docker-security-opt apparmor=docker-codex --docker-pids-limit 512 --egress-proxy-port 8888 --docker-runtime runsc --websearch-mcp
# worker 侧 MCP/技能播种四项与生产主机现行 unit 一致
# (--websearch-mcp --seed-skills --imagegen-mcp --playwright-mcp);
# 主机 unit 再改时须同步改这里,deploy/tests/test_autoscaler_unit.py 会拦漂移。
ExecStart=/usr/bin/python3 -m cloud.autoscaler --db /var/lib/codex-app-server/control.db --interval 30 --min-workers 1 --max-workers 2 --per-worker-capacity 2 --scale-down-after 3 --drain-timeout 60 --state-file /var/lib/codex-app-server/autoscaler-state.json --log-dir /var/lib/codex-app-server/autoscaler-logs -- --interval 10 --config /opt/codex-app-server/host-config-docker.json --executor docker --docker-image codex-app-server:latest --docker-user 1003:1004 --docker-cpus 2 --docker-memory 2g --docker-extra-mounts /opt/codex-app-server/work:/app/work,/var/lib/codex-app-server/codex-home:/var/lib/codex-app-server/codex-home,/var/lib/codex-app-server/repos:/var/lib/codex-app-server/repos,/var/lib/codex-app-server/test-repo:/var/lib/codex-app-server/test-repo --repos-dir /var/lib/codex-app-server/repos --tenant-isolation --tenants-root /var/lib/codex-app-server/tenants --tenant-template-home /var/lib/codex-app-server/codex-home --tenant-template-repo /var/lib/codex-app-server/test-repo --docker-security-opt apparmor=docker-codex --docker-pids-limit 512 --egress-proxy-port 8888 --docker-runtime runsc --websearch-mcp --seed-skills --imagegen-mcp --playwright-mcp
Restart=on-failure
RestartSec=10
# 停 scaler 时要等它把 worker 优雅排空(drain 60 秒 + 缓冲),
+94
View File
@@ -0,0 +1,94 @@
"""autoscaler unit 与生产主机现行 unit 的同步回归。
背景:仓库版 deploy/autoscaler/codex-autoscaler.service 的 ExecStart
长期只带 --websearch-mcp,而生产主机现行 unit 在此之外还带
--seed-skills --imagegen-mcp --playwright-mcp(2026-11-01 主机只读核对:
主机 ExecStart = 仓库版 ExecStart + 这三项,别无差异)。
仓库版滞后意味着按仓库重装会静默丢掉技能播种与两路 MCP。
本文件只读仓库内两个文件,不碰主机、不联网、不跑 worker:
- unit 的 worker 侧(`--` 之后)必须含全部四项 MCP/技能播种 flag;
- worker 侧所有 --flag 必须在 cloud/worker.py 里有 add_argument 定义,
防止拼写漂移导致 worker 启动即报未知参数;
- 四项 flag 绝不能出现在 scaler 侧(`--` 之前),它们是 worker 参数。
"""
import os
import unittest
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(
os.path.abspath(__file__))))
UNIT_PATH = os.path.join(REPO_ROOT, "deploy", "autoscaler",
"codex-autoscaler.service")
WORKER_PATH = os.path.join(REPO_ROOT, "cloud", "worker.py")
# 生产主机现行 unit 带的 worker 侧 MCP/技能播种四项。
MCP_SEED_FLAGS = ("--websearch-mcp", "--seed-skills",
"--imagegen-mcp", "--playwright-mcp")
def _execstart():
with open(UNIT_PATH, encoding="utf-8") as f:
lines = [ln.rstrip("\n") for ln in f]
starts = [ln for ln in lines if ln.startswith("ExecStart=")]
assert len(starts) == 1, "unit 应恰好有一行 ExecStart,实得 %d 行" % len(starts)
return starts[0][len("ExecStart="):]
def _split_sides():
cmd = _execstart()
assert cmd.startswith("/usr/bin/python3 -m cloud.autoscaler"), \
"unit 应以 cloud.autoscaler 启动,实得:%s" % cmd[:60]
parts = cmd.split(" -- ", 1)
assert len(parts) == 2, "ExecStart 缺少 scaler/worker 分隔符 ' -- '"
return parts[0], parts[1]
def _flag_tokens(side):
"""side 里以 -- 开头的 token(去重前顺序保留,含 =value 形式剥离)。"""
flags = []
for tok in side.split():
if tok.startswith("--"):
flags.append(tok.split("=", 1)[0])
return flags
class TestAutoscalerUnitSync(unittest.TestCase):
def test_worker_side_includes_all_four_mcp_seed_flags(self):
_, worker_side = _split_sides()
flags = _flag_tokens(worker_side)
for flag in MCP_SEED_FLAGS:
self.assertIn(flag, flags,
"worker 侧缺 %s,仓库 unit 已滞后于生产主机" % flag)
def test_worker_side_flags_have_no_duplicates(self):
_, worker_side = _split_sides()
flags = _flag_tokens(worker_side)
self.assertEqual(len(flags), len(set(flags)),
"worker 侧 flag 重复:%s" % (flags,))
def test_worker_side_flags_are_defined_in_worker_parser(self):
_, worker_side = _split_sides()
with open(WORKER_PATH, encoding="utf-8") as f:
worker_src = f.read()
for flag in _flag_tokens(worker_side):
self.assertIn('add_argument("%s"' % flag, worker_src,
"%s 未在 cloud/worker.py 定义,worker 会报未知参数"
% flag)
def test_mcp_seed_flags_not_on_scaler_side(self):
scaler_side, _ = _split_sides()
flags = _flag_tokens(scaler_side)
for flag in MCP_SEED_FLAGS:
self.assertNotIn(flag, flags,
"%s 不应出现在 scaler 侧(`--` 之前)" % flag)
def test_worker_side_keeps_docker_runtime_runsc(self):
_, worker_side = _split_sides()
tokens = worker_side.split()
self.assertIn("--docker-runtime", tokens)
self.assertEqual(tokens[tokens.index("--docker-runtime") + 1], "runsc")
if __name__ == "__main__":
unittest.main()