Compare commits
3 Commits
293c59060c
...
v0.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ad09e5b67 | ||
|
|
7a896c8c56 | ||
|
|
745913ca5b |
4
backend-java/.dockerignore
Normal file
4
backend-java/.dockerignore
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
build/
|
||||||
|
.gradle/
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
61
build_spec.yaml
Normal file
61
build_spec.yaml
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
version: 0.1
|
||||||
|
component: build
|
||||||
|
timeoutInSeconds: 1800
|
||||||
|
runAs: root
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
env:
|
||||||
|
variables:
|
||||||
|
REGISTRY: "icn.ocir.io/idyhsdamac8c/tasteby"
|
||||||
|
exportedVariables:
|
||||||
|
- IMAGE_TAG
|
||||||
|
- BACKEND_IMAGE
|
||||||
|
- FRONTEND_IMAGE
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- type: Command
|
||||||
|
name: "Setup buildx for ARM64"
|
||||||
|
command: |
|
||||||
|
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||||
|
docker buildx create --name armbuilder --use
|
||||||
|
docker buildx inspect --bootstrap
|
||||||
|
|
||||||
|
- 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 buildx build --platform linux/arm64 \
|
||||||
|
-t "${BACKEND_IMAGE}" \
|
||||||
|
-t "${REGISTRY}/backend:latest" \
|
||||||
|
--load \
|
||||||
|
.
|
||||||
|
|
||||||
|
- type: Command
|
||||||
|
name: "Build frontend image"
|
||||||
|
command: |
|
||||||
|
cd frontend
|
||||||
|
docker buildx 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" \
|
||||||
|
--load \
|
||||||
|
.
|
||||||
|
|
||||||
|
outputArtifacts:
|
||||||
|
- name: backend-image
|
||||||
|
type: DOCKER_IMAGE
|
||||||
|
location: ${BACKEND_IMAGE}
|
||||||
|
- name: frontend-image
|
||||||
|
type: DOCKER_IMAGE
|
||||||
|
location: ${FRONTEND_IMAGE}
|
||||||
405
docs/cicd-architecture.md
Normal file
405
docs/cicd-architecture.md
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
# 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하여 소스와 빌드를 동기화합니다.
|
||||||
|
|
||||||
|
### OCI IAM 권한 설정 (빌드/배포용)
|
||||||
|
|
||||||
|
OCI DevOps Build Pipeline이 코드 레포, OCIR, 시크릿 등에 접근하려면 **Dynamic Group**과 **IAM Policy**가 필요합니다.
|
||||||
|
|
||||||
|
#### Dynamic Group
|
||||||
|
|
||||||
|
| 이름 | 설명 |
|
||||||
|
|------|------|
|
||||||
|
| `tasteby-build-pipeline` | DevOps 빌드/배포 파이프라인 리소스 |
|
||||||
|
|
||||||
|
**Matching Rule:**
|
||||||
|
```
|
||||||
|
ANY {
|
||||||
|
resource.type = 'devopsbuildpipeline',
|
||||||
|
resource.type = 'devopsrepository',
|
||||||
|
resource.type = 'devopsdeploypipeline',
|
||||||
|
resource.type = 'devopsconnection'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### IAM Policy
|
||||||
|
|
||||||
|
| 이름 | 설명 |
|
||||||
|
|------|------|
|
||||||
|
| `tasteby-devops-policy` | DevOps 파이프라인 리소스 접근 권한 |
|
||||||
|
|
||||||
|
**Policy Statements:**
|
||||||
|
```
|
||||||
|
Allow dynamic-group tasteby-build-pipeline to manage devops-family in tenancy
|
||||||
|
Allow dynamic-group tasteby-build-pipeline to manage repos in tenancy
|
||||||
|
Allow dynamic-group tasteby-build-pipeline to read secret-family in tenancy
|
||||||
|
Allow dynamic-group tasteby-build-pipeline to manage generic-artifacts in tenancy
|
||||||
|
Allow dynamic-group tasteby-build-pipeline to use ons-topics in tenancy
|
||||||
|
```
|
||||||
|
|
||||||
|
> **참고**: IAM 정책은 적용 후 전파에 최대 수 분이 걸릴 수 있습니다.
|
||||||
|
> 빌드 실행 시 `RelatedResourceNotAuthorizedOrNotFound` 오류가 나면 정책 전파를 기다린 후 재시도하세요.
|
||||||
|
|
||||||
|
#### OCI Code Repository 인증 (HTTPS)
|
||||||
|
|
||||||
|
```
|
||||||
|
Username: <tenancy-name>/oracleidentitycloudservice/<oci-username>
|
||||||
|
Password: OCI Auth Token (User Settings에서 생성)
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Git remote 추가 예시
|
||||||
|
git remote add oci https://devops.scmservice.ap-seoul-1.oci.oraclecloud.com/namespaces/<namespace>/projects/tasteby/repositories/tasteby
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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) — 환경변수 및 시크릿 관리
|
||||||
239
docs/troubleshooting.md
Normal file
239
docs/troubleshooting.md
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
# Tasteby 배포 트러블슈팅 기록
|
||||||
|
|
||||||
|
## 1. OCI DevOps Build Pipeline - 코드 접근 권한 오류
|
||||||
|
|
||||||
|
**오류:**
|
||||||
|
```
|
||||||
|
Unable to fetch build_spec file build_spec.yaml due to RelatedResourceNotAuthorizedOrNotFound.
|
||||||
|
Please check if dynamic groups and the corresponding policies are properly configured.
|
||||||
|
```
|
||||||
|
|
||||||
|
**원인:** OCI DevOps Build Pipeline이 Code Repository에 접근할 IAM 권한이 없음
|
||||||
|
|
||||||
|
**해결:**
|
||||||
|
1. Dynamic Group 생성 — 빌드 파이프라인 리소스를 포함하는 매칭 룰:
|
||||||
|
```
|
||||||
|
ANY {
|
||||||
|
resource.type = 'devopsbuildpipeline',
|
||||||
|
resource.type = 'devopsrepository',
|
||||||
|
resource.type = 'devopsdeploypipeline',
|
||||||
|
resource.type = 'devopsconnection'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
2. IAM Policy 생성:
|
||||||
|
```
|
||||||
|
Allow dynamic-group tasteby-build-pipeline to manage devops-family in tenancy
|
||||||
|
Allow dynamic-group tasteby-build-pipeline to manage repos in tenancy
|
||||||
|
Allow dynamic-group tasteby-build-pipeline to read secret-family in tenancy
|
||||||
|
Allow dynamic-group tasteby-build-pipeline to manage generic-artifacts in tenancy
|
||||||
|
Allow dynamic-group tasteby-build-pipeline to use ons-topics in tenancy
|
||||||
|
```
|
||||||
|
3. IAM 정책 전파에 수 분 소요 — 적용 후 바로 빌드하면 동일 오류 발생할 수 있음
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. OCI DevOps Build Pipeline - Logs 미설정
|
||||||
|
|
||||||
|
**오류:**
|
||||||
|
```
|
||||||
|
Logs need to be enabled in order to run the builds.
|
||||||
|
Please enable logs for your project.
|
||||||
|
```
|
||||||
|
|
||||||
|
**원인:** DevOps 프로젝트에 OCI Logging이 설정되지 않음
|
||||||
|
|
||||||
|
**해결:**
|
||||||
|
1. OCI Logging > Log Group 생성 (예: `tasteby-devops-logs`)
|
||||||
|
2. Log Group에 Service Log 생성:
|
||||||
|
- Source: `devops` 서비스
|
||||||
|
- Resource: DevOps 프로젝트 OCID
|
||||||
|
- Category: `all`
|
||||||
|
3. DevOps 프로젝트에 Notification Topic 설정 필요
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. OCI DevOps Build Pipeline - ARM64 이미지 빌드 불가
|
||||||
|
|
||||||
|
**오류:**
|
||||||
|
```
|
||||||
|
Step 'Step_CommandV1_2' failed with exit code: '1' (docker build --platform linux/arm64)
|
||||||
|
Step 'Step_CommandV1_1' failed with exit code: '125' (QEMU 설정 시도)
|
||||||
|
```
|
||||||
|
|
||||||
|
**원인:**
|
||||||
|
- OCI DevOps Managed Build는 x86_64 러너만 제공 (`OL7_X86_64_STANDARD_10`)
|
||||||
|
- ARM64 이미지를 직접 빌드할 수 없음
|
||||||
|
- `--privileged` 모드가 허용되지 않아 QEMU 크로스빌드도 불가
|
||||||
|
|
||||||
|
**해결:**
|
||||||
|
- Colima (macOS 경량 Docker) 설치로 로컬 ARM64 빌드:
|
||||||
|
```bash
|
||||||
|
brew install colima docker
|
||||||
|
colima start --arch aarch64 --cpu 2 --memory 4
|
||||||
|
```
|
||||||
|
- deploy.sh로 로컬 빌드 → OCIR push → K8s 배포
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. OCI Code Repository - HTTPS 인증 실패
|
||||||
|
|
||||||
|
**오류:**
|
||||||
|
```
|
||||||
|
fatal: Authentication failed for 'https://devops.scmservice.ap-seoul-1.oci.oraclecloud.com/...'
|
||||||
|
```
|
||||||
|
|
||||||
|
**원인:** OCI Code Repository HTTPS 인증의 username 형식이 특수함
|
||||||
|
|
||||||
|
**해결:**
|
||||||
|
- IDCS 연동 사용자의 경우 username 형식:
|
||||||
|
```
|
||||||
|
<tenancy-name>/oracleidentitycloudservice/<oci-username>
|
||||||
|
```
|
||||||
|
예시: `joungminkoaws/oracleidentitycloudservice/joungmin.ko.aws@gmail.com`
|
||||||
|
- Password: OCI Auth Token (User Settings > Auth Tokens에서 생성)
|
||||||
|
- `idyhsdamac8c` (namespace)가 아닌 `joungminkoaws` (tenancy name)을 사용해야 함
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Docker 빌드 컨텍스트 과대 (276MB)
|
||||||
|
|
||||||
|
**증상:**
|
||||||
|
```
|
||||||
|
Sending build context to Docker daemon 276.4MB
|
||||||
|
```
|
||||||
|
|
||||||
|
**원인:** `.dockerignore` 파일이 없어 `build/`, `.gradle/`, `node_modules/` 등이 포함됨
|
||||||
|
|
||||||
|
**해결:**
|
||||||
|
- `backend-java/.dockerignore` 생성:
|
||||||
|
```
|
||||||
|
build/
|
||||||
|
.gradle/
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
```
|
||||||
|
- `frontend/.dockerignore` 생성:
|
||||||
|
```
|
||||||
|
node_modules/
|
||||||
|
.next/
|
||||||
|
.env.local
|
||||||
|
```
|
||||||
|
- 결과: 276MB → 336KB (backend), 602KB (frontend)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Redis ImageInspectError (OKE CRI-O)
|
||||||
|
|
||||||
|
**오류:**
|
||||||
|
```
|
||||||
|
Failed to inspect image "": rpc error: code = Unknown desc = short name mode is enforcing,
|
||||||
|
but image name redis:7-alpine returns ambiguous list
|
||||||
|
```
|
||||||
|
|
||||||
|
**원인:** OKE는 CRI-O 컨테이너 런타임을 사용하며, short name (예: `redis:7-alpine`)을 허용하지 않음
|
||||||
|
|
||||||
|
**해결:**
|
||||||
|
- 이미지명에 full registry prefix 추가:
|
||||||
|
```yaml
|
||||||
|
# 변경 전
|
||||||
|
image: redis:7-alpine
|
||||||
|
# 변경 후
|
||||||
|
image: docker.io/library/redis:7-alpine
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. OCIR ImagePullBackOff (K8s)
|
||||||
|
|
||||||
|
**오류:**
|
||||||
|
```
|
||||||
|
Failed to pull image "icn.ocir.io/.../backend:latest": unable to retrieve auth token:
|
||||||
|
invalid username/password: unknown: Unauthorized
|
||||||
|
```
|
||||||
|
|
||||||
|
**원인:** K8s `ocir-secret`의 username 형식이 잘못됨
|
||||||
|
|
||||||
|
**해결:**
|
||||||
|
- IDCS 사용자의 경우 OCIR pull secret 생성 시:
|
||||||
|
```bash
|
||||||
|
kubectl create secret docker-registry ocir-secret \
|
||||||
|
--docker-server=icn.ocir.io \
|
||||||
|
--docker-username='<namespace>/oracleidentitycloudservice/<username>' \
|
||||||
|
--docker-password='<auth-token>' \
|
||||||
|
-n tasteby
|
||||||
|
```
|
||||||
|
- Docker login 시에도 동일한 형식:
|
||||||
|
```bash
|
||||||
|
docker login icn.ocir.io \
|
||||||
|
-u "<namespace>/oracleidentitycloudservice/<username>"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. kubectl 인증 실패
|
||||||
|
|
||||||
|
**오류:**
|
||||||
|
```
|
||||||
|
error: You must be logged in to the server (Unauthorized)
|
||||||
|
```
|
||||||
|
|
||||||
|
**원인:** kubeconfig 생성 시 OCI 프로필이 지정되지 않음
|
||||||
|
|
||||||
|
**해결:**
|
||||||
|
- `~/.kube/config`의 user args에 `--profile JOUNGMINKOAWS` 추가:
|
||||||
|
```yaml
|
||||||
|
users:
|
||||||
|
- name: user-xxx
|
||||||
|
user:
|
||||||
|
exec:
|
||||||
|
args:
|
||||||
|
- ce
|
||||||
|
- cluster
|
||||||
|
- generate-token
|
||||||
|
- --cluster-id
|
||||||
|
- ocid1.cluster.oc1...
|
||||||
|
- --region
|
||||||
|
- ap-seoul-1
|
||||||
|
- --profile # ← 추가
|
||||||
|
- JOUNGMINKOAWS # ← 추가
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Let's Encrypt 인증서 발급 실패 - Timeout during connect
|
||||||
|
|
||||||
|
**오류:**
|
||||||
|
```
|
||||||
|
acme: authorization error for www.tasteby.net: 400 urn:ietf:params:acme:error:connection:
|
||||||
|
64.110.90.89: Timeout during connect (likely firewall problem)
|
||||||
|
```
|
||||||
|
|
||||||
|
**원인:** OKE VCN의 Security List에서 LB 서브넷으로의 80/443 포트가 열려있지 않음
|
||||||
|
|
||||||
|
**해결:**
|
||||||
|
1. **LB 서브넷 Security List**에 Ingress 규칙 추가:
|
||||||
|
- `0.0.0.0/0` → TCP 80 (HTTP)
|
||||||
|
- `0.0.0.0/0` → TCP 443 (HTTPS)
|
||||||
|
- Egress: `0.0.0.0/0` → All protocols
|
||||||
|
|
||||||
|
2. **노드 서브넷 Security List**에 LB→노드 Ingress 규칙 추가:
|
||||||
|
- `10.0.20.0/24` (LB 서브넷 CIDR) → TCP 30000-32767 (NodePort)
|
||||||
|
- `10.0.20.0/24` → TCP 10256 (Health check)
|
||||||
|
|
||||||
|
3. 인증서 재발급:
|
||||||
|
```bash
|
||||||
|
kubectl delete certificate tasteby-tls -n tasteby
|
||||||
|
kubectl apply -f k8s/ingress.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. tasteby.net (root domain) DNS 미전파
|
||||||
|
|
||||||
|
**증상:** www.tasteby.net은 되지만 tasteby.net challenge가 `pending` 상태
|
||||||
|
|
||||||
|
**원인:** Namecheap에서 @ (root) A 레코드가 설정되지 않았거나 전파가 안 됨
|
||||||
|
|
||||||
|
**해결:**
|
||||||
|
- Ingress TLS에서 www.tasteby.net만 먼저 설정하여 인증서 발급
|
||||||
|
- root domain DNS 전파 완료 후 TLS hosts에 tasteby.net 추가하고 인증서 재발급
|
||||||
3
frontend/.dockerignore
Normal file
3
frontend/.dockerignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
node_modules/
|
||||||
|
.next/
|
||||||
|
.env.local
|
||||||
@@ -16,7 +16,6 @@ spec:
|
|||||||
tls:
|
tls:
|
||||||
- hosts:
|
- hosts:
|
||||||
- www.tasteby.net
|
- www.tasteby.net
|
||||||
- tasteby.net
|
|
||||||
secretName: tasteby-tls
|
secretName: tasteby-tls
|
||||||
rules:
|
rules:
|
||||||
- host: www.tasteby.net
|
- host: www.tasteby.net
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: redis
|
- name: redis
|
||||||
image: redis:7-alpine
|
image: docker.io/library/redis:7-alpine
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 6379
|
- containerPort: 6379
|
||||||
resources:
|
resources:
|
||||||
|
|||||||
Reference in New Issue
Block a user