diff --git a/docs/oke-deploy-howto.md b/docs/oke-deploy-howto.md new file mode 100644 index 0000000..731ff6e --- /dev/null +++ b/docs/oke-deploy-howto.md @@ -0,0 +1,766 @@ +# 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.. \ + --file $HOME/.kube/config \ + --region \ + --token-version 2.0.0 \ + --kube-endpoint PUBLIC_ENDPOINT + +# 연결 확인 +kubectl get nodes +``` + +### OCIR(Oracle Container Image Registry) 로그인 + +```bash +# OCIR 로그인 +# 사용자명 형식: /oracleidentitycloudservice/ +# 비밀번호: OCI Console → User Settings → Auth Tokens에서 발급 + +docker login .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 +``` + +--- + +## 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 \ + --docker-server=.ocir.io \ + --docker-username='/oracleidentitycloudservice/' \ + --docker-password='' + +# Oracle Wallet (DB mTLS 인증) +kubectl create secret generic oracle-wallet \ + --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 \ + --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 +kubectl rollout status deployment/frontend -n +``` + +--- + +## 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: +spec: + replicas: 1 + selector: + matchLabels: + app: backend + template: + metadata: + labels: + app: backend + spec: + imagePullSecrets: + - name: ocir-secret + containers: + - name: backend + image: .ocir.io///backend:latest + ports: + - containerPort: 8000 + envFrom: + - configMapRef: + name: -config + - secretRef: + name: -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: +spec: + selector: + app: backend + ports: + - port: 8000 + targetPort: 8000 +``` + +### Ingress 예시 + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: -ingress + 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: -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=".ocir.io//" +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 + +# 3. 배포 실행 +./deploy.sh "변경 내용 설명" # 전체 배포 +./deploy.sh --backend-only "API 수정" # 백엔드만 +./deploy.sh --frontend-only "UI 수정" # 프론트엔드만 + +# 4. 확인 +kubectl get pods -n +kubectl logs -f deployment/backend -n + +# 5. (선택) Colima 중지 (리소스 절약) +colima stop +``` + +### 시크릿/설정만 변경할 때 + +```bash +# secrets.yaml 수정 후 +kubectl apply -f k8s/secrets.yaml + +# 시크릿 변경은 Pod를 자동 재시작하지 않음 → 수동 재시작 필요 +kubectl rollout restart deployment/backend -n +``` + +### 롤백 + +```bash +# 이전 버전으로 롤백 +kubectl rollout undo deployment/backend -n +kubectl rollout undo deployment/frontend -n + +# 특정 리비전으로 롤백 +kubectl rollout history deployment/backend -n +kubectl rollout undo deployment/backend -n --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 @ 300 +A www 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: -tls # cert-manager가 자동 생성 +``` + +인증서 상태 확인: + +```bash +kubectl get certificate -n +kubectl describe certificate -tls -n +``` + +--- + +## 10. 트러블슈팅 + +### 이미지 Pull 실패 (ImagePullBackOff) + +```bash +kubectl describe pod -n +# Events 섹션에서 에러 확인 + +# 원인 1: OCIR 인증 실패 → pull secret 재생성 +kubectl delete secret ocir-secret -n +kubectl create secret docker-registry ocir-secret \ + --namespace \ + --docker-server=.ocir.io \ + --docker-username='/oracleidentitycloudservice/' \ + --docker-password='' + +# 원인 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 | grep -i "hikari\|oracle\|connection" + +# 원인 1: Wallet이 마운트되지 않음 +kubectl exec deployment/backend -n -- ls /etc/oracle/wallet/ + +# 원인 2: ORACLE_DSN에 TNS_ADMIN 경로가 잘못됨 +# 형식: _medium?TNS_ADMIN=/etc/oracle/wallet +``` + +### Let's Encrypt 인증서 발급 실패 + +```bash +# Challenge 상태 확인 +kubectl get challenges -n +kubectl describe challenge -n + +# 원인: VCN Security List에서 80번 포트가 막혀있음 +# OCI Console → VCN → Security Lists → Ingress Rules에 HTTP 80 허용 추가 +``` + +### kubectl 인증 실패 + +```bash +# kubeconfig 재생성 +oci ce cluster create-kubeconfig \ + --cluster-id \ + --file $HOME/.kube/config \ + --region \ + --token-version 2.0.0 \ + --kube-endpoint PUBLIC_ENDPOINT + +# OCI CLI 프로파일 지정 (여러 프로파일 사용 시) +# kubeconfig의 args에 --profile 추가 +``` + +### Colima 시작 후 kubectl 안 됨 + +```bash +# Colima가 kubectl 컨텍스트를 가져감 +kubectl config get-contexts +kubectl config use-context # OKE로 복원 +``` + +--- + +## 11. 유용한 kubectl 명령어 + +```bash +# ── 상태 확인 ── +kubectl get pods -n # Pod 목록 +kubectl get pods -n -w # 실시간 감시 +kubectl get svc -n # Service 목록 +kubectl get ingress -n # Ingress 확인 +kubectl top pods -n # 리소스 사용량 + +# ── 로그 ── +kubectl logs deployment/backend -n # 최근 로그 +kubectl logs deployment/backend -n -f # 실시간 로그 +kubectl logs deployment/backend -n --previous # 이전 Pod 로그 (크래시 시) + +# ── 디버깅 ── +kubectl describe pod -n # Pod 상세 (이벤트 포함) +kubectl exec -it deployment/backend -n -- sh # Pod 접속 +kubectl port-forward svc/backend 8000:8000 -n # 로컬 포트 포워딩 + +# ── 배포 관리 ── +kubectl rollout status deployment/backend -n # 롤아웃 상태 +kubectl rollout restart deployment/backend -n # 재시작 (설정 변경 반영) +kubectl rollout undo deployment/backend -n # 이전 버전 롤백 +kubectl rollout history deployment/backend -n # 배포 이력 + +# ── 설정 변경 ── +kubectl apply -f k8s/configmap.yaml # ConfigMap 업데이트 +kubectl apply -f k8s/secrets.yaml # Secret 업데이트 +kubectl edit deployment backend -n # 직접 수정 (비추천) + +# ── 정리 ── +kubectl delete pod -n # Pod 강제 재시작 +kubectl scale deployment/backend --replicas=0 -n # 일시 중지 +kubectl scale deployment/backend --replicas=1 -n # 복원 +```