feat(dev): 다중 로컬 dev 클러스터(--name/--port) + 노드 리소스 사용현황 릴레이

`yakcloud dev up --name dev2 --port 8081` 로 머신에 여러 kind dev 클러스터(공유 소스 컨테이너 재사용).
agent 가 노드 CPU/MEM/DISK(예약·실측)를 릴레이 → 콘솔이 원격과 동일한 노드 리소스 카드로 표시.
dev up 이 metrics-server 자동 설치(실측용). 리뷰 반영(pidfile 정리·포트 범위·phase·디스크).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-29 19:33:45 +09:00
parent 761666aade
commit da1911a036
3 changed files with 190 additions and 22 deletions

View File

@ -41,6 +41,8 @@ CTX = os.environ.get("YAK_DEV_CONTEXT", "kind-yak-dev")
KIND_CLUSTER = os.environ.get("YAK_DEV_CLUSTER", "yak-dev")
RUNTIME = os.environ.get("YAK_DEV_RUNTIME", "docker")
HOST_SUFFIX = os.environ.get("YAK_DEV_HOST_SUFFIX", "dev.localhost")
DEV_NAME = os.environ.get("YAK_DEV_NAME", "dev") # 여러 dev 클러스터 구분(에이전트 pidfile 키)
HTTP_PORT = os.environ.get("YAK_DEV_HTTP_PORT", "80") # 인그레스 host 포트(이름 붙은 클러스터는 비80)
TAG = os.environ.get("TAG", "dev")
# 로컬 데이터 소스 = kind '밖'의 **공유** 도커/포드만 컨테이너(타입별 1개) + 프로젝트별 격리 DB.
@ -364,6 +366,12 @@ def _wl_hosts(project: str, w: dict) -> list:
return hosts or [f"{project}.{HOST_SUFFIX}"]
def _disp_hosts(project: str, w: dict) -> list:
"""콘솔 표시용 호스트 — 비80 인그레스 포트면 host:port(인그레스 규칙 자체엔 포트 안 붙임)."""
sfx = "" if HTTP_PORT == "80" else f":{HTTP_PORT}"
return [h + sfx for h in _wl_hosts(project, w)]
def _report_to_console(project: str, reqs: list, wls: list) -> None:
"""dev deploy 결과(소스·워크로드 요약)를 콘솔에 best-effort 리포트 — LOCAL 클러스터 데이터소스·앱 탭 렌더용.
로그인(URL+TOKEN)+등록명(YAK_REG_NAME) 없으면 조용히 스킵(오프라인 dev 불변식)."""
@ -378,9 +386,9 @@ def _report_to_console(project: str, reqs: list, wls: list) -> None:
"sources": [{"name": r["name"], "type": str(r.get("type", "")).upper(),
"secret": f"yak-dev-src-{sanitize(r['name'])}"} for r in reqs],
"workloads": [{"name": sanitize(w["name"]), "image": w["image"].replace("${TAG}", TAG),
"hosts": _wl_hosts(project, w), "port": int(w.get("port", 8080))} for w in wls],
"hosts": _disp_hosts(project, w), "port": int(w.get("port", 8080))} for w in wls],
}
body = json.dumps({"name": name, "displayName": "로컬 개발 (kind)", "report": report}).encode()
body = json.dumps({"name": name, "displayName": name, "report": report}).encode()
req = urllib.request.Request(url.rstrip("/") + "/api/v1/clusters/local", data=body, method="POST",
headers={"Authorization": "Bearer " + tok, "content-type": "application/json"})
try:
@ -398,7 +406,8 @@ def _agent_dir() -> str:
def _agent_pidfile(ns: str) -> str:
return os.path.join(_agent_dir(), f"{ns}.pid")
# 클러스터별 구분 — 서로 다른 dev 클러스터가 같은 ns(프로젝트)를 가져도 pidfile 이 안 겹치게.
return os.path.join(_agent_dir(), f"{DEV_NAME}-{ns}.pid")
def _stop_agent(ns: str) -> None:
@ -493,8 +502,9 @@ def cmd_deploy(m: dict) -> None:
log("✓ 로컬 배포 완료(직선 dev). 접속:")
first_host = results[0][0] if results else None
if first_host:
print(f" http://{first_host}/ (kind 인그레스 80 포트)")
print(f" curl -s http://{first_host}/ | python3 -m json.tool")
psfx = "" if HTTP_PORT == "80" else f":{HTTP_PORT}"
print(f" http://{first_host}{psfx}/ (kind 인그레스 포트 {HTTP_PORT})")
print(f" curl -s http://{first_host}{psfx}/ | python3 -m json.tool")
print(f" ↑ 응답의 bound_sources 에 바인딩 소스 <ALIAS>_URL 키가 보이면 직선 파리티 성립")
print(f" kubectl --context {CTX} -n {ns} get deploy,svc,ing,pods")
print(f" 승격: yakcloud project deploy --local <tag> (레지스트리 push) → yakcloud project promote --to val")