61 lines
2.9 KiB
Python
61 lines
2.9 KiB
Python
"""Read-only server discovery and conservative starter recommendations."""
|
||
import base64
|
||
import hashlib
|
||
import math
|
||
import socket
|
||
|
||
from .docker import DockerEngine, DockerError
|
||
|
||
|
||
def host_fingerprint(host: str, port: int) -> str:
|
||
import paramiko
|
||
transport = None
|
||
try:
|
||
with socket.create_connection((host, port), timeout=10) as connection:
|
||
transport = paramiko.Transport(connection)
|
||
transport.start_client(timeout=10)
|
||
key = transport.get_remote_server_key()
|
||
return "SHA256:" + base64.b64encode(hashlib.sha256(key.asbytes()).digest()).decode().rstrip("=")
|
||
except Exception as exc:
|
||
raise DockerError(f"读取服务器指纹失败({type(exc).__name__})") from None
|
||
finally:
|
||
if transport is not None:
|
||
transport.close()
|
||
|
||
|
||
def recommend(cpus, memory_mb, reserved_cpu=0, reserved_memory_mb=0, connection_budget=64):
|
||
# Reserve at least half the host for the main app, DB and other services.
|
||
cpu_budget = max(0, min(cpus / 2, cpus - reserved_cpu - 1))
|
||
memory_budget = max(0, min(memory_mb // 2, memory_mb - reserved_memory_mb - 2048))
|
||
cpu = 2 if cpu_budget >= 2 else 1
|
||
memory = 2048
|
||
count = max(0, min(4, math.floor(cpu_budget / cpu), memory_budget // memory, connection_budget // 12))
|
||
return {"desired": int(count), "cpu": cpu, "memory_mb": memory,
|
||
"total_cpu": max(cpu, count * cpu), "total_memory_mb": max(memory, count * memory),
|
||
"concurrency": 4, "database_pool_size": 4, "postgres_pool_size": 4}
|
||
|
||
|
||
def discover(settings, instance):
|
||
engine = DockerEngine(settings)
|
||
info = engine.request("GET", "/info")
|
||
containers = engine.request("GET", "/containers/json")
|
||
cpu_reserved = 0
|
||
memory_reserved = 0
|
||
templates = []
|
||
for item in containers:
|
||
if item.get("Labels", {}).get("deerflow.worker-pool") == instance:
|
||
continue
|
||
detail = engine.request("GET", f"/containers/{item['Id']}/json")
|
||
limits = detail.get("HostConfig", {})
|
||
cpu_reserved += limits.get("NanoCpus", 0) / 1_000_000_000
|
||
memory_reserved += limits.get("Memory", 0) // (1024 ** 2)
|
||
if item.get("Labels", {}).get("deerflow.worker-role") == "online":
|
||
continue
|
||
networks = [name for name in detail.get("NetworkSettings", {}).get("Networks", {}) if name not in {"host", "none", "bridge"}]
|
||
templates.append({"name": detail.get("Name", "").lstrip("/"), "networks": networks})
|
||
cpus = int(info.get("NCPU", 0))
|
||
memory_mb = int(info.get("MemTotal", 0)) // (1024 ** 2)
|
||
return {"cpus": cpus, "memory_mb": memory_mb, "containers": templates,
|
||
"recommendation": recommend(cpus, memory_mb, cpu_reserved, memory_reserved, settings.database_connection_budget),
|
||
"note": "按硬件总量和其他容器已配置的资源上限估算,至少为主服务、数据库及系统保留一半资源;不是实时空闲内存。建议先用最多 4 个节点,再按实际负载调整。"}
|