verify_runtime.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import json
  2. import os
  3. import subprocess
  4. import time
  5. from pathlib import Path
  6. import httpx
  7. from prometheus_client.parser import text_string_to_metric_families
  8. ROOT = Path(__file__).resolve().parents[1]
  9. COMPOSE = ["docker", "compose", "-f", "compose.runtime.yaml"]
  10. ROLES = ["agent", "memory", "fulfillment", "knowledge", "channel-delivery"]
  11. def run(*arguments, check=True):
  12. return subprocess.run(
  13. [*COMPOSE, *arguments], cwd=ROOT, check=check, capture_output=True, encoding="utf-8"
  14. )
  15. def workers(expected):
  16. with httpx.Client(timeout=10, trust_env=False) as client:
  17. for _ in range(40):
  18. response = client.get("http://127.0.0.1:28300/metrics")
  19. response.raise_for_status()
  20. counts = {
  21. sample.labels["role"]: sample.value
  22. for family in text_string_to_metric_families(response.text)
  23. for sample in family.samples
  24. if sample.name == "zhifu_workers_alive"
  25. }
  26. if counts == dict.fromkeys(ROLES, expected):
  27. return counts
  28. time.sleep(1)
  29. raise RuntimeError(f"工作进程心跳不符合要求:{counts}")
  30. def main():
  31. if not os.environ.get("DEEPSEEK_API_KEY"):
  32. raise ValueError("运行验证需要已配置的 DEEPSEEK_API_KEY")
  33. report = {"release_check": json.loads(run("run", "--rm", "release-check").stdout)}
  34. missing = run("run", "--rm", "-e", "AGENT_MODEL_NAME=", "release-check", check=False)
  35. if missing.returncode != 1 or "模型名称、服务地址及环境变量凭据必须齐全" not in missing.stderr:
  36. raise RuntimeError("发布检查未拒绝缺少模型配置的环境")
  37. report["missing_model_configuration_rejected"] = True
  38. report["workers_before"] = workers(1)
  39. started = time.monotonic()
  40. try:
  41. run("stop", *ROLES)
  42. ids = run("ps", "--all", "--quiet", *ROLES).stdout.split()
  43. metadata = json.loads(subprocess.check_output(["docker", "inspect", *ids]))
  44. if len(metadata) != len(ROLES):
  45. raise RuntimeError("工作进程容器数量不符合要求")
  46. states = {
  47. value["Config"]["Labels"]["com.docker.compose.service"]: {
  48. "exit_code": value["State"]["ExitCode"],
  49. "oom_killed": value["State"]["OOMKilled"],
  50. "running": value["State"]["Running"],
  51. }
  52. for value in metadata
  53. }
  54. if any(
  55. value != {"exit_code": 0, "oom_killed": False, "running": False}
  56. for value in states.values()
  57. ):
  58. raise RuntimeError(f"工作进程未正常停止:{states}")
  59. report["workers_stopped"] = states
  60. report["workers_after_stop"] = workers(0)
  61. report["stop_seconds"] = round(time.monotonic() - started, 2)
  62. finally:
  63. run("up", "-d", *ROLES)
  64. report["workers_after_start"] = workers(1)
  65. with httpx.Client(timeout=10, trust_env=False) as client:
  66. report["http_statuses"] = {}
  67. for name, url in {
  68. "api": "http://127.0.0.1:28000/health/ready",
  69. "oms": "http://127.0.0.1:28100/health",
  70. "inference": "http://127.0.0.1:28200/health/ready",
  71. }.items():
  72. response = client.get(url)
  73. response.raise_for_status()
  74. report["http_statuses"][name] = response.status_code
  75. containers = run("ps", "--quiet", "api", "oms", "operations", "inference", *ROLES)
  76. metadata = json.loads(
  77. subprocess.check_output(["docker", "inspect", *containers.stdout.split()])
  78. )
  79. report["containers"] = {
  80. value["Config"]["Labels"]["com.docker.compose.service"]: {
  81. "image": value["Image"],
  82. "user": value["Config"]["User"],
  83. "read_only": value["HostConfig"]["ReadonlyRootfs"],
  84. "restart_count": value["RestartCount"],
  85. }
  86. for value in metadata
  87. }
  88. if len(metadata) != 4 + len(ROLES) or any(
  89. value["user"] != "10001:10001" or not value["read_only"]
  90. for value in report["containers"].values()
  91. ):
  92. raise RuntimeError("容器数量或权限配置不符合要求")
  93. (ROOT / "docs/verification/container-runtime.json").write_text(
  94. json.dumps(report, indent=2), encoding="utf-8"
  95. )
  96. print(json.dumps(report, indent=2))
  97. if __name__ == "__main__":
  98. main()