fix: promote 실패 시 거짓 성공 방지 + 기본 소스 이름 프로젝트 고유화(공용 클러스터 충돌 예방) (v0.29.0)

- bin/yakcloud: promote 의 리컨실(deploy.py) 실패면 '✓ 승격 완료' 대신 die(운영 승인 대기 403 안내 포함).
  set -e 미사용이라 이전엔 배포 차단돼도 성공으로 표시되던 버그.
- yakcloud.yaml: 기본 소스 이름 appdb→my-app-db(init 이 <project>-db 로 치환). 공용 클러스터에서
  리컨실이 동명 기존 소스를 재사용·바인딩 → 남의 DB 에 테이블 만드는 사고 예방.
- deploy.py: 기존 소스 재사용 시 로그를 ⚠ 로 강화(공유 위험·고유 이름 권고 명시).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jungwoo choi
2026-08-31 15:01:26 +09:00
parent 63021f63b2
commit 6ffc6425bb
3 changed files with 14 additions and 6 deletions

View File

@ -109,7 +109,10 @@ def reconcile_source(req: dict) -> str | None:
if cur and cur != mode: if cur and cur != mode:
log(f"⚠ source '{name}' 은 이미 mode={cur} 로 존재 → 매니페스트 mode={mode} 는 무시(재프로비저닝 안 함). " log(f"⚠ source '{name}' 은 이미 mode={cur} 로 존재 → 매니페스트 mode={mode} 는 무시(재프로비저닝 안 함). "
f"바꾸려면 삭제 후 재배포: 'yakcloud source rm {name}'.") f"바꾸려면 삭제 후 재배포: 'yakcloud source rm {name}'.")
log(f"source '{name}' ({stype}) 이미 READY → 스킵 (id={match['id']})") log(f"source '{name}' ({stype}) 이미 READY → 재사용·바인딩 (id={match['id']}). "
f"본인 프로젝트가 만든 소스면 정상이나, 공용 클러스터라면 '다른 프로젝트의 소스'일 수 있음 "
f"— 이 앱의 마이그레이션이 그 소스(DB)에 테이블을 만든다. 전용이 필요하면 매니페스트에서 "
f"프로젝트 고유 이름(예: <project>-db)으로 바꿔 재배포하라.")
return match["id"] return match["id"]
if DRY: if DRY:
if match: if match:

View File

@ -16,7 +16,7 @@ set -uo pipefail # -e 미사용: 'test && action' 관용구가 값 없을 때
REPO="${YAKCLOUD_STARTER_REPO:-https://gitea.yakenator.io/yakenator/yakcloud-starter}" REPO="${YAKCLOUD_STARTER_REPO:-https://gitea.yakenator.io/yakenator/yakcloud-starter}"
BRANCH="${YAKCLOUD_STARTER_BRANCH:-main}" BRANCH="${YAKCLOUD_STARTER_BRANCH:-main}"
VERSION="0.28.0" VERSION="0.29.0"
# 로컬 개발 클러스터(kind) — 개발 후 운영까지 '직선 배포'의 dev 구간. # 로컬 개발 클러스터(kind) — 개발 후 운영까지 '직선 배포'의 dev 구간.
DEV_CTX="${YAK_DEV_CONTEXT:-kind-yak-dev}" DEV_CTX="${YAK_DEV_CONTEXT:-kind-yak-dev}"
DEV_CLUSTER="${YAK_DEV_CLUSTER:-yak-dev}" DEV_CLUSTER="${YAK_DEV_CLUSTER:-yak-dev}"
@ -470,7 +470,9 @@ cmd_project_promote() {
[ "$env" = prod ] && _require_dev_confirm "$tag" [ "$env" = prod ] && _require_dev_confirm "$tag"
info "'$env' 승격: $tag → 관리형 '$env' 클러스터(이미지 build+push 후 리컨실; 이미 있으면 재빌드 없이 그 이미지)" info "'$env' 승격: $tag → 관리형 '$env' 클러스터(이미지 build+push 후 리컨실; 이미 있으면 재빌드 없이 그 이미지)"
_build_and_push "$tag" _build_and_push "$tag"
YAKCLOUD_ENV="$env" TAG="$tag" python3 "$(_engine deploy)" yakcloud.yaml # 리컨실 실패(운영 승인 대기 403 등)면 절대 '성공'이라 하지 않는다 — set -e 미사용이라 명시적 || die.
YAKCLOUD_ENV="$env" TAG="$tag" python3 "$(_engine deploy)" yakcloud.yaml \
|| die "승격 실패 — 위 오류를 확인하세요. '운영 승격 승인 필요(403)' 면 콘솔 → 배포 탭 → '운영 승격 승인'에서 $tag 을 승인한 뒤 다시 'yakcloud project promote --to $env $tag'."
echo "✓ 승격 완료: $tag → '$env' 클러스터 + 도메인(기본 도메인은 자동)" echo "✓ 승격 완료: $tag → '$env' 클러스터 + 도메인(기본 도메인은 자동)"
} }

View File

@ -35,9 +35,12 @@ environments:
# REDIS_URL 로 전권(pub/sub 포함). 프리픽스-인지 앱이면 `mode: shared` 로 공유 사용 가능. # REDIS_URL 로 전권(pub/sub 포함). 프리픽스-인지 앱이면 `mode: shared` 로 공유 사용 가능.
# - 이름이 클러스터의 기존 READY 소스와 일치하면 스킵·바인딩. ('yakcloud project check' 로 미리 확인) # - 이름이 클러스터의 기존 READY 소스와 일치하면 스킵·바인딩. ('yakcloud project check' 로 미리 확인)
# (기존 소스의 mode 를 바꾸려면 삭제 후 재배포 — 리컨실은 재프로비저닝하지 않고 경고만 함) # (기존 소스의 mode 를 바꾸려면 삭제 후 재배포 — 리컨실은 재프로비저닝하지 않고 경고만 함)
# ⚠ 소스 이름은 '프로젝트 고유'로 두라(my-app-db → init 이 프로젝트명으로 치환). 공용 클러스터에선
# 리컨실이 '같은 이름의 기존 READY 소스'를 재사용·바인딩하므로, appdb 같은 일반 이름은 다른 프로젝트의
# DB 에 붙어 그 DB 에 테이블을 만드는 사고가 난다. 고유 이름이면 각 프로젝트가 자기 소스를 갖는다.
requires: requires:
- { name: appdb, type: postgresql } # 기본 shared. 전용 인스턴스는 { …, mode: local, plan: small } - { name: my-app-db, type: postgresql } # 기본 shared. 전용 인스턴스는 { …, mode: local, plan: small }
# - { name: cache, type: redis } # redis = 기본 전용(pub/sub 전권). 공유는 { …, mode: shared } # - { name: my-app-cache, type: redis } # redis = 기본 전용(pub/sub 전권). 공유는 { …, mode: shared }
# 워크로드(앱 컨테이너). CI 는 각 workload.build 를 도커 빌드해 image 로 push 한다(${TAG}=릴리스 태그). # 워크로드(앱 컨테이너). CI 는 각 workload.build 를 도커 빌드해 image 로 push 한다(${TAG}=릴리스 태그).
workloads: workloads:
@ -52,4 +55,4 @@ workloads:
expose: { path: /, rewrite: false } expose: { path: /, rewrite: false }
binds: binds:
# source=위 requires 이름, alias=env 프리픽스(대문자화). 예 alias=db → DB_URL/DB_HOST/DB_PORT/… # source=위 requires 이름, alias=env 프리픽스(대문자화). 예 alias=db → DB_URL/DB_HOST/DB_PORT/…
- { alias: db, source: appdb } - { alias: db, source: my-app-db }