Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f80418f5ee | |||
| 108ee10293 | |||
| 20e0d24930 | |||
| 640e94bf17 | |||
| 98ed508a6f | |||
| e8d0a1d4b4 | |||
| 857fa5501c | |||
| ab39c76585 | |||
| 1e38d52967 | |||
| 6205a98ca0 | |||
| ebd7ae12b6 | |||
| 2cd1ba76f5 | |||
| a41e431daf | |||
| 3da9303091 | |||
| 3075a5d49f | |||
| 2bce7cfb24 | |||
| bcfbb6c7f9 | |||
| da173d79e9 | |||
| 7711f2d527 | |||
| edcb519800 | |||
| e7ffdcfe44 | |||
| 95a419f104 | |||
| 1b73d2880b | |||
| 5a93205f30 | |||
| df04f85346 | |||
| 23265b5849 | |||
| 8fef09df02 | |||
| 63ba449f93 | |||
| 64aec0fda5 | |||
| 436c0bf2b8 | |||
| ea026d7fa3 | |||
| 019ac96daa | |||
| bc57b27852 | |||
| b9514257b0 | |||
| 977a287a91 | |||
| 3f0eccb69a | |||
| f30213d1a2 | |||
| 284278180c | |||
| 9438e0d285 | |||
| 02a4e966e8 | |||
| d36dc5be27 | |||
| 9305dfdb7f | |||
| d511140ecb | |||
| 4421f4447f | |||
| 5a82fe3610 | |||
| 02fd82e0af | |||
| 0c718c67f6 | |||
| ea4aa5d072 | |||
| e807bdbd59 | |||
| cf2689390d | |||
| 89a86c1301 | |||
| c768fff11e | |||
| f07002ac33 | |||
| 2ca453f89e | |||
| e2179daaf7 | |||
| de32a70f29 | |||
| 435ba1a86c | |||
| 16a91c85bf | |||
| 429f737066 | |||
| 7a99dc95fe | |||
| d56ff7684b | |||
| c152faff54 | |||
| ee664a6134 | |||
| 50043add5d | |||
| d89ee4edf7 | |||
| 397a23063d | |||
| 5f8bd7cf68 | |||
| bea547a463 | |||
| c126c71e00 | |||
| 29dddd89b7 | |||
| e0fc4286c7 | |||
| 2da2f124a2 | |||
| 453f77ef01 | |||
| 375fcb390b | |||
| 55e546e0b3 | |||
| f0699b2e2b | |||
| 828a76b630 | |||
| 074be336d6 | |||
| b0d8a6d10e | |||
| 9f50c7feaa | |||
| 28a7a91ca2 |
@@ -1,10 +1,13 @@
|
||||
---
|
||||
command: "/deploy-actions-cicd-guide-back"
|
||||
description: "백엔드 GitHub Actions CI/CD 파이프라인 가이드 작성"
|
||||
---
|
||||
|
||||
@cicd
|
||||
'백엔드GitHubActions파이프라인작성가이드'에 따라 GitHub Actions를 이용한 CI/CD 가이드를 작성해 주세요.
|
||||
|
||||
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
|
||||
|
||||
{안내메시지}
|
||||
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
|
||||
[실행정보]
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
---
|
||||
command: "/deploy-actions-cicd-guide-front"
|
||||
description: "프론트엔드 GitHub Actions CI/CD 파이프라인 가이드 작성"
|
||||
---
|
||||
|
||||
@cicd
|
||||
'프론트엔드GitHubActions파이프라인작성가이드'에 따라 GitHub Actions를 이용한 CI/CD 가이드를 작성해 주세요.
|
||||
|
||||
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
|
||||
|
||||
{안내메시지}
|
||||
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
|
||||
[실행정보]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
command: "/deploy-build-image-back"
|
||||
description: "백엔드 컨테이너 이미지 작성"
|
||||
---
|
||||
|
||||
@cicd
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
command: "/deploy-build-image-front"
|
||||
description: "프론트엔드 컨테이너 이미지 작성"
|
||||
---
|
||||
|
||||
@cicd
|
||||
|
||||
@@ -1,81 +1,64 @@
|
||||
---
|
||||
command: "/deploy-help"
|
||||
description: "배포 작업 순서 및 명령어 안내"
|
||||
---
|
||||
|
||||
# 배포 작업 순서
|
||||
|
||||
## 1단계: 컨테이너 이미지 작성
|
||||
## 컨테이너 이미지 작성
|
||||
### 백엔드
|
||||
```
|
||||
/deploy-build-image-back
|
||||
```
|
||||
- 백엔드컨테이너이미지작성가이드를 참고하여 컨테이너 이미지를 빌드합니다
|
||||
- 백엔드 서비스들의 컨테이너 이미지를 작성합니다
|
||||
|
||||
### 프론트엔드
|
||||
```
|
||||
/deploy-build-image-front
|
||||
```
|
||||
- 프론트엔드컨테이너이미지작성가이드를 참고하여 컨테이너 이미지를 빌드합니다
|
||||
- 프론트엔드 서비스의 컨테이너 이미지를 작성합니다
|
||||
|
||||
## 2단계: 컨테이너 실행 가이드 작성
|
||||
## 컨테이너 실행 가이드 작성
|
||||
### 백엔드
|
||||
```
|
||||
/deploy-run-container-guide-back
|
||||
```
|
||||
- 백엔드컨테이너실행방법가이드를 참고하여 컨테이너 실행 방법을 작성합니다
|
||||
- 실행정보(ACR명, VM정보)가 필요합니다
|
||||
- 백엔드 컨테이너 실행 가이드를 작성합니다
|
||||
- [실행정보] 섹션에 ACR명, VM 접속 정보 제공 필요
|
||||
|
||||
### 프론트엔드
|
||||
```
|
||||
/deploy-run-container-guide-front
|
||||
```
|
||||
- 프론트엔드컨테이너실행방법가이드를 참고하여 컨테이너 실행 방법을 작성합니다
|
||||
- 실행정보(시스템명, ACR명, VM정보)가 필요합니다
|
||||
- 프론트엔드 컨테이너 실행 가이드를 작성합니다
|
||||
- [실행정보] 섹션에 시스템명, ACR명, VM 접속 정보 제공 필요
|
||||
|
||||
## 3단계: Kubernetes 배포 가이드 작성
|
||||
## Kubernetes 배포 가이드 작성
|
||||
### 백엔드
|
||||
```
|
||||
/deploy-k8s-guide-back
|
||||
```
|
||||
- 백엔드배포가이드를 참고하여 쿠버네티스 배포 방법을 작성합니다
|
||||
- 실행정보(ACR명, k8s명, 네임스페이스, 리소스 설정)가 필요합니다
|
||||
- 백엔드 서비스 Kubernetes 배포 가이드를 작성합니다
|
||||
- [실행정보] 섹션에 ACR명, k8s명, 네임스페이스, 리소스 정보 제공 필요
|
||||
|
||||
### 프론트엔드
|
||||
```
|
||||
/deploy-k8s-guide-front
|
||||
```
|
||||
- 프론트엔드배포가이드를 참고하여 쿠버네티스 배포 방법을 작성합니다
|
||||
- 실행정보(시스템명, ACR명, k8s명, 네임스페이스, Gateway Host, 리소스 설정)가 필요합니다
|
||||
- 프론트엔드 서비스 Kubernetes 배포 가이드를 작성합니다
|
||||
- [실행정보] 섹션에 시스템명, ACR명, k8s명, 네임스페이스, Gateway Host 정보 제공 필요
|
||||
|
||||
## 4단계: CI/CD 파이프라인 구성
|
||||
|
||||
### Jenkins 사용 시
|
||||
## CI/CD 파이프라인 작성
|
||||
### Jenkins CI/CD
|
||||
#### 백엔드
|
||||
```
|
||||
/deploy-jenkins-cicd-guide-back
|
||||
```
|
||||
- 백엔드Jenkins파이프라인작성가이드를 참고하여 Jenkins CI/CD 파이프라인을 구성합니다
|
||||
- Jenkins를 이용한 백엔드 CI/CD 파이프라인 가이드를 작성합니다
|
||||
- [실행정보] 섹션에 ACR_NAME, RESOURCE_GROUP, AKS_CLUSTER, NAMESPACE 제공 필요
|
||||
|
||||
#### 프론트엔드
|
||||
```
|
||||
/deploy-jenkins-cicd-guide-front
|
||||
```
|
||||
- 프론트엔드Jenkins파이프라인작성가이드를 참고하여 Jenkins CI/CD 파이프라인을 구성합니다
|
||||
- Jenkins를 이용한 프론트엔드 CI/CD 파이프라인 가이드를 작성합니다
|
||||
- [실행정보] 섹션에 SYSTEM_NAME, ACR_NAME, RESOURCE_GROUP, AKS_CLUSTER, NAMESPACE 제공 필요
|
||||
|
||||
### GitHub Actions 사용 시
|
||||
### GitHub Actions CI/CD
|
||||
#### 백엔드
|
||||
```
|
||||
/deploy-actions-cicd-guide-back
|
||||
```
|
||||
- 백엔드GitHubActions파이프라인작성가이드를 참고하여 GitHub Actions CI/CD 파이프라인을 구성합니다
|
||||
- GitHub Actions를 이용한 백엔드 CI/CD 파이프라인 가이드를 작성합니다
|
||||
- [실행정보] 섹션에 ACR_NAME, RESOURCE_GROUP, AKS_CLUSTER, NAMESPACE 제공 필요
|
||||
|
||||
#### 프론트엔드
|
||||
```
|
||||
/deploy-actions-cicd-guide-front
|
||||
```
|
||||
- 프론트엔드GitHubActions파이프라인작성가이드를 참고하여 GitHub Actions CI/CD 파이프라인을 구성합니다
|
||||
- GitHub Actions를 이용한 프론트엔드 CI/CD 파이프라인 가이드를 작성합니다
|
||||
- [실행정보] 섹션에 SYSTEM_NAME, ACR_NAME, RESOURCE_GROUP, AKS_CLUSTER, NAMESPACE 제공 필요
|
||||
|
||||
## 참고사항
|
||||
- 각 명령 실행 전 필요한 실행정보를 프롬프트에 포함해야 합니다
|
||||
- 실행정보가 없으면 안내 메시지가 표시되며 작업이 중단됩니다
|
||||
- CI/CD 도구는 Jenkins 또는 GitHub Actions 중 선택하여 사용합니다
|
||||
---
|
||||
|
||||
**참고**: 각 명령어 실행 시 [실행정보] 섹션에 필요한 정보를 함께 제공해야 합니다.
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
---
|
||||
command: "/deploy-jenkins-cicd-guide-back"
|
||||
description: "백엔드 Jenkins CI/CD 파이프라인 가이드 작성"
|
||||
---
|
||||
|
||||
@cicd
|
||||
'백엔드Jenkins파이프라인작성가이드'에 따라 Jenkins를 이용한 CI/CD 가이드를 작성해 주세요.
|
||||
|
||||
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
|
||||
|
||||
{안내메시지}
|
||||
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
|
||||
[실행정보]
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
---
|
||||
command: "/deploy-jenkins-cicd-guide-front"
|
||||
description: "프론트엔드 Jenkins CI/CD 파이프라인 가이드 작성"
|
||||
---
|
||||
|
||||
@cicd
|
||||
'프론트엔드Jenkins파이프라인작성가이드'에 따라 Jenkins를 이용한 CI/CD 가이드를 작성해 주세요.
|
||||
|
||||
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
|
||||
|
||||
{안내메시지}
|
||||
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
|
||||
[실행정보]
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
---
|
||||
command: "/deploy-k8s-guide-back"
|
||||
description: "백엔드 Kubernetes 배포 가이드 작성"
|
||||
---
|
||||
|
||||
@cicd
|
||||
'백엔드배포가이드'에 따라 백엔드 서비스 배포 방법을 작성해 주세요.
|
||||
|
||||
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
|
||||
|
||||
{안내메시지}
|
||||
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
|
||||
[실행정보]
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
---
|
||||
command: "/deploy-k8s-guide-front"
|
||||
description: "프론트엔드 Kubernetes 배포 가이드 작성"
|
||||
---
|
||||
|
||||
@cicd
|
||||
'프론트엔드배포가이드'에 따라 프론트엔드 서비스 배포 방법을 작성해 주세요.
|
||||
|
||||
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
|
||||
|
||||
{안내메시지}
|
||||
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
|
||||
[실행정보]
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
---
|
||||
command: "/deploy-run-container-guide-back"
|
||||
description: "백엔드 컨테이너 실행방법 가이드 작성"
|
||||
---
|
||||
|
||||
@cicd
|
||||
'백엔드컨테이너실행방법가이드'에 따라 컨테이너 실행 가이드를 작성해 주세요.
|
||||
|
||||
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
|
||||
|
||||
{안내메시지}
|
||||
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
|
||||
[실행정보]
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
---
|
||||
command: "/deploy-run-container-guide-front"
|
||||
description: "프론트엔드 컨테이너 실행방법 가이드 작성"
|
||||
---
|
||||
|
||||
@cicd
|
||||
'프론트엔드컨테이너실행방법가이드'에 따라 컨테이너 실행 가이드를 작성해 주세요.
|
||||
|
||||
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
|
||||
|
||||
{안내메시지}
|
||||
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
|
||||
[실행정보]
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
# KT Event Marketing - CI/CD Infrastructure
|
||||
|
||||
이 디렉토리는 KT Event Marketing 백엔드 서비스의 CI/CD 인프라를 포함합니다.
|
||||
|
||||
## 디렉토리 구조
|
||||
|
||||
```
|
||||
.github/
|
||||
├── README.md # 이 파일
|
||||
├── workflows/
|
||||
│ └── backend-cicd.yaml # GitHub Actions 워크플로우
|
||||
├── kustomize/ # Kubernetes 매니페스트 관리
|
||||
│ ├── base/ # 기본 리소스 정의
|
||||
│ │ ├── kustomization.yaml
|
||||
│ │ ├── cm-common.yaml
|
||||
│ │ ├── secret-common.yaml
|
||||
│ │ ├── secret-imagepull.yaml
|
||||
│ │ ├── ingress.yaml
|
||||
│ │ └── {service}-*.yaml # 각 서비스별 리소스
|
||||
│ └── overlays/ # 환경별 설정
|
||||
│ ├── dev/
|
||||
│ │ ├── kustomization.yaml
|
||||
│ │ └── *-patch.yaml # 1 replica, 256Mi-1024Mi
|
||||
│ ├── staging/
|
||||
│ │ ├── kustomization.yaml
|
||||
│ │ └── *-patch.yaml # 2 replicas, 512Mi-2048Mi
|
||||
│ └── prod/
|
||||
│ ├── kustomization.yaml
|
||||
│ └── *-patch.yaml # 3 replicas, 1024Mi-4096Mi
|
||||
├── config/
|
||||
│ ├── deploy_env_vars_dev # Dev 환경 변수
|
||||
│ ├── deploy_env_vars_staging # Staging 환경 변수
|
||||
│ └── deploy_env_vars_prod # Prod 환경 변수
|
||||
└── scripts/
|
||||
├── deploy.sh # 수동 배포 스크립트
|
||||
├── generate-patches.sh # 패치 파일 생성 스크립트
|
||||
└── copy-manifests-to-base.py # 매니페스트 복사 스크립트
|
||||
```
|
||||
|
||||
## 주요 파일 설명
|
||||
|
||||
### workflows/backend-cicd.yaml
|
||||
GitHub Actions 워크플로우 정의 파일입니다.
|
||||
|
||||
**트리거**:
|
||||
- develop 브랜치 push → dev 환경 배포
|
||||
- main 브랜치 push → prod 환경 배포
|
||||
- Manual workflow dispatch → 원하는 환경과 서비스 선택
|
||||
|
||||
**Jobs**:
|
||||
1. `detect-changes`: 변경된 서비스 감지
|
||||
2. `build-and-push`: 서비스 빌드 및 ACR 푸시
|
||||
3. `deploy`: AKS에 배포
|
||||
4. `notify`: 배포 결과 알림
|
||||
|
||||
### kustomize/base/kustomization.yaml
|
||||
모든 환경에서 공통으로 사용하는 기본 리소스를 정의합니다.
|
||||
|
||||
**포함 리소스**:
|
||||
- Common ConfigMaps and Secrets
|
||||
- Ingress
|
||||
- 7개 서비스의 Deployment, Service, ConfigMap, Secret
|
||||
|
||||
### kustomize/overlays/{env}/kustomization.yaml
|
||||
환경별 설정을 오버라이드합니다.
|
||||
|
||||
**주요 차이점**:
|
||||
- 이미지 태그 (dev/staging/prod)
|
||||
- Replica 수 (1/2/3)
|
||||
- 리소스 할당량 (작음/중간/큼)
|
||||
|
||||
### scripts/deploy.sh
|
||||
로컬에서 수동 배포를 위한 스크립트입니다.
|
||||
|
||||
**사용법**:
|
||||
```bash
|
||||
# 모든 서비스를 dev 환경에 배포
|
||||
./scripts/deploy.sh dev
|
||||
|
||||
# 특정 서비스만 prod 환경에 배포
|
||||
./scripts/deploy.sh prod user-service
|
||||
```
|
||||
|
||||
## 배포 프로세스
|
||||
|
||||
### 자동 배포 (GitHub Actions)
|
||||
|
||||
1. **Dev 환경**:
|
||||
```bash
|
||||
git checkout develop
|
||||
git push origin develop
|
||||
```
|
||||
|
||||
2. **Prod 환경**:
|
||||
```bash
|
||||
git checkout main
|
||||
git merge develop
|
||||
git push origin main
|
||||
```
|
||||
|
||||
3. **수동 배포**:
|
||||
- GitHub Actions UI → Run workflow
|
||||
- Environment 선택 (dev/staging/prod)
|
||||
- Service 선택 (all 또는 특정 서비스)
|
||||
|
||||
### 수동 배포 (로컬)
|
||||
|
||||
```bash
|
||||
# 사전 요구사항: Azure CLI, kubectl, kustomize 설치
|
||||
# Azure 로그인 필요
|
||||
|
||||
# Dev 환경에 모든 서비스 배포
|
||||
./.github/scripts/deploy.sh dev
|
||||
|
||||
# Prod 환경에 user-service만 배포
|
||||
./.github/scripts/deploy.sh prod user-service
|
||||
```
|
||||
|
||||
## 환경별 설정
|
||||
|
||||
| 환경 | 브랜치 | 이미지 태그 | Replicas | CPU Request | Memory Request |
|
||||
|------|--------|-------------|----------|-------------|----------------|
|
||||
| Dev | develop | dev | 1 | 256m | 256Mi |
|
||||
| Staging | manual | staging | 2 | 512m | 512Mi |
|
||||
| Prod | main | prod | 3 | 1024m | 1024Mi |
|
||||
|
||||
## 서비스 목록
|
||||
|
||||
1. **user-service** (8081) - 사용자 관리
|
||||
2. **event-service** (8082) - 이벤트 관리
|
||||
3. **ai-service** (8083) - AI 기반 콘텐츠 생성
|
||||
4. **content-service** (8084) - 콘텐츠 관리
|
||||
5. **distribution-service** (8085) - 경품 배포
|
||||
6. **participation-service** (8086) - 이벤트 참여
|
||||
7. **analytics-service** (8087) - 분석 및 통계
|
||||
|
||||
## 모니터링
|
||||
|
||||
### Pod 상태 확인
|
||||
```bash
|
||||
kubectl get pods -n kt-event-marketing
|
||||
```
|
||||
|
||||
### 로그 확인
|
||||
```bash
|
||||
# 실시간 로그
|
||||
kubectl logs -n kt-event-marketing -l app=user-service -f
|
||||
|
||||
# 이전 컨테이너 로그
|
||||
kubectl logs -n kt-event-marketing <pod-name> --previous
|
||||
```
|
||||
|
||||
### 리소스 사용량
|
||||
```bash
|
||||
# Pod 리소스
|
||||
kubectl top pods -n kt-event-marketing
|
||||
|
||||
# Node 리소스
|
||||
kubectl top nodes
|
||||
```
|
||||
|
||||
## 트러블슈팅
|
||||
|
||||
상세한 트러블슈팅 가이드는 [deployment/cicd/CICD-GUIDE.md](../../deployment/cicd/CICD-GUIDE.md)를 참조하세요.
|
||||
|
||||
**주요 문제 해결**:
|
||||
- ImagePullBackOff → ACR Secret 확인
|
||||
- CrashLoopBackOff → 로그 확인 및 환경 변수 검증
|
||||
- Readiness Probe Failed → Context Path 및 Actuator 경로 확인
|
||||
|
||||
## 롤백
|
||||
|
||||
```bash
|
||||
# 이전 버전으로 롤백
|
||||
kubectl rollout undo deployment/user-service -n kt-event-marketing
|
||||
|
||||
# 특정 리비전으로 롤백
|
||||
kubectl rollout undo deployment/user-service --to-revision=2 -n kt-event-marketing
|
||||
```
|
||||
|
||||
## 참고 자료
|
||||
|
||||
- [CI/CD 가이드 (한글)](../../deployment/cicd/CICD-GUIDE.md)
|
||||
- [GitHub Actions 공식 문서](https://docs.github.com/en/actions)
|
||||
- [Kustomize 공식 문서](https://kustomize.io/)
|
||||
- [Azure AKS 공식 문서](https://docs.microsoft.com/en-us/azure/aks/)
|
||||
@@ -0,0 +1,11 @@
|
||||
# Development Environment Variables
|
||||
ENVIRONMENT=dev
|
||||
ACR_NAME=acrdigitalgarage01
|
||||
RESOURCE_GROUP=rg-digitalgarage-01
|
||||
AKS_CLUSTER=aks-digitalgarage-01
|
||||
NAMESPACE=kt-event-marketing
|
||||
REPLICAS=1
|
||||
CPU_REQUEST=256m
|
||||
MEMORY_REQUEST=256Mi
|
||||
CPU_LIMIT=1024m
|
||||
MEMORY_LIMIT=1024Mi
|
||||
@@ -0,0 +1,11 @@
|
||||
# Production Environment Variables
|
||||
ENVIRONMENT=prod
|
||||
ACR_NAME=acrdigitalgarage01
|
||||
RESOURCE_GROUP=rg-digitalgarage-01
|
||||
AKS_CLUSTER=aks-digitalgarage-01
|
||||
NAMESPACE=kt-event-marketing
|
||||
REPLICAS=3
|
||||
CPU_REQUEST=1024m
|
||||
MEMORY_REQUEST=1024Mi
|
||||
CPU_LIMIT=4096m
|
||||
MEMORY_LIMIT=4096Mi
|
||||
@@ -0,0 +1,11 @@
|
||||
# Staging Environment Variables
|
||||
ENVIRONMENT=staging
|
||||
ACR_NAME=acrdigitalgarage01
|
||||
RESOURCE_GROUP=rg-digitalgarage-01
|
||||
AKS_CLUSTER=aks-digitalgarage-01
|
||||
NAMESPACE=kt-event-marketing
|
||||
REPLICAS=2
|
||||
CPU_REQUEST=512m
|
||||
MEMORY_REQUEST=512Mi
|
||||
CPU_LIMIT=2048m
|
||||
MEMORY_LIMIT=2048Mi
|
||||
@@ -0,0 +1,55 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm-ai-service
|
||||
data:
|
||||
# Server Configuration
|
||||
SERVER_PORT: "8083"
|
||||
|
||||
# Redis Configuration (service-specific)
|
||||
REDIS_DATABASE: "3"
|
||||
REDIS_TIMEOUT: "3000"
|
||||
REDIS_POOL_MIN: "2"
|
||||
|
||||
# Kafka Configuration (service-specific)
|
||||
KAFKA_CONSUMER_GROUP: "ai-service-consumers"
|
||||
|
||||
# Kafka Topics Configuration
|
||||
KAFKA_TOPICS_AI_JOB: "ai-event-generation-job"
|
||||
KAFKA_TOPICS_AI_JOB_DLQ: "ai-event-generation-job-dlq"
|
||||
|
||||
# AI Provider Configuration
|
||||
AI_PROVIDER: "CLAUDE"
|
||||
AI_CLAUDE_API_URL: "https://api.anthropic.com/v1/messages"
|
||||
AI_CLAUDE_ANTHROPIC_VERSION: "2023-06-01"
|
||||
AI_CLAUDE_MODEL: "claude-sonnet-4-5-20250929"
|
||||
AI_CLAUDE_MAX_TOKENS: "4096"
|
||||
AI_CLAUDE_TEMPERATURE: "0.7"
|
||||
AI_CLAUDE_TIMEOUT: "300000"
|
||||
|
||||
# Circuit Breaker Configuration
|
||||
RESILIENCE4J_CIRCUITBREAKER_FAILURE_RATE_THRESHOLD: "50"
|
||||
RESILIENCE4J_CIRCUITBREAKER_SLOW_CALL_RATE_THRESHOLD: "50"
|
||||
RESILIENCE4J_CIRCUITBREAKER_SLOW_CALL_DURATION_THRESHOLD: "60s"
|
||||
RESILIENCE4J_CIRCUITBREAKER_PERMITTED_CALLS_HALF_OPEN: "3"
|
||||
RESILIENCE4J_CIRCUITBREAKER_SLIDING_WINDOW_SIZE: "10"
|
||||
RESILIENCE4J_CIRCUITBREAKER_MINIMUM_CALLS: "5"
|
||||
RESILIENCE4J_CIRCUITBREAKER_WAIT_DURATION_OPEN: "60s"
|
||||
RESILIENCE4J_TIMELIMITER_TIMEOUT_DURATION: "300s"
|
||||
|
||||
# Redis Cache TTL Configuration (seconds)
|
||||
CACHE_TTL_RECOMMENDATION: "86400"
|
||||
CACHE_TTL_JOB_STATUS: "86400"
|
||||
CACHE_TTL_TREND: "3600"
|
||||
CACHE_TTL_FALLBACK: "604800"
|
||||
|
||||
# Logging Configuration
|
||||
LOG_LEVEL_ROOT: "INFO"
|
||||
LOG_LEVEL_AI: "DEBUG"
|
||||
LOG_LEVEL_KAFKA: "INFO"
|
||||
LOG_LEVEL_REDIS: "INFO"
|
||||
LOG_LEVEL_RESILIENCE4J: "DEBUG"
|
||||
LOG_FILE_NAME: "logs/ai-service.log"
|
||||
LOG_FILE_MAX_SIZE: "10MB"
|
||||
LOG_FILE_MAX_HISTORY: "7"
|
||||
LOG_FILE_TOTAL_CAP: "100MB"
|
||||
@@ -0,0 +1,62 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ai-service
|
||||
labels:
|
||||
app: ai-service
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ai-service
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ai-service
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: kt-event-marketing
|
||||
containers:
|
||||
- name: ai-service
|
||||
image: acrdigitalgarage01.azurecr.io/kt-event-marketing/ai-service:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8083
|
||||
name: http
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: cm-common
|
||||
- configMapRef:
|
||||
name: cm-ai-service
|
||||
- secretRef:
|
||||
name: secret-common
|
||||
- secretRef:
|
||||
name: secret-ai-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "256m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "1024m"
|
||||
memory: "1024Mi"
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /actuator/health
|
||||
port: 8083
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
failureThreshold: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/readiness
|
||||
port: 8083
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
failureThreshold: 3
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/liveness
|
||||
port: 8083
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
failureThreshold: 3
|
||||
@@ -0,0 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secret-ai-service
|
||||
type: Opaque
|
||||
stringData:
|
||||
# Claude API Key
|
||||
AI_CLAUDE_API_KEY: "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"
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ai-service
|
||||
labels:
|
||||
app: ai-service
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8083
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app: ai-service
|
||||
@@ -0,0 +1,37 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm-analytics-service
|
||||
data:
|
||||
# Server Configuration
|
||||
SERVER_PORT: "8086"
|
||||
|
||||
# Database Configuration
|
||||
DB_HOST: "analytic-postgresql"
|
||||
DB_PORT: "5432"
|
||||
DB_NAME: "analytics_db"
|
||||
DB_USERNAME: "eventuser"
|
||||
|
||||
# Redis Configuration (service-specific)
|
||||
REDIS_DATABASE: "5"
|
||||
|
||||
# Kafka Configuration (service-specific)
|
||||
KAFKA_ENABLED: "true"
|
||||
KAFKA_CONSUMER_GROUP_ID: "analytics-service"
|
||||
|
||||
# Sample Data Configuration (MVP only)
|
||||
SAMPLE_DATA_ENABLED: "true"
|
||||
|
||||
# Batch Scheduler Configuration
|
||||
BATCH_REFRESH_INTERVAL: "300000" # 5분 (밀리초)
|
||||
BATCH_INITIAL_DELAY: "30000" # 30초 (밀리초)
|
||||
BATCH_ENABLED: "true"
|
||||
|
||||
# Logging Configuration
|
||||
LOG_LEVEL_APP: "INFO"
|
||||
LOG_LEVEL_WEB: "INFO"
|
||||
LOG_LEVEL_SQL: "WARN"
|
||||
LOG_LEVEL_SQL_TYPE: "WARN"
|
||||
SHOW_SQL: "false"
|
||||
DDL_AUTO: "update"
|
||||
LOG_FILE: "logs/analytics-service.log"
|
||||
@@ -0,0 +1,62 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: analytics-service
|
||||
labels:
|
||||
app: analytics-service
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: analytics-service
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: analytics-service
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: kt-event-marketing
|
||||
containers:
|
||||
- name: analytics-service
|
||||
image: acrdigitalgarage01.azurecr.io/kt-event-marketing/analytics-service:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8086
|
||||
name: http
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: cm-common
|
||||
- configMapRef:
|
||||
name: cm-analytics-service
|
||||
- secretRef:
|
||||
name: secret-common
|
||||
- secretRef:
|
||||
name: secret-analytics-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "256m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "1024m"
|
||||
memory: "1024Mi"
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/liveness
|
||||
port: 8086
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 10
|
||||
failureThreshold: 30
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/liveness
|
||||
port: 8086
|
||||
initialDelaySeconds: 0
|
||||
periodSeconds: 10
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/readiness
|
||||
port: 8086
|
||||
initialDelaySeconds: 0
|
||||
periodSeconds: 10
|
||||
failureThreshold: 3
|
||||
@@ -0,0 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secret-analytics-service
|
||||
type: Opaque
|
||||
stringData:
|
||||
DB_PASSWORD: "Hi5Jessica!"
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: analytics-service
|
||||
labels:
|
||||
app: analytics-service
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8086
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app: analytics-service
|
||||
@@ -0,0 +1,46 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm-common
|
||||
data:
|
||||
# Redis Configuration
|
||||
REDIS_ENABLED: "true"
|
||||
REDIS_HOST: "redis"
|
||||
REDIS_PORT: "6379"
|
||||
REDIS_TIMEOUT: "2000ms"
|
||||
REDIS_POOL_MAX: "8"
|
||||
REDIS_POOL_IDLE: "8"
|
||||
REDIS_POOL_MIN: "0"
|
||||
REDIS_POOL_WAIT: "-1ms"
|
||||
|
||||
# Kafka Configuration
|
||||
KAFKA_BOOTSTRAP_SERVERS: "20.249.182.13:9095,4.217.131.59:9095"
|
||||
EXCLUDE_KAFKA: ""
|
||||
EXCLUDE_REDIS: ""
|
||||
|
||||
# CORS Configuration
|
||||
CORS_ALLOWED_ORIGINS: "http://localhost:8081,http://localhost:8082,http://localhost:8083,http://localhost:8084,http://kt-event-marketing.20.214.196.128.nip.io"
|
||||
CORS_ALLOWED_METHODS: "GET,POST,PUT,DELETE,OPTIONS,PATCH"
|
||||
CORS_ALLOWED_HEADERS: "*"
|
||||
CORS_ALLOW_CREDENTIALS: "true"
|
||||
CORS_MAX_AGE: "3600"
|
||||
|
||||
# JWT Configuration
|
||||
JWT_ACCESS_TOKEN_VALIDITY: "604800000"
|
||||
JWT_REFRESH_TOKEN_VALIDITY: "86400000"
|
||||
|
||||
# JPA Configuration
|
||||
DDL_AUTO: "update"
|
||||
SHOW_SQL: "false"
|
||||
JPA_DIALECT: "org.hibernate.dialect.PostgreSQLDialect"
|
||||
H2_CONSOLE_ENABLED: "false"
|
||||
|
||||
# Logging Configuration
|
||||
LOG_LEVEL_APP: "INFO"
|
||||
LOG_LEVEL_WEB: "INFO"
|
||||
LOG_LEVEL_SQL: "WARN"
|
||||
LOG_LEVEL_SQL_TYPE: "WARN"
|
||||
LOG_LEVEL_ROOT: "INFO"
|
||||
LOG_FILE_MAX_SIZE: "10MB"
|
||||
LOG_FILE_MAX_HISTORY: "7"
|
||||
LOG_FILE_TOTAL_CAP: "100MB"
|
||||
@@ -0,0 +1,24 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm-content-service
|
||||
data:
|
||||
# Server Configuration
|
||||
SERVER_PORT: "8084"
|
||||
|
||||
# Redis Configuration (service-specific)
|
||||
REDIS_DATABASE: "1"
|
||||
|
||||
# Replicate API Configuration (Stable Diffusion)
|
||||
REPLICATE_API_URL: "https://api.replicate.com"
|
||||
REPLICATE_MODEL_VERSION: "stability-ai/sdxl:39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b"
|
||||
|
||||
# HuggingFace API Configuration
|
||||
HUGGINGFACE_API_URL: "https://api-inference.huggingface.co"
|
||||
HUGGINGFACE_MODEL: "runwayml/stable-diffusion-v1-5"
|
||||
|
||||
# Azure Blob Storage Configuration
|
||||
AZURE_CONTAINER_NAME: "content-images"
|
||||
|
||||
# Logging Configuration
|
||||
LOG_FILE_PATH: "logs/content-service.log"
|
||||
@@ -0,0 +1,62 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: content-service
|
||||
labels:
|
||||
app: content-service
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: content-service
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: content-service
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: kt-event-marketing
|
||||
containers:
|
||||
- name: content-service
|
||||
image: acrdigitalgarage01.azurecr.io/kt-event-marketing/content-service:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8084
|
||||
name: http
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: cm-common
|
||||
- configMapRef:
|
||||
name: cm-content-service
|
||||
- secretRef:
|
||||
name: secret-common
|
||||
- secretRef:
|
||||
name: secret-content-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "256m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "1024m"
|
||||
memory: "1024Mi"
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /api/v1/content/actuator/health
|
||||
port: 8084
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
failureThreshold: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /api/v1/content/actuator/health/readiness
|
||||
port: 8084
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
failureThreshold: 3
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /api/v1/content/actuator/health/liveness
|
||||
port: 8084
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
failureThreshold: 3
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secret-content-service
|
||||
type: Opaque
|
||||
stringData:
|
||||
# Azure Blob Storage Connection String
|
||||
AZURE_STORAGE_CONNECTION_STRING: "DefaultEndpointsProtocol=https;AccountName=blobkteventstorage;AccountKey=tcBN7mAfojbl0uGsOpU7RNuKNhHnzmwDiWjN31liSMVSrWaEK+HHnYKZrjBXXAC6ZPsuxUDlsf8x+AStd++QYg==;EndpointSuffix=core.windows.net"
|
||||
|
||||
# Replicate API Token
|
||||
REPLICATE_API_TOKEN: ""
|
||||
|
||||
# HuggingFace API Token
|
||||
HUGGINGFACE_API_TOKEN: ""
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: content-service
|
||||
labels:
|
||||
app: content-service
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8084
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app: content-service
|
||||
@@ -0,0 +1,28 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm-distribution-service
|
||||
data:
|
||||
# Server Configuration
|
||||
SERVER_PORT: "8085"
|
||||
|
||||
# Database Configuration
|
||||
DB_HOST: "distribution-postgresql"
|
||||
DB_PORT: "5432"
|
||||
DB_NAME: "distributiondb"
|
||||
DB_USERNAME: "eventuser"
|
||||
|
||||
# Kafka Configuration
|
||||
KAFKA_ENABLED: "true"
|
||||
KAFKA_CONSUMER_GROUP: "distribution-service"
|
||||
|
||||
# External Channel APIs
|
||||
URIDONGNETV_API_URL: "http://localhost:9001/api/uridongnetv"
|
||||
RINGOBIZ_API_URL: "http://localhost:9002/api/ringobiz"
|
||||
GINITV_API_URL: "http://localhost:9003/api/ginitv"
|
||||
INSTAGRAM_API_URL: "http://localhost:9004/api/instagram"
|
||||
NAVER_API_URL: "http://localhost:9005/api/naver"
|
||||
KAKAO_API_URL: "http://localhost:9006/api/kakao"
|
||||
|
||||
# Logging Configuration
|
||||
LOG_FILE: "logs/distribution-service.log"
|
||||
@@ -0,0 +1,62 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: distribution-service
|
||||
labels:
|
||||
app: distribution-service
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: distribution-service
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: distribution-service
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: kt-event-marketing
|
||||
containers:
|
||||
- name: distribution-service
|
||||
image: acrdigitalgarage01.azurecr.io/kt-event-marketing/distribution-service:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8085
|
||||
name: http
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: cm-common
|
||||
- configMapRef:
|
||||
name: cm-distribution-service
|
||||
- secretRef:
|
||||
name: secret-common
|
||||
- secretRef:
|
||||
name: secret-distribution-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "256m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "1024m"
|
||||
memory: "1024Mi"
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /api/v1/distribution/actuator/health
|
||||
port: 8085
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
failureThreshold: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /api/v1/distribution/actuator/health/readiness
|
||||
port: 8085
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
failureThreshold: 3
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /api/v1/distribution/actuator/health/liveness
|
||||
port: 8085
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
failureThreshold: 3
|
||||
@@ -0,0 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secret-distribution-service
|
||||
type: Opaque
|
||||
stringData:
|
||||
DB_PASSWORD: "Hi5Jessica!"
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: distribution-service
|
||||
labels:
|
||||
app: distribution-service
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: distribution-service
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 8085
|
||||
protocol: TCP
|
||||
@@ -0,0 +1,28 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm-event-service
|
||||
data:
|
||||
# Server Configuration
|
||||
SERVER_PORT: "8080"
|
||||
|
||||
# Database Configuration
|
||||
DB_HOST: "event-postgresql"
|
||||
DB_PORT: "5432"
|
||||
DB_NAME: "eventdb"
|
||||
DB_USERNAME: "eventuser"
|
||||
|
||||
# Redis Configuration (service-specific)
|
||||
REDIS_DATABASE: "2"
|
||||
|
||||
# Kafka Configuration (service-specific)
|
||||
KAFKA_CONSUMER_GROUP: "event-service-consumers"
|
||||
|
||||
# Service URLs
|
||||
CONTENT_SERVICE_URL: "http://content-service"
|
||||
DISTRIBUTION_SERVICE_URL: "http://distribution-service"
|
||||
|
||||
# Logging Configuration
|
||||
LOG_LEVEL: "INFO"
|
||||
SQL_LOG_LEVEL: "WARN"
|
||||
LOG_FILE: "logs/event-service.log"
|
||||
@@ -0,0 +1,62 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: event-service
|
||||
labels:
|
||||
app: event-service
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: event-service
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: event-service
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: kt-event-marketing
|
||||
containers:
|
||||
- name: event-service
|
||||
image: acrdigitalgarage01.azurecr.io/kt-event-marketing/event-service:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: http
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: cm-common
|
||||
- configMapRef:
|
||||
name: cm-event-service
|
||||
- secretRef:
|
||||
name: secret-common
|
||||
- secretRef:
|
||||
name: secret-event-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "256m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "1024m"
|
||||
memory: "1024Mi"
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /actuator/health
|
||||
port: 8080
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
failureThreshold: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/readiness
|
||||
port: 8080
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
failureThreshold: 3
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/liveness
|
||||
port: 8080
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
failureThreshold: 3
|
||||
@@ -0,0 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secret-event-service
|
||||
type: Opaque
|
||||
stringData:
|
||||
# Database Password
|
||||
DB_PASSWORD: "Hi5Jessica!"
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: event-service
|
||||
labels:
|
||||
app: event-service
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8080
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app: event-service
|
||||
@@ -0,0 +1,116 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: kt-event-marketing
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "false"
|
||||
nginx.ingress.kubernetes.io/use-regex: "true"
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
- host: kt-event-marketing-api.20.214.196.128.nip.io
|
||||
http:
|
||||
paths:
|
||||
# User Service
|
||||
- path: /api/v1/users
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: user-service
|
||||
port:
|
||||
number: 80
|
||||
|
||||
# Content Service
|
||||
- path: /api/v1/content
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: content-service
|
||||
port:
|
||||
number: 80
|
||||
|
||||
# Event Service
|
||||
- path: /api/v1/events
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: event-service
|
||||
port:
|
||||
number: 80
|
||||
|
||||
- path: /api/v1/jobs
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: event-service
|
||||
port:
|
||||
number: 80
|
||||
|
||||
- path: /api/v1/redis-test
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: event-service
|
||||
port:
|
||||
number: 80
|
||||
|
||||
# AI Service
|
||||
- path: /api/v1/ai-service
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: ai-service
|
||||
port:
|
||||
number: 80
|
||||
|
||||
# Participation Service
|
||||
- path: /api/v1/participations
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: participation-service
|
||||
port:
|
||||
number: 80
|
||||
|
||||
- path: /api/v1/winners
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: participation-service
|
||||
port:
|
||||
number: 80
|
||||
|
||||
- path: /debug
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: participation-service
|
||||
port:
|
||||
number: 80
|
||||
|
||||
# Analytics Service - Event Analytics
|
||||
- path: /api/v1/events/([0-9]+)/analytics
|
||||
pathType: ImplementationSpecific
|
||||
backend:
|
||||
service:
|
||||
name: analytics-service
|
||||
port:
|
||||
number: 80
|
||||
|
||||
# Analytics Service - User Analytics
|
||||
- path: /api/v1/users/([0-9]+)/analytics
|
||||
pathType: ImplementationSpecific
|
||||
backend:
|
||||
service:
|
||||
name: analytics-service
|
||||
port:
|
||||
number: 80
|
||||
|
||||
# Distribution Service
|
||||
- path: /distribution
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: distribution-service
|
||||
port:
|
||||
number: 80
|
||||
@@ -0,0 +1,76 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
# Common resources
|
||||
resources:
|
||||
# Common ConfigMaps and Secrets
|
||||
- cm-common.yaml
|
||||
- secret-common.yaml
|
||||
- secret-imagepull.yaml
|
||||
|
||||
# Ingress
|
||||
- ingress.yaml
|
||||
|
||||
# user-service
|
||||
- user-service-deployment.yaml
|
||||
- user-service-service.yaml
|
||||
- user-service-cm-user-service.yaml
|
||||
- user-service-secret-user-service.yaml
|
||||
|
||||
# event-service
|
||||
- event-service-deployment.yaml
|
||||
- event-service-service.yaml
|
||||
- event-service-cm-event-service.yaml
|
||||
- event-service-secret-event-service.yaml
|
||||
|
||||
# ai-service
|
||||
- ai-service-deployment.yaml
|
||||
- ai-service-service.yaml
|
||||
- ai-service-cm-ai-service.yaml
|
||||
- ai-service-secret-ai-service.yaml
|
||||
|
||||
# content-service
|
||||
- content-service-deployment.yaml
|
||||
- content-service-service.yaml
|
||||
- content-service-cm-content-service.yaml
|
||||
- content-service-secret-content-service.yaml
|
||||
|
||||
# distribution-service
|
||||
- distribution-service-deployment.yaml
|
||||
- distribution-service-service.yaml
|
||||
- distribution-service-cm-distribution-service.yaml
|
||||
- distribution-service-secret-distribution-service.yaml
|
||||
|
||||
# participation-service
|
||||
- participation-service-deployment.yaml
|
||||
- participation-service-service.yaml
|
||||
- participation-service-cm-participation-service.yaml
|
||||
- participation-service-secret-participation-service.yaml
|
||||
|
||||
# analytics-service
|
||||
- analytics-service-deployment.yaml
|
||||
- analytics-service-service.yaml
|
||||
- analytics-service-cm-analytics-service.yaml
|
||||
- analytics-service-secret-analytics-service.yaml
|
||||
|
||||
# Common labels for all resources
|
||||
commonLabels:
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
app.kubernetes.io/part-of: kt-event-marketing
|
||||
|
||||
# Image tag replacement (will be overridden by overlays)
|
||||
images:
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/user-service
|
||||
newTag: latest
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/event-service
|
||||
newTag: latest
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/ai-service
|
||||
newTag: latest
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/content-service
|
||||
newTag: latest
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/distribution-service
|
||||
newTag: latest
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/participation-service
|
||||
newTag: latest
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/analytics-service
|
||||
newTag: latest
|
||||
@@ -0,0 +1,24 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm-participation-service
|
||||
data:
|
||||
# Server Configuration
|
||||
SERVER_PORT: "8084"
|
||||
|
||||
# Database Configuration
|
||||
DB_HOST: "participation-postgresql"
|
||||
DB_PORT: "5432"
|
||||
DB_NAME: "participationdb"
|
||||
DB_USERNAME: "eventuser"
|
||||
|
||||
# Redis Configuration (service-specific)
|
||||
REDIS_DATABASE: "4"
|
||||
|
||||
# Kafka Configuration (service-specific)
|
||||
KAFKA_CONSUMER_GROUP: "participation-service-consumers"
|
||||
|
||||
# Logging Configuration
|
||||
LOG_LEVEL: "INFO"
|
||||
SHOW_SQL: "false"
|
||||
LOG_FILE: "logs/participation-service.log"
|
||||
@@ -0,0 +1,62 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: participation-service
|
||||
labels:
|
||||
app: participation-service
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: participation-service
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: participation-service
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: kt-event-marketing
|
||||
containers:
|
||||
- name: participation-service
|
||||
image: acrdigitalgarage01.azurecr.io/kt-event-marketing/participation-service:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8084
|
||||
name: http
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: cm-common
|
||||
- configMapRef:
|
||||
name: cm-participation-service
|
||||
- secretRef:
|
||||
name: secret-common
|
||||
- secretRef:
|
||||
name: secret-participation-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "256m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "1024m"
|
||||
memory: "1024Mi"
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/liveness
|
||||
port: 8084
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 10
|
||||
failureThreshold: 30
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/liveness
|
||||
port: 8084
|
||||
initialDelaySeconds: 0
|
||||
periodSeconds: 10
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/readiness
|
||||
port: 8084
|
||||
initialDelaySeconds: 0
|
||||
periodSeconds: 10
|
||||
failureThreshold: 3
|
||||
@@ -0,0 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secret-participation-service
|
||||
type: Opaque
|
||||
stringData:
|
||||
DB_PASSWORD: "Hi5Jessica!"
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: participation-service
|
||||
labels:
|
||||
app: participation-service
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8084
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app: participation-service
|
||||
@@ -0,0 +1,11 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secret-common
|
||||
type: Opaque
|
||||
stringData:
|
||||
# Redis Password
|
||||
REDIS_PASSWORD: "Hi5Jessica!"
|
||||
|
||||
# JWT Secret
|
||||
JWT_SECRET: "QL0czzXckz18kHnxpaTDoWFkq+3qKO7VQXeNvf2bOoU="
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: kt-event-marketing
|
||||
type: kubernetes.io/dockerconfigjson
|
||||
stringData:
|
||||
.dockerconfigjson: |
|
||||
{
|
||||
"auths": {
|
||||
"acrdigitalgarage01.azurecr.io": {
|
||||
"username": "acrdigitalgarage01",
|
||||
"password": "+OY+rmOagorjWvQe/tTk6oqvnZI8SmNbY/Y2o5EDcY+ACRDCDbYk",
|
||||
"auth": "YWNyZGlnaXRhbGdhcmFnZTAxOitPWStybU9hZ29yald2UWUvdFRrNm9xdm5aSThTbU5iWS9ZMm81RURjWStBQ1JEQ0RiWWs="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm-user-service
|
||||
data:
|
||||
# Server Configuration
|
||||
SERVER_PORT: "8081"
|
||||
|
||||
# Database Configuration
|
||||
DB_URL: "jdbc:postgresql://user-postgresql:5432/userdb"
|
||||
DB_HOST: "user-postgresql"
|
||||
DB_PORT: "5432"
|
||||
DB_NAME: "userdb"
|
||||
DB_USERNAME: "eventuser"
|
||||
DB_DRIVER: "org.postgresql.Driver"
|
||||
DB_KIND: "postgresql"
|
||||
DB_POOL_MAX: "20"
|
||||
DB_POOL_MIN: "5"
|
||||
DB_CONN_TIMEOUT: "30000"
|
||||
DB_IDLE_TIMEOUT: "600000"
|
||||
DB_MAX_LIFETIME: "1800000"
|
||||
DB_LEAK_THRESHOLD: "60000"
|
||||
|
||||
# Redis Configuration (service-specific)
|
||||
REDIS_DATABASE: "0"
|
||||
|
||||
# Kafka Configuration (service-specific)
|
||||
KAFKA_CONSUMER_GROUP: "user-service-consumers"
|
||||
|
||||
# Logging Configuration
|
||||
LOG_FILE_PATH: "logs/user-service.log"
|
||||
@@ -0,0 +1,62 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: user-service
|
||||
labels:
|
||||
app: user-service
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: user-service
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: user-service
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: kt-event-marketing
|
||||
containers:
|
||||
- name: user-service
|
||||
image: acrdigitalgarage01.azurecr.io/kt-event-marketing/user-service:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8081
|
||||
name: http
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: cm-common
|
||||
- configMapRef:
|
||||
name: cm-user-service
|
||||
- secretRef:
|
||||
name: secret-common
|
||||
- secretRef:
|
||||
name: secret-user-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "256m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "1024m"
|
||||
memory: "1024Mi"
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /actuator/health
|
||||
port: 8081
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
failureThreshold: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/readiness
|
||||
port: 8081
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
failureThreshold: 3
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/liveness
|
||||
port: 8081
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
failureThreshold: 3
|
||||
@@ -0,0 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secret-user-service
|
||||
type: Opaque
|
||||
stringData:
|
||||
# Database Password
|
||||
DB_PASSWORD: "Hi5Jessica!"
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: user-service
|
||||
labels:
|
||||
app: user-service
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8081
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app: user-service
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ai-service
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: ai-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "256m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "1024m"
|
||||
memory: "1024Mi"
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: analytics-service
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: analytics-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "256m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "1024m"
|
||||
memory: "1024Mi"
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: content-service
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: content-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "256m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "1024m"
|
||||
memory: "1024Mi"
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: distribution-service
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: distribution-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "256m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "1024m"
|
||||
memory: "1024Mi"
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: event-service
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: event-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "256m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "1024m"
|
||||
memory: "1024Mi"
|
||||
@@ -0,0 +1,38 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: kt-event-marketing
|
||||
|
||||
bases:
|
||||
- ../../base
|
||||
|
||||
# Environment-specific labels
|
||||
commonLabels:
|
||||
environment: dev
|
||||
|
||||
# Environment-specific patches
|
||||
patchesStrategicMerge:
|
||||
- user-service-patch.yaml
|
||||
- event-service-patch.yaml
|
||||
- ai-service-patch.yaml
|
||||
- content-service-patch.yaml
|
||||
- distribution-service-patch.yaml
|
||||
- participation-service-patch.yaml
|
||||
- analytics-service-patch.yaml
|
||||
|
||||
# Override image tags for dev environment
|
||||
images:
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/user-service
|
||||
newTag: dev
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/event-service
|
||||
newTag: dev
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/ai-service
|
||||
newTag: dev
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/content-service
|
||||
newTag: dev
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/distribution-service
|
||||
newTag: dev
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/participation-service
|
||||
newTag: dev
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/analytics-service
|
||||
newTag: dev
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: participation-service
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: participation-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "256m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "1024m"
|
||||
memory: "1024Mi"
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: user-service
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: user-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "256m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "1024m"
|
||||
memory: "1024Mi"
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ai-service
|
||||
spec:
|
||||
replicas: 3
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: ai-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "1024m"
|
||||
memory: "1024Mi"
|
||||
limits:
|
||||
cpu: "4096m"
|
||||
memory: "4096Mi"
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: analytics-service
|
||||
spec:
|
||||
replicas: 3
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: analytics-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "1024m"
|
||||
memory: "1024Mi"
|
||||
limits:
|
||||
cpu: "4096m"
|
||||
memory: "4096Mi"
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: content-service
|
||||
spec:
|
||||
replicas: 3
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: content-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "1024m"
|
||||
memory: "1024Mi"
|
||||
limits:
|
||||
cpu: "4096m"
|
||||
memory: "4096Mi"
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: distribution-service
|
||||
spec:
|
||||
replicas: 3
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: distribution-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "1024m"
|
||||
memory: "1024Mi"
|
||||
limits:
|
||||
cpu: "4096m"
|
||||
memory: "4096Mi"
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: event-service
|
||||
spec:
|
||||
replicas: 3
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: event-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "1024m"
|
||||
memory: "1024Mi"
|
||||
limits:
|
||||
cpu: "4096m"
|
||||
memory: "4096Mi"
|
||||
@@ -0,0 +1,38 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: kt-event-marketing
|
||||
|
||||
bases:
|
||||
- ../../base
|
||||
|
||||
# Environment-specific labels
|
||||
commonLabels:
|
||||
environment: prod
|
||||
|
||||
# Environment-specific patches
|
||||
patchesStrategicMerge:
|
||||
- user-service-patch.yaml
|
||||
- event-service-patch.yaml
|
||||
- ai-service-patch.yaml
|
||||
- content-service-patch.yaml
|
||||
- distribution-service-patch.yaml
|
||||
- participation-service-patch.yaml
|
||||
- analytics-service-patch.yaml
|
||||
|
||||
# Override image tags for prod environment
|
||||
images:
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/user-service
|
||||
newTag: prod
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/event-service
|
||||
newTag: prod
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/ai-service
|
||||
newTag: prod
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/content-service
|
||||
newTag: prod
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/distribution-service
|
||||
newTag: prod
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/participation-service
|
||||
newTag: prod
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/analytics-service
|
||||
newTag: prod
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: participation-service
|
||||
spec:
|
||||
replicas: 3
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: participation-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "1024m"
|
||||
memory: "1024Mi"
|
||||
limits:
|
||||
cpu: "4096m"
|
||||
memory: "4096Mi"
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: user-service
|
||||
spec:
|
||||
replicas: 3
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: user-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "1024m"
|
||||
memory: "1024Mi"
|
||||
limits:
|
||||
cpu: "4096m"
|
||||
memory: "4096Mi"
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ai-service
|
||||
spec:
|
||||
replicas: 2
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: ai-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "512m"
|
||||
memory: "512Mi"
|
||||
limits:
|
||||
cpu: "2048m"
|
||||
memory: "2048Mi"
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: analytics-service
|
||||
spec:
|
||||
replicas: 2
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: analytics-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "512m"
|
||||
memory: "512Mi"
|
||||
limits:
|
||||
cpu: "2048m"
|
||||
memory: "2048Mi"
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: content-service
|
||||
spec:
|
||||
replicas: 2
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: content-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "512m"
|
||||
memory: "512Mi"
|
||||
limits:
|
||||
cpu: "2048m"
|
||||
memory: "2048Mi"
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: distribution-service
|
||||
spec:
|
||||
replicas: 2
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: distribution-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "512m"
|
||||
memory: "512Mi"
|
||||
limits:
|
||||
cpu: "2048m"
|
||||
memory: "2048Mi"
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: event-service
|
||||
spec:
|
||||
replicas: 2
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: event-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "512m"
|
||||
memory: "512Mi"
|
||||
limits:
|
||||
cpu: "2048m"
|
||||
memory: "2048Mi"
|
||||
@@ -0,0 +1,38 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: kt-event-marketing
|
||||
|
||||
bases:
|
||||
- ../../base
|
||||
|
||||
# Environment-specific labels
|
||||
commonLabels:
|
||||
environment: staging
|
||||
|
||||
# Environment-specific patches
|
||||
patchesStrategicMerge:
|
||||
- user-service-patch.yaml
|
||||
- event-service-patch.yaml
|
||||
- ai-service-patch.yaml
|
||||
- content-service-patch.yaml
|
||||
- distribution-service-patch.yaml
|
||||
- participation-service-patch.yaml
|
||||
- analytics-service-patch.yaml
|
||||
|
||||
# Override image tags for staging environment
|
||||
images:
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/user-service
|
||||
newTag: staging
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/event-service
|
||||
newTag: staging
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/ai-service
|
||||
newTag: staging
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/content-service
|
||||
newTag: staging
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/distribution-service
|
||||
newTag: staging
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/participation-service
|
||||
newTag: staging
|
||||
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/analytics-service
|
||||
newTag: staging
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: participation-service
|
||||
spec:
|
||||
replicas: 2
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: participation-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "512m"
|
||||
memory: "512Mi"
|
||||
limits:
|
||||
cpu: "2048m"
|
||||
memory: "2048Mi"
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: user-service
|
||||
spec:
|
||||
replicas: 2
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: user-service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "512m"
|
||||
memory: "512Mi"
|
||||
limits:
|
||||
cpu: "2048m"
|
||||
memory: "2048Mi"
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Copy K8s manifests to Kustomize base directory and remove namespace declarations
|
||||
"""
|
||||
import os
|
||||
import shutil
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
|
||||
# Service names
|
||||
SERVICES = [
|
||||
'user-service',
|
||||
'event-service',
|
||||
'ai-service',
|
||||
'content-service',
|
||||
'distribution-service',
|
||||
'participation-service',
|
||||
'analytics-service'
|
||||
]
|
||||
|
||||
# Base directories
|
||||
SOURCE_DIR = Path('deployment/k8s')
|
||||
BASE_DIR = Path('.github/kustomize/base')
|
||||
|
||||
def remove_namespace_from_yaml(content):
|
||||
"""Remove namespace field from YAML content"""
|
||||
docs = list(yaml.safe_load_all(content))
|
||||
|
||||
for doc in docs:
|
||||
if doc and isinstance(doc, dict):
|
||||
if 'metadata' in doc and 'namespace' in doc['metadata']:
|
||||
del doc['metadata']['namespace']
|
||||
|
||||
return yaml.dump_all(docs, default_flow_style=False, sort_keys=False)
|
||||
|
||||
def copy_and_process_file(source_path, dest_path):
|
||||
"""Copy file and remove namespace declaration"""
|
||||
with open(source_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Remove namespace from YAML
|
||||
processed_content = remove_namespace_from_yaml(content)
|
||||
|
||||
# Write to destination
|
||||
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(dest_path, 'w', encoding='utf-8') as f:
|
||||
f.write(processed_content)
|
||||
|
||||
print(f"✓ Copied and processed: {source_path} -> {dest_path}")
|
||||
|
||||
def main():
|
||||
print("Starting manifest copy to Kustomize base...")
|
||||
|
||||
# Copy common resources
|
||||
print("\n[Common Resources]")
|
||||
common_dir = SOURCE_DIR / 'common'
|
||||
for file in ['cm-common.yaml', 'secret-common.yaml', 'secret-imagepull.yaml', 'ingress.yaml']:
|
||||
source = common_dir / file
|
||||
if source.exists():
|
||||
dest = BASE_DIR / file
|
||||
copy_and_process_file(source, dest)
|
||||
|
||||
# Copy service-specific resources
|
||||
print("\n[Service Resources]")
|
||||
for service in SERVICES:
|
||||
service_dir = SOURCE_DIR / service
|
||||
if not service_dir.exists():
|
||||
print(f"⚠ Service directory not found: {service_dir}")
|
||||
continue
|
||||
|
||||
print(f"\nProcessing {service}...")
|
||||
for file in service_dir.glob('*.yaml'):
|
||||
dest = BASE_DIR / f"{service}-{file.name}"
|
||||
copy_and_process_file(file, dest)
|
||||
|
||||
print("\n✅ All manifests copied to base directory!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,181 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
###############################################################################
|
||||
# Backend Services Deployment Script for AKS
|
||||
#
|
||||
# Usage:
|
||||
# ./deploy.sh <environment> [service-name]
|
||||
#
|
||||
# Arguments:
|
||||
# environment - Target environment (dev, staging, prod)
|
||||
# service-name - Specific service to deploy (optional, deploys all if not specified)
|
||||
#
|
||||
# Examples:
|
||||
# ./deploy.sh dev # Deploy all services to dev
|
||||
# ./deploy.sh prod user-service # Deploy only user-service to prod
|
||||
###############################################################################
|
||||
|
||||
# Color output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Functions
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Validate arguments
|
||||
if [ $# -lt 1 ]; then
|
||||
log_error "Usage: $0 <environment> [service-name]"
|
||||
log_error "Environment must be one of: dev, staging, prod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ENVIRONMENT=$1
|
||||
SERVICE=${2:-all}
|
||||
|
||||
# Validate environment
|
||||
if [[ ! "$ENVIRONMENT" =~ ^(dev|staging|prod)$ ]]; then
|
||||
log_error "Invalid environment: $ENVIRONMENT"
|
||||
log_error "Must be one of: dev, staging, prod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Load environment variables
|
||||
ENV_FILE=".github/config/deploy_env_vars_${ENVIRONMENT}"
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
log_error "Environment file not found: $ENV_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source "$ENV_FILE"
|
||||
log_info "Loaded environment configuration: $ENVIRONMENT"
|
||||
|
||||
# Service list
|
||||
SERVICES=(
|
||||
"user-service"
|
||||
"event-service"
|
||||
"ai-service"
|
||||
"content-service"
|
||||
"distribution-service"
|
||||
"participation-service"
|
||||
"analytics-service"
|
||||
)
|
||||
|
||||
# Validate service if specified
|
||||
if [ "$SERVICE" != "all" ]; then
|
||||
if [[ ! " ${SERVICES[@]} " =~ " ${SERVICE} " ]]; then
|
||||
log_error "Invalid service: $SERVICE"
|
||||
log_error "Must be one of: ${SERVICES[*]}"
|
||||
exit 1
|
||||
fi
|
||||
SERVICES=("$SERVICE")
|
||||
fi
|
||||
|
||||
log_info "Services to deploy: ${SERVICES[*]}"
|
||||
|
||||
# Check prerequisites
|
||||
log_info "Checking prerequisites..."
|
||||
|
||||
if ! command -v az &> /dev/null; then
|
||||
log_error "Azure CLI not found. Please install Azure CLI."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v kubectl &> /dev/null; then
|
||||
log_error "kubectl not found. Please install kubectl."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v kustomize &> /dev/null; then
|
||||
log_error "kustomize not found. Please install kustomize."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Azure login check
|
||||
log_info "Checking Azure authentication..."
|
||||
if ! az account show &> /dev/null; then
|
||||
log_error "Not logged in to Azure. Please run 'az login'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get AKS credentials
|
||||
log_info "Getting AKS credentials..."
|
||||
az aks get-credentials \
|
||||
--resource-group "$RESOURCE_GROUP" \
|
||||
--name "$AKS_CLUSTER" \
|
||||
--overwrite-existing
|
||||
|
||||
# Check namespace
|
||||
log_info "Checking namespace: $NAMESPACE"
|
||||
if ! kubectl get namespace "$NAMESPACE" &> /dev/null; then
|
||||
log_warn "Namespace $NAMESPACE does not exist. Creating..."
|
||||
kubectl create namespace "$NAMESPACE"
|
||||
fi
|
||||
|
||||
# Build and deploy with Kustomize
|
||||
OVERLAY_DIR=".github/kustomize/overlays/${ENVIRONMENT}"
|
||||
if [ ! -d "$OVERLAY_DIR" ]; then
|
||||
log_error "Kustomize overlay directory not found: $OVERLAY_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Building Kustomize manifests for $ENVIRONMENT..."
|
||||
cd "$OVERLAY_DIR"
|
||||
|
||||
# Update image tags
|
||||
log_info "Updating image tags to: $ENVIRONMENT"
|
||||
kustomize edit set image \
|
||||
${ACR_NAME}.azurecr.io/kt-event-marketing/user-service:${ENVIRONMENT} \
|
||||
${ACR_NAME}.azurecr.io/kt-event-marketing/event-service:${ENVIRONMENT} \
|
||||
${ACR_NAME}.azurecr.io/kt-event-marketing/ai-service:${ENVIRONMENT} \
|
||||
${ACR_NAME}.azurecr.io/kt-event-marketing/content-service:${ENVIRONMENT} \
|
||||
${ACR_NAME}.azurecr.io/kt-event-marketing/distribution-service:${ENVIRONMENT} \
|
||||
${ACR_NAME}.azurecr.io/kt-event-marketing/participation-service:${ENVIRONMENT} \
|
||||
${ACR_NAME}.azurecr.io/kt-event-marketing/analytics-service:${ENVIRONMENT}
|
||||
|
||||
# Apply manifests
|
||||
log_info "Applying manifests to AKS..."
|
||||
kustomize build . | kubectl apply -f -
|
||||
|
||||
cd - > /dev/null
|
||||
|
||||
# Wait for deployments
|
||||
log_info "Waiting for deployments to be ready..."
|
||||
for service in "${SERVICES[@]}"; do
|
||||
log_info "Waiting for $service deployment..."
|
||||
if ! kubectl rollout status deployment/"$service" -n "$NAMESPACE" --timeout=5m; then
|
||||
log_error "Deployment of $service failed!"
|
||||
exit 1
|
||||
fi
|
||||
log_info "✓ $service is ready"
|
||||
done
|
||||
|
||||
# Verify deployment
|
||||
log_info "Verifying deployment..."
|
||||
echo ""
|
||||
echo "=== Pods Status ==="
|
||||
kubectl get pods -n "$NAMESPACE" -l app.kubernetes.io/part-of=kt-event-marketing
|
||||
|
||||
echo ""
|
||||
echo "=== Services ==="
|
||||
kubectl get svc -n "$NAMESPACE"
|
||||
|
||||
echo ""
|
||||
echo "=== Ingress ==="
|
||||
kubectl get ingress -n "$NAMESPACE"
|
||||
|
||||
log_info "Deployment completed successfully!"
|
||||
log_info "Environment: $ENVIRONMENT"
|
||||
log_info "Services: ${SERVICES[*]}"
|
||||
@@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
|
||||
SERVICES=(user-service event-service ai-service content-service distribution-service participation-service analytics-service)
|
||||
|
||||
# Staging patches (2 replicas, increased resources)
|
||||
for service in "${SERVICES[@]}"; do
|
||||
cat > ".github/kustomize/overlays/staging/${service}-patch.yaml" << YAML
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ${service}
|
||||
spec:
|
||||
replicas: 2
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: ${service}
|
||||
resources:
|
||||
requests:
|
||||
cpu: "512m"
|
||||
memory: "512Mi"
|
||||
limits:
|
||||
cpu: "2048m"
|
||||
memory: "2048Mi"
|
||||
YAML
|
||||
done
|
||||
|
||||
# Prod patches (3 replicas, maximum resources)
|
||||
for service in "${SERVICES[@]}"; do
|
||||
cat > ".github/kustomize/overlays/prod/${service}-patch.yaml" << YAML
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ${service}
|
||||
spec:
|
||||
replicas: 3
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: ${service}
|
||||
resources:
|
||||
requests:
|
||||
cpu: "1024m"
|
||||
memory: "1024Mi"
|
||||
limits:
|
||||
cpu: "4096m"
|
||||
memory: "4096Mi"
|
||||
YAML
|
||||
done
|
||||
|
||||
echo "✅ Generated all patch files for staging and prod"
|
||||
@@ -0,0 +1,207 @@
|
||||
name: Backend CI/CD Pipeline
|
||||
|
||||
on:
|
||||
# push:
|
||||
# branches:
|
||||
# - develop
|
||||
# - main
|
||||
# paths:
|
||||
# - '*-service/**'
|
||||
# - '.github/workflows/backend-cicd.yaml'
|
||||
# - '.github/kustomize/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
- main
|
||||
paths:
|
||||
- '*-service/**'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
description: 'Target environment'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- dev
|
||||
- staging
|
||||
- prod
|
||||
service:
|
||||
description: 'Service to deploy (all for all services)'
|
||||
required: true
|
||||
default: 'all'
|
||||
|
||||
env:
|
||||
ACR_NAME: acrdigitalgarage01
|
||||
RESOURCE_GROUP: rg-digitalgarage-01
|
||||
AKS_CLUSTER: aks-digitalgarage-01
|
||||
NAMESPACE: kt-event-marketing
|
||||
JDK_VERSION: '21'
|
||||
|
||||
jobs:
|
||||
detect-changes:
|
||||
name: Detect Changed Services
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
services: ${{ steps.detect.outputs.services }}
|
||||
environment: ${{ steps.env.outputs.environment }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Determine environment
|
||||
id: env
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
echo "environment=${{ github.event.inputs.environment }}" >> $GITHUB_OUTPUT
|
||||
elif [ "${{ github.ref }}" = "refs/heads/main" ]; then
|
||||
echo "environment=prod" >> $GITHUB_OUTPUT
|
||||
elif [ "${{ github.ref }}" = "refs/heads/develop" ]; then
|
||||
echo "environment=dev" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "environment=dev" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Detect changed services
|
||||
id: detect
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ github.event.inputs.service }}" != "all" ]; then
|
||||
echo "services=[\"${{ github.event.inputs.service }}\"]" >> $GITHUB_OUTPUT
|
||||
elif [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ github.event.inputs.service }}" = "all" ]; then
|
||||
echo "services=[\"user-service\",\"event-service\",\"ai-service\",\"content-service\",\"distribution-service\",\"participation-service\",\"analytics-service\"]" >> $GITHUB_OUTPUT
|
||||
else
|
||||
CHANGED_SERVICES=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} | \
|
||||
grep -E '^(user|event|ai|content|distribution|participation|analytics)-service/' | \
|
||||
cut -d'/' -f1 | sort -u | \
|
||||
jq -R -s -c 'split("\n") | map(select(length > 0))')
|
||||
|
||||
if [ "$CHANGED_SERVICES" = "[]" ] || [ -z "$CHANGED_SERVICES" ]; then
|
||||
echo "services=[\"user-service\",\"event-service\",\"ai-service\",\"content-service\",\"distribution-service\",\"participation-service\",\"analytics-service\"]" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "services=$CHANGED_SERVICES" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
fi
|
||||
|
||||
build-and-push:
|
||||
name: Build and Push - ${{ matrix.service }}
|
||||
needs: detect-changes
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
service: ${{ fromJson(needs.detect-changes.outputs.services) }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK ${{ env.JDK_VERSION }}
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: ${{ env.JDK_VERSION }}
|
||||
distribution: 'temurin'
|
||||
cache: 'gradle'
|
||||
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew ${{ matrix.service }}:build -x test
|
||||
|
||||
# - name: Run tests
|
||||
# run: ./gradlew ${{ matrix.service }}:test
|
||||
|
||||
- name: Build JAR
|
||||
run: ./gradlew ${{ matrix.service }}:bootJar
|
||||
|
||||
- name: Log in to Azure Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.ACR_NAME }}.azurecr.io
|
||||
username: ${{ secrets.ACR_USERNAME }}
|
||||
password: ${{ secrets.ACR_PASSWORD }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./${{ matrix.service }}
|
||||
file: ./${{ matrix.service }}/Dockerfile
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.ACR_NAME }}.azurecr.io/kt-event-marketing/${{ matrix.service }}:${{ needs.detect-changes.outputs.environment }}
|
||||
${{ env.ACR_NAME }}.azurecr.io/kt-event-marketing/${{ matrix.service }}:${{ github.sha }}
|
||||
${{ env.ACR_NAME }}.azurecr.io/kt-event-marketing/${{ matrix.service }}:latest
|
||||
|
||||
deploy:
|
||||
name: Deploy to AKS - ${{ needs.detect-changes.outputs.environment }}
|
||||
needs: [detect-changes, build-and-push]
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ needs.detect-changes.outputs.environment }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Azure login
|
||||
uses: azure/login@v1
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_CREDENTIALS }}
|
||||
|
||||
- name: Get AKS credentials
|
||||
run: |
|
||||
az aks get-credentials \
|
||||
--resource-group ${{ env.RESOURCE_GROUP }} \
|
||||
--name ${{ env.AKS_CLUSTER }} \
|
||||
--overwrite-existing
|
||||
|
||||
- name: Setup Kustomize
|
||||
uses: imranismail/setup-kustomize@v2
|
||||
|
||||
- name: Deploy with Kustomize
|
||||
run: |
|
||||
cd .github/kustomize/overlays/${{ needs.detect-changes.outputs.environment }}
|
||||
kustomize edit set image \
|
||||
acrdigitalgarage01.azurecr.io/kt-event-marketing/user-service:${{ needs.detect-changes.outputs.environment }} \
|
||||
acrdigitalgarage01.azurecr.io/kt-event-marketing/event-service:${{ needs.detect-changes.outputs.environment }} \
|
||||
acrdigitalgarage01.azurecr.io/kt-event-marketing/ai-service:${{ needs.detect-changes.outputs.environment }} \
|
||||
acrdigitalgarage01.azurecr.io/kt-event-marketing/content-service:${{ needs.detect-changes.outputs.environment }} \
|
||||
acrdigitalgarage01.azurecr.io/kt-event-marketing/distribution-service:${{ needs.detect-changes.outputs.environment }} \
|
||||
acrdigitalgarage01.azurecr.io/kt-event-marketing/participation-service:${{ needs.detect-changes.outputs.environment }} \
|
||||
acrdigitalgarage01.azurecr.io/kt-event-marketing/analytics-service:${{ needs.detect-changes.outputs.environment }}
|
||||
|
||||
kustomize build . | kubectl apply -f -
|
||||
|
||||
- name: Wait for deployment rollout
|
||||
run: |
|
||||
for service in $(echo '${{ needs.detect-changes.outputs.services }}' | jq -r '.[]'); do
|
||||
echo "Waiting for ${service} deployment..."
|
||||
kubectl rollout status deployment/${service} -n ${{ env.NAMESPACE }} --timeout=5m
|
||||
done
|
||||
|
||||
- name: Verify deployment
|
||||
run: |
|
||||
echo "=== Pods Status ==="
|
||||
kubectl get pods -n ${{ env.NAMESPACE }} -l app.kubernetes.io/part-of=kt-event-marketing
|
||||
|
||||
echo "=== Services ==="
|
||||
kubectl get svc -n ${{ env.NAMESPACE }}
|
||||
|
||||
echo "=== Ingress ==="
|
||||
kubectl get ingress -n ${{ env.NAMESPACE }}
|
||||
|
||||
notify:
|
||||
name: Notify Deployment Result
|
||||
needs: [detect-changes, deploy]
|
||||
runs-on: ubuntu-latest
|
||||
if: always()
|
||||
steps:
|
||||
- name: Deployment Success
|
||||
if: needs.deploy.result == 'success'
|
||||
run: |
|
||||
echo "✅ Deployment to ${{ needs.detect-changes.outputs.environment }} succeeded!"
|
||||
echo "Services: ${{ needs.detect-changes.outputs.services }}"
|
||||
|
||||
- name: Deployment Failure
|
||||
if: needs.deploy.result == 'failure'
|
||||
run: |
|
||||
echo "❌ Deployment to ${{ needs.detect-changes.outputs.environment }} failed!"
|
||||
echo "Services: ${{ needs.detect-changes.outputs.services }}"
|
||||
exit 1
|
||||
+10
@@ -8,6 +8,7 @@ yarn-error.log*
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
.run/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
@@ -31,6 +32,13 @@ logs/
|
||||
logs/
|
||||
*.log
|
||||
|
||||
# Gradle
|
||||
.gradle/
|
||||
gradle-app.setting
|
||||
!gradle-wrapper.jar
|
||||
!gradle-wrapper.properties
|
||||
.gradletasknamecache
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
@@ -53,3 +61,5 @@ k8s/**/*-local.yaml
|
||||
|
||||
# Gradle (로컬 환경 설정)
|
||||
gradle.properties
|
||||
*.hprof
|
||||
test-data.json
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 94 KiB |
@@ -0,0 +1,31 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="AiServiceApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
|
||||
<option name="ACTIVE_PROFILES" />
|
||||
<module name="kt-event-marketing.ai-service.main" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="com.kt.ai.AiApplication" />
|
||||
<extension name="coverage">
|
||||
<pattern>
|
||||
<option name="PATTERN" value="com.kt.ai.*" />
|
||||
<option name="ENABLED" value="true" />
|
||||
</pattern>
|
||||
</extension>
|
||||
<envs>
|
||||
<env name="SERVER_PORT" value="8081" />
|
||||
<env name="DB_HOST" value="4.230.112.141" />
|
||||
<env name="DB_PORT" value="5432" />
|
||||
<env name="DB_NAME" value="aidb" />
|
||||
<env name="DB_USERNAME" value="eventuser" />
|
||||
<env name="DB_PASSWORD" value="Hi5Jessica!" />
|
||||
<env name="REDIS_HOST" value="20.214.210.71" />
|
||||
<env name="REDIS_PORT" value="6379" />
|
||||
<env name="REDIS_PASSWORD" value="Hi5Jessica!" />
|
||||
<env name="KAFKA_BOOTSTRAP_SERVERS" value="4.230.50.63:9092" />
|
||||
<env name="KAFKA_CONSUMER_GROUP" value="ai" />
|
||||
<env name="JPA_DDL_AUTO" value="update" />
|
||||
<env name="JPA_SHOW_SQL" value="false" />
|
||||
</envs>
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -0,0 +1,31 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="AnalyticsServiceApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
|
||||
<option name="ACTIVE_PROFILES" />
|
||||
<module name="kt-event-marketing.analytics-service.main" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="com.kt.analytics.AnalyticsApplication" />
|
||||
<extension name="coverage">
|
||||
<pattern>
|
||||
<option name="PATTERN" value="com.kt.analytics.*" />
|
||||
<option name="ENABLED" value="true" />
|
||||
</pattern>
|
||||
</extension>
|
||||
<envs>
|
||||
<env name="SERVER_PORT" value="8087" />
|
||||
<env name="DB_HOST" value="4.230.49.9" />
|
||||
<env name="DB_PORT" value="5432" />
|
||||
<env name="DB_NAME" value="analyticdb" />
|
||||
<env name="DB_USERNAME" value="eventuser" />
|
||||
<env name="DB_PASSWORD" value="Hi5Jessica!" />
|
||||
<env name="REDIS_HOST" value="20.214.210.71" />
|
||||
<env name="REDIS_PORT" value="6379" />
|
||||
<env name="REDIS_PASSWORD" value="Hi5Jessica!" />
|
||||
<env name="KAFKA_BOOTSTRAP_SERVERS" value="4.230.50.63:9092" />
|
||||
<env name="KAFKA_CONSUMER_GROUP" value="analytic" />
|
||||
<env name="JPA_DDL_AUTO" value="update" />
|
||||
<env name="JPA_SHOW_SQL" value="false" />
|
||||
</envs>
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -0,0 +1,29 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="ContentServiceApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
|
||||
<option name="ACTIVE_PROFILES" />
|
||||
<module name="kt-event-marketing.content-service.main" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="com.kt.content.ContentApplication" />
|
||||
<extension name="coverage">
|
||||
<pattern>
|
||||
<option name="PATTERN" value="com.kt.content.*" />
|
||||
<option name="ENABLED" value="true" />
|
||||
</pattern>
|
||||
</extension>
|
||||
<envs>
|
||||
<env name="SERVER_PORT" value="8084" />
|
||||
<env name="DB_HOST" value="4.217.131.139" />
|
||||
<env name="DB_PORT" value="5432" />
|
||||
<env name="DB_NAME" value="contentdb" />
|
||||
<env name="DB_USERNAME" value="eventuser" />
|
||||
<env name="DB_PASSWORD" value="Hi5Jessica!" />
|
||||
<env name="REDIS_HOST" value="20.214.210.71" />
|
||||
<env name="REDIS_PORT" value="6379" />
|
||||
<env name="REDIS_PASSWORD" value="Hi5Jessica!" />
|
||||
<env name="JPA_DDL_AUTO" value="update" />
|
||||
<env name="JPA_SHOW_SQL" value="false" />
|
||||
</envs>
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -0,0 +1,31 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="DistributionServiceApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
|
||||
<option name="ACTIVE_PROFILES" />
|
||||
<module name="kt-event-marketing.distribution-service.main" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="com.kt.distribution.DistributionApplication" />
|
||||
<extension name="coverage">
|
||||
<pattern>
|
||||
<option name="PATTERN" value="com.kt.distribution.*" />
|
||||
<option name="ENABLED" value="true" />
|
||||
</pattern>
|
||||
</extension>
|
||||
<envs>
|
||||
<env name="SERVER_PORT" value="8085" />
|
||||
<env name="DB_HOST" value="4.217.133.59" />
|
||||
<env name="DB_PORT" value="5432" />
|
||||
<env name="DB_NAME" value="distributiondb" />
|
||||
<env name="DB_USERNAME" value="eventuser" />
|
||||
<env name="DB_PASSWORD" value="Hi5Jessica!" />
|
||||
<env name="REDIS_HOST" value="20.214.210.71" />
|
||||
<env name="REDIS_PORT" value="6379" />
|
||||
<env name="REDIS_PASSWORD" value="Hi5Jessica!" />
|
||||
<env name="KAFKA_BOOTSTRAP_SERVERS" value="4.230.50.63:9092" />
|
||||
<env name="KAFKA_CONSUMER_GROUP" value="distribution-service" />
|
||||
<env name="JPA_DDL_AUTO" value="update" />
|
||||
<env name="JPA_SHOW_SQL" value="false" />
|
||||
</envs>
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -0,0 +1,31 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="EventServiceApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
|
||||
<option name="ACTIVE_PROFILES" />
|
||||
<module name="kt-event-marketing.event-service.main" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="com.kt.event.EventApplication" />
|
||||
<extension name="coverage">
|
||||
<pattern>
|
||||
<option name="PATTERN" value="com.kt.event.*" />
|
||||
<option name="ENABLED" value="true" />
|
||||
</pattern>
|
||||
</extension>
|
||||
<envs>
|
||||
<env name="SERVER_PORT" value="8082" />
|
||||
<env name="DB_HOST" value="20.249.177.232" />
|
||||
<env name="DB_PORT" value="5432" />
|
||||
<env name="DB_NAME" value="eventdb" />
|
||||
<env name="DB_USERNAME" value="eventuser" />
|
||||
<env name="DB_PASSWORD" value="Hi5Jessica!" />
|
||||
<env name="REDIS_HOST" value="20.214.210.71" />
|
||||
<env name="REDIS_PORT" value="6379" />
|
||||
<env name="REDIS_PASSWORD" value="Hi5Jessica!" />
|
||||
<env name="KAFKA_BOOTSTRAP_SERVERS" value="4.230.50.63:9092" />
|
||||
<env name="DISTRIBUTION_SERVICE_URL" value="http://localhost:8085" />
|
||||
<env name="JPA_DDL_AUTO" value="update" />
|
||||
<env name="JPA_SHOW_SQL" value="false" />
|
||||
</envs>
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -43,7 +43,7 @@
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value="participation-service:bootRun" />
|
||||
<option value=":participation-service:bootRun" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="UserServiceApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
|
||||
<option name="ACTIVE_PROFILES" />
|
||||
<module name="kt-event-marketing.user-service.main" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="com.kt.user.UserApplication" />
|
||||
<extension name="coverage">
|
||||
<pattern>
|
||||
<option name="PATTERN" value="com.kt.user.*" />
|
||||
<option name="ENABLED" value="true" />
|
||||
</pattern>
|
||||
</extension>
|
||||
<envs>
|
||||
<env name="SERVER_PORT" value="8083" />
|
||||
<env name="DB_HOST" value="20.249.125.115" />
|
||||
<env name="DB_PORT" value="5432" />
|
||||
<env name="DB_NAME" value="userdb" />
|
||||
<env name="DB_USERNAME" value="eventuser" />
|
||||
<env name="DB_PASSWORD" value="Hi5Jessica!" />
|
||||
<env name="REDIS_HOST" value="20.214.210.71" />
|
||||
<env name="REDIS_PORT" value="6379" />
|
||||
<env name="REDIS_PASSWORD" value="Hi5Jessica!" />
|
||||
<env name="JPA_DDL_AUTO" value="update" />
|
||||
<env name="JPA_SHOW_SQL" value="false" />
|
||||
</envs>
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -0,0 +1,84 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="analytics-service" type="GradleRunConfiguration" factoryName="Gradle">
|
||||
<ExternalSystemSettings>
|
||||
<option name="env">
|
||||
<map>
|
||||
<!-- Database Configuration -->
|
||||
<entry key="DB_KIND" value="postgresql" />
|
||||
<entry key="DB_HOST" value="4.230.49.9" />
|
||||
<entry key="DB_PORT" value="5432" />
|
||||
<entry key="DB_NAME" value="analyticdb" />
|
||||
<entry key="DB_USERNAME" value="eventuser" />
|
||||
<entry key="DB_PASSWORD" value="Hi5Jessica!" />
|
||||
|
||||
<!-- JPA Configuration -->
|
||||
<entry key="DDL_AUTO" value="create" />
|
||||
<entry key="SHOW_SQL" value="true" />
|
||||
|
||||
<!-- Redis Configuration -->
|
||||
<entry key="REDIS_HOST" value="20.214.210.71" />
|
||||
<entry key="REDIS_PORT" value="6379" />
|
||||
<entry key="REDIS_PASSWORD" value="Hi5Jessica!" />
|
||||
<entry key="REDIS_DATABASE" value="5" />
|
||||
|
||||
<!-- Kafka Configuration (원격 서버) -->
|
||||
<entry key="KAFKA_ENABLED" value="true" />
|
||||
<entry key="KAFKA_BOOTSTRAP_SERVERS" value="20.249.182.13:9095,4.217.131.59:9095" />
|
||||
<entry key="KAFKA_CONSUMER_GROUP_ID" value="analytics-service-consumers-v3" />
|
||||
|
||||
<!-- Sample Data Configuration (MVP Only) -->
|
||||
<!-- ⚠️ Kafka Producer로 이벤트 발행 (Consumer가 처리) -->
|
||||
<entry key="SAMPLE_DATA_ENABLED" value="true" />
|
||||
|
||||
<!-- Server Configuration -->
|
||||
<entry key="SERVER_PORT" value="8086" />
|
||||
|
||||
<!-- JWT Configuration -->
|
||||
<entry key="JWT_SECRET" value="dev-jwt-secret-key-for-development-only-kt-event-marketing" />
|
||||
<entry key="JWT_ACCESS_TOKEN_VALIDITY" value="1800" />
|
||||
<entry key="JWT_REFRESH_TOKEN_VALIDITY" value="86400" />
|
||||
|
||||
<!-- CORS Configuration -->
|
||||
<entry key="CORS_ALLOWED_ORIGINS" value="http://localhost:*" />
|
||||
|
||||
<!-- Logging Configuration -->
|
||||
<entry key="LOG_FILE" value="logs/analytics-service.log" />
|
||||
<entry key="LOG_LEVEL_APP" value="DEBUG" />
|
||||
<entry key="LOG_LEVEL_WEB" value="INFO" />
|
||||
<entry key="LOG_LEVEL_SQL" value="DEBUG" />
|
||||
<entry key="LOG_LEVEL_SQL_TYPE" value="TRACE" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value="analytics-service:bootRun" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<EXTENSION ID="com.intellij.execution.ExternalSystemRunConfigurationJavaExtension">
|
||||
<extension name="net.ashald.envfile">
|
||||
<option name="IS_ENABLED" value="false" />
|
||||
<option name="IS_SUBST" value="false" />
|
||||
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
|
||||
<option name="IS_IGNORE_MISSING_FILES" value="false" />
|
||||
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
|
||||
<ENTRIES>
|
||||
<ENTRY IS_ENABLED="true" PARSER="runconfig" IS_EXECUTABLE="false" />
|
||||
</ENTRIES>
|
||||
</extension>
|
||||
</EXTENSION>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>false</RunAsTest>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -0,0 +1,244 @@
|
||||
# API 연동 테스트 결과 보고서
|
||||
|
||||
**테스트 일시**: 2025-10-29
|
||||
**테스트 대상**: 프론트엔드(localhost:3000)와 event-service(localhost:8080) API 연동
|
||||
|
||||
---
|
||||
|
||||
## ✅ 테스트 결과 요약
|
||||
|
||||
### 1. 서비스 실행 상태
|
||||
- **프론트엔드**: ✅ Next.js 서버 (port 3000) 정상 실행
|
||||
- **백엔드**: ✅ Event-service (port 8080) 정상 실행
|
||||
- **데이터베이스**: ✅ PostgreSQL 연결 정상 (헬스체크 통과)
|
||||
|
||||
### 2. API 연동 테스트
|
||||
|
||||
#### 2.1 백엔드 API 직접 호출 테스트
|
||||
**테스트 명령어**:
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/api/v1/events?page=0&size=20" \
|
||||
-H "Authorization: Bearer <JWT_TOKEN>" \
|
||||
-H "Content-Type: application/json"
|
||||
```
|
||||
|
||||
**결과**: ✅ **성공**
|
||||
- 응답 코드: 200 OK
|
||||
- 조회된 이벤트: 8개
|
||||
- 응답 형식: JSON (표준 API 응답 포맷)
|
||||
- 페이지네이션: 정상 작동
|
||||
|
||||
**샘플 응답 데이터**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"content": [
|
||||
{
|
||||
"eventId": "2a91c77c-9276-49d3-94d5-0ab8f0b3d343",
|
||||
"userId": "11111111-1111-1111-1111-111111111111",
|
||||
"storeId": "22222222-2222-2222-2222-222222222222",
|
||||
"objective": "awareness",
|
||||
"status": "DRAFT",
|
||||
"createdAt": "2025-10-29T11:08:38.556326"
|
||||
}
|
||||
// ... 7개 더
|
||||
],
|
||||
"page": 0,
|
||||
"size": 20,
|
||||
"totalElements": 8,
|
||||
"totalPages": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 인증 테스트
|
||||
**JWT 토큰**: ✅ 정상 작동
|
||||
- 토큰 생성 스크립트: `generate-test-token.py`
|
||||
- 유효 기간: 365일 (테스트용)
|
||||
- 알고리즘: HS256
|
||||
- Secret: 백엔드와 일치
|
||||
|
||||
#### 2.3 프론트엔드 설정
|
||||
**환경 변수 파일**: `.env.local` 생성 완료
|
||||
```env
|
||||
NEXT_PUBLIC_API_BASE_URL=http://localhost:8081
|
||||
NEXT_PUBLIC_EVENT_HOST=http://localhost:8080
|
||||
NEXT_PUBLIC_API_VERSION=v1
|
||||
```
|
||||
|
||||
**현재 상태**: ⚠️ **Mock 데이터 사용 중**
|
||||
- 파일: `src/app/(main)/events/page.tsx`
|
||||
- 이벤트 목록 페이지가 하드코딩된 Mock 데이터 표시
|
||||
- 실제 API 연동 코드 미구현 상태
|
||||
|
||||
---
|
||||
|
||||
## 📊 API 엔드포인트 정보
|
||||
|
||||
### Event Service (localhost:8080)
|
||||
|
||||
#### 1. 이벤트 목록 조회
|
||||
- **URL**: `GET /api/v1/events`
|
||||
- **인증**: Bearer Token 필수
|
||||
- **파라미터**:
|
||||
- `status`: EventStatus (optional) - DRAFT, PUBLISHED, ENDED
|
||||
- `search`: String (optional) - 검색어
|
||||
- `objective`: String (optional) - 목적 필터
|
||||
- `page`: int (default: 0)
|
||||
- `size`: int (default: 20)
|
||||
- `sort`: String (default: createdAt)
|
||||
- `order`: String (default: desc)
|
||||
|
||||
#### 2. 이벤트 상세 조회
|
||||
- **URL**: `GET /api/v1/events/{eventId}`
|
||||
- **인증**: Bearer Token 필수
|
||||
|
||||
#### 3. 이벤트 생성 (목적 선택)
|
||||
- **URL**: `POST /api/v1/events/objectives`
|
||||
- **인증**: Bearer Token 필수
|
||||
- **Request Body**:
|
||||
```json
|
||||
{
|
||||
"objective": "CUSTOMER_ACQUISITION"
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. 추가 엔드포인트
|
||||
- `DELETE /api/v1/events/{eventId}` - 이벤트 삭제
|
||||
- `POST /api/v1/events/{eventId}/publish` - 이벤트 배포
|
||||
- `POST /api/v1/events/{eventId}/end` - 이벤트 종료
|
||||
- `POST /api/v1/events/{eventId}/ai-recommendations` - AI 추천 요청
|
||||
- `POST /api/v1/events/{eventId}/images` - 이미지 생성 요청
|
||||
- `PUT /api/v1/events/{eventId}` - 이벤트 수정
|
||||
|
||||
---
|
||||
|
||||
## 🔍 발견 사항
|
||||
|
||||
### ✅ 정상 작동 항목
|
||||
1. **백엔드 서비스**
|
||||
- Event-service 정상 실행 (port 8080)
|
||||
- PostgreSQL 데이터베이스 연결 정상
|
||||
- API 엔드포인트 정상 응답
|
||||
- JWT 인증 시스템 작동
|
||||
|
||||
2. **프론트엔드 서비스**
|
||||
- Next.js 개발 서버 정상 실행 (port 3000)
|
||||
- 페이지 렌더링 정상
|
||||
- 환경 변수 설정 완료
|
||||
|
||||
### ⚠️ 개선 필요 항목
|
||||
|
||||
#### 1. 프론트엔드 API 연동 미구현
|
||||
**현재 상태**:
|
||||
- `src/app/(main)/events/page.tsx` 파일이 Mock 데이터 사용
|
||||
- 실제 API 호출 코드 없음
|
||||
|
||||
**권장 수정 사항**:
|
||||
```typescript
|
||||
// src/entities/event/api/eventApi.ts (신규 생성 필요)
|
||||
import { apiClient } from '@/shared/api';
|
||||
|
||||
export const eventApi = {
|
||||
getEvents: async (params) => {
|
||||
const response = await apiClient.get('/api/v1/events', { params });
|
||||
return response.data;
|
||||
},
|
||||
// ... 기타 메서드
|
||||
};
|
||||
```
|
||||
|
||||
#### 2. API 클라이언트 설정 개선
|
||||
**현재**:
|
||||
- `apiClient` 기본 URL이 user-service(8081)를 가리킴
|
||||
- Event API는 별도 서비스(8080)
|
||||
|
||||
**개선 방안**:
|
||||
```typescript
|
||||
// 서비스별 클라이언트 분리 또는
|
||||
// NEXT_PUBLIC_EVENT_HOST 환경 변수 활용
|
||||
const eventApiClient = axios.create({
|
||||
baseURL: process.env.NEXT_PUBLIC_EVENT_HOST || 'http://localhost:8080',
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 테스트 체크리스트
|
||||
|
||||
### 완료된 항목 ✅
|
||||
- [x] 백엔드 서비스 실행 상태 확인
|
||||
- [x] 프론트엔드 서비스 실행 상태 확인
|
||||
- [x] Event Service API 직접 호출 테스트
|
||||
- [x] JWT 인증 토큰 생성 및 테스트
|
||||
- [x] 환경 변수 설정 (`.env.local`)
|
||||
- [x] API 응답 형식 확인
|
||||
- [x] 페이지네이션 동작 확인
|
||||
- [x] 데이터베이스 연결 확인
|
||||
|
||||
### 추가 작업 필요 ⏳
|
||||
- [ ] 프론트엔드 API 연동 코드 작성
|
||||
- [ ] Event API 클라이언트 구현
|
||||
- [ ] React Query 또는 SWR 통합
|
||||
- [ ] 에러 핸들링 구현
|
||||
- [ ] 로딩 상태 UI 구현
|
||||
- [ ] 실제 데이터 렌더링 테스트
|
||||
- [ ] E2E 테스트 작성
|
||||
|
||||
---
|
||||
|
||||
## 🎯 다음 단계 권장사항
|
||||
|
||||
### 1단계: Event API 클라이언트 작성
|
||||
```bash
|
||||
# 파일 생성
|
||||
src/entities/event/api/eventApi.ts
|
||||
src/entities/event/model/types.ts
|
||||
```
|
||||
|
||||
### 2단계: React Query 설정
|
||||
```bash
|
||||
# useEvents 훅 작성
|
||||
src/entities/event/model/useEvents.ts
|
||||
```
|
||||
|
||||
### 3단계: 페이지 수정
|
||||
```bash
|
||||
# Mock 데이터를 실제 API 호출로 교체
|
||||
src/app/(main)/events/page.tsx
|
||||
```
|
||||
|
||||
### 4단계: 통합 테스트
|
||||
- 브라우저에서 실제 데이터 렌더링 확인
|
||||
- 필터링 및 검색 기능 테스트
|
||||
- 페이지네이션 동작 확인
|
||||
|
||||
---
|
||||
|
||||
## 📌 참고 정보
|
||||
|
||||
### 테스트 토큰 정보
|
||||
- User ID: `6db043d0-b303-4577-b9dd-6d366cc59fa0`
|
||||
- Store ID: `34000028-01fd-4ed1-975c-35f7c88b6547`
|
||||
- Email: `test@example.com`
|
||||
- 유효 기간: 2026-10-29까지
|
||||
|
||||
### 서비스 포트 매핑
|
||||
| 서비스 | 포트 | 상태 |
|
||||
|--------|------|------|
|
||||
| 프론트엔드 | 3000 | ✅ Running |
|
||||
| User Service | 8081 | ⚠️ 미확인 |
|
||||
| Event Service | 8080 | ✅ Running |
|
||||
| Content Service | 8082 | ⚠️ 미확인 |
|
||||
| AI Service | 8083 | ⚠️ 미확인 |
|
||||
| Participation Service | 8084 | ⚠️ 미확인 |
|
||||
|
||||
---
|
||||
|
||||
## ✨ 결론
|
||||
|
||||
**백엔드 API는 정상적으로 작동하고 있으며, 프론트엔드와의 연동을 위한 환경은 준비되었습니다.**
|
||||
|
||||
다음 작업은 프론트엔드에서 Mock 데이터를 실제 API 호출로 교체하는 것입니다.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Multi-stage build for Spring Boot application
|
||||
FROM eclipse-temurin:21-jre-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY build/libs/*.jar app.jar
|
||||
RUN java -Djarmode=layertools -jar app.jar extract
|
||||
|
||||
FROM eclipse-temurin:21-jre-alpine
|
||||
WORKDIR /app
|
||||
|
||||
# Create non-root user
|
||||
RUN addgroup -S spring && adduser -S spring -G spring
|
||||
USER spring:spring
|
||||
|
||||
# Copy layers from builder
|
||||
COPY --from=builder /app/dependencies/ ./
|
||||
COPY --from=builder /app/spring-boot-loader/ ./
|
||||
COPY --from=builder /app/snapshot-dependencies/ ./
|
||||
COPY --from=builder /app/application/ ./
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost:8083/api/v1/ai-service/actuator/health || exit 1
|
||||
|
||||
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
|
||||
+22
-2
@@ -1,9 +1,13 @@
|
||||
bootJar {
|
||||
archiveFileName = 'ai-service.jar'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Kafka Consumer
|
||||
implementation 'org.springframework.kafka:spring-kafka'
|
||||
|
||||
// Redis for result caching
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
|
||||
// Redis for result caching (already in root build.gradle)
|
||||
// implementation 'org.springframework.boot:spring-boot-starter-data-redis'
|
||||
|
||||
// OpenFeign for Claude/GPT API
|
||||
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
|
||||
@@ -14,4 +18,20 @@ dependencies {
|
||||
|
||||
// Jackson for JSON
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind'
|
||||
|
||||
// JWT (for security)
|
||||
implementation "io.jsonwebtoken:jjwt-api:${jjwtVersion}"
|
||||
runtimeOnly "io.jsonwebtoken:jjwt-impl:${jjwtVersion}"
|
||||
runtimeOnly "io.jsonwebtoken:jjwt-jackson:${jjwtVersion}"
|
||||
|
||||
// Note: PostgreSQL dependency is in root build.gradle but AI Service doesn't use DB
|
||||
// We still include it for consistency, but no JPA entities will be created
|
||||
}
|
||||
|
||||
// Kafka Manual Test 실행 태스크
|
||||
task runKafkaManualTest(type: JavaExec) {
|
||||
group = 'verification'
|
||||
description = 'Run Kafka manual test'
|
||||
classpath = sourceSets.test.runtimeClasspath
|
||||
mainClass = 'com.kt.ai.test.manual.KafkaManualTest'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.kt.ai;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
|
||||
/**
|
||||
* AI Service Application
|
||||
* - Kafka를 통한 비동기 AI 추천 처리
|
||||
* - Claude API / GPT-4 API 연동
|
||||
* - Redis 기반 결과 캐싱
|
||||
*
|
||||
* @author AI Service Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@EnableFeignClients
|
||||
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
|
||||
public class AiServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AiServiceApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.kt.ai.circuitbreaker;
|
||||
|
||||
import com.kt.ai.exception.CircuitBreakerOpenException;
|
||||
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
|
||||
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Circuit Breaker Manager
|
||||
* - Claude API / GPT-4 API 호출 시 Circuit Breaker 적용
|
||||
* - Fallback 처리
|
||||
*
|
||||
* @author AI Service Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CircuitBreakerManager {
|
||||
|
||||
private final CircuitBreakerRegistry circuitBreakerRegistry;
|
||||
|
||||
/**
|
||||
* Circuit Breaker를 통한 API 호출
|
||||
*
|
||||
* @param circuitBreakerName Circuit Breaker 이름 (claudeApi, gpt4Api)
|
||||
* @param supplier API 호출 로직
|
||||
* @param fallback Fallback 로직
|
||||
* @return API 호출 결과 또는 Fallback 결과
|
||||
*/
|
||||
public <T> T executeWithCircuitBreaker(
|
||||
String circuitBreakerName,
|
||||
Supplier<T> supplier,
|
||||
Supplier<T> fallback
|
||||
) {
|
||||
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(circuitBreakerName);
|
||||
|
||||
try {
|
||||
// Circuit Breaker 상태 확인
|
||||
if (circuitBreaker.getState() == CircuitBreaker.State.OPEN) {
|
||||
log.warn("Circuit Breaker is OPEN: {}", circuitBreakerName);
|
||||
throw new CircuitBreakerOpenException(circuitBreakerName);
|
||||
}
|
||||
|
||||
// Circuit Breaker를 통한 API 호출
|
||||
return circuitBreaker.executeSupplier(() -> {
|
||||
log.debug("Executing with Circuit Breaker: {}", circuitBreakerName);
|
||||
return supplier.get();
|
||||
});
|
||||
|
||||
} catch (CircuitBreakerOpenException e) {
|
||||
// Circuit Breaker가 열린 경우 Fallback 실행
|
||||
log.warn("Circuit Breaker OPEN, executing fallback: {}", circuitBreakerName);
|
||||
if (fallback != null) {
|
||||
return fallback.get();
|
||||
}
|
||||
throw e;
|
||||
|
||||
} catch (Exception e) {
|
||||
// 기타 예외 발생 시 Fallback 실행
|
||||
log.error("API call failed, executing fallback: {}", circuitBreakerName, e);
|
||||
if (fallback != null) {
|
||||
return fallback.get();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Circuit Breaker를 통한 API 호출 (Fallback 없음)
|
||||
*/
|
||||
public <T> T executeWithCircuitBreaker(String circuitBreakerName, Supplier<T> supplier) {
|
||||
return executeWithCircuitBreaker(circuitBreakerName, supplier, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Circuit Breaker 상태 조회
|
||||
*/
|
||||
public CircuitBreaker.State getCircuitBreakerState(String circuitBreakerName) {
|
||||
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(circuitBreakerName);
|
||||
return circuitBreaker.getState();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package com.kt.ai.circuitbreaker.fallback;
|
||||
|
||||
import com.kt.ai.model.dto.response.EventRecommendation;
|
||||
import com.kt.ai.model.dto.response.ExpectedMetrics;
|
||||
import com.kt.ai.model.dto.response.TrendAnalysis;
|
||||
import com.kt.ai.model.enums.EventMechanicsType;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* AI Service Fallback 처리
|
||||
* - Circuit Breaker가 열린 경우 기본 데이터 반환
|
||||
*
|
||||
* @author AI Service Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AIServiceFallback {
|
||||
|
||||
/**
|
||||
* 기본 트렌드 분석 결과 반환
|
||||
*/
|
||||
public TrendAnalysis getDefaultTrendAnalysis(String industry, String region) {
|
||||
log.info("Fallback: 기본 트렌드 분석 결과 반환 - industry={}, region={}", industry, region);
|
||||
|
||||
List<TrendAnalysis.TrendKeyword> industryTrends = List.of(
|
||||
TrendAnalysis.TrendKeyword.builder()
|
||||
.keyword("고객 만족도 향상")
|
||||
.relevance(0.8)
|
||||
.description(industry + " 업종에서 고객 만족도가 중요한 트렌드입니다")
|
||||
.build(),
|
||||
TrendAnalysis.TrendKeyword.builder()
|
||||
.keyword("디지털 마케팅")
|
||||
.relevance(0.75)
|
||||
.description("SNS 및 온라인 마케팅이 효과적입니다")
|
||||
.build()
|
||||
);
|
||||
|
||||
List<TrendAnalysis.TrendKeyword> regionalTrends = List.of(
|
||||
TrendAnalysis.TrendKeyword.builder()
|
||||
.keyword("지역 커뮤니티")
|
||||
.relevance(0.7)
|
||||
.description(region + " 지역 커뮤니티 참여가 효과적입니다")
|
||||
.build()
|
||||
);
|
||||
|
||||
List<TrendAnalysis.TrendKeyword> seasonalTrends = List.of(
|
||||
TrendAnalysis.TrendKeyword.builder()
|
||||
.keyword("시즌 이벤트")
|
||||
.relevance(0.85)
|
||||
.description("계절 특성을 반영한 이벤트가 효과적입니다")
|
||||
.build()
|
||||
);
|
||||
|
||||
return TrendAnalysis.builder()
|
||||
.industryTrends(industryTrends)
|
||||
.regionalTrends(regionalTrends)
|
||||
.seasonalTrends(seasonalTrends)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 기본 이벤트 추천안 반환
|
||||
*/
|
||||
public List<EventRecommendation> getDefaultRecommendations(String objective, String industry) {
|
||||
log.info("Fallback: 기본 이벤트 추천안 반환 - objective={}, industry={}", objective, industry);
|
||||
|
||||
List<EventRecommendation> recommendations = new ArrayList<>();
|
||||
|
||||
// 옵션 1: 저비용 이벤트
|
||||
recommendations.add(createDefaultRecommendation(1, "저비용 SNS 이벤트", objective, industry, 100000, 200000));
|
||||
|
||||
// 옵션 2: 중비용 이벤트
|
||||
recommendations.add(createDefaultRecommendation(2, "중비용 방문 유도 이벤트", objective, industry, 300000, 500000));
|
||||
|
||||
// 옵션 3: 고비용 이벤트
|
||||
recommendations.add(createDefaultRecommendation(3, "고비용 프리미엄 이벤트", objective, industry, 500000, 1000000));
|
||||
|
||||
return recommendations;
|
||||
}
|
||||
|
||||
/**
|
||||
* 기본 추천안 생성
|
||||
*/
|
||||
private EventRecommendation createDefaultRecommendation(
|
||||
int optionNumber,
|
||||
String concept,
|
||||
String objective,
|
||||
String industry,
|
||||
int minCost,
|
||||
int maxCost
|
||||
) {
|
||||
return EventRecommendation.builder()
|
||||
.optionNumber(optionNumber)
|
||||
.concept(concept)
|
||||
.title(objective + " - " + concept)
|
||||
.description("AI 서비스가 일시적으로 사용 불가능하여 기본 추천안을 제공합니다. " +
|
||||
industry + " 업종에 적합한 " + concept + "입니다.")
|
||||
.targetAudience("일반 고객")
|
||||
.duration(EventRecommendation.Duration.builder()
|
||||
.recommendedDays(14)
|
||||
.recommendedPeriod("2주")
|
||||
.build())
|
||||
.mechanics(EventRecommendation.Mechanics.builder()
|
||||
.type(EventMechanicsType.DISCOUNT)
|
||||
.details("할인 쿠폰 제공 또는 경품 추첨")
|
||||
.build())
|
||||
.promotionChannels(List.of("Instagram", "네이버 블로그", "카카오톡 채널"))
|
||||
.estimatedCost(EventRecommendation.EstimatedCost.builder()
|
||||
.min(minCost)
|
||||
.max(maxCost)
|
||||
.breakdown(Map.of(
|
||||
"경품비", minCost / 2,
|
||||
"홍보비", minCost / 2
|
||||
))
|
||||
.build())
|
||||
.expectedMetrics(ExpectedMetrics.builder()
|
||||
.newCustomers(ExpectedMetrics.Range.builder().min(30.0).max(50.0).build())
|
||||
.revenueIncrease(ExpectedMetrics.Range.builder().min(10.0).max(20.0).build())
|
||||
.roi(ExpectedMetrics.Range.builder().min(100.0).max(150.0).build())
|
||||
.build())
|
||||
.differentiator("AI 분석이 제한적으로 제공되는 기본 추천안입니다")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.kt.ai.client;
|
||||
|
||||
import com.kt.ai.client.config.FeignClientConfig;
|
||||
import com.kt.ai.client.dto.ClaudeRequest;
|
||||
import com.kt.ai.client.dto.ClaudeResponse;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
|
||||
/**
|
||||
* Claude API Feign Client
|
||||
* API Docs: https://docs.anthropic.com/claude/reference/messages_post
|
||||
*
|
||||
* @author AI Service Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@FeignClient(
|
||||
name = "claudeApiClient",
|
||||
url = "${ai.claude.api-url}",
|
||||
configuration = FeignClientConfig.class
|
||||
)
|
||||
public interface ClaudeApiClient {
|
||||
|
||||
/**
|
||||
* Claude Messages API 호출
|
||||
*
|
||||
* @param apiKey Claude API Key
|
||||
* @param anthropicVersion API Version (2023-06-01)
|
||||
* @param request Claude 요청
|
||||
* @return Claude 응답
|
||||
*/
|
||||
@PostMapping(consumes = "application/json", produces = "application/json")
|
||||
ClaudeResponse sendMessage(
|
||||
@RequestHeader("x-api-key") String apiKey,
|
||||
@RequestHeader("anthropic-version") String anthropicVersion,
|
||||
@RequestBody ClaudeRequest request
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.kt.ai.client.config;
|
||||
|
||||
import feign.Logger;
|
||||
import feign.Request;
|
||||
import feign.Retryer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Feign Client 설정
|
||||
* - Claude API / GPT-4 API 연동 설정
|
||||
* - Timeout, Retry 설정
|
||||
*
|
||||
* @author AI Service Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Configuration
|
||||
public class FeignClientConfig {
|
||||
|
||||
/**
|
||||
* Feign Logger Level 설정
|
||||
*/
|
||||
@Bean
|
||||
public Logger.Level feignLoggerLevel() {
|
||||
return Logger.Level.FULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Feign Request Options (Timeout 설정)
|
||||
* - Connect Timeout: 10초
|
||||
* - Read Timeout: 5분 (300초)
|
||||
*/
|
||||
@Bean
|
||||
public Request.Options requestOptions() {
|
||||
return new Request.Options(
|
||||
10, TimeUnit.SECONDS, // connectTimeout
|
||||
300, TimeUnit.SECONDS, // readTimeout (5분)
|
||||
true // followRedirects
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Feign Retryer 설정
|
||||
* - 최대 3회 재시도
|
||||
* - Exponential Backoff: 1초, 5초, 10초
|
||||
*/
|
||||
@Bean
|
||||
public Retryer retryer() {
|
||||
return new Retryer.Default(
|
||||
1000L, // period (1초)
|
||||
5000L, // maxPeriod (5초)
|
||||
3 // maxAttempts (3회)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.kt.ai.client.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Claude API 요청 DTO
|
||||
* API Docs: https://docs.anthropic.com/claude/reference/messages_post
|
||||
*
|
||||
* @author AI Service Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ClaudeRequest {
|
||||
/**
|
||||
* 모델명 (예: claude-3-5-sonnet-20241022)
|
||||
*/
|
||||
private String model;
|
||||
|
||||
/**
|
||||
* 메시지 목록
|
||||
*/
|
||||
private List<Message> messages;
|
||||
|
||||
/**
|
||||
* 최대 토큰 수
|
||||
*/
|
||||
@JsonProperty("max_tokens")
|
||||
private Integer maxTokens;
|
||||
|
||||
/**
|
||||
* Temperature (0.0 ~ 1.0)
|
||||
*/
|
||||
private Double temperature;
|
||||
|
||||
/**
|
||||
* System 프롬프트 (선택)
|
||||
*/
|
||||
private String system;
|
||||
|
||||
/**
|
||||
* 메시지
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class Message {
|
||||
/**
|
||||
* 역할 (user, assistant)
|
||||
*/
|
||||
private String role;
|
||||
|
||||
/**
|
||||
* 메시지 내용
|
||||
*/
|
||||
private String content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.kt.ai.client.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Claude API 응답 DTO
|
||||
* API Docs: https://docs.anthropic.com/claude/reference/messages_post
|
||||
*
|
||||
* @author AI Service Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ClaudeResponse {
|
||||
/**
|
||||
* 응답 ID
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 타입 (message)
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 역할 (assistant)
|
||||
*/
|
||||
private String role;
|
||||
|
||||
/**
|
||||
* 콘텐츠 목록
|
||||
*/
|
||||
private List<Content> content;
|
||||
|
||||
/**
|
||||
* 모델명
|
||||
*/
|
||||
private String model;
|
||||
|
||||
/**
|
||||
* 중단 이유 (end_turn, max_tokens, stop_sequence)
|
||||
*/
|
||||
@JsonProperty("stop_reason")
|
||||
private String stopReason;
|
||||
|
||||
/**
|
||||
* 사용량
|
||||
*/
|
||||
private Usage usage;
|
||||
|
||||
/**
|
||||
* 콘텐츠
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class Content {
|
||||
/**
|
||||
* 타입 (text)
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 텍스트 내용
|
||||
*/
|
||||
private String text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 토큰 사용량
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class Usage {
|
||||
/**
|
||||
* 입력 토큰 수
|
||||
*/
|
||||
@JsonProperty("input_tokens")
|
||||
private Integer inputTokens;
|
||||
|
||||
/**
|
||||
* 출력 토큰 수
|
||||
*/
|
||||
@JsonProperty("output_tokens")
|
||||
private Integer outputTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* 텍스트 내용 추출
|
||||
*/
|
||||
public String extractText() {
|
||||
if (content != null && !content.isEmpty()) {
|
||||
return content.get(0).getText();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.kt.ai.config;
|
||||
|
||||
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
|
||||
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.SlidingWindowType;
|
||||
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
|
||||
import io.github.resilience4j.timelimiter.TimeLimiterConfig;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Circuit Breaker 설정
|
||||
* - Claude API / GPT-4 API 장애 대응
|
||||
* - Timeout: 5분 (300초)
|
||||
* - Failure Threshold: 50%
|
||||
*
|
||||
* @author AI Service Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Configuration
|
||||
public class CircuitBreakerConfig {
|
||||
|
||||
/**
|
||||
* Circuit Breaker Registry 설정
|
||||
*/
|
||||
@Bean
|
||||
public CircuitBreakerRegistry circuitBreakerRegistry() {
|
||||
io.github.resilience4j.circuitbreaker.CircuitBreakerConfig config =
|
||||
io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.custom()
|
||||
.failureRateThreshold(50)
|
||||
.slowCallRateThreshold(50)
|
||||
.slowCallDurationThreshold(Duration.ofSeconds(60))
|
||||
.permittedNumberOfCallsInHalfOpenState(3)
|
||||
.maxWaitDurationInHalfOpenState(Duration.ZERO)
|
||||
.slidingWindowType(SlidingWindowType.COUNT_BASED)
|
||||
.slidingWindowSize(10)
|
||||
.minimumNumberOfCalls(5)
|
||||
.waitDurationInOpenState(Duration.ofSeconds(60))
|
||||
.automaticTransitionFromOpenToHalfOpenEnabled(true)
|
||||
.build();
|
||||
|
||||
return CircuitBreakerRegistry.of(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Claude API Circuit Breaker
|
||||
*/
|
||||
@Bean
|
||||
public CircuitBreaker claudeApiCircuitBreaker(CircuitBreakerRegistry registry) {
|
||||
return registry.circuitBreaker("claudeApi");
|
||||
}
|
||||
|
||||
/**
|
||||
* GPT-4 API Circuit Breaker
|
||||
*/
|
||||
@Bean
|
||||
public CircuitBreaker gpt4ApiCircuitBreaker(CircuitBreakerRegistry registry) {
|
||||
return registry.circuitBreaker("gpt4Api");
|
||||
}
|
||||
|
||||
/**
|
||||
* Time Limiter 설정 (5분)
|
||||
*/
|
||||
@Bean
|
||||
public TimeLimiterConfig timeLimiterConfig() {
|
||||
return TimeLimiterConfig.custom()
|
||||
.timeoutDuration(Duration.ofSeconds(300))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.kt.ai.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Jackson ObjectMapper 설정
|
||||
*
|
||||
* @author AI Service Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Configuration
|
||||
public class JacksonConfig {
|
||||
|
||||
@Bean
|
||||
public ObjectMapper objectMapper() {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.registerModule(new JavaTimeModule());
|
||||
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
return mapper;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.kt.ai.config;
|
||||
|
||||
import com.kt.ai.kafka.message.AIJobMessage;
|
||||
import org.apache.kafka.clients.consumer.ConsumerConfig;
|
||||
import org.apache.kafka.common.serialization.StringDeserializer;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.kafka.annotation.EnableKafka;
|
||||
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
|
||||
import org.springframework.kafka.core.ConsumerFactory;
|
||||
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
|
||||
import org.springframework.kafka.listener.ContainerProperties;
|
||||
import org.springframework.kafka.support.serializer.ErrorHandlingDeserializer;
|
||||
import org.springframework.kafka.support.serializer.JsonDeserializer;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Kafka Consumer 설정
|
||||
* - Topic: ai-event-generation-job
|
||||
* - Consumer Group: ai-service-consumers
|
||||
* - Manual ACK 모드
|
||||
*
|
||||
* @author AI Service Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@EnableKafka
|
||||
@Configuration
|
||||
public class KafkaConsumerConfig {
|
||||
|
||||
@Value("${spring.kafka.bootstrap-servers}")
|
||||
private String bootstrapServers;
|
||||
|
||||
@Value("${spring.kafka.consumer.group-id}")
|
||||
private String groupId;
|
||||
|
||||
/**
|
||||
* Kafka Consumer 팩토리 설정
|
||||
*/
|
||||
@Bean
|
||||
public ConsumerFactory<String, AIJobMessage> consumerFactory() {
|
||||
Map<String, Object> props = new HashMap<>();
|
||||
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
|
||||
props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
|
||||
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
|
||||
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
|
||||
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 10);
|
||||
props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 30000);
|
||||
|
||||
// Key Deserializer
|
||||
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
|
||||
|
||||
// Value Deserializer with Error Handling
|
||||
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer.class);
|
||||
props.put(ErrorHandlingDeserializer.VALUE_DESERIALIZER_CLASS, JsonDeserializer.class.getName());
|
||||
props.put(JsonDeserializer.VALUE_DEFAULT_TYPE, AIJobMessage.class.getName());
|
||||
props.put(JsonDeserializer.TRUSTED_PACKAGES, "*");
|
||||
|
||||
return new DefaultKafkaConsumerFactory<>(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Kafka Listener Container Factory 설정
|
||||
* - Manual ACK 모드
|
||||
*/
|
||||
@Bean
|
||||
public ConcurrentKafkaListenerContainerFactory<String, AIJobMessage> kafkaListenerContainerFactory() {
|
||||
ConcurrentKafkaListenerContainerFactory<String, AIJobMessage> factory =
|
||||
new ConcurrentKafkaListenerContainerFactory<>();
|
||||
factory.setConsumerFactory(consumerFactory());
|
||||
factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL);
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.kt.ai.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import io.lettuce.core.ClientOptions;
|
||||
import io.lettuce.core.SocketOptions;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Redis 설정
|
||||
* - 작업 상태 및 추천 결과 캐싱
|
||||
* - TTL: 추천 24시간, Job 상태 24시간, 트렌드 1시간
|
||||
*
|
||||
* @author AI Service Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
@Value("${spring.data.redis.host}")
|
||||
private String redisHost;
|
||||
|
||||
@Value("${spring.data.redis.port}")
|
||||
private int redisPort;
|
||||
|
||||
@Value("${spring.data.redis.password}")
|
||||
private String redisPassword;
|
||||
|
||||
@Value("${spring.data.redis.database}")
|
||||
private int redisDatabase;
|
||||
|
||||
@Value("${spring.data.redis.timeout:3000}")
|
||||
private long redisTimeout;
|
||||
|
||||
/**
|
||||
* Redis 연결 팩토리 설정
|
||||
*/
|
||||
@Bean
|
||||
public RedisConnectionFactory redisConnectionFactory() {
|
||||
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
|
||||
config.setHostName(redisHost);
|
||||
config.setPort(redisPort);
|
||||
if (redisPassword != null && !redisPassword.isEmpty()) {
|
||||
config.setPassword(redisPassword);
|
||||
}
|
||||
config.setDatabase(redisDatabase);
|
||||
|
||||
// Lettuce Client 설정: Timeout 및 Connection 옵션
|
||||
SocketOptions socketOptions = SocketOptions.builder()
|
||||
.connectTimeout(Duration.ofMillis(redisTimeout))
|
||||
.keepAlive(true)
|
||||
.build();
|
||||
|
||||
ClientOptions clientOptions = ClientOptions.builder()
|
||||
.socketOptions(socketOptions)
|
||||
.autoReconnect(true)
|
||||
.build();
|
||||
|
||||
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
|
||||
.commandTimeout(Duration.ofMillis(redisTimeout))
|
||||
.clientOptions(clientOptions)
|
||||
.build();
|
||||
|
||||
// afterPropertiesSet() 제거: Spring이 자동으로 호출함
|
||||
return new LettuceConnectionFactory(config, clientConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* ObjectMapper for Redis (Java 8 Date/Time 지원)
|
||||
*/
|
||||
@Bean
|
||||
public ObjectMapper redisObjectMapper() {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
// Java 8 Date/Time 모듈 등록
|
||||
mapper.registerModule(new JavaTimeModule());
|
||||
|
||||
// Timestamp 대신 ISO-8601 형식으로 직렬화
|
||||
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
|
||||
return mapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* RedisTemplate 설정
|
||||
* - Key: String
|
||||
* - Value: JSON (Jackson with Java 8 Date/Time support)
|
||||
*/
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
|
||||
// Key Serializer: String
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
|
||||
// Value Serializer: JSON with Java 8 Date/Time support
|
||||
GenericJackson2JsonRedisSerializer serializer =
|
||||
new GenericJackson2JsonRedisSerializer(redisObjectMapper());
|
||||
|
||||
template.setValueSerializer(serializer);
|
||||
template.setHashValueSerializer(serializer);
|
||||
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user