feat(dev): 로컬 dev 소스를 공유 도커 컨테이너+격리 DB로 재작성(클러스터=비즈니스만)
로컬 kind 개발에서 데이터 소스를 타입별 공유 도커 컨테이너(yak-dev-*)로 올리고 프로젝트-소스별 격리 DB·계정(<project>_<source>)을 민팅한다. 클러스터에는 no-selector Service + 수동 Endpoints 로 kind 밖 컨테이너를 가리켜 앱만 배포한다. 관리형 shared 모드와 동일한 바인딩 env 파리티 유지. dev down 시 공유 컨테이너 정리. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
241
.yakcloud/dev.py
241
.yakcloud/dev.py
@ -24,8 +24,10 @@ import base64
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import secrets
|
import secrets
|
||||||
|
import hashlib
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
@ -40,14 +42,16 @@ RUNTIME = os.environ.get("YAK_DEV_RUNTIME", "docker")
|
|||||||
HOST_SUFFIX = os.environ.get("YAK_DEV_HOST_SUFFIX", "dev.localhost")
|
HOST_SUFFIX = os.environ.get("YAK_DEV_HOST_SUFFIX", "dev.localhost")
|
||||||
TAG = os.environ.get("TAG", "dev")
|
TAG = os.environ.get("TAG", "dev")
|
||||||
|
|
||||||
# 소스 타입별 로컬 컨테이너 스펙 — 관리형과 동일한 인클러스터 시크릿 키(_bind_env_for 입력)를 세팅.
|
# 로컬 데이터 소스 = kind '밖'의 **공유** 도커/포드만 컨테이너(타입별 1개) + 프로젝트별 격리 DB.
|
||||||
# port = 백엔드 _SVC_PORT 와 동일. img = 공개 오피셜 이미지(로컬 dev 는 파리티보다 기동성 우선).
|
# 관리형 shared 모델과 동일 계약 — 이미 있는 공유 서버에 <project>_<src> 격리 DB/유저만 민팅(소스 생성비 최소).
|
||||||
# 유저명은 백엔드와 동일하게 고정 'appuser'(소스명 파생 금지 — 'root' 등 예약어 충돌·환경 간 값 발산 방지).
|
# 클러스터(kind)는 앱(비즈니스)만. 앱은 no-selector Service+Endpoints(host=소스명)로 공유 컨테이너의 격리 DB 접속.
|
||||||
APP_USER = "appuser"
|
_SVC_PORT = {"POSTGRESQL": 5432, "MYSQL": 3306, "MARIADB": 3306, "MONGODB": 27017, "REDIS": 6379}
|
||||||
_SVC_PORT = {"POSTGRESQL": 5432, "MYSQL": 3306, "MARIADB": 3306,
|
|
||||||
"MONGODB": 27017, "REDIS": 6379}
|
|
||||||
_IMG = {"POSTGRESQL": "postgres:16-alpine", "MYSQL": "mysql:8", "MARIADB": "mariadb:11",
|
_IMG = {"POSTGRESQL": "postgres:16-alpine", "MYSQL": "mysql:8", "MARIADB": "mariadb:11",
|
||||||
"MONGODB": "mongo:7", "REDIS": "redis:7-alpine"}
|
"MONGODB": "mongo:7", "REDIS": "redis:7-alpine"}
|
||||||
|
_SHARED_CT = {"POSTGRESQL": "yak-dev-postgres", "MYSQL": "yak-dev-mysql", "MARIADB": "yak-dev-mariadb",
|
||||||
|
"MONGODB": "yak-dev-mongo", "REDIS": "yak-dev-redis"}
|
||||||
|
SHARED_NET = os.environ.get("YAK_DEV_NET", "kind") # kind 가 쓰는 도커 네트워크
|
||||||
|
SHARED_ADMIN = "yakdevadmin" # dev 전용 공유 컨테이너 관리자 비밀번호(로컬 한정)
|
||||||
SUPPORTED = set(_SVC_PORT)
|
SUPPORTED = set(_SVC_PORT)
|
||||||
MANAGED = "app.kubernetes.io/managed-by=yakcloud-dev"
|
MANAGED = "app.kubernetes.io/managed-by=yakcloud-dev"
|
||||||
|
|
||||||
@ -109,128 +113,129 @@ def ensure_ns(ns: str) -> None:
|
|||||||
|
|
||||||
# ── 소스 자격: 최초 생성 시 랜덤(유저=고정 appuser, db=소스별), 이후 재사용(idempotent) ──────
|
# ── 소스 자격: 최초 생성 시 랜덤(유저=고정 appuser, db=소스별), 이후 재사용(idempotent) ──────
|
||||||
def get_or_make_creds(ns: str, name: str) -> dict:
|
def get_or_make_creds(ns: str, name: str) -> dict:
|
||||||
|
"""프로젝트-소스별 비밀번호 — 기존 Secret 이 있으면 재사용(멱등), 없으면 랜덤 생성."""
|
||||||
sec = f"yak-dev-src-{sanitize(name)}"
|
sec = f"yak-dev-src-{sanitize(name)}"
|
||||||
r = kubectl(["get", "secret", sec, "-n", ns, "-o", "json"], check=False)
|
r = kubectl(["get", "secret", sec, "-n", ns, "-o", "json"], check=False)
|
||||||
if r.returncode == 0:
|
if r.returncode == 0:
|
||||||
data = json.loads(r.stdout).get("data", {})
|
pw = (json.loads(r.stdout).get("data", {}) or {}).get("password")
|
||||||
c = {k: base64.b64decode(v).decode() for k, v in data.items()}
|
if pw:
|
||||||
c["secret"] = sec # 재사용 경로도 secret 이름을 포함(멱등 재배포 시 KeyError 방지)
|
return {"password": base64.b64decode(pw).decode(), "secret": sec}
|
||||||
return c
|
return {"password": secrets.token_hex(16), "secret": sec}
|
||||||
return {"user": APP_USER, "password": secrets.token_hex(16),
|
|
||||||
"db": sanitize(name)[:24] or "app", "secret": sec}
|
|
||||||
|
|
||||||
|
|
||||||
def _source_container(stype: str, c: dict) -> dict:
|
def _rt(args: list[str], **kw) -> subprocess.CompletedProcess:
|
||||||
"""소스 타입별 컨테이너 정의 + 인클러스터 시크릿 키(관리형과 동일) + '앱-유저 인증' readiness.
|
return sh([RUNTIME, *args], **kw)
|
||||||
|
|
||||||
readiness 를 tcp 가 아니라 **앱 유저 로그인 exec** 으로 게이트한다 — 초기화(유저/DB 생성) 중
|
|
||||||
포트만 열린 상태를 'Ready' 로 오판해 workload 가 먼저 붙어 인증 실패로 크래시하는 것을 막는다."""
|
def _dexec(cname: str, shell_cmd: str, check: bool = True) -> subprocess.CompletedProcess:
|
||||||
port = _SVC_PORT[stype]
|
return _rt(["exec", cname, "sh", "-c", shell_cmd], check=check)
|
||||||
rootpw = c["password"] + "root"
|
|
||||||
env: list[dict] = []
|
|
||||||
args: list[str] | None = None
|
def _dbname(project: str, source: str) -> str:
|
||||||
init_cm: dict | None = None
|
"""프로젝트별 격리 단위(DB·유저)명 — DB 안전(소문자·영숫자·언더스코어)."""
|
||||||
vol_mounts: list[dict] = []
|
raw = f"{project}_{source}".lower()
|
||||||
|
out = "".join(c if (c.isalnum() or c == "_") else "_" for c in raw).strip("_")
|
||||||
|
return (out or "app")[:40]
|
||||||
|
|
||||||
|
|
||||||
|
def _container_ip(cname: str) -> str:
|
||||||
|
r = _rt(["inspect", "-f",
|
||||||
|
'{{(index .NetworkSettings.Networks "' + SHARED_NET + '").IPAddress}}', cname], check=False)
|
||||||
|
return (r.stdout or "").strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_shared(stype: str) -> tuple[str, str]:
|
||||||
|
"""타입별 '공유' 컨테이너를 kind 네트워크에 보장(idempotent, 관리자 자격) → (이름, IP)."""
|
||||||
|
cname = _SHARED_CT[stype]
|
||||||
|
if not _rt(["ps", "-q", "-f", f"name=^{cname}$"], check=False).stdout.strip():
|
||||||
|
_rt(["rm", "-f", cname], check=False) # 죽은 동명 컨테이너 정리
|
||||||
|
img = _IMG[stype]
|
||||||
|
run = ["run", "-d", "--name", cname, "--network", SHARED_NET,
|
||||||
|
"--label", "yakcloud.dev/shared=1", "--label", f"yakcloud.dev/type={stype}"]
|
||||||
|
if stype == "POSTGRESQL":
|
||||||
|
run += ["-e", f"POSTGRES_PASSWORD={SHARED_ADMIN}", img]
|
||||||
|
ready = "pg_isready -U postgres >/dev/null 2>&1"
|
||||||
|
elif stype == "MYSQL":
|
||||||
|
run += ["-e", f"MYSQL_ROOT_PASSWORD={SHARED_ADMIN}", img]
|
||||||
|
ready = f'mysql -uroot -p{SHARED_ADMIN} -e "SELECT 1" >/dev/null 2>&1'
|
||||||
|
elif stype == "MARIADB":
|
||||||
|
run += ["-e", f"MARIADB_ROOT_PASSWORD={SHARED_ADMIN}", img]
|
||||||
|
ready = f'mariadb -uroot -p{SHARED_ADMIN} -e "SELECT 1" >/dev/null 2>&1'
|
||||||
|
elif stype == "MONGODB":
|
||||||
|
run += ["-e", "MONGO_INITDB_ROOT_USERNAME=root", "-e", f"MONGO_INITDB_ROOT_PASSWORD={SHARED_ADMIN}", img]
|
||||||
|
ready = (f'mongosh "mongodb://root:{SHARED_ADMIN}@127.0.0.1:27017/admin" --quiet '
|
||||||
|
'--eval "db.runCommand({ping:1}).ok" | grep -q 1')
|
||||||
|
else: # REDIS
|
||||||
|
run += [img, "redis-server", "--requirepass", SHARED_ADMIN]
|
||||||
|
ready = f'redis-cli -a {SHARED_ADMIN} ping 2>/dev/null | grep -q PONG'
|
||||||
|
log(f"공유 소스 컨테이너 '{cname}' ({stype}) 기동 ({RUNTIME}, net={SHARED_NET})")
|
||||||
|
_rt(run, capture=False)
|
||||||
|
for _ in range(90):
|
||||||
|
if _dexec(cname, ready, check=False).returncode == 0:
|
||||||
|
break
|
||||||
|
time.sleep(2)
|
||||||
|
else:
|
||||||
|
die(f"공유 컨테이너 '{cname}' 준비 대기 초과 — '{RUNTIME} logs {cname}' 확인")
|
||||||
|
ip = _container_ip(cname)
|
||||||
|
if not ip:
|
||||||
|
die(f"공유 컨테이너 '{cname}' IP 조회 실패 — 도커 네트워크 '{SHARED_NET}' 확인")
|
||||||
|
return cname, ip
|
||||||
|
|
||||||
|
|
||||||
|
def _mint(stype: str, cname: str, unit: str, pw: str) -> None:
|
||||||
|
"""공유 컨테이너에 프로젝트별 격리 DB/유저 생성(idempotent). unit=DB·유저명(영숫자·언더스코어)."""
|
||||||
if stype == "POSTGRESQL":
|
if stype == "POSTGRESQL":
|
||||||
env = [{"name": "POSTGRES_USER", "value": c["user"]},
|
_dexec(cname, f'psql -U postgres -tAc "SELECT 1 FROM pg_roles WHERE rolname=\'{unit}\'" | grep -q 1 '
|
||||||
{"name": "POSTGRES_PASSWORD", "value": c["password"]},
|
f'|| psql -U postgres -c "CREATE ROLE {unit} LOGIN PASSWORD \'{pw}\'"')
|
||||||
{"name": "POSTGRES_DB", "value": c["db"]},
|
_dexec(cname, f'psql -U postgres -tAc "SELECT 1 FROM pg_database WHERE datname=\'{unit}\'" | grep -q 1 '
|
||||||
{"name": "PGDATA", "value": "/var/lib/postgresql/data/pgdata"}]
|
f'|| psql -U postgres -c "CREATE DATABASE {unit} OWNER {unit}"')
|
||||||
probe = ["sh", "-c", 'PGPASSWORD="$POSTGRES_PASSWORD" psql -h 127.0.0.1 -U "$POSTGRES_USER" '
|
elif stype in ("MYSQL", "MARIADB"):
|
||||||
'-d "$POSTGRES_DB" -c "SELECT 1" >/dev/null 2>&1']
|
client = "mysql" if stype == "MYSQL" else "mariadb"
|
||||||
elif stype == "MYSQL":
|
sql = (f"CREATE DATABASE IF NOT EXISTS {unit}; "
|
||||||
env = [{"name": "MYSQL_USER", "value": c["user"]},
|
f"CREATE USER IF NOT EXISTS '{unit}'@'%' IDENTIFIED BY '{pw}'; "
|
||||||
{"name": "MYSQL_PASSWORD", "value": c["password"]},
|
f"GRANT ALL ON {unit}.* TO '{unit}'@'%'; FLUSH PRIVILEGES;")
|
||||||
{"name": "MYSQL_DATABASE", "value": c["db"]},
|
_dexec(cname, f'{client} -uroot -p{SHARED_ADMIN} -e "{sql}"')
|
||||||
{"name": "MYSQL_ROOT_PASSWORD", "value": rootpw}]
|
|
||||||
probe = ["sh", "-c", 'mysql -h127.0.0.1 -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" '
|
|
||||||
'"$MYSQL_DATABASE" -e "SELECT 1" >/dev/null 2>&1']
|
|
||||||
elif stype == "MARIADB":
|
|
||||||
env = [{"name": "MARIADB_USER", "value": c["user"]},
|
|
||||||
{"name": "MARIADB_PASSWORD", "value": c["password"]},
|
|
||||||
{"name": "MARIADB_DATABASE", "value": c["db"]},
|
|
||||||
{"name": "MARIADB_ROOT_PASSWORD", "value": rootpw}]
|
|
||||||
probe = ["sh", "-c", 'mariadb -h127.0.0.1 -u"$MARIADB_USER" -p"$MARIADB_PASSWORD" '
|
|
||||||
'"$MARIADB_DATABASE" -e "SELECT 1" >/dev/null 2>&1']
|
|
||||||
elif stype == "REDIS":
|
|
||||||
args = ["redis-server", "--requirepass", c["password"]]
|
|
||||||
env = [{"name": "REDISCLI_AUTH", "value": c["password"]}]
|
|
||||||
probe = ["sh", "-c", 'redis-cli ping | grep -q PONG']
|
|
||||||
elif stype == "MONGODB":
|
elif stype == "MONGODB":
|
||||||
# 루트 유저(root) + 앱 db 에 앱 유저(appuser) 생성(authSource=db 파리티) via init.js
|
js = (f'try{{db.getSiblingDB("{unit}").createUser({{user:"{unit}",pwd:"{pw}",'
|
||||||
env = [{"name": "MONGO_INITDB_ROOT_USERNAME", "value": "root"},
|
f'roles:[{{role:"readWrite",db:"{unit}"}}]}})}}catch(e){{if(!/already exists/i.test(e.message))throw e}}')
|
||||||
{"name": "MONGO_INITDB_ROOT_PASSWORD", "value": rootpw},
|
_dexec(cname, f'mongosh "mongodb://root:{SHARED_ADMIN}@127.0.0.1:27017/admin" --quiet --eval \'{js}\'')
|
||||||
{"name": "MONGO_INITDB_DATABASE", "value": c["db"]},
|
|
||||||
{"name": "MONGO_APP_USER", "value": c["user"]},
|
|
||||||
{"name": "MONGO_APP_PASSWORD", "value": c["password"]},
|
|
||||||
{"name": "MONGO_APP_DB", "value": c["db"]}]
|
|
||||||
js = (f'db.getSiblingDB("{c["db"]}").createUser({{user:"{c["user"]}",'
|
|
||||||
f'pwd:"{c["password"]}",roles:[{{role:"readWrite",db:"{c["db"]}"}}]}});')
|
|
||||||
init_cm = {"js": js}
|
|
||||||
vol_mounts = [{"name": "initdb", "mountPath": "/docker-entrypoint-initdb.d"}]
|
|
||||||
probe = ["sh", "-c", 'mongosh "mongodb://$MONGO_APP_USER:$MONGO_APP_PASSWORD@127.0.0.1:'
|
|
||||||
f'{port}/$MONGO_APP_DB?authSource=$MONGO_APP_DB" --quiet '
|
|
||||||
'--eval "db.runCommand({ping:1}).ok" | grep -q 1']
|
|
||||||
else: # pragma: no cover
|
|
||||||
die(f"내부오류: 미지원 타입 {stype}")
|
|
||||||
cont = {"name": "db", "image": _IMG[stype], "imagePullPolicy": "IfNotPresent",
|
|
||||||
"ports": [{"containerPort": port}], "env": env,
|
|
||||||
"readinessProbe": {"exec": {"command": probe}, "initialDelaySeconds": 5,
|
|
||||||
"periodSeconds": 5, "failureThreshold": 40, "timeoutSeconds": 5},
|
|
||||||
"volumeMounts": vol_mounts}
|
|
||||||
if args:
|
|
||||||
cont["args"] = args
|
|
||||||
return {"container": cont, "port": port, "init_cm": init_cm, "vol_mounts": vol_mounts}
|
|
||||||
|
|
||||||
|
|
||||||
def provision_source(ns: str, req: dict) -> dict | None:
|
def provision_source(ns: str, project: str, req: dict) -> dict | None:
|
||||||
"""requires 소스를 로컬 컨테이너로 프로비저닝(idempotent) → 접속정보 반환."""
|
"""requires 소스 → 공유 도커 컨테이너의 프로젝트별 격리 DB + no-selector Service/Endpoints. 접속정보 반환."""
|
||||||
name = req["name"]
|
name = req["name"]
|
||||||
stype = req["type"].upper()
|
stype = req["type"].upper()
|
||||||
if req.get("mode") == "shared":
|
if req.get("mode") == "local":
|
||||||
warn(f"source '{name}': mode=shared 는 로컬 dev 에서 일반 로컬 컨테이너로 대체(격리 DB 민팅 없음).")
|
warn(f"source '{name}': mode=local(인클러스터)는 로컬 dev 에서 공유 컨테이너로 대체됩니다.")
|
||||||
if stype not in SUPPORTED:
|
if stype not in SUPPORTED:
|
||||||
die(f"source '{name}' 타입 {stype} — 로컬 dev 미지원(지원: {', '.join(sorted(SUPPORTED))}). "
|
die(f"source '{name}' 타입 {stype} — 로컬 dev 미지원(지원: {', '.join(sorted(SUPPORTED))}).")
|
||||||
f"관리형(deploy/promote)에서는 지원됩니다.")
|
|
||||||
dep = sanitize(f"src-{name}")
|
|
||||||
svc = sanitize(name)
|
svc = sanitize(name)
|
||||||
creds = get_or_make_creds(ns, name)
|
unit = _dbname(project, name)
|
||||||
spec = _source_container(stype, creds)
|
port = _SVC_PORT[stype]
|
||||||
labels = {"app": dep, "app.kubernetes.io/managed-by": "yakcloud-dev",
|
pw = get_or_make_creds(ns, name)["password"] # 프로젝트-소스별 비밀번호(Secret 재사용=멱등)
|
||||||
"yakcloud.dev/role": "source", "yakcloud.dev/source": svc}
|
cname, ip = _ensure_shared(stype)
|
||||||
objs: list[dict] = [{
|
if stype == "REDIS":
|
||||||
"apiVersion": "v1", "kind": "Secret",
|
dbnum = str(int(hashlib.sha1(unit.encode()).hexdigest(), 16) % 16) # DB 번호 격리(0~15)
|
||||||
"metadata": {"name": creds["secret"], "namespace": ns, "labels": labels},
|
conn = {"stype": stype, "host": svc, "port": port, "user": "", "password": SHARED_ADMIN, "db": dbnum}
|
||||||
"stringData": {"user": creds["user"], "password": creds["password"], "db": creds["db"]},
|
log(f"source '{name}' (REDIS) → 공유 '{cname}' · DB#{dbnum} · 서비스 {svc}:{port}")
|
||||||
}]
|
else:
|
||||||
volumes: list[dict] = []
|
_mint(stype, cname, unit, pw)
|
||||||
if spec["init_cm"]:
|
conn = {"stype": stype, "host": svc, "port": port, "user": unit, "password": pw, "db": unit}
|
||||||
cm = f"{dep}-init"
|
log(f"source '{name}' ({stype}) → 공유 '{cname}' · 격리 DB '{unit}' · 서비스 {svc}:{port}")
|
||||||
objs.append({"apiVersion": "v1", "kind": "ConfigMap",
|
labels = {"app.kubernetes.io/managed-by": "yakcloud-dev", "yakcloud.dev/role": "source", "yakcloud.dev/source": svc}
|
||||||
"metadata": {"name": cm, "namespace": ns, "labels": labels},
|
# no-selector Service + 수동 Endpoints → kind 밖 공유 컨테이너 IP 로 라우팅.
|
||||||
"data": {"init.js": spec["init_cm"]["js"]}})
|
apply([
|
||||||
volumes.append({"name": "initdb", "configMap": {"name": cm}})
|
{"apiVersion": "v1", "kind": "Secret",
|
||||||
objs.append({
|
"metadata": {"name": f"yak-dev-src-{svc}", "namespace": ns, "labels": labels},
|
||||||
"apiVersion": "apps/v1", "kind": "Deployment",
|
"stringData": {"password": pw, "unit": unit}},
|
||||||
"metadata": {"name": dep, "namespace": ns, "labels": labels},
|
{"apiVersion": "v1", "kind": "Service",
|
||||||
"spec": {"replicas": 1, "selector": {"matchLabels": {"app": dep}},
|
"metadata": {"name": svc, "namespace": ns, "labels": labels},
|
||||||
"strategy": {"type": "Recreate"},
|
"spec": {"ports": [{"port": port, "targetPort": port}]}},
|
||||||
"template": {"metadata": {"labels": labels},
|
{"apiVersion": "v1", "kind": "Endpoints",
|
||||||
"spec": {"containers": [spec["container"]], "volumes": volumes}}},
|
"metadata": {"name": svc, "namespace": ns, "labels": labels},
|
||||||
})
|
"subsets": [{"addresses": [{"ip": ip}], "ports": [{"port": port}]}]},
|
||||||
objs.append({
|
])
|
||||||
"apiVersion": "v1", "kind": "Service",
|
return conn
|
||||||
"metadata": {"name": svc, "namespace": ns, "labels": labels},
|
|
||||||
"spec": {"selector": {"app": dep},
|
|
||||||
"ports": [{"port": spec["port"], "targetPort": spec["port"]}]},
|
|
||||||
})
|
|
||||||
log(f"source '{name}' ({stype}) 프로비저닝 → 서비스 {svc}:{spec['port']}")
|
|
||||||
apply(objs)
|
|
||||||
r = kubectl(["rollout", "status", f"deployment/{dep}", "-n", ns, "--timeout=300s"], check=False)
|
|
||||||
if r.returncode != 0:
|
|
||||||
die(f"source '{name}' 기동/인증 준비 대기 실패 — 'kubectl --context {CTX} -n {ns} get pods' 확인")
|
|
||||||
return {"stype": stype, "host": svc, "port": spec["port"],
|
|
||||||
"user": creds["user"], "password": creds["password"], "db": creds["db"]}
|
|
||||||
|
|
||||||
|
|
||||||
# ── 바인딩 env: 백엔드 _bind_env_for 와 동일(하이픈 alias → 언더스코어 프리픽스 포함) ────────
|
# ── 바인딩 env: 백엔드 _bind_env_for 와 동일(하이픈 alias → 언더스코어 프리픽스 포함) ────────
|
||||||
@ -245,8 +250,8 @@ def bind_env(alias: str, conn: dict) -> dict:
|
|||||||
out.update({f"{p}_USERNAME": user, f"{p}_PASSWORD": pw, f"{p}_DB": db,
|
out.update({f"{p}_USERNAME": user, f"{p}_PASSWORD": pw, f"{p}_DB": db,
|
||||||
f"{p}_URL": f"mongodb://{qu}:{qp}@{host}:{port}/{db}?authSource={db}"})
|
f"{p}_URL": f"mongodb://{qu}:{qp}@{host}:{port}/{db}?authSource={db}"})
|
||||||
elif st == "REDIS":
|
elif st == "REDIS":
|
||||||
out.update({f"{p}_USERNAME": "", f"{p}_PASSWORD": pw, f"{p}_DB": "0",
|
out.update({f"{p}_USERNAME": "", f"{p}_PASSWORD": pw, f"{p}_DB": db,
|
||||||
f"{p}_URL": f"redis://:{qp}@{host}:{port}/0"})
|
f"{p}_URL": f"redis://:{qp}@{host}:{port}/{db}"})
|
||||||
elif st == "MYSQL":
|
elif st == "MYSQL":
|
||||||
out.update({f"{p}_USERNAME": user, f"{p}_PASSWORD": pw, f"{p}_DB": db,
|
out.update({f"{p}_USERNAME": user, f"{p}_PASSWORD": pw, f"{p}_DB": db,
|
||||||
f"{p}_URL": f"mysql://{qu}:{qp}@{host}:{port}/{db}"})
|
f"{p}_URL": f"mysql://{qu}:{qp}@{host}:{port}/{db}"})
|
||||||
@ -338,7 +343,7 @@ def deploy_workload(project: str, ns: str, w: dict, src_conns: dict) -> tuple[st
|
|||||||
|
|
||||||
# ── 고아 정리: 매니페스트에 없는(라벨된) 소스/워크로드 리소스 삭제(직선 리컨실) ──────────────
|
# ── 고아 정리: 매니페스트에 없는(라벨된) 소스/워크로드 리소스 삭제(직선 리컨실) ──────────────
|
||||||
def _prune(ns: str, role: str, label_key: str, desired: set) -> None:
|
def _prune(ns: str, role: str, label_key: str, desired: set) -> None:
|
||||||
r = kubectl(["get", "deployment,service,secret,configmap,ingress", "-n", ns,
|
r = kubectl(["get", "deployment,service,endpoints,secret,configmap,ingress", "-n", ns,
|
||||||
"-l", f"{MANAGED},yakcloud.dev/role={role}", "-o", "json"], check=False)
|
"-l", f"{MANAGED},yakcloud.dev/role={role}", "-o", "json"], check=False)
|
||||||
if r.returncode != 0:
|
if r.returncode != 0:
|
||||||
return
|
return
|
||||||
@ -405,7 +410,7 @@ def cmd_deploy(m: dict) -> None:
|
|||||||
ensure_ns(ns)
|
ensure_ns(ns)
|
||||||
src_conns: dict[str, dict] = {}
|
src_conns: dict[str, dict] = {}
|
||||||
for req in reqs:
|
for req in reqs:
|
||||||
conn = provision_source(ns, req)
|
conn = provision_source(ns, project, req)
|
||||||
if conn:
|
if conn:
|
||||||
src_conns[req["name"]] = conn
|
src_conns[req["name"]] = conn
|
||||||
results = [deploy_workload(project, ns, w, src_conns) for w in wls]
|
results = [deploy_workload(project, ns, w, src_conns) for w in wls]
|
||||||
|
|||||||
@ -492,6 +492,9 @@ cmd_dev() {
|
|||||||
local rt kenv=""; rt="$(_dev_runtime)"; [ "$rt" = podman ] && kenv="KIND_EXPERIMENTAL_PROVIDER=podman"
|
local rt kenv=""; rt="$(_dev_runtime)"; [ "$rt" = podman ] && kenv="KIND_EXPERIMENTAL_PROVIDER=podman"
|
||||||
info "kind 클러스터 '$DEV_CLUSTER' 삭제…"
|
info "kind 클러스터 '$DEV_CLUSTER' 삭제…"
|
||||||
env $kenv kind delete cluster --name "$DEV_CLUSTER" && echo "✓ 삭제 완료"
|
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; }
|
||||||
_dev_deregister
|
_dev_deregister
|
||||||
;;
|
;;
|
||||||
*) echo "yakcloud dev <up|deploy [tag]|status|clean|down>"; exit 1 ;;
|
*) echo "yakcloud dev <up|deploy [tag]|status|clean|down>"; exit 1 ;;
|
||||||
|
|||||||
Reference in New Issue
Block a user