feat(dev): 릴레이 에이전트를 클러스터 단위로 — dev up 에서 기동(배포 전 노드 모니터링·연결)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -59,11 +59,16 @@ def log(m: str) -> None:
|
||||
|
||||
# ── 로컬 자격 해석: kind Secret(yak-dev-src-*) → password/unit (자격은 여기서만) ──────────────
|
||||
def _read_secret(service: str) -> dict:
|
||||
r = subprocess.run(["kubectl", "--context", CTX, "-n", NS, "get", "secret", service, "-o", "json"],
|
||||
# 클러스터 전 네임스페이스에서 이름으로 Secret 조회(에이전트가 특정 프로젝트 ns 에 묶이지 않음).
|
||||
r = subprocess.run(["kubectl", "--context", CTX, "get", "secrets", "-A",
|
||||
"--field-selector", f"metadata.name={service}", "-o", "json"],
|
||||
capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
raise DsErr("NOT_FOUND")
|
||||
data = (json.loads(r.stdout or "{}").get("data", {}) or {})
|
||||
items = json.loads(r.stdout or "{}").get("items", []) or []
|
||||
if not items:
|
||||
raise DsErr("NOT_FOUND")
|
||||
data = (items[0].get("data", {}) or {})
|
||||
def dec(k: str) -> str:
|
||||
v = data.get(k)
|
||||
return base64.b64decode(v).decode() if v else ""
|
||||
@ -263,7 +268,8 @@ def _h_redis(sec: dict, action: str, params: dict) -> dict:
|
||||
|
||||
# ── 앱(워크로드) 라이브 조회 — 로컬 kind 에 kubectl(호스트). 원격 앱 화면 파리티(P2). ───────────
|
||||
def _kubectl(argv: list[str]) -> str:
|
||||
r = subprocess.run(["kubectl", "--context", CTX, "-n", NS, *argv], capture_output=True, text=True)
|
||||
# 클러스터 단위 에이전트 — NS 고정 안 함(호출자가 -A / --field-selector 로 네임스페이스 무관 조회).
|
||||
r = subprocess.run(["kubectl", "--context", CTX, *argv], capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
err = (r.stderr or "").lower()
|
||||
if "notfound" in err.replace(" ", "") or "not found" in err:
|
||||
@ -380,7 +386,7 @@ def _h_kube(action: str, params: dict) -> dict:
|
||||
if action == "pods":
|
||||
app = params.get("app") or ""
|
||||
sel = ["-l", f"app={app}"] if app else []
|
||||
items = json.loads(_kubectl(["get", "pods", *sel, "-o", "json"]) or "{}").get("items", [])
|
||||
items = json.loads(_kubectl(["get", "pods", "-A", *sel, "-o", "json"]) or "{}").get("items", [])
|
||||
pods = []
|
||||
for p in items:
|
||||
st = p.get("status", {}) or {}
|
||||
@ -393,7 +399,11 @@ def _h_kube(action: str, params: dict) -> dict:
|
||||
return {"app": app, "pods": pods[:100]}
|
||||
if action == "describe":
|
||||
pod = params["pod"]
|
||||
p = json.loads(_kubectl(["get", "pod", pod, "-o", "json"]) or "{}")
|
||||
# 전 네임스페이스에서 파드 이름으로 조회(에이전트가 ns 무관).
|
||||
_items = json.loads(_kubectl(["get", "pods", "-A", "--field-selector", f"metadata.name={pod}", "-o", "json"]) or "{}").get("items", [])
|
||||
if not _items:
|
||||
raise DsErr("NOT_FOUND")
|
||||
p = _items[0]
|
||||
meta = p.get("metadata", {}) or {}
|
||||
spec = p.get("spec", {}) or {}
|
||||
st = p.get("status", {}) or {}
|
||||
@ -440,7 +450,7 @@ def _h_kube(action: str, params: dict) -> dict:
|
||||
"events": [],
|
||||
}
|
||||
if action == "workloads":
|
||||
items = json.loads(_kubectl(["get", "deploy", "-o", "json"]) or "{}").get("items", [])
|
||||
items = json.loads(_kubectl(["get", "deploy", "-A", "-l", "app.kubernetes.io/managed-by=yakcloud-dev", "-o", "json"]) or "{}").get("items", [])
|
||||
wls = []
|
||||
for d in items:
|
||||
sp = d.get("spec", {}) or {}
|
||||
@ -517,9 +527,9 @@ def poll_once() -> int:
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not (RELAY_KEY and DS_TOKEN and API_BASE and NS):
|
||||
raise SystemExit("agent: YAK_RELAY_KEY/YAK_DS_TOKEN/YAK_API_BASE/YAK_DEV_NS 필요")
|
||||
log(f"릴레이 연결: {API_BASE}/clusters/{RELAY_KEY}/ds-actions (ns={NS}, ctx={CTX})")
|
||||
if not (RELAY_KEY and DS_TOKEN and API_BASE):
|
||||
raise SystemExit("agent: YAK_RELAY_KEY/YAK_DS_TOKEN/YAK_API_BASE 필요")
|
||||
log(f"릴레이 연결(클러스터 단위): {API_BASE}/clusters/{RELAY_KEY}/ds-actions (ctx={CTX})")
|
||||
while True:
|
||||
if poll_once() < 0:
|
||||
break
|
||||
|
||||
Reference in New Issue
Block a user