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:
@ -272,7 +272,111 @@ def _kubectl(argv: list[str]) -> str:
|
||||
return r.stdout
|
||||
|
||||
|
||||
def _cpu_m(v) -> int:
|
||||
"""CPU 수량 → millicores. '25m'→25, '1'→1000, '1.5'→1500."""
|
||||
if not v:
|
||||
return 0
|
||||
v = str(v)
|
||||
if v.endswith("m"):
|
||||
try:
|
||||
return int(float(v[:-1]))
|
||||
except ValueError:
|
||||
return 0
|
||||
if v.endswith("n"): # nanocores(top 일부)
|
||||
return int(float(v[:-1]) / 1_000_000)
|
||||
try:
|
||||
return int(float(v) * 1000)
|
||||
except ValueError:
|
||||
return 0
|
||||
|
||||
|
||||
def _mem_b(v) -> int:
|
||||
"""메모리 수량 → bytes. Ki/Mi/Gi/Ti + K/M/G 접미사."""
|
||||
if not v:
|
||||
return 0
|
||||
v = str(v).strip()
|
||||
units = {"Ki": 1024, "Mi": 1024**2, "Gi": 1024**3, "Ti": 1024**4,
|
||||
"K": 1000, "M": 1000**2, "G": 1000**3, "T": 1000**4}
|
||||
for u, mult in units.items():
|
||||
if v.endswith(u):
|
||||
try:
|
||||
return int(float(v[:-len(u)]) * mult)
|
||||
except ValueError:
|
||||
return 0
|
||||
try:
|
||||
return int(float(v))
|
||||
except ValueError:
|
||||
return 0
|
||||
|
||||
|
||||
def _h_kube(action: str, params: dict) -> dict:
|
||||
if action == "usage":
|
||||
nodes_j = json.loads(_kubectl(["get", "nodes", "-o", "json"]) or "{}").get("items", [])
|
||||
pods_j = json.loads(_kubectl(["get", "pods", "-A", "-o", "json"]) or "{}").get("items", [])
|
||||
# 노드별 예약(스케줄된 비종료 파드 컨테이너 requests 합)
|
||||
req = {}
|
||||
for p in pods_j:
|
||||
nn = (p.get("spec", {}) or {}).get("nodeName")
|
||||
if not nn or (p.get("status", {}) or {}).get("phase") in ("Succeeded", "Failed"):
|
||||
continue
|
||||
acc = req.setdefault(nn, [0, 0])
|
||||
for c in (p.get("spec", {}) or {}).get("containers", []) or []:
|
||||
rq = ((c.get("resources", {}) or {}).get("requests", {}) or {})
|
||||
acc[0] += _cpu_m(rq.get("cpu"))
|
||||
acc[1] += _mem_b(rq.get("memory"))
|
||||
# 실측(metrics-server 있으면): kubectl top nodes
|
||||
top = {}
|
||||
r = subprocess.run(["kubectl", "--context", CTX, "top", "nodes", "--no-headers"],
|
||||
capture_output=True, text=True)
|
||||
if r.returncode == 0:
|
||||
for ln in r.stdout.splitlines():
|
||||
pt = ln.split()
|
||||
if len(pt) >= 5: # NAME CPU(cores) CPU% MEM MEM%
|
||||
top[pt[0]] = {"cpu": pt[1], "cpu_pct": pt[2].rstrip("%"), "mem": pt[3], "mem_pct": pt[4].rstrip("%")}
|
||||
out = []
|
||||
for n in nodes_j:
|
||||
meta, st = n.get("metadata", {}) or {}, n.get("status", {}) or {}
|
||||
name = meta.get("name")
|
||||
labels = meta.get("labels", {}) or {}
|
||||
roles = [k.split("/", 1)[1] for k in labels if k.startswith("node-role.kubernetes.io/")]
|
||||
cap, alloc = st.get("capacity", {}) or {}, st.get("allocatable", {}) or {}
|
||||
cpu_alloc = round(_cpu_m(alloc.get("cpu")) / 1000.0, 2)
|
||||
mem_alloc = round(_mem_b(alloc.get("memory")) / (1024**3), 2)
|
||||
rq = req.get(name, [0, 0])
|
||||
cpu_req = round(rq[0] / 1000.0, 2)
|
||||
mem_req = round(rq[1] / (1024**3), 2)
|
||||
ready = any(c.get("type") == "Ready" and c.get("status") == "True"
|
||||
for c in (st.get("conditions", []) or []))
|
||||
t = top.get(name, {})
|
||||
# 디스크 실측 — kubelet Summary API(metrics-server 불필요, kind 지원). 원격(백엔드)과 동일 필드.
|
||||
disk = disk_pct = disk_cap = None
|
||||
ds = subprocess.run(["kubectl", "--context", CTX, "get", "--raw",
|
||||
f"/api/v1/nodes/{name}/proxy/stats/summary"], capture_output=True, text=True)
|
||||
if ds.returncode == 0:
|
||||
try:
|
||||
fs = ((json.loads(ds.stdout).get("node", {}) or {}).get("fs", {})) or {}
|
||||
used, capb = fs.get("usedBytes"), fs.get("capacityBytes")
|
||||
if used is not None and capb:
|
||||
disk = f"{round(used / 1024**3, 1)}Gi"
|
||||
disk_cap = round(capb / 1024**3, 1)
|
||||
disk_pct = str(round(used / capb * 100))
|
||||
except (ValueError, ZeroDivisionError, TypeError):
|
||||
pass
|
||||
out.append({
|
||||
"name": name, "roles": roles or ["control-plane"], "joined": ready, "ready": ready,
|
||||
"pending": False, "maas_status": None, # kind 노드 — MAAS 프로비저닝 개념 없음
|
||||
"phase": "Ready" if ready else "NotReady", # NodeMetricDTO.phase(필수) — 상태 배지
|
||||
"cpu_cap": round(_cpu_m(cap.get("cpu")) / 1000.0, 2), "cpu_alloc": cpu_alloc,
|
||||
"cpu_req": cpu_req, "cpu_req_pct": round(cpu_req / cpu_alloc * 100) if cpu_alloc else None,
|
||||
"cpu": t.get("cpu"), "cpu_pct": t.get("cpu_pct"),
|
||||
"mem_cap": round(_mem_b(cap.get("memory")) / (1024**3), 2), "mem_alloc": mem_alloc,
|
||||
"mem_req": mem_req, "mem_req_pct": round(mem_req / mem_alloc * 100) if mem_alloc else None,
|
||||
"mem": t.get("mem"), "mem_pct": t.get("mem_pct"),
|
||||
"disk": disk, "disk_pct": disk_pct, "disk_cap": disk_cap,
|
||||
})
|
||||
return {"metrics_available": bool(top), "nodes": out,
|
||||
"autoscale": {"enabled": False, "min": None, "max": None},
|
||||
"provisioning": False, "history": []}
|
||||
if action == "pods":
|
||||
app = params.get("app") or ""
|
||||
sel = ["-l", f"app={app}"] if app else []
|
||||
|
||||
@ -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")
|
||||
|
||||
88
bin/yakcloud
88
bin/yakcloud
@ -411,6 +411,28 @@ _dev_runtime() {
|
||||
echo ""
|
||||
}
|
||||
|
||||
# 여러 로컬 dev 클러스터 지원 — --name/--port 를 파싱해 클러스터명·컨텍스트·등록명·인그레스 포트를 파생.
|
||||
# 기본(--name 없음) = dev → kind 'yak-dev', ctx 'kind-yak-dev', 등록명 <host>, 인그레스 80/443
|
||||
# --name dev2 --port N → kind 'yak-dev2', ctx 'kind-yak-dev2', 등록명 <host>-dev2, 인그레스 N (80은 기본 클러스터 몫)
|
||||
# 나머지 인자(태그 등)는 DEV_ARGS 배열에 남긴다(--name 값이 태그로 오인되지 않게 여기서 소비).
|
||||
_dev_resolve() {
|
||||
DEV_NAME="dev"; DEV_HTTP_PORT="80"; DEV_PORT_EXPLICIT=0; DEV_ARGS=()
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--name|--cluster) DEV_NAME="${2:-}"; shift 2 || shift ;;
|
||||
--name=*) DEV_NAME="${1#--name=}"; shift ;;
|
||||
--cluster=*) DEV_NAME="${1#--cluster=}"; shift ;;
|
||||
--port) DEV_HTTP_PORT="${2:-}"; DEV_PORT_EXPLICIT=1; shift 2 || shift ;;
|
||||
--port=*) DEV_HTTP_PORT="${1#--port=}"; DEV_PORT_EXPLICIT=1; shift ;;
|
||||
*) DEV_ARGS+=("$1"); shift ;;
|
||||
esac
|
||||
done
|
||||
DEV_NAME="$(printf '%s' "$DEV_NAME" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9-' '-' | sed 's/^-*//; s/-*$//')"
|
||||
[ -z "$DEV_NAME" ] && DEV_NAME="dev"
|
||||
DEV_CLUSTER="${YAK_DEV_CLUSTER:-yak-$DEV_NAME}"
|
||||
DEV_CTX="kind-$DEV_CLUSTER" # kind 는 항상 컨텍스트를 'kind-<클러스터명>' 으로 만든다
|
||||
}
|
||||
|
||||
cmd_dev_up() {
|
||||
command -v kind >/dev/null 2>&1 || die "kind 필요 — 'brew install kind' (https://kind.sigs.k8s.io)"
|
||||
command -v kubectl >/dev/null 2>&1 || die "kubectl 필요"
|
||||
@ -419,8 +441,19 @@ cmd_dev_up() {
|
||||
if env $kenv kind get clusters 2>/dev/null | grep -qx "$DEV_CLUSTER"; then
|
||||
info "kind 클러스터 '$DEV_CLUSTER' 이미 존재 → 재사용 ($rt)"
|
||||
else
|
||||
info "kind 클러스터 '$DEV_CLUSTER' 생성 ($rt) — 인그레스 80/443 host 매핑…"
|
||||
env $kenv kind create cluster --name "$DEV_CLUSTER" --config - <<'EOF' || die "kind 생성 실패"
|
||||
# 이름 붙은(비기본) 클러스터는 80 이 기본 클러스터 몫 → --port 필수. 기본(dev)은 80/443.
|
||||
if [ "$DEV_NAME" != "dev" ] && [ "${DEV_PORT_EXPLICIT:-0}" = 0 ]; then
|
||||
die "이름 붙은 dev 클러스터('$DEV_NAME')는 인그레스 포트가 필요합니다 — 예: yakcloud dev up --name $DEV_NAME --port 8081"
|
||||
fi
|
||||
case "$DEV_HTTP_PORT" in ''|*[!0-9]*) die "포트는 숫자여야 합니다: '$DEV_HTTP_PORT'";; esac
|
||||
if [ "$DEV_HTTP_PORT" -lt 1 ] || [ "$DEV_HTTP_PORT" -gt 65535 ]; then
|
||||
die "포트는 1-65535 범위여야 합니다: '$DEV_HTTP_PORT'"
|
||||
fi
|
||||
info "kind 클러스터 '$DEV_CLUSTER' 생성 ($rt) — 인그레스 http 포트 ${DEV_HTTP_PORT}…"
|
||||
local ports=" - { containerPort: 80, hostPort: ${DEV_HTTP_PORT}, protocol: TCP }"
|
||||
[ "$DEV_HTTP_PORT" = "80" ] && ports="$ports
|
||||
- { containerPort: 443, hostPort: 443, protocol: TCP }"
|
||||
env $kenv kind create cluster --name "$DEV_CLUSTER" --config - <<EOF || die "kind 생성 실패(포트 ${DEV_HTTP_PORT} 가 이미 사용 중일 수 있음)"
|
||||
kind: Cluster
|
||||
apiVersion: kind.x-k8s.io/v1alpha4
|
||||
nodes:
|
||||
@ -432,8 +465,7 @@ nodes:
|
||||
kubeletExtraArgs:
|
||||
node-labels: "ingress-ready=true"
|
||||
extraPortMappings:
|
||||
- { containerPort: 80, hostPort: 80, protocol: TCP }
|
||||
- { containerPort: 443, hostPort: 443, protocol: TCP }
|
||||
${ports}
|
||||
EOF
|
||||
fi
|
||||
info "ingress-nginx 설치/확인…"
|
||||
@ -443,6 +475,12 @@ EOF
|
||||
sleep 3
|
||||
kubectl --context "$DEV_CTX" -n ingress-nginx rollout status deploy/ingress-nginx-controller --timeout=180s >/dev/null 2>&1 \
|
||||
|| warn_line "ingress 컨트롤러 준비 지연 — 잠시 후 'yakcloud dev deploy'."
|
||||
# metrics-server(실측 노드/파드 CPU·메모리) — 콘솔 '노드 리소스 사용현황'의 실측 바용. kind 는 kubelet TLS 자체서명 → --kubelet-insecure-tls.
|
||||
info "metrics-server 설치/확인(노드 리소스 실측)…"
|
||||
kubectl --context "$DEV_CTX" apply -f "${YAK_DEV_METRICS:-https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml}" >/dev/null 2>&1 \
|
||||
&& kubectl --context "$DEV_CTX" -n kube-system patch deployment metrics-server --type=json \
|
||||
-p '[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]' >/dev/null 2>&1 \
|
||||
|| warn_line "metrics-server 설치 건너뜀(무시 가능 — 실측 대신 예약값만 표시)."
|
||||
echo "✓ 로컬 dev 클러스터 준비 ($DEV_CTX). 다음: yakcloud dev deploy"
|
||||
_dev_register
|
||||
}
|
||||
@ -451,7 +489,10 @@ warn_line() { echo " ⚠ $*" >&2; }
|
||||
# 콘솔 클러스터 목록에 이 로컬 클러스터를 메타 등록 — 머신 호스트명 기준(머신당 1개, 프로젝트는 네임스페이스).
|
||||
_local_reg_name() {
|
||||
local h; h="$(hostname -s 2>/dev/null | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9-' '-' | sed 's/^-*//; s/-*$//')"
|
||||
echo "${h:-local-dev}"
|
||||
h="${h:-local-dev}"
|
||||
# 여러 dev 클러스터 구분 — 기본(dev)은 <host>, 이름 붙은 클러스터는 <host>-<name>(콘솔에 구분 표시).
|
||||
[ -n "${DEV_NAME:-}" ] && [ "$DEV_NAME" != "dev" ] && h="$h-$DEV_NAME"
|
||||
echo "$h"
|
||||
}
|
||||
# best-effort 등록/heartbeat — 로그인(URL+토큰) 없으면 조용히 스킵(오프라인 dev 불변식 유지).
|
||||
_dev_register() {
|
||||
@ -459,7 +500,7 @@ _dev_register() {
|
||||
local name; name="$(_local_reg_name)"
|
||||
curl -fsS -m 6 -X POST "${YAKCLOUD_URL%/}/api/v1/clusters/local" \
|
||||
-H "Authorization: Bearer $YAKCLOUD_TOKEN" -H 'content-type: application/json' \
|
||||
-d "{\"name\":\"$name\",\"displayName\":\"로컬 개발 (kind)\"}" >/dev/null 2>&1 \
|
||||
-d "{\"name\":\"$name\",\"displayName\":\"$name\"}" >/dev/null 2>&1 \
|
||||
&& info "콘솔 클러스터 목록에 로컬 '$name' 등록/갱신" || true
|
||||
return 0
|
||||
}
|
||||
@ -474,16 +515,17 @@ _dev_deregister() {
|
||||
|
||||
cmd_dev() {
|
||||
local sub="${1:-}"; [ "$#" -gt 0 ] && shift
|
||||
_dev_resolve "$@" # --name/--port 파싱 → DEV_NAME/DEV_CLUSTER/DEV_CTX/DEV_HTTP_PORT/DEV_ARGS
|
||||
case "$sub" in
|
||||
up) cmd_dev_up "$@" ;;
|
||||
up) cmd_dev_up ;;
|
||||
deploy|status|clean)
|
||||
[ -f yakcloud.yaml ] || die "프로젝트 폴더가 아닙니다 — 'yakcloud project init' 먼저"
|
||||
[ -f yakcloud.yaml ] || die "yakcloud.yaml 없음"
|
||||
ensure_pyyaml
|
||||
local rt tag=""; rt="$(_dev_runtime)"
|
||||
for a in "$@"; do case "$a" in -*) : ;; *) [ -z "$tag" ] && tag="$a" ;; esac; done
|
||||
for a in "${DEV_ARGS[@]+"${DEV_ARGS[@]}"}"; do case "$a" in -*) : ;; *) [ -z "$tag" ] && tag="$a" ;; esac; done
|
||||
local pysub="$sub"; [ "$sub" = clean ] && pysub="down"
|
||||
YAK_DEV_CONTEXT="$DEV_CTX" YAK_DEV_CLUSTER="$DEV_CLUSTER" YAK_DEV_RUNTIME="${rt:-docker}" \
|
||||
YAK_DEV_CONTEXT="$DEV_CTX" YAK_DEV_CLUSTER="$DEV_CLUSTER" YAK_DEV_NAME="$DEV_NAME" \
|
||||
YAK_DEV_RUNTIME="${rt:-docker}" YAK_DEV_HTTP_PORT="$DEV_HTTP_PORT" \
|
||||
YAK_REG_NAME="$(_local_reg_name)" TAG="${tag:-${TAG:-dev}}" python3 "$(_engine dev)" "$pysub" yakcloud.yaml
|
||||
[ "$sub" = deploy ] && _dev_register || true
|
||||
;;
|
||||
@ -492,23 +534,35 @@ cmd_dev() {
|
||||
local rt kenv=""; rt="$(_dev_runtime)"; [ "$rt" = podman ] && kenv="KIND_EXPERIMENTAL_PROVIDER=podman"
|
||||
info "kind 클러스터 '$DEV_CLUSTER' 삭제…"
|
||||
env $kenv kind delete cluster --name "$DEV_CLUSTER" && echo "✓ 삭제 완료"
|
||||
# 공유 데이터 소스 컨테이너(yak-dev-*)도 정리(머신 레벨 전체 teardown).
|
||||
local scs; scs="$("${rt:-docker}" ps -aq -f label=yakcloud.dev/shared=1 2>/dev/null)"
|
||||
[ -n "$scs" ] && { info "공유 소스 컨테이너 정리…"; echo "$scs" | xargs "${rt:-docker}" rm -f >/dev/null 2>&1; }
|
||||
# 릴레이 에이전트 데몬(모든 프로젝트) 정리 — kind 삭제되면 폴링해도 무의미.
|
||||
# 이 클러스터의 릴레이 에이전트만 정리(pidfile <name>-<ns>.pid). 다른 dev 클러스터 것은 건드리지 않음.
|
||||
local agd="${YAK_CONFIG_DIR:-$HOME/.config/yakcloud}/agent"
|
||||
if [ -d "$agd" ]; then
|
||||
for pf in "$agd"/*.pid; do
|
||||
for pf in "$agd"/"$DEV_NAME"-*.pid; do
|
||||
[ -f "$pf" ] || continue
|
||||
local apid; apid="$(cat "$pf" 2>/dev/null)"
|
||||
[ -n "$apid" ] && kill "$apid" 2>/dev/null
|
||||
rm -f "$pf"
|
||||
done
|
||||
info "릴레이 에이전트 정리"
|
||||
info "릴레이 에이전트 정리($DEV_NAME)"
|
||||
fi
|
||||
# 공유 소스 컨테이너(yak-dev-*)는 여러 클러스터가 공유 → '마지막' dev 클러스터 삭제 시에만 정리.
|
||||
if env $kenv kind get clusters 2>/dev/null | grep -q '^yak-'; then
|
||||
info "다른 로컬 dev 클러스터가 남아 공유 소스 컨테이너는 유지"
|
||||
else
|
||||
local scs; scs="$("${rt:-docker}" ps -aq -f label=yakcloud.dev/shared=1 2>/dev/null)"
|
||||
[ -n "$scs" ] && { info "공유 소스 컨테이너 정리(마지막 dev 클러스터)…"; echo "$scs" | xargs "${rt:-docker}" rm -f >/dev/null 2>&1; }
|
||||
# 마지막 클러스터 → 남은 모든 에이전트 pidfile 정리(구 <ns>.pid 명명 포함 — 전부 고아).
|
||||
if [ -d "$agd" ]; then
|
||||
for pf in "$agd"/*.pid; do
|
||||
[ -f "$pf" ] || continue
|
||||
local lp; lp="$(cat "$pf" 2>/dev/null)"; [ -n "$lp" ] && kill "$lp" 2>/dev/null
|
||||
rm -f "$pf"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
_dev_deregister
|
||||
;;
|
||||
*) echo "yakcloud dev <up|deploy [tag]|status|clean|down>"; exit 1 ;;
|
||||
*) echo "yakcloud dev <up [--name N --port P] | deploy [--name N] [tag] | status [--name N] | clean | down [--name N]>"; exit 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user