feat: 로컬 kind 개발 클러스터(yakcloud dev) — 개발 후 운영까지 직선 배포

yakcloud dev up|deploy|status|clean|down. 로컬 kind 를 dev 구간으로 삼아 관리형과 동일한
바인딩 env 계약을 로컬에서 재현(pg/mysql/mariadb/mongodb/redis). dev 에선 CLI 가 곧 컨트롤플레인
이라 오프라인 반복. 같은 매니페스트·같은 이미지로 promote --to val/prod 직선. VERSION 0.14.0.
문서 동시 갱신(SKILL·/yakcloud 명령·yakcloud.yaml·README·DATA-SOURCES).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-29 10:47:42 +09:00
parent 89fca872c0
commit 7a90cb2be8
7 changed files with 541 additions and 11 deletions

View File

@ -1,5 +1,5 @@
#!/usr/bin/env bash
# yakcloud — YakCloud 프로젝트 CLI. 개발·검증·운영 모두 yakcloud 클러스터(로컬 dev 없음, val 선택).
# yakcloud — YakCloud 프로젝트 CLI. 개발=로컬 kind(직선 dev) 또는 yakcloud 클러스터, 검증·운영=yakcloud 클러스터(val 선택).
#
# yakcloud cluster create <name> [--template S|M] [--nodes N] [--golden] 클러스터 생성(완료까지 대기)
# yakcloud cluster ls 내 클러스터 목록
@ -16,7 +16,11 @@ set -uo pipefail # -e 미사용: 'test && action' 관용구가 값 없을 때
REPO="${YAKCLOUD_STARTER_REPO:-https://gitea.yakenator.io/yakenator/yakcloud-starter}"
BRANCH="${YAKCLOUD_STARTER_BRANCH:-main}"
VERSION="0.13.0"
VERSION="0.14.0"
# 로컬 개발 클러스터(kind) — 개발 후 운영까지 '직선 배포'의 dev 구간.
DEV_CTX="${YAK_DEV_CONTEXT:-kind-yak-dev}"
DEV_CLUSTER="${YAK_DEV_CLUSTER:-yak-dev}"
INGRESS_MANIFEST="${YAK_DEV_INGRESS:-https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.3/deploy/static/provider/kind/deploy.yaml}"
CONFIG_DIR="${YAKCLOUD_CONFIG_DIR:-$HOME/.config/yakcloud}"
CONFIG_FILE="$CONFIG_DIR/config"
@ -381,6 +385,77 @@ PY
esac
}
# ── 로컬 개발 클러스터(kind) — '개발 후 운영까지 직선 배포'의 dev 구간 ─────────────
# dev up 로컬 kind 생성 + 인그레스(80/443) — 최초 1회
# dev deploy 매니페스트대로 이미지 빌드+kind load + 소스/워크로드/인그레스 로컬 리컨실(콘솔 API 불필요)
# dev status 로컬 배포 상태 · dev clean 이 프로젝트 리소스만 삭제 · dev down kind 클러스터 삭제
# 로컬 dev = CLI 로컬 완결(오프라인 반복). 같은 이미지를 push 하면 promote --to val/prod 로 그대로 직선.
_dev_runtime() {
if command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then echo docker; return; fi
if command -v podman >/dev/null 2>&1 && podman info >/dev/null 2>&1; then echo podman; return; fi
echo ""
}
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 필요"
local rt; rt="$(_dev_runtime)"; [ -n "$rt" ] || die "docker 또는 podman 데몬이 실행 중이어야 합니다."
local kenv=""; [ "$rt" = podman ] && kenv="KIND_EXPERIMENTAL_PROVIDER=podman"
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 생성 실패"
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraPortMappings:
- { containerPort: 80, hostPort: 80, protocol: TCP }
- { containerPort: 443, hostPort: 443, protocol: TCP }
EOF
fi
info "ingress-nginx 설치/확인…"
kubectl --context "$DEV_CTX" apply -f "$INGRESS_MANIFEST" >/dev/null 2>&1 \
|| warn_line "ingress-nginx 매니페스트 적용 경고(네트워크 확인). 이미 설치돼 있으면 무시."
# 컨트롤러 파드가 아직 안 생겼을 때 'kubectl wait' 는 즉시 에러 → rollout status 로 견고하게 대기(짧은 초기 유예).
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'."
echo "✓ 로컬 dev 클러스터 준비 ($DEV_CTX). 다음: yakcloud dev deploy"
}
warn_line() { echo " ⚠ $*" >&2; }
cmd_dev() {
local sub="${1:-}"; [ "$#" -gt 0 ] && shift
case "$sub" in
up) cmd_dev_up "$@" ;;
deploy|status|clean)
[ -f scripts/yakcloud_dev.py ] || 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
local pysub="$sub"; [ "$sub" = clean ] && pysub="down"
YAK_DEV_CONTEXT="$DEV_CTX" YAK_DEV_CLUSTER="$DEV_CLUSTER" YAK_DEV_RUNTIME="${rt:-docker}" \
TAG="${tag:-${TAG:-dev}}" python3 scripts/yakcloud_dev.py "$pysub" yakcloud.yaml
;;
down)
command -v kind >/dev/null 2>&1 || die "kind 필요"
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 "✓ 삭제 완료"
;;
*) echo "yakcloud dev <up|deploy [tag]|status|clean|down>"; exit 1 ;;
esac
}
# 전역 CLI 자기 갱신 — 버전이 같아도 항상 최신 파일을 강제로 다시 받아 덮어쓴다(캐시 우회).
# Claude Code 전역 슬래시 명령(/yakcloud) 설치/최신화 — 설치·업그레이드 시 함께 실행.
# opt-out: YAKCLOUD_NO_CLAUDE_CMD=1. 실패해도 CLI 동작엔 지장 없음(비치명적).
@ -427,7 +502,8 @@ cmd_project_update() {
mkdir -p scripts .gitea/workflows .claude/skills
cp "$tmp/s/scripts/yakcloud_deploy.py" scripts/ 2>/dev/null || true
cp "$tmp/s/scripts/yakcloud_ctl.py" scripts/ 2>/dev/null || true
rm -f scripts/yakcloud_dev.py scripts/yakcloud_migrate.py # 로컬 dev/migrate 잔재 제거
cp "$tmp/s/scripts/yakcloud_dev.py" scripts/ 2>/dev/null || true # 로컬 kind 리컨실 엔진(직선 dev)
rm -f scripts/yakcloud_migrate.py # 옛 migrate 잔재 제거
cp "$tmp/s/.gitea/workflows/deploy.yml" .gitea/workflows/ 2>/dev/null || true
cp "$tmp/s/DATA-SOURCES.md" DATA-SOURCES.md 2>/dev/null || true
rm -rf .claude/skills/yakcloud-deploy
@ -442,6 +518,11 @@ usage() {
yakcloud $VERSION — YakCloud 프로젝트 CLI (개발·검증·운영 모두 yakcloud 클러스터, val 선택)
cluster create <name> [--template S|M] [--nodes N] [--golden] 클러스터 생성(프로비저닝 완료까지 대기)
cluster ls 내 클러스터 목록(이름·상태·노드·기본 도메인)
── 로컬 개발 클러스터(kind) — 개발 후 운영까지 직선 배포의 dev 구간 ──
dev up 로컬 kind 생성 + 인그레스(80/443) — 최초 1회
dev deploy [tag] 매니페스트대로 로컬 리컨실(이미지 빌드+load, 소스/워크로드/인그레스; 콘솔 API 불필요)
dev status | dev clean 로컬 배포 상태 / 이 프로젝트 리소스 삭제
dev down 로컬 kind 클러스터 삭제
project init [name] 빈 폴더에 스타터 스캐폴딩(대화형 마법사) + 자격 프롬프트
project deploy [vX.Y.Z] 커밋 + 태그 push → CI 빌드 → **개발 클러스터** 배포(버전 생략=자동 버전업)
project deploy --local 러너 없이 로컬 build/push/리컨실(CI 대안, 개발 클러스터)
@ -481,6 +562,7 @@ case "${1:-help}" in
*) echo "yakcloud project <init|deploy|promote|info|check|update>"; exit 1 ;;
esac ;;
cluster) shift; cmd_cluster "$@" ;;
dev) shift; cmd_dev "$@" ;;
login) cmd_login ;;
config) cmd_config ;;
logout) cmd_logout ;;