# 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) ```javascript 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 ```bash #!/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 ``` ### 코드 수정 후 반영 방법 ```bash # 프론트엔드: 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 | 500m~1 CPU, 768Mi~1536Mi | | frontend | `icn.ocir.io/idyhsdamac8c/tasteby/frontend:TAG` | 3001 | 200m~500m CPU, 256Mi~512Mi | | redis | `docker.io/library/redis:7-alpine` | 6379 | 100m~200m CPU, 128Mi~256Mi | ### 배포 명령어 (deploy.sh) ```bash # 전체 배포 (백엔드 + 프론트엔드) ./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 image` → `kubectl rollout status` (롤링 업데이트) 5. **Git tag**: `vX.Y.Z` 태그 생성 후 origin push ### Docker 빌드 상세 **백엔드 Dockerfile** (multi-stage): ```dockerfile # 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): ```dockerfile # Build: node:22-alpine에서 npm ci + npm run build # Runtime: node:22-alpine에서 standalone 출력물 복사 + node server.js # ⚠️ standalone 모드는 Docker(prod) 전용. .next/static과 public을 직접 복사해야 함 ``` ### Ingress 설정 ```yaml # 주요 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` ### 운영 확인 명령어 ```bash # 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 (컨테이너 레지스트리) 인증 ```bash # 로그인 docker login icn.ocir.io -u idyhsdamac8c/oracleidentitycloudservice/ -p ``` - 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 인바운드 추가 |