# OKE(Oracle Kubernetes Engine) 배포 가이드

> Spring Boot + Next.js 앱을 OKE에 배포하는 전체 과정을 정리한 문서.
> Colima(로컬 ARM64 Docker) → OCIR(이미지 레지스트리) → OKE(K8s 클러스터) 파이프라인 기준.

---

## 목차

1. [사전 준비](#1-사전-준비)
2. [인프라 아키텍처](#2-인프라-아키텍처)
3. [최초 클러스터 설정 (1회성)](#3-최초-클러스터-설정-1회성)
4. [K8s 매니페스트 구조](#4-k8s-매니페스트-구조)
5. [Dockerfile 작성](#5-dockerfile-작성)
6. [배포 스크립트 (deploy.sh)](#6-배포-스크립트-deploysh)
7. [일상적인 배포 절차](#7-일상적인-배포-절차)
8. [환경별 설정 관리](#8-환경별-설정-관리)
9. [도메인 및 SSL 설정](#9-도메인-및-ssl-설정)
10. [트러블슈팅](#10-트러블슈팅)
11. [유용한 kubectl 명령어](#11-유용한-kubectl-명령어)

---

## 1. 사전 준비

### 필요한 도구

| 도구 | 용도 | 설치 |
|------|------|------|
| **OCI CLI** | OKE 인증, kubeconfig 설정 | `brew install oci-cli` |
| **kubectl** | K8s 클러스터 관리 | `brew install kubectl` |
| **Colima** | 로컬 ARM64 Docker 빌드 (Docker Desktop 대체) | `brew install colima` |
| **Docker CLI** | 이미지 빌드/푸시 | `brew install docker` |
| **Helm** | Nginx Ingress, cert-manager 설치 | `brew install helm` |

### OCI 설정

```bash
# OCI CLI 프로파일 설정 (~/.oci/config)
[DEFAULT]
user=ocid1.user.oc1..xxxx
fingerprint=xx:xx:xx:...
tenancy=ocid1.tenancy.oc1..xxxx
region=ap-seoul-1
key_file=~/.oci/oci_api_key.pem
```

### kubeconfig 설정

```bash
# OKE 클러스터 접근 설정
oci ce cluster create-kubeconfig \
  --cluster-id ocid1.cluster.oc1.<region>.<cluster-id> \
  --file $HOME/.kube/config \
  --region <region> \
  --token-version 2.0.0 \
  --kube-endpoint PUBLIC_ENDPOINT

# 연결 확인
kubectl get nodes
```

### OCIR(Oracle Container Image Registry) 로그인

```bash
# OCIR 로그인
# 사용자명 형식: <namespace>/oracleidentitycloudservice/<email>
# 비밀번호: OCI Console → User Settings → Auth Tokens에서 발급

docker login <region-code>.ocir.io
# 예) docker login icn.ocir.io
```

### Colima 시작 (ARM64 빌드용)

```bash
# OKE 노드가 ARM64인 경우 반드시 ARM64로 빌드해야 함
colima start --arch aarch64 --cpu 4 --memory 4

# ⚠️ Colima 시작 시 kubectl 컨텍스트가 'colima'로 바뀜
# OKE 컨텍스트로 복원 필수
kubectl config use-context <oke-context-name>
```

---

## 2. 인프라 아키텍처

```
┌─────────────────────────────────────────────────────┐
│ Internet (사용자)                                    │
│ https://www.example.com                              │
└────────────────┬────────────────────────────────────┘
                 │
         ┌───────▼────────┐
         │ DNS Provider    │ ← A 레코드 → NLB Public IP
         └───────┬────────┘
                 │
    ┌────────────▼──────────────┐
    │ OCI Network Load Balancer  │ ← Nginx Ingress가 자동 생성
    └────────────┬──────────────┘
                 │
    ┌────────────▼──────────────────────────────────┐
    │ OKE Cluster                                    │
    │                                                │
    │  ┌─────────────────────────────┐              │
    │  │ Nginx Ingress Controller     │              │
    │  │ + cert-manager (Let's Encrypt)│              │
    │  └───┬─────────────┬───────────┘              │
    │      │             │                           │
    │   /api/*        /*                            │
    │      │             │                           │
    │  ┌───▼──────┐  ┌──▼───────┐  ┌──────────┐   │
    │  │ Backend  │  │ Frontend │  │ Redis    │   │
    │  │ :8000    │  │ :3001    │  │ :6379    │   │
    │  └──────────┘  └──────────┘  └──────────┘   │
    └───────────────────────────────────────────────┘
                 │
    ┌────────────▼──────────────────────┐
    │ External Services                  │
    │ ├─ Oracle ADB (mTLS + Wallet)     │
    │ ├─ OCI GenAI                       │
    │ └─ 기타 외부 API                   │
    └────────────────────────────────────┘
```

### 리소스 할당 예시 (ARM64 × 2노드, 2CPU/8GB 각)

| 컴포넌트 | CPU request/limit | Memory request/limit |
|----------|-------------------|----------------------|
| Backend  | 500m / 1         | 768Mi / 1536Mi       |
| Frontend | 200m / 500m      | 256Mi / 512Mi        |
| Redis    | 100m / 200m      | 128Mi / 256Mi        |
| **합계** | **~800m**        | **~1.2GB**           |

---

## 3. 최초 클러스터 설정 (1회성)

### 3.1 Nginx Ingress Controller 설치

```bash
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx \
  --create-namespace \
  --set controller.service.type=LoadBalancer \
  --set controller.service.annotations."oci\.oraclecloud\.com/load-balancer-type"="nlb" \
  --set controller.service.externalTrafficPolicy=Local

# NLB 외부 IP 확인 (DNS에 연결할 IP)
kubectl get svc -n ingress-nginx ingress-nginx-controller
```

> **OKE 네트워크 설정**: VCN Security List에서 NodePort 범위(30000~32767)를 NLB IP 대역에서 허용해야 함.

### 3.2 cert-manager 설치 (Let's Encrypt 자동 인증서)

```bash
helm repo add jetstack https://charts.jetstack.io
helm repo update

helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --set crds.enabled=true
```

### 3.3 ClusterIssuer 생성

```yaml
# k8s/cert-manager/cluster-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: your-email@example.com
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
      - http01:
          ingress:
            class: nginx
```

```bash
kubectl apply -f k8s/cert-manager/cluster-issuer.yaml
```

### 3.4 앱 네임스페이스 및 시크릿 생성

```bash
# 네임스페이스
kubectl apply -f k8s/namespace.yaml

# OCIR pull secret (이미지 다운로드용)
kubectl create secret docker-registry ocir-secret \
  --namespace <app-namespace> \
  --docker-server=<region>.ocir.io \
  --docker-username='<namespace>/oracleidentitycloudservice/<email>' \
  --docker-password='<auth-token>'

# Oracle Wallet (DB mTLS 인증)
kubectl create secret generic oracle-wallet \
  --namespace <app-namespace> \
  --from-file=cwallet.sso \
  --from-file=ewallet.p12 \
  --from-file=tnsnames.ora \
  --from-file=sqlnet.ora \
  --from-file=ojdbc.properties \
  --from-file=keystore.jks \
  --from-file=truststore.jks

# OCI 설정 (GenAI 등 OCI SDK 인증)
kubectl create secret generic oci-config \
  --namespace <app-namespace> \
  --from-file=config=~/.oci/config \
  --from-file=oci_api_key.pem=~/.oci/oci_api_key.pem

# 앱 시크릿/설정
kubectl apply -f k8s/secrets.yaml
kubectl apply -f k8s/configmap.yaml
```

### 3.5 앱 배포 (최초)

```bash
kubectl apply -f k8s/redis-deployment.yaml
kubectl apply -f k8s/backend-deployment.yaml
kubectl apply -f k8s/frontend-deployment.yaml
kubectl apply -f k8s/ingress.yaml

# 롤아웃 확인
kubectl rollout status deployment/backend -n <app-namespace>
kubectl rollout status deployment/frontend -n <app-namespace>
```

---

## 4. K8s 매니페스트 구조

```
k8s/
├── namespace.yaml           # 네임스페이스 정의
├── configmap.yaml           # 비민감 설정 (Redis 호스트, API 엔드포인트 등)
├── secrets.yaml             # 민감 정보 (DB 비밀번호, API 키 등) ← .gitignore
├── secrets.yaml.template    # 시크릿 템플릿 (Git에 포함)
├── backend-deployment.yaml  # Backend Deployment + Service
├── frontend-deployment.yaml # Frontend Deployment + Service
├── redis-deployment.yaml    # Redis Deployment + Service
├── ingress.yaml             # Ingress (라우팅 + TLS)
└── cert-manager/
    └── cluster-issuer.yaml  # Let's Encrypt ClusterIssuer
```

### Backend Deployment 예시

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
  namespace: <app-namespace>
spec:
  replicas: 1
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      imagePullSecrets:
        - name: ocir-secret
      containers:
        - name: backend
          image: <region>.ocir.io/<namespace>/<repo>/backend:latest
          ports:
            - containerPort: 8000
          envFrom:
            - configMapRef:
                name: <app>-config
            - secretRef:
                name: <app>-secrets
          volumeMounts:
            - name: oracle-wallet
              mountPath: /etc/oracle/wallet
              readOnly: true
            - name: oci-config
              mountPath: /root/.oci
              readOnly: true
          resources:
            requests:
              cpu: "500m"
              memory: "768Mi"
            limits:
              cpu: "1"
              memory: "1536Mi"
          readinessProbe:
            tcpSocket:
              port: 8000
            initialDelaySeconds: 30
            periodSeconds: 10
          livenessProbe:
            tcpSocket:
              port: 8000
            initialDelaySeconds: 60
            periodSeconds: 30
      volumes:
        - name: oracle-wallet
          secret:
            secretName: oracle-wallet
        - name: oci-config
          secret:
            secretName: oci-config
---
apiVersion: v1
kind: Service
metadata:
  name: backend
  namespace: <app-namespace>
spec:
  selector:
    app: backend
  ports:
    - port: 8000
      targetPort: 8000
```

### Ingress 예시

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: <app>-ingress
  namespace: <app-namespace>
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
    nginx.ingress.kubernetes.io/proxy-body-size: "10m"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - www.example.com
      secretName: <app>-tls
  rules:
    - host: www.example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: backend
                port:
                  number: 8000
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend
                port:
                  number: 3001
```

---

## 5. Dockerfile 작성

### Backend (Spring Boot 멀티스테이지)

```dockerfile
# ── Build Stage ──
FROM eclipse-temurin:21-jdk AS build
WORKDIR /app

# 의존성 캐싱 (소스 변경 시에도 재사용)
COPY gradlew settings.gradle build.gradle ./
COPY gradle/ gradle/
RUN chmod +x gradlew && ./gradlew dependencies --no-daemon || true

# 소스 복사 & 빌드
COPY src/ src/
RUN ./gradlew bootJar -x test --no-daemon

# ── Runtime Stage ──
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /app/build/libs/*.jar app.jar
EXPOSE 8000
ENV JAVA_OPTS="-XX:MaxRAMPercentage=75.0 -XX:+UseG1GC"
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
```

**포인트:**
- Gradle 의존성을 먼저 복사/다운로드 → Docker 레이어 캐시 활용
- `-XX:MaxRAMPercentage=75.0` → 컨테이너 메모리 제한의 75%를 JVM 힙으로 사용
- 최종 이미지에 JDK 대신 JRE만 포함 → 이미지 크기 절감

### Frontend (Next.js standalone 멀티스테이지)

```dockerfile
# ── Build Stage ──
FROM node:22-alpine AS build
WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci

COPY . .

# 빌드 시점 환경변수 주입 (NEXT_PUBLIC_* 변수)
ARG NEXT_PUBLIC_GOOGLE_MAPS_API_KEY
ARG NEXT_PUBLIC_GOOGLE_CLIENT_ID
RUN npm run build

# ── Runtime Stage ──
FROM node:22-alpine
WORKDIR /app

# standalone 출력 + 정적 파일 + public 복사
COPY --from=build /app/.next/standalone ./
COPY --from=build /app/.next/static ./.next/static
COPY --from=build /app/public ./public

EXPOSE 3001
ENV PORT=3001 HOSTNAME=0.0.0.0
CMD ["node", "server.js"]
```

**포인트:**
- `next.config.ts`에 `output: "standalone"` 필수
- `NEXT_PUBLIC_*` 환경변수는 **빌드 시점**에만 주입 가능 (런타임 X)
- `.next/static`과 `public/`은 standalone에 포함되지 않으므로 수동 복사

---

## 6. 배포 스크립트 (deploy.sh)

```bash
#!/bin/bash
set -euo pipefail

# ── Configuration ──
REGISTRY="<region>.ocir.io/<namespace>/<repo>"
APP_NAMESPACE="<app-namespace>"
PLATFORM="linux/arm64"    # OKE 노드 아키텍처에 맞춤

# ── Parse arguments ──
TARGET="all"   # all | backend | frontend
MESSAGE=""

while [[ $# -gt 0 ]]; do
  case $1 in
    --backend-only) TARGET="backend"; shift ;;
    --frontend-only) TARGET="frontend"; shift ;;
    --dry-run) echo "[DRY RUN]"; exit 0 ;;
    -m) MESSAGE="$2"; shift 2 ;;
    *) MESSAGE="$1"; shift ;;
  esac
done

# ── Auto-increment version ──
LATEST_TAG=$(git tag --sort=-v:refname | grep '^v' | head -1 2>/dev/null || echo "v0.1.0")
MAJOR=$(echo "$LATEST_TAG" | cut -d. -f1)
MINOR=$(echo "$LATEST_TAG" | cut -d. -f2)
PATCH=$(echo "$LATEST_TAG" | cut -d. -f3)
TAG="${MAJOR}.${MINOR}.$((PATCH + 1))"

echo "━━━ Deploying ${TAG} (${TARGET}) ━━━"

cd "$(git rev-parse --show-toplevel)"

# ── Build & Push ──
if [[ "$TARGET" == "all" || "$TARGET" == "backend" ]]; then
  echo "▶ Building backend..."
  docker build --platform "$PLATFORM" \
    -t "$REGISTRY/backend:$TAG" \
    -t "$REGISTRY/backend:latest" \
    backend-java/
  docker push "$REGISTRY/backend:$TAG"
  docker push "$REGISTRY/backend:latest"
fi

if [[ "$TARGET" == "all" || "$TARGET" == "frontend" ]]; then
  echo "▶ Building frontend..."

  # .env.local에서 빌드 인자 읽기
  MAPS_KEY=$(grep NEXT_PUBLIC_GOOGLE_MAPS_API_KEY frontend/.env.local 2>/dev/null | cut -d= -f2)
  CLIENT_ID=$(grep NEXT_PUBLIC_GOOGLE_CLIENT_ID frontend/.env.local 2>/dev/null | cut -d= -f2)

  docker build --platform "$PLATFORM" \
    --build-arg NEXT_PUBLIC_GOOGLE_MAPS_API_KEY="$MAPS_KEY" \
    --build-arg NEXT_PUBLIC_GOOGLE_CLIENT_ID="$CLIENT_ID" \
    -t "$REGISTRY/frontend:$TAG" \
    -t "$REGISTRY/frontend:latest" \
    frontend/
  docker push "$REGISTRY/frontend:$TAG"
  docker push "$REGISTRY/frontend:latest"
fi

# ── Rolling Update ──
# ⚠️ kubectl 컨텍스트가 OKE를 가리키는지 반드시 확인
if [[ "$TARGET" == "all" || "$TARGET" == "backend" ]]; then
  kubectl set image deployment/backend \
    backend="$REGISTRY/backend:$TAG" -n "$APP_NAMESPACE"
  kubectl rollout status deployment/backend -n "$APP_NAMESPACE" --timeout=180s
fi

if [[ "$TARGET" == "all" || "$TARGET" == "frontend" ]]; then
  kubectl set image deployment/frontend \
    frontend="$REGISTRY/frontend:$TAG" -n "$APP_NAMESPACE"
  kubectl rollout status deployment/frontend -n "$APP_NAMESPACE" --timeout=120s
fi

# ── Git Tag ──
git tag -a "$TAG" -m "Deploy ${TAG}: ${MESSAGE}"
git push origin "$TAG"

echo "━━━ ✅ Deploy complete: ${TAG} ━━━"
kubectl get pods -n "$APP_NAMESPACE"
```

---

## 7. 일상적인 배포 절차

```bash
# 1. Colima 시작 (ARM64 Docker 빌드용, 꺼져있을 때만)
colima start --arch aarch64 --cpu 4 --memory 4

# 2. kubectl 컨텍스트를 OKE로 복원 (Colima가 바꿔버림)
kubectl config use-context <oke-context-name>

# 3. 배포 실행
./deploy.sh "변경 내용 설명"              # 전체 배포
./deploy.sh --backend-only "API 수정"     # 백엔드만
./deploy.sh --frontend-only "UI 수정"     # 프론트엔드만

# 4. 확인
kubectl get pods -n <app-namespace>
kubectl logs -f deployment/backend -n <app-namespace>

# 5. (선택) Colima 중지 (리소스 절약)
colima stop
```

### 시크릿/설정만 변경할 때

```bash
# secrets.yaml 수정 후
kubectl apply -f k8s/secrets.yaml

# 시크릿 변경은 Pod를 자동 재시작하지 않음 → 수동 재시작 필요
kubectl rollout restart deployment/backend -n <app-namespace>
```

### 롤백

```bash
# 이전 버전으로 롤백
kubectl rollout undo deployment/backend -n <app-namespace>
kubectl rollout undo deployment/frontend -n <app-namespace>

# 특정 리비전으로 롤백
kubectl rollout history deployment/backend -n <app-namespace>
kubectl rollout undo deployment/backend -n <app-namespace> --to-revision=3
```

---

## 8. 환경별 설정 관리

### 개발(Local) vs 운영(OKE) 비교

| 항목 | 개발 (Local) | 운영 (OKE) |
|------|-------------|-----------|
| DB 설정 | `backend/.env` | K8s Secret (`secrets.yaml`) |
| 환경변수 | 파일 기반 (`.env`, `.env.local`) | ConfigMap + Secret |
| Oracle Wallet | 로컬 디렉토리 | K8s Secret → Volume 마운트 |
| OCI 인증 | `~/.oci/config` | K8s Secret → Volume 마운트 |
| Redis | 로컬 IP (`192.168.x.x`) | K8s Service DNS (`redis`) |
| 프로세스 관리 | PM2 | K8s Deployment |
| 프론트엔드 모드 | `npm run dev` | `node server.js` (standalone) |
| DB 프로파일 | `_low` (리소스 절약) | `_medium` (적정 성능) |

### Oracle ADB 프로파일 선택

| 프로파일 | 병렬 처리 | 용도 |
|---------|----------|------|
| `_high` | 최대 | 대규모 배치/분석 |
| `_medium` | 중간 | **운영 권장** |
| `_low` | 최소 | **개발/테스트** (OCPU 절약) |
| `_tp` | 트랜잭션 | OLTP 워크로드 |

---

## 9. 도메인 및 SSL 설정

### DNS 설정

```
# DNS Provider (예: Namecheap)에서 설정
Type   Host   Value                 TTL
A      @      <NLB Public IP>       300
A      www    <NLB Public IP>       300
```

NLB Public IP 확인:

```bash
kubectl get svc -n ingress-nginx ingress-nginx-controller \
  -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
```

### SSL 인증서

cert-manager + ClusterIssuer가 설정되어 있으면 Ingress에 annotation만 추가하면 자동 발급/갱신됨.

```yaml
# Ingress에 추가
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts:
        - www.example.com
      secretName: <app>-tls   # cert-manager가 자동 생성
```

인증서 상태 확인:

```bash
kubectl get certificate -n <app-namespace>
kubectl describe certificate <app>-tls -n <app-namespace>
```

---

## 10. 트러블슈팅

### 이미지 Pull 실패 (ImagePullBackOff)

```bash
kubectl describe pod <pod-name> -n <app-namespace>
# Events 섹션에서 에러 확인

# 원인 1: OCIR 인증 실패 → pull secret 재생성
kubectl delete secret ocir-secret -n <app-namespace>
kubectl create secret docker-registry ocir-secret \
  --namespace <app-namespace> \
  --docker-server=<region>.ocir.io \
  --docker-username='<namespace>/oracleidentitycloudservice/<email>' \
  --docker-password='<auth-token>'

# 원인 2: 이미지가 존재하지 않음 → OCIR 콘솔에서 확인
```

### OKE CRI-O: short image name 오류

```
# ❌ 틀림
image: redis:7-alpine

# ✅ 맞음 (docker.io/library/ 접두사 필수)
image: docker.io/library/redis:7-alpine
```

OKE는 CRI-O 런타임을 사용하며, Docker Hub의 `library/` 이미지도 전체 경로를 명시해야 함.

### DB 연결 실패 (HikariPool timeout)

```bash
# Pod 로그 확인
kubectl logs deployment/backend -n <app-namespace> | grep -i "hikari\|oracle\|connection"

# 원인 1: Wallet이 마운트되지 않음
kubectl exec deployment/backend -n <app-namespace> -- ls /etc/oracle/wallet/

# 원인 2: ORACLE_DSN에 TNS_ADMIN 경로가 잘못됨
# 형식: <tns-alias>_medium?TNS_ADMIN=/etc/oracle/wallet
```

### Let's Encrypt 인증서 발급 실패

```bash
# Challenge 상태 확인
kubectl get challenges -n <app-namespace>
kubectl describe challenge <name> -n <app-namespace>

# 원인: VCN Security List에서 80번 포트가 막혀있음
# OCI Console → VCN → Security Lists → Ingress Rules에 HTTP 80 허용 추가
```

### kubectl 인증 실패

```bash
# kubeconfig 재생성
oci ce cluster create-kubeconfig \
  --cluster-id <cluster-ocid> \
  --file $HOME/.kube/config \
  --region <region> \
  --token-version 2.0.0 \
  --kube-endpoint PUBLIC_ENDPOINT

# OCI CLI 프로파일 지정 (여러 프로파일 사용 시)
# kubeconfig의 args에 --profile <PROFILE_NAME> 추가
```

### Colima 시작 후 kubectl 안 됨

```bash
# Colima가 kubectl 컨텍스트를 가져감
kubectl config get-contexts
kubectl config use-context <oke-context-name>  # OKE로 복원
```

---

## 11. 유용한 kubectl 명령어

```bash
# ── 상태 확인 ──
kubectl get pods -n <ns>                          # Pod 목록
kubectl get pods -n <ns> -w                       # 실시간 감시
kubectl get svc -n <ns>                           # Service 목록
kubectl get ingress -n <ns>                       # Ingress 확인
kubectl top pods -n <ns>                          # 리소스 사용량

# ── 로그 ──
kubectl logs deployment/backend -n <ns>           # 최근 로그
kubectl logs deployment/backend -n <ns> -f        # 실시간 로그
kubectl logs deployment/backend -n <ns> --previous # 이전 Pod 로그 (크래시 시)

# ── 디버깅 ──
kubectl describe pod <pod> -n <ns>                # Pod 상세 (이벤트 포함)
kubectl exec -it deployment/backend -n <ns> -- sh # Pod 접속
kubectl port-forward svc/backend 8000:8000 -n <ns> # 로컬 포트 포워딩

# ── 배포 관리 ──
kubectl rollout status deployment/backend -n <ns>  # 롤아웃 상태
kubectl rollout restart deployment/backend -n <ns> # 재시작 (설정 변경 반영)
kubectl rollout undo deployment/backend -n <ns>    # 이전 버전 롤백
kubectl rollout history deployment/backend -n <ns> # 배포 이력

# ── 설정 변경 ──
kubectl apply -f k8s/configmap.yaml               # ConfigMap 업데이트
kubectl apply -f k8s/secrets.yaml                 # Secret 업데이트
kubectl edit deployment backend -n <ns>            # 직접 수정 (비추천)

# ── 정리 ──
kubectl delete pod <pod> -n <ns>                  # Pod 강제 재시작
kubectl scale deployment/backend --replicas=0 -n <ns>  # 일시 중지
kubectl scale deployment/backend --replicas=1 -n <ns>  # 복원
```
