Files
tasteby/docs/deployment-guide.md
joungmin f2861b6b79 홈 탭 장르 카드 UI + Tabler Icons 적용 + 지역 필터 추가
- 홈 탭: 장르 가로 스크롤 카드 (Tabler Icons 픽토그램)
- 홈 탭: 가격/지역/내위치 필터 2줄 배치
- 리스트 탭: 기존 바텀시트 필터 UI 유지
- cuisine-icons: Tabler 아이콘 매핑 추가 (getTablerCuisineIcon)
- 드래그 스크롤 장르 카드에 적용
- 배포 가이드 문서 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 22:52:42 +09:00

8.1 KiB
Raw Blame History

Tasteby 배포 가이드

환경 요약

항목 Dev (개발) Prod (운영)
URL dev.tasteby.net www.tasteby.net
호스트 로컬 Mac mini OKE (Oracle Kubernetes Engine)
프로세스 관리 PM2 Kubernetes Deployment
프론트엔드 실행 npm run dev (Next.js dev server) node server.js (standalone 빌드)
백엔드 실행 ./gradlew bootRun java -jar app.jar (bootJar 빌드)
Redis 로컬 Redis 서버 K8s Pod (redis:7-alpine)
TLS Nginx(192.168.0.147) + Certbot cert-manager + Let's Encrypt
리버스 프록시 Nginx (192.168.0.147 → 192.168.0.208) Nginx Ingress Controller (K8s)
도메인 DNS dev.tasteby.net → Mac mini IP www.tasteby.net → OCI NLB 217.142.131.194

1. Dev 환경 (dev.tasteby.net)

구조

브라우저 → dev.tasteby.net (HTTPS)
    ↓
Nginx (192.168.0.147) — Certbot Let's Encrypt TLS
    ├── /api/*  → proxy_pass http://192.168.0.208:8000 (tasteby-api)
    └── /*      → proxy_pass http://192.168.0.208:3001 (tasteby-web)
    ↓
Mac mini (192.168.0.208) — PM2 프로세스 매니저
    ├── tasteby-api  → ./gradlew bootRun (:8000)
    └── tasteby-web  → npm run dev (:3001)
  • 192.168.0.147: Nginx 리버스 프록시 서버 (TLS 종료, Certbot 자동 갱신)
  • 192.168.0.208: Mac mini (실제 앱 서버, PM2 관리)

PM2 프로세스 구성 (ecosystem.config.js)

module.exports = {
  apps: [
    {
      name: "tasteby-api",
      cwd: "/Users/joungmin/workspaces/tasteby/backend-java",
      script: "./start.sh",        // gradlew bootRun 실행
      interpreter: "/bin/bash",
    },
    {
      name: "tasteby-web",
      cwd: "/Users/joungmin/workspaces/tasteby/frontend",
      script: "npm",
      args: "run dev",              // ⚠️ 절대 standalone으로 바꾸지 말 것!
    },
  ],
};

백엔드 start.sh

#!/bin/bash
export JAVA_HOME="/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home"
export PATH="/opt/homebrew/opt/openjdk@21/bin:$PATH"
set -a
source /Users/joungmin/workspaces/tasteby/backend/.env    # 환경변수 로드
set +a
exec ./gradlew bootRun

코드 수정 후 반영 방법

# 프론트엔드: npm run dev라서 코드 수정 시 자동 Hot Reload (재시작 불필요)

# 백엔드: 코드 수정 후 재시작 필요
pm2 restart tasteby-api

# 전체 재시작
pm2 restart tasteby-api tasteby-web

# PM2 상태 확인
pm2 status

# 로그 확인
pm2 logs tasteby-api --lines 50
pm2 logs tasteby-web --lines 50

주의사항

  • tasteby-web은 반드시 npm run dev로 실행 (dev server)
    • standalone 모드(node .next/standalone/server.js)로 바꾸면 static/public 파일을 못 찾아서 404 발생
    • standalone은 prod(Docker/K8s) 전용
  • dev 포트: 프론트 3001, 백엔드 8000 (3000은 Gitea가 사용 중)
  • 환경변수는 backend/.env에서 로드

2. Prod 환경 (www.tasteby.net)

구조

브라우저 → www.tasteby.net (HTTPS)
    ↓
OCI Network Load Balancer (217.142.131.194)
    ↓ 80→NodePort:32530, 443→NodePort:31437
Nginx Ingress Controller (K8s)
    ├── /api/*  → backend Service (:8000)
    └── /*      → frontend Service (:3001)

클러스터 정보

  • OKE 클러스터: tasteby-cluster-prod
  • 노드: ARM64 × 2 (2 CPU / 8GB)
  • 네임스페이스: tasteby
  • K8s context: context-c6ap7ecrdeq

Pod 구성

Pod Image Port 리소스
backend icn.ocir.io/idyhsdamac8c/tasteby/backend:TAG 8000 500m1 CPU, 768Mi1536Mi
frontend icn.ocir.io/idyhsdamac8c/tasteby/frontend:TAG 3001 200m500m CPU, 256Mi512Mi
redis docker.io/library/redis:7-alpine 6379 100m200m CPU, 128Mi256Mi

배포 명령어 (deploy.sh)

# 전체 배포 (백엔드 + 프론트엔드)
./deploy.sh "배포 메시지"

# 백엔드만 배포
./deploy.sh --backend-only "백엔드 수정 사항"

# 프론트엔드만 배포
./deploy.sh --frontend-only "프론트 수정 사항"

# 드라이런 (실제 배포 없이 확인)
./deploy.sh --dry-run "테스트"

deploy.sh 동작 순서

  1. 버전 계산: 최신 git tag에서 patch +1 (v0.1.9 → v0.1.10)
  2. Docker 빌드: Colima로 linux/arm64 이미지 빌드 (로컬 Mac에서)
    • 백엔드: backend-java/Dockerfile → multi-stage (JDK build → JRE runtime)
    • 프론트: frontend/Dockerfile → multi-stage (node build → standalone runtime)
  3. OCIR Push: icn.ocir.io/idyhsdamac8c/tasteby/{backend,frontend}:TAG + :latest
  4. K8s 배포: kubectl set imagekubectl rollout status (롤링 업데이트)
  5. Git tag: vX.Y.Z 태그 생성 후 origin push

Docker 빌드 상세

백엔드 Dockerfile (multi-stage):

# Build: eclipse-temurin:21-jdk에서 gradlew bootJar
# Runtime: eclipse-temurin:21-jre에서 java -jar app.jar
# JVM 옵션: -XX:MaxRAMPercentage=75.0 -XX:+UseG1GC

프론트엔드 Dockerfile (multi-stage):

# Build: node:22-alpine에서 npm ci + npm run build
# Runtime: node:22-alpine에서 standalone 출력물 복사 + node server.js
# ⚠️ standalone 모드는 Docker(prod) 전용. .next/static과 public을 직접 복사해야 함

Ingress 설정

# 주요 annotation
cert-manager.io/cluster-issuer: letsencrypt-prod     # 자동 TLS 인증서
nginx.ingress.kubernetes.io/ssl-redirect: "true"      # HTTP → HTTPS 리다이렉트
nginx.ingress.kubernetes.io/from-to-www-redirect: "true"  # tasteby.net → www 리다이렉트

# 라우팅
www.tasteby.net/api/* → backend:8000
www.tasteby.net/*     → frontend:3001

TLS 인증서 (cert-manager)

  • ClusterIssuer: letsencrypt-prod
  • HTTP-01 challenge 방식 (포트 80 필수)
  • Secret: tasteby-tls
  • 인증서 상태 확인: kubectl get certificate -n tasteby

운영 확인 명령어

# Pod 상태
kubectl get pods -n tasteby

# 로그 확인
kubectl logs -f deployment/backend -n tasteby
kubectl logs -f deployment/frontend -n tasteby

# 인증서 상태
kubectl get certificate -n tasteby

# Ingress 상태
kubectl get ingress -n tasteby

# 롤백 (이전 이미지로)
kubectl rollout undo deployment/backend -n tasteby
kubectl rollout undo deployment/frontend -n tasteby

3. OCI 네트워크 구성

VCN 서브넷

서브넷 CIDR 용도
oke-k8sApiEndpoint-subnet 10.0.0.0/28 K8s API 서버
oke-nodesubnet 10.0.10.0/24 워커 노드
oke-svclbsubnet 10.0.20.0/24 NLB (로드밸런서)

보안 리스트 (Security List)

LB 서브넷 (oke-svclbsubnet):

  • Ingress: 0.0.0.0/0 → TCP 80, 443
  • Egress: 10.0.10.0/24 → all (노드 서브넷 전체 허용)

노드 서브넷 (oke-nodesubnet):

  • Ingress: 10.0.10.0/24 → all (노드 간 통신)
  • Ingress: 10.0.0.0/28 → TCP all (API 서버)
  • Ingress: 0.0.0.0/0 → TCP 22 (SSH)
  • Ingress: 10.0.20.0/24 → TCP 30000-32767 (LB → NodePort)
  • Ingress: 0.0.0.0/0 → TCP 30000-32767 (NLB preserve-source 대응)

⚠️ NLB is-preserve-source: true 설정으로 클라이언트 원본 IP가 보존됨. 따라서 노드 서브넷에 0.0.0.0/0 → NodePort 인바운드가 반드시 필요.


4. OCIR (컨테이너 레지스트리) 인증

# 로그인
docker login icn.ocir.io -u idyhsdamac8c/oracleidentitycloudservice/<email> -p <auth-token>
  • Registry: icn.ocir.io/idyhsdamac8c/tasteby/
  • K8s imagePullSecret: ocir-secret (namespace: tasteby)

5. 자주 하는 실수 / 주의사항

실수 원인 해결
dev에서 static 404 PM2를 standalone 모드로 바꿈 npm run dev로 원복
prod HTTPS 타임아웃 NLB 보안 리스트 NodePort 불일치 egress를 노드 서브넷 all 허용
인증서 발급 실패 포트 80 방화벽 차단 LB 서브넷 ingress 80 + 노드 서브넷 NodePort 허용
OKE에서 이미지 pull 실패 CRI-O short name 불가 docker.io/library/ 풀네임 사용
NLB 헬스체크 실패 preserve-source + 노드 보안 리스트 0.0.0.0/0 → NodePort 인바운드 추가