From 745913ca5b6212014bf68fabeacbb48c77df1c4a Mon Sep 17 00:00:00 2001 From: joungmin Date: Mon, 9 Mar 2026 23:22:04 +0900 Subject: [PATCH] Add OCI DevOps build spec and CI/CD architecture docs Co-Authored-By: Claude Opus 4.6 --- build_spec.yaml | 52 ++++++ docs/cicd-architecture.md | 355 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 407 insertions(+) create mode 100644 build_spec.yaml create mode 100644 docs/cicd-architecture.md diff --git a/build_spec.yaml b/build_spec.yaml new file mode 100644 index 0000000..813462e --- /dev/null +++ b/build_spec.yaml @@ -0,0 +1,52 @@ +version: 0.1 +component: build +timeoutInSeconds: 1200 +runAs: root +shell: bash + +env: + variables: + REGISTRY: "icn.ocir.io/idyhsdamac8c/tasteby" + exportedVariables: + - IMAGE_TAG + - BACKEND_IMAGE + - FRONTEND_IMAGE + +steps: + - type: Command + name: "Set image tag" + command: | + IMAGE_TAG="${OCI_BUILD_RUN_ID:0:8}-$(date +%Y%m%d%H%M)" + BACKEND_IMAGE="${REGISTRY}/backend:${IMAGE_TAG}" + FRONTEND_IMAGE="${REGISTRY}/frontend:${IMAGE_TAG}" + echo "IMAGE_TAG=${IMAGE_TAG}" + echo "BACKEND_IMAGE=${BACKEND_IMAGE}" + echo "FRONTEND_IMAGE=${FRONTEND_IMAGE}" + + - type: Command + name: "Build backend image" + command: | + cd backend-java + docker build --platform linux/arm64 \ + -t "${BACKEND_IMAGE}" \ + -t "${REGISTRY}/backend:latest" \ + . + + - type: Command + name: "Build frontend image" + command: | + cd frontend + docker build --platform linux/arm64 \ + --build-arg NEXT_PUBLIC_GOOGLE_MAPS_API_KEY="${NEXT_PUBLIC_GOOGLE_MAPS_API_KEY}" \ + --build-arg NEXT_PUBLIC_GOOGLE_CLIENT_ID="${NEXT_PUBLIC_GOOGLE_CLIENT_ID}" \ + -t "${FRONTEND_IMAGE}" \ + -t "${REGISTRY}/frontend:latest" \ + . + +outputArtifacts: + - name: backend-image + type: DOCKER_IMAGE + location: ${BACKEND_IMAGE} + - name: frontend-image + type: DOCKER_IMAGE + location: ${FRONTEND_IMAGE} diff --git a/docs/cicd-architecture.md b/docs/cicd-architecture.md new file mode 100644 index 0000000..42d3750 --- /dev/null +++ b/docs/cicd-architecture.md @@ -0,0 +1,355 @@ +# Tasteby CI/CD 파이프라인 & 전체 아키텍처 + +## 전체 시스템 아키텍처 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ Internet │ +│ www.tasteby.net │ +└──────────────────────────────┬──────────────────────────────────────────┘ + │ + ┌─────────▼──────────┐ + │ Namecheap DNS │ + │ A → LB External IP │ + └─────────┬──────────┘ + │ +┌──────────────────────────────▼──────────────────────────────────────────┐ +│ OCI Load Balancer (NLB) │ +│ (Nginx Ingress Controller가 자동 생성) │ +└──────────────────────────────┬──────────────────────────────────────────┘ + │ +┌──────────────────────────────▼──────────────────────────────────────────┐ +│ OKE Cluster (tasteby-cluster) │ +│ ap-seoul-1 │ ARM64 × 2 노드 (2CPU/8GB 각) │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ Nginx Ingress Controller (ingress-nginx) │ │ +│ │ │ │ +│ │ TLS termination (Let's Encrypt via cert-manager) │ │ +│ │ tasteby.net → www.tasteby.net 리다이렉트 │ │ +│ │ │ │ +│ │ /api/* ──→ backend Service :8000 │ │ +│ │ /* ──→ frontend Service :3001 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─── namespace: tasteby ─────────────────────────────────────────────┐ │ +│ │ │ │ +│ │ ┌──────────────────┐ ┌──────────────────┐ ┌────────────────┐ │ │ +│ │ │ backend (×1) │ │ frontend (×1) │ │ redis (×1) │ │ │ +│ │ │ Spring Boot 3 │ │ Next.js 15 │ │ Redis 7 │ │ │ +│ │ │ Java 21 │ │ standalone mode │ │ alpine │ │ │ +│ │ │ :8000 │ │ :3001 │ │ :6379 │ │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ │ ┌─ Volumes ─────┐│ └──────────────────┘ └────────────────┘ │ │ +│ │ │ │ oracle-wallet ││ │ │ +│ │ │ │ oci-config ││ │ │ +│ │ │ └───────────────┘│ │ │ +│ │ └──────────────────┘ │ │ +│ │ │ │ +│ │ ┌─── ConfigMap / Secrets ──────────────────────────────────────┐ │ │ +│ │ │ tasteby-config : REDIS_HOST, OCI endpoints, 등 │ │ │ +│ │ │ tasteby-secrets : DB credentials, API keys, JWT │ │ │ +│ │ │ oracle-wallet : cwallet.sso, tnsnames.ora, keystore.jks │ │ │ +│ │ │ oci-config : OCI API config + PEM key │ │ │ +│ │ │ ocir-secret : OCIR 이미지 Pull 인증 │ │ │ +│ │ └──────────────────────────────────────────────────────────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─── namespace: cert-manager ──┐ ┌─── namespace: ingress-nginx ──┐ │ +│ │ cert-manager (×3 pods) │ │ ingress-nginx-controller │ │ +│ │ ClusterIssuer: letsencrypt │ │ (NLB 자동 생성) │ │ +│ └──────────────────────────────┘ └────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ┌─────────▼──────────┐ + │ Oracle ADB 23ai │ + │ (mTLS via Wallet) │ + └────────────────────┘ +``` + +## 외부 서비스 연동 + +``` +backend (Spring Boot) + ├─→ Oracle ADB 23ai : JDBC + mTLS (Wallet) + ├─→ OCI GenAI (Chat) : 식당 정보 추출, 음식 태그, 평가 생성 + ├─→ OCI GenAI (Embed) : 벡터 임베딩 (Cohere embed-v4) + ├─→ Google Maps Places API : 식당 검색, 좌표 조회 + ├─→ YouTube Data API v3 : 채널/영상 정보 조회 + └─→ Redis : 캐시 (API 응답, 검색 결과) + +frontend (Next.js) + ├─→ Google Maps JavaScript API : 지도 렌더링 + └─→ Google OAuth 2.0 : 사용자 인증 +``` + +--- + +## CI/CD 파이프라인 + +### 파이프라인 개요 + +``` +┌──────────┐ ┌──────────────────┐ ┌──────────────────────┐ ┌─────────┐ +│ 개발자 │────→│ OCI Code Repo │────→│ OCI DevOps Build │────→│ OKE │ +│ git push │ │ (tasteby) │ │ Pipeline │ │ 배포 │ +└──────────┘ └──────────────────┘ └──────────────────────┘ └─────────┘ +``` + +### 상세 흐름 + +``` +1. 코드 푸시 + ┌──────────────┐ + │ git push oci │ ← OCI Code Repository remote + └──────┬───────┘ + │ + ▼ +2. 빌드 파이프라인 (OCI DevOps Build Pipeline) + ┌───────────────────────────────────────────────────────────┐ + │ │ + │ Stage 1: Managed Build │ + │ ┌──────────────────────────────────────────────────────┐ │ + │ │ build_spec.yaml 실행 │ │ + │ │ │ │ + │ │ 1) IMAGE_TAG 생성 (BuildRunID + timestamp) │ │ + │ │ 2) docker build --platform linux/arm64 │ │ + │ │ - backend-java/Dockerfile → backend image │ │ + │ │ - frontend/Dockerfile → frontend image │ │ + │ │ 3) Output: BACKEND_IMAGE, FRONTEND_IMAGE │ │ + │ └──────────────────────────────────────────────────────┘ │ + │ │ │ + │ ▼ │ + │ Stage 2: Deliver Artifacts │ + │ ┌──────────────────────────────────────────────────────┐ │ + │ │ Docker images → OCIR 푸시 │ │ + │ │ │ │ + │ │ icn.ocir.io/idyhsdamac8c/tasteby/backend:TAG │ │ + │ │ icn.ocir.io/idyhsdamac8c/tasteby/frontend:TAG │ │ + │ └──────────────────────────────────────────────────────┘ │ + └───────────────────────────────────────────────────────────┘ + │ + ▼ +3. 배포 (수동 또는 deploy.sh) + ┌───────────────────────────────────────────────────────────┐ + │ kubectl set image deployment/backend backend=IMAGE:TAG │ + │ kubectl set image deployment/frontend frontend=IMAGE:TAG │ + │ kubectl rollout status ... │ + │ │ + │ git tag -a "vX.Y.Z" -m "Deploy: ..." │ + │ git push origin "vX.Y.Z" │ + └───────────────────────────────────────────────────────────┘ +``` + +### Git Remote 구성 + +``` +origin → Gitea (gittea.cloud-handson.com) ← 소스 코드 관리 +oci → OCI Code Repository ← CI/CD 트리거용 +``` + +두 리모트에 모두 push하여 소스와 빌드를 동기화합니다. + +### build_spec.yaml 구조 + +```yaml +# OCI DevOps Build Pipeline 설정 +version: 0.1 +component: build +shell: bash + +env: + variables: + REGISTRY: "icn.ocir.io/idyhsdamac8c/tasteby" + exportedVariables: + - IMAGE_TAG # 다음 stage에서 사용 + - BACKEND_IMAGE + - FRONTEND_IMAGE + +steps: + - Set image tag # BuildRunID + timestamp 조합 + - Build backend image # backend-java/Dockerfile, ARM64 + - Build frontend image # frontend/Dockerfile, ARM64 + +outputArtifacts: + - backend-image (DOCKER_IMAGE) + - frontend-image (DOCKER_IMAGE) +``` + +--- + +## 로컬 배포 (deploy.sh) + +OCI DevOps 없이 로컬에서 직접 배포할 때 사용합니다. + +```bash +# 전체 배포 +./deploy.sh "초기 배포" + +# 백엔드만 +./deploy.sh --backend-only "API 버그 수정" + +# 프론트엔드만 +./deploy.sh --frontend-only "UI 개선" + +# 드라이런 +./deploy.sh --dry-run "테스트" +``` + +**deploy.sh 동작:** +1. 최신 git tag에서 다음 버전 계산 (v0.1.X → v0.1.X+1) +2. Docker build (ARM64) + OCIR push +3. `kubectl set image` → rollout 대기 +4. git tag 생성 + push + +--- + +## Docker 이미지 빌드 + +### Backend (Spring Boot) + +``` +eclipse-temurin:21-jdk (build) + └─ gradlew bootJar + └─ app.jar + +eclipse-temurin:21-jre (runtime) + └─ java -XX:MaxRAMPercentage=75.0 -jar app.jar + └─ EXPOSE 8000 +``` + +### Frontend (Next.js) + +``` +node:22-alpine (build) + └─ npm ci + npm run build + └─ NEXT_PUBLIC_* 빌드 시 주입 (build args) + +node:22-alpine (runtime) + └─ .next/standalone + .next/static + public/ + └─ node server.js + └─ EXPOSE 3001 +``` + +--- + +## K8s 리소스 구성 + +### 파일 구조 + +``` +k8s/ +├── namespace.yaml # tasteby namespace +├── configmap.yaml # 비밀이 아닌 설정 +├── secrets.yaml.template # 시크릿 템플릿 (실제 파일은 .gitignore) +├── redis-deployment.yaml # Redis 7 + Service +├── backend-deployment.yaml # Spring Boot + Service +├── frontend-deployment.yaml # Next.js + Service +├── ingress.yaml # Nginx Ingress + TLS +└── cert-manager/ + └── cluster-issuer.yaml # Let's Encrypt ClusterIssuer +``` + +### 리소스 할당 + +| Pod | replicas | CPU req/lim | Memory req/lim | +|-----|----------|-------------|----------------| +| backend | 1 | 500m / 1 | 768Mi / 1536Mi | +| frontend | 1 | 200m / 500m | 256Mi / 512Mi | +| redis | 1 | 100m / 200m | 128Mi / 256Mi | +| ingress-controller | 1 | 100m / 200m | 128Mi / 256Mi | +| cert-manager (×3) | 1 each | 50m / 100m | 64Mi / 128Mi | +| **합계** | | **~1.2 CPU** | **~1.6GB** | + +클러스터: ARM64 × 2 노드 (4 CPU / 16GB 총) → 여유 충분 + +### 네트워크 흐름 + +``` +Client → NLB:443 → Ingress Controller → /api/* → backend:8000 + → /* → frontend:3001 + +backend → redis:6379 (K8s Service DNS, 클러스터 내부) +backend → Oracle ADB (mTLS, Wallet Volume Mount) +backend → OCI GenAI (OCI SDK, oci-config Volume Mount) +backend → Google APIs (API Key, 환경변수) +``` + +### Volume Mounts (backend) + +``` +/etc/oracle/wallet/ ← Secret: oracle-wallet + ├── cwallet.sso + ├── tnsnames.ora + ├── sqlnet.ora + ├── keystore.jks + ├── truststore.jks + └── ojdbc.properties + +/root/.oci/ ← Secret: oci-config + ├── config + └── oci_api_key.pem +``` + +--- + +## 환경 분리 + +``` +┌────────────────────────────┬──────────────────────────────────────┐ +│ 개발 (Local) │ 운영 (OKE) │ +├────────────────────────────┼──────────────────────────────────────┤ +│ backend/.env │ ConfigMap + Secret │ +│ frontend/.env.local │ Dockerfile build args │ +│ ~/.oci/config │ Secret: oci-config → /root/.oci/ │ +│ 로컬 Wallet 디렉토리 │ Secret: oracle-wallet → /etc/oracle/ │ +│ Redis: 192.168.0.147:6379 │ Redis: redis:6379 (K8s DNS) │ +│ PM2로 프로세스 관리 │ K8s Deployment로 관리 │ +│ nginx + certbot (SSL) │ Ingress + cert-manager (SSL) │ +└────────────────────────────┴──────────────────────────────────────┘ +``` + +**변수명이 동일**하므로 코드 변경 없이 환경만 교체 가능합니다. +자세한 환경변수 목록은 [environment-guide.md](./environment-guide.md) 참고. + +--- + +## OCI 리소스 정보 + +| 항목 | 값 | +|------|-----| +| 리전 | ap-seoul-1 (Seoul) | +| OCI 프로필 | JOUNGMINKOAWS | +| OKE 클러스터 | tasteby-cluster | +| OCIR Registry | icn.ocir.io/idyhsdamac8c/tasteby | +| DevOps 프로젝트 | tasteby | +| Code Repository | tasteby (OCI DevOps SCM) | +| 도메인 | www.tasteby.net (Namecheap) | +| SSL | Let's Encrypt (cert-manager HTTP-01) | +| 노드 | ARM64 × 2대 (2 CPU / 8GB 각) | + +--- + +## 배포 버전 관리 + +- 태그 형식: `v0.1.X` (patch 자동 증가) +- deploy.sh가 자동으로 git tag 생성 + push +- 태그 메시지에 배포 대상(backend/frontend)과 이미지 태그 포함 + +```bash +# 태그 목록 확인 +git tag -l 'v*' --sort=-v:refname + +# 특정 태그 상세 확인 +git tag -n20 v0.1.5 + +# 롤백 +kubectl rollout undo deployment/backend -n tasteby +``` + +--- + +## 관련 문서 + +- [OKE 배포 가이드](./oke-deployment-guide.md) — 인프라 설치 및 배포 절차 +- [환경 관리 가이드](./environment-guide.md) — 환경변수 및 시크릿 관리