| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108 |
- import json
- import os
- import subprocess
- import time
- from pathlib import Path
- import httpx
- from prometheus_client.parser import text_string_to_metric_families
- ROOT = Path(__file__).resolve().parents[1]
- COMPOSE = ["docker", "compose", "-f", "compose.runtime.yaml"]
- ROLES = ["agent", "memory", "fulfillment", "knowledge", "channel-delivery"]
- def run(*arguments, check=True):
- return subprocess.run(
- [*COMPOSE, *arguments], cwd=ROOT, check=check, capture_output=True, encoding="utf-8"
- )
- def workers(expected):
- with httpx.Client(timeout=10, trust_env=False) as client:
- for _ in range(40):
- response = client.get("http://127.0.0.1:28300/metrics")
- response.raise_for_status()
- counts = {
- sample.labels["role"]: sample.value
- for family in text_string_to_metric_families(response.text)
- for sample in family.samples
- if sample.name == "zhifu_workers_alive"
- }
- if counts == dict.fromkeys(ROLES, expected):
- return counts
- time.sleep(1)
- raise RuntimeError(f"工作进程心跳不符合要求:{counts}")
- def main():
- if not os.environ.get("DEEPSEEK_API_KEY"):
- raise ValueError("运行验证需要已配置的 DEEPSEEK_API_KEY")
- report = {"release_check": json.loads(run("run", "--rm", "release-check").stdout)}
- missing = run("run", "--rm", "-e", "AGENT_MODEL_NAME=", "release-check", check=False)
- if missing.returncode != 1 or "模型名称、服务地址及环境变量凭据必须齐全" not in missing.stderr:
- raise RuntimeError("发布检查未拒绝缺少模型配置的环境")
- report["missing_model_configuration_rejected"] = True
- report["workers_before"] = workers(1)
- started = time.monotonic()
- try:
- run("stop", *ROLES)
- ids = run("ps", "--all", "--quiet", *ROLES).stdout.split()
- metadata = json.loads(subprocess.check_output(["docker", "inspect", *ids]))
- if len(metadata) != len(ROLES):
- raise RuntimeError("工作进程容器数量不符合要求")
- states = {
- value["Config"]["Labels"]["com.docker.compose.service"]: {
- "exit_code": value["State"]["ExitCode"],
- "oom_killed": value["State"]["OOMKilled"],
- "running": value["State"]["Running"],
- }
- for value in metadata
- }
- if any(
- value != {"exit_code": 0, "oom_killed": False, "running": False}
- for value in states.values()
- ):
- raise RuntimeError(f"工作进程未正常停止:{states}")
- report["workers_stopped"] = states
- report["workers_after_stop"] = workers(0)
- report["stop_seconds"] = round(time.monotonic() - started, 2)
- finally:
- run("up", "-d", *ROLES)
- report["workers_after_start"] = workers(1)
- with httpx.Client(timeout=10, trust_env=False) as client:
- report["http_statuses"] = {}
- for name, url in {
- "api": "http://127.0.0.1:28000/health/ready",
- "oms": "http://127.0.0.1:28100/health",
- "inference": "http://127.0.0.1:28200/health/ready",
- }.items():
- response = client.get(url)
- response.raise_for_status()
- report["http_statuses"][name] = response.status_code
- containers = run("ps", "--quiet", "api", "oms", "operations", "inference", *ROLES)
- metadata = json.loads(
- subprocess.check_output(["docker", "inspect", *containers.stdout.split()])
- )
- report["containers"] = {
- value["Config"]["Labels"]["com.docker.compose.service"]: {
- "image": value["Image"],
- "user": value["Config"]["User"],
- "read_only": value["HostConfig"]["ReadonlyRootfs"],
- "restart_count": value["RestartCount"],
- }
- for value in metadata
- }
- if len(metadata) != 4 + len(ROLES) or any(
- value["user"] != "10001:10001" or not value["read_only"]
- for value in report["containers"].values()
- ):
- raise RuntimeError("容器数量或权限配置不符合要求")
- (ROOT / "docs/verification/container-runtime.json").write_text(
- json.dumps(report, indent=2), encoding="utf-8"
- )
- print(json.dumps(report, indent=2))
- if __name__ == "__main__":
- main()
|