# 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/<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 인바운드 추가 |
