fix: meshsafe-cron 定时任务自引入以来从未成功执行 — 三处缺陷

复现而非等日志:容器内直接跑 archive_old_tasks.py 即崩。

1. app/config.py 的 admin_password 是必填字段,cron 环境没有它 →
   get_settings() 在 import 阶段抛 ValidationError,归档与 VACUUM 全崩。
   证据:policy_versions 留 10 行(策略写的留 5 删 5)、scan_tasks_archive 0 行,
   而容器 Up 且 RestartCount=0,没有任何存活信号暴露。给占位 env,不把真实口令复制进 cron。
2. VACUUM 不能在事务块里跑(ActiveSqlTransaction),原代码用了 engine.begin(),
   改用 AUTOCOMMIT 连接;实测 6 张表 0.25s 完成。
3. ValidationError 把整个 settings dict 打进 stderr,被 run() 原样写进 docker 日志,
   含 DB 连接串与 master key 尾部 → 加 _redact() 落盘前抹掉,验证输出中不可见。

cron 不监听 8443,镜像层 HEALTHCHECK 恒为 unhealthy,调度器真挂掉时该信号失效,故关闭。
This commit is contained in:
2026-11-02 20:26:08 +08:00
parent 1079ca16de
commit 64b682319e
2 changed files with 33 additions and 5 deletions

View File

@@ -25,6 +25,20 @@ logging.basicConfig(
log = logging.getLogger("scheduler")
_SECRET_ENV_KEYS = ("MESHSAFE_DB_URL", "MESHSAFE_MASTER_KEY", "MESHSAFE_ADMIN_PASSWORD",
"MESHSAFE_API_KEY", "MESHSAFE_API_CLIENTS")
def _redact(text: str) -> str:
"""崩溃日志里 pydantic 会把整个 settings dict(含 DB 口令)原样打出来,落盘前抹掉。"""
out = text
for key in _SECRET_ENV_KEYS:
val = os.environ.get(key, "")
if val and len(val) >= 8:
out = out.replace(val, "***")
return out
def run(cmd: list[str], label: str) -> None:
"""跑一个命令,捕获输出并记日志。"""
log.info(f"[{label}] 开始执行: {' '.join(cmd)}")
@@ -32,10 +46,10 @@ def run(cmd: list[str], label: str) -> None:
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=1800)
if proc.stdout:
for line in proc.stdout.strip().splitlines():
log.info(f"[{label}] {line}")
log.info(f"[{label}] {_redact(line)}")
if proc.stderr:
for line in proc.stderr.strip().splitlines():
log.warning(f"[{label}] {line}")
log.warning(f"[{label}] {_redact(line)}")
if proc.returncode == 0:
log.info(f"[{label}] ✅ 完成")
else:
@@ -47,7 +61,11 @@ def run(cmd: list[str], label: str) -> None:
def run_vacuum() -> None:
"""VACUUM ANALYZE — 维护表统计信息。"""
"""VACUUM ANALYZE — 维护表统计信息。
VACUUM 不能在事务块里跑(psycopg2 会抛 ActiveSqlTransaction),
所以用 AUTOCOMMIT 连接而不是 engine.begin()。
"""
try:
from app.db import engine
from sqlalchemy import text
@@ -57,13 +75,14 @@ def run_vacuum() -> None:
"scan_findings", "idempotency_records",
"policy_versions", "audit_logs",
]
with engine.begin() as conn:
with engine.connect() as raw:
conn = raw.execution_options(isolation_level="AUTOCOMMIT")
for tbl in tables:
log.info(f"[VACUUM] {tbl} ...")
conn.execute(text(f"VACUUM ANALYZE {tbl}"))
log.info("[VACUUM] ✅ 全部完成")
except Exception as e:
log.error(f"[VACUUM] ❌ 异常: {e}")
log.error(f"[VACUUM] ❌ 异常: {_redact(str(e))}")
def seconds_until(target_hour: int, target_minute: int, weekday: int | None = None) -> float: