docs(deploy): OKE 배포 가이드 추가
- Colima(로컬 ARM64 Docker) → OCIR → OKE 파이프라인 전체 절차 - deploy.sh 사용법, kubectl context, secret 등록 흐름 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
766
docs/oke-deploy-howto.md
Normal file
766
docs/oke-deploy-howto.md
Normal file
@@ -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.<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> # 복원
|
||||
```
|
||||
Reference in New Issue
Block a user