Compare commits
No commits in common. "58434ff27691c97992f922b9ca4fe296be9da09d" and "41587a1233af38ebec035ad615d8dcf46fbf9095" have entirely different histories.
58434ff276
...
41587a1233
@ -1,15 +0,0 @@
|
|||||||
images
|
|
||||||
node_modules
|
|
||||||
npm-debug.log
|
|
||||||
build
|
|
||||||
.git
|
|
||||||
.github
|
|
||||||
coverage
|
|
||||||
.env*
|
|
||||||
.cache
|
|
||||||
dist
|
|
||||||
logs
|
|
||||||
**/*.log
|
|
||||||
**/.DS_Store
|
|
||||||
|
|
||||||
|
|
||||||
219
README.md
219
README.md
@ -1,8 +1,8 @@
|
|||||||
# 마이구독 서비스 (LifeSub)
|
# 마이구독 서비스 (LifeSub)
|
||||||
|
|
||||||
|
|
||||||
## 1. 소개
|
## 1. 소개
|
||||||
마이구독은 다양한, 증가하는 생활 구독 서비스를 한 곳에서 편리하게 관리할 수 있는 애플리케이션입니다.
|
마이구독은 다양한, 증가하는 생활 구독 서비스를 한 곳에서 편리하게 관리할 수 있는 애플리케이션입니다. 사용자가 구독 중인 서비스를 한눈에 확인하고, 월별 구독료를 관리하며, 새로운 구독 서비스를 추천받을 수 있습니다.
|
||||||
사용자가 구독 중인 서비스를 한눈에 확인하고, 월별 구독료를 관리하며, 새로운 구독 서비스를 추천받을 수 있습니다.
|
|
||||||
|
|
||||||
### 1.1 핵심 기능
|
### 1.1 핵심 기능
|
||||||
- **구독 관리**: 다양한 구독 서비스를 한 곳에서 관리
|
- **구독 관리**: 다양한 구독 서비스를 한 곳에서 관리
|
||||||
@ -40,8 +40,116 @@
|
|||||||
- **Message Queue**: RabbitMQ
|
- **Message Queue**: RabbitMQ
|
||||||
- **기타**: Redis
|
- **기타**: Redis
|
||||||
|
|
||||||
## 3. 백킹 서비스 설치
|
## 3. 프론트엔드 (lifesub-web)
|
||||||
1. Database 설치
|
|
||||||
|
### 3.1 주요 화면
|
||||||
|
- **로그인 화면**: 사용자 인증
|
||||||
|
- **메인 대시보드**: 월별 구독료 및 구독 서비스 목록, 추천 카테고리 표시
|
||||||
|
- **구독 서비스 목록**: 카테고리별 구독 서비스 브라우징
|
||||||
|
- **구독 상세**: 구독 서비스 상세 정보 및 구독/취소 기능
|
||||||
|
|
||||||
|
### 3.2 API 연동
|
||||||
|
애플리케이션은 다음 백엔드 서비스와 API를 통해 통신합니다:
|
||||||
|
- `MEMBER_URL`: 인증 관련 API
|
||||||
|
- `MYSUB_URL`: 구독 관리 관련 API
|
||||||
|
- `RECOMMEND_URL`: 추천 관련 API
|
||||||
|
|
||||||
|
## 4. 백엔드 서비스
|
||||||
|
|
||||||
|
### 4.1 인증 서비스 (Member)
|
||||||
|
회원 로그인 및 로그아웃, JWT 토큰 관리를 담당합니다.
|
||||||
|
|
||||||
|
#### 4.1.1 주요 API
|
||||||
|
- `POST /login`: 로그인
|
||||||
|
- `POST /logout`: 로그아웃
|
||||||
|
|
||||||
|
### 4.2 구독 서비스 (MySub)
|
||||||
|
사용자의 구독 정보 관리 및 카테고리 관리를 담당합니다.
|
||||||
|
|
||||||
|
#### 4.2.1 주요 API
|
||||||
|
- `GET /total-fee`: 총 구독료 조회
|
||||||
|
- `GET /list`: 사용자 구독 목록 조회
|
||||||
|
- `GET /services/{id}`: 구독 서비스 상세 조회
|
||||||
|
- `POST /services/{id}/subscribe`: 구독 신청
|
||||||
|
- `DELETE /services/{id}`: 구독 취소
|
||||||
|
- `GET /categories`: 카테고리 목록 조회
|
||||||
|
- `GET /services`: 카테고리별 서비스 조회
|
||||||
|
|
||||||
|
### 4.3 추천 서비스 (Recommend)
|
||||||
|
사용자의 지출 패턴 기반 구독 서비스를 추천합니다.
|
||||||
|
|
||||||
|
#### 4.3.1 주요 API
|
||||||
|
- `GET /categories`: 추천 카테고리 조회
|
||||||
|
|
||||||
|
## 5. 개발 환경 설정
|
||||||
|
|
||||||
|
### 5.1 필수 요구사항
|
||||||
|
- Java 21 (백엔드)
|
||||||
|
- Node.js 20.x (프론트엔드)
|
||||||
|
- Docker 또는 Podman
|
||||||
|
- kubectl (Kubernetes CLI)
|
||||||
|
- Gradle (백엔드 빌드)
|
||||||
|
|
||||||
|
### 5.2 프론트엔드 개발 환경 설정
|
||||||
|
1. 저장소 클론
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/your-org/lifesub-web.git
|
||||||
|
cd lifesub-web
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 의존성 설치
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 개발 서버 실행
|
||||||
|
```bash
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 환경 설정
|
||||||
|
프로젝트에서는 `public/runtime-env.js` 파일을 통해 런타임 환경 설정을 관리합니다:
|
||||||
|
```javascript
|
||||||
|
window.__runtime_config__ = {
|
||||||
|
MEMBER_URL: 'http://localhost:8081/api/auth',
|
||||||
|
MYSUB_URL: 'http://localhost:8082/api/mysub',
|
||||||
|
RECOMMEND_URL: 'http://localhost:8083/api/recommend'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 필요한 백킹 서비스 설치
|
||||||
|
|
||||||
|
#### 5.3.1 PostgreSQL 데이터베이스 설치
|
||||||
|
각 마이크로서비스는 별도의 PostgreSQL 데이터베이스를 사용합니다.
|
||||||
|
|
||||||
|
1. Docker로 설치
|
||||||
|
```bash
|
||||||
|
# Member 서비스용 DB
|
||||||
|
docker run -d --name member-postgres \
|
||||||
|
-e POSTGRES_DB=member \
|
||||||
|
-e POSTGRES_USER=admin \
|
||||||
|
-e POSTGRES_PASSWORD=Passw0rd \
|
||||||
|
-p 5432:5432 \
|
||||||
|
postgres:13.2-alpine
|
||||||
|
|
||||||
|
# MySub 서비스용 DB
|
||||||
|
docker run -d --name mysub-postgres \
|
||||||
|
-e POSTGRES_DB=mysub \
|
||||||
|
-e POSTGRES_USER=admin \
|
||||||
|
-e POSTGRES_PASSWORD=Passw0rd \
|
||||||
|
-p 5433:5432 \
|
||||||
|
postgres:13.2-alpine
|
||||||
|
|
||||||
|
# Recommend 서비스용 DB
|
||||||
|
docker run -d --name recommend-postgres \
|
||||||
|
-e POSTGRES_DB=recommend \
|
||||||
|
-e POSTGRES_USER=admin \
|
||||||
|
-e POSTGRES_PASSWORD=Passw0rd \
|
||||||
|
-p 5434:5432 \
|
||||||
|
postgres:13.2-alpine
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Kubernetes Helm Chart로 설치
|
||||||
```bash
|
```bash
|
||||||
# Helm 저장소 추가
|
# Helm 저장소 추가
|
||||||
helm repo add bitnami https://charts.bitnami.com/bitnami
|
helm repo add bitnami https://charts.bitnami.com/bitnami
|
||||||
@ -49,36 +157,89 @@
|
|||||||
|
|
||||||
# Member 서비스용 DB
|
# Member 서비스용 DB
|
||||||
helm install member bitnami/postgresql \
|
helm install member bitnami/postgresql \
|
||||||
--set global.postgresql.auth.postgresPassword=Passw0rd \
|
--set global.postgresql.auth.postgresPassword=Passw0rd \
|
||||||
--set global.postgresql.auth.username=admin \
|
--set global.postgresql.auth.username=admin \
|
||||||
--set global.postgresql.auth.password=Passw0rd \
|
--set global.postgresql.auth.password=Passw0rd \
|
||||||
--set global.postgresql.auth.database=member
|
--set global.postgresql.auth.database=member
|
||||||
|
|
||||||
# MySub 서비스용 DB
|
# MySub 서비스용 DB
|
||||||
helm install mysub bitnami/postgresql \
|
helm install mysub bitnami/postgresql \
|
||||||
--set global.postgresql.auth.postgresPassword=Passw0rd \
|
--set global.postgresql.auth.postgresPassword=Passw0rd \
|
||||||
--set global.postgresql.auth.username=admin \
|
--set global.postgresql.auth.username=admin \
|
||||||
--set global.postgresql.auth.password=Passw0rd \
|
--set global.postgresql.auth.password=Passw0rd \
|
||||||
--set global.postgresql.auth.database=mysub
|
--set global.postgresql.auth.database=mysub
|
||||||
|
|
||||||
# Recommend 서비스용 DB
|
# Recommend 서비스용 DB
|
||||||
helm install recommend bitnami/postgresql \
|
helm install recommend bitnami/postgresql \
|
||||||
--set global.postgresql.auth.postgresPassword=Passw0rd \
|
--set global.postgresql.auth.postgresPassword=Passw0rd \
|
||||||
--set global.postgresql.auth.username=admin \
|
--set global.postgresql.auth.username=admin \
|
||||||
--set global.postgresql.auth.password=Passw0rd \
|
--set global.postgresql.auth.password=Passw0rd \
|
||||||
--set global.postgresql.auth.database=recommend
|
--set global.postgresql.auth.database=recommend
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Message Queue 설치
|
#### 5.3.2 Message Queue 설치
|
||||||
설치 방법
|
|
||||||
{MQ별 설치 방법 안내}
|
|
||||||
|
|
||||||
|
### 5.4 백엔드 개발 환경 설정
|
||||||
|
1. 저장소 클론
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/your-org/lifesub.git
|
||||||
|
cd lifesub
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 의존성 설치 및 각 서비스 개별 빌드
|
||||||
|
```bash
|
||||||
|
# 각 서비스 모듈을 개별적으로 빌드
|
||||||
|
./gradlew :member:build
|
||||||
|
./gradlew :mysub-infra:build
|
||||||
|
./gradlew :recommend:build
|
||||||
|
|
||||||
## 4. 빌드 및 배포
|
# 또는 테스트 스킵 옵션으로 빌드
|
||||||
|
./gradlew :member:build -x test
|
||||||
|
./gradlew :mysub-infra:build -x test
|
||||||
|
./gradlew :recommend:build -x test
|
||||||
|
```
|
||||||
|
|
||||||
### 4.1 프론트엔드 빌드 및 배포
|
3. 각 서비스 실행
|
||||||
1. 컨테이너 이미지 빌드
|
```bash
|
||||||
|
# Member 서비스 실행
|
||||||
|
java -jar member/build/libs/member.jar
|
||||||
|
|
||||||
|
# MySub 서비스 실행 (mysub-biz는 라이브러리 모듈로 mysub-infra에 포함됨)
|
||||||
|
java -jar mysub-infra/build/libs/mysub.jar
|
||||||
|
|
||||||
|
# Recommend 서비스 실행
|
||||||
|
java -jar recommend/build/libs/recommend.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 환경 설정
|
||||||
|
각 서비스는 `application.yml` 파일에서 설정을 관리합니다. 기본 구성:
|
||||||
|
```yaml
|
||||||
|
server:
|
||||||
|
port: 8081 # 각 서비스별로 다른 포트 사용
|
||||||
|
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:postgresql://localhost:5432/member # 각 서비스별 DB 이름 변경 필요
|
||||||
|
username: admin
|
||||||
|
password: Passw0rd
|
||||||
|
|
||||||
|
jwt:
|
||||||
|
secret-key: '8O2HQ13etL2BWZvYOiWsJ5uWFoLi6NBUG8divYVoCgtHVvlk3dqRksMl16toztDUeBTSIuOOPvHIrYq11G2BwQ'
|
||||||
|
access-token-validity: 3600000
|
||||||
|
refresh-token-validity: 86400000
|
||||||
|
|
||||||
|
allowed-origins: http://localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 빌드 및 배포
|
||||||
|
|
||||||
|
### 6.1 프론트엔드 빌드 및 배포
|
||||||
|
1. 애플리케이션 빌드
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 컨테이너 이미지 빌드
|
||||||
```bash
|
```bash
|
||||||
docker build \
|
docker build \
|
||||||
--build-arg PROJECT_FOLDER="." \
|
--build-arg PROJECT_FOLDER="." \
|
||||||
@ -91,17 +252,17 @@
|
|||||||
-t {Image Registry주소}/lifesub/lifesub-web:latest .
|
-t {Image Registry주소}/lifesub/lifesub-web:latest .
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 이미지 푸시
|
3. 이미지 푸시
|
||||||
```bash
|
```bash
|
||||||
docker push {Image Registry주소}/lifesub/lifesub-web:latest
|
docker push {Image Registry주소}/lifesub/lifesub-web:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Kubernetes 배포
|
4. Kubernetes 배포
|
||||||
```bash
|
```bash
|
||||||
kubectl apply -f deployment/manifest/
|
kubectl apply -f deployment/manifest/
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4.2 백엔드 빌드 및 배포
|
### 6.2 백엔드 빌드 및 배포
|
||||||
1. 애플리케이션 빌드
|
1. 애플리케이션 빌드
|
||||||
```bash
|
```bash
|
||||||
# 각 서비스 모듈을 개별적으로 빌드
|
# 각 서비스 모듈을 개별적으로 빌드
|
||||||
@ -146,18 +307,19 @@
|
|||||||
kubectl apply -f deployment/manifest/
|
kubectl apply -f deployment/manifest/
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4.3 테스트
|
### 6.3 로그인
|
||||||
1) 프론트 페이지 주소 구하기
|
1) 프론트 페이지 주소 구하기
|
||||||
```
|
```
|
||||||
kubens {namespace}
|
kubens {namespace}
|
||||||
k get svc
|
k get ing
|
||||||
```
|
```
|
||||||
|
|
||||||
2) 로그인
|
2) 로그인
|
||||||
- ID: user01 ~ user05
|
- ID: user01 ~ user05
|
||||||
- PW: P@ssw0rd$
|
- PW: P@ssw0rd$
|
||||||
|
|
||||||
## 5. 팀
|
|
||||||
|
## 7. 팀
|
||||||
|
|
||||||
- 오유진 "피오" - Product Owner
|
- 오유진 "피오" - Product Owner
|
||||||
- 강동훈 "테키" - Tech Lead
|
- 강동훈 "테키" - Tech Lead
|
||||||
@ -166,4 +328,3 @@ k get svc
|
|||||||
- 박소연 "프개" - Frontend Developer
|
- 박소연 "프개" - Frontend Developer
|
||||||
- 최진우 "큐에이" - QA Engineer
|
- 최진우 "큐에이" - QA Engineer
|
||||||
- 정해린 "데브옵스" - DevOps Engineer
|
- 정해린 "데브옵스" - DevOps Engineer
|
||||||
|
|
||||||
|
|||||||
19
deployment/Jenkinsfile
vendored
19
deployment/Jenkinsfile
vendored
@ -37,14 +37,14 @@ podTemplate(
|
|||||||
stage('Code Analysis & Quality Gate') {
|
stage('Code Analysis & Quality Gate') {
|
||||||
container('node') {
|
container('node') {
|
||||||
sh "npm install"
|
sh "npm install"
|
||||||
sh "npm test -- --coverage --passWithNoTests"
|
sh "npm test -- --coverage --passWithNoTests" //test code 없어도 통과되게 함
|
||||||
}
|
}
|
||||||
|
|
||||||
container('sonar-scanner') {
|
container('sonar-scanner') {
|
||||||
withSonarQubeEnv('SonarQube') {
|
withSonarQubeEnv('SonarQube') {
|
||||||
sh """
|
sh """
|
||||||
${sonarScannerHome}/bin/sonar-scanner \
|
${sonarScannerHome}/bin/sonar-scanner \
|
||||||
-Dsonar.projectKey=lifesub-web-dg0400 \
|
-Dsonar.projectKey=lifesub-web \
|
||||||
-Dsonar.sources=src \
|
-Dsonar.sources=src \
|
||||||
-Dsonar.tests=src \
|
-Dsonar.tests=src \
|
||||||
-Dsonar.test.inclusions=src/**/*.test.js,src/**/*.test.jsx \
|
-Dsonar.test.inclusions=src/**/*.test.js,src/**/*.test.jsx \
|
||||||
@ -66,7 +66,7 @@ podTemplate(
|
|||||||
withCredentials([azureServicePrincipal('azure-credentials')]) {
|
withCredentials([azureServicePrincipal('azure-credentials')]) {
|
||||||
sh """
|
sh """
|
||||||
az login --service-principal -u \$AZURE_CLIENT_ID -p \$AZURE_CLIENT_SECRET -t \$AZURE_TENANT_ID
|
az login --service-principal -u \$AZURE_CLIENT_ID -p \$AZURE_CLIENT_SECRET -t \$AZURE_TENANT_ID
|
||||||
az aks get-credentials --resource-group rg-digitalgarage-01 --name aks-digitalgarage-01 --overwrite-existing
|
az aks get-credentials --resource-group ictcoe-edu --name ${props.teamid}-aks --overwrite-existing
|
||||||
kubectl create namespace ${namespace} --dry-run=client -o yaml | kubectl apply -f -
|
kubectl create namespace ${namespace} --dry-run=client -o yaml | kubectl apply -f -
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
@ -87,9 +87,12 @@ podTemplate(
|
|||||||
|
|
||||||
podman build \
|
podman build \
|
||||||
--build-arg PROJECT_FOLDER="." \
|
--build-arg PROJECT_FOLDER="." \
|
||||||
--build-arg BUILD_FOLDER="deployment/container" \
|
--build-arg REACT_APP_MEMBER_URL="${props.react_app_member_url}" \
|
||||||
|
--build-arg REACT_APP_MYSUB_URL="${props.react_app_mysub_url}" \
|
||||||
|
--build-arg REACT_APP_RECOMMEND_URL="${props.react_app_recommend_url}" \
|
||||||
|
--build-arg BUILD_FOLDER="deployment" \
|
||||||
--build-arg EXPORT_PORT="${props.export_port}" \
|
--build-arg EXPORT_PORT="${props.export_port}" \
|
||||||
-f deployment/container/Dockerfile-lifesub-web \
|
-f deployment/Dockerfile-lifesub-web \
|
||||||
-t ${imagePath} .
|
-t ${imagePath} .
|
||||||
|
|
||||||
podman push ${imagePath}
|
podman push ${imagePath}
|
||||||
@ -105,14 +108,12 @@ podTemplate(
|
|||||||
export lifesub_web_image_path=${props.registry}/${props.image_org}/lifesub-web:${imageTag}
|
export lifesub_web_image_path=${props.registry}/${props.image_org}/lifesub-web:${imageTag}
|
||||||
export replicas=${props.replicas}
|
export replicas=${props.replicas}
|
||||||
export export_port=${props.export_port}
|
export export_port=${props.export_port}
|
||||||
export ingress_host=${props.ingress_host}
|
|
||||||
export resources_requests_cpu=${props.resources_requests_cpu}
|
export resources_requests_cpu=${props.resources_requests_cpu}
|
||||||
export resources_requests_memory=${props.resources_requests_memory}
|
export resources_requests_memory=${props.resources_requests_memory}
|
||||||
export resources_limits_cpu=${props.resources_limits_cpu}
|
export resources_limits_cpu=${props.resources_limits_cpu}
|
||||||
export resources_limits_memory=${props.resources_limits_memory}
|
export resources_limits_memory=${props.resources_limits_memory}
|
||||||
|
|
||||||
envsubst < deployment/${manifest}.template > deployment/${manifest}
|
envsubst < deployment/${manifest}.template > deployment/${manifest}
|
||||||
echo "Generated manifest file:"
|
|
||||||
cat deployment/${manifest}
|
cat deployment/${manifest}
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
@ -125,10 +126,10 @@ podTemplate(
|
|||||||
kubectl -n ${namespace} wait --for=condition=available deployment/lifesub-web --timeout=300s
|
kubectl -n ${namespace} wait --for=condition=available deployment/lifesub-web --timeout=300s
|
||||||
|
|
||||||
echo "Waiting for service external IP..."
|
echo "Waiting for service external IP..."
|
||||||
while [[ -z \$(kubectl -n ${namespace} get svc lifesub-web-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}') ]]; do
|
while [[ -z \$(kubectl -n ${namespace} get svc lifesub-web -o jsonpath='{.status.loadBalancer.ingress[0].ip}') ]]; do
|
||||||
sleep 5
|
sleep 5
|
||||||
done
|
done
|
||||||
echo "Service external IP: \$(kubectl -n ${namespace} get svc lifesub-web-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}')"
|
echo "Service external IP: \$(kubectl -n ${namespace} get svc lifesub-web -o jsonpath='{.status.loadBalancer.ingress[0].ip}')"
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,34 +12,42 @@ podTemplate(
|
|||||||
containers: [
|
containers: [
|
||||||
containerTemplate(name: 'node', image: 'node:20-slim', ttyEnabled: true, command: 'cat'),
|
containerTemplate(name: 'node', image: 'node:20-slim', ttyEnabled: true, command: 'cat'),
|
||||||
containerTemplate(name: 'podman', image: "mgoltzsche/podman", ttyEnabled: true, command: 'cat', privileged: true),
|
containerTemplate(name: 'podman', image: "mgoltzsche/podman", ttyEnabled: true, command: 'cat', privileged: true),
|
||||||
containerTemplate(name: 'git', image: 'alpine/git:latest', command: 'cat', ttyEnabled: true),
|
containerTemplate(name: 'azure-cli', image: 'hiondal/azure-kubectl:latest', command: 'cat', ttyEnabled: true),
|
||||||
containerTemplate(name: 'sonar-scanner', image: 'sonarsource/sonar-scanner-cli:latest', command: 'cat', ttyEnabled: true)
|
containerTemplate(name: 'envsubst', image: "hiondal/envsubst", command: 'sleep', args: '1h'),
|
||||||
|
containerTemplate(name: 'sonar-scanner', image: 'sonarsource/sonar-scanner-cli:latest', command: 'cat', ttyEnabled: true),
|
||||||
|
containerTemplate(name: 'git', image: 'alpine/git:latest', command: 'cat', ttyEnabled: true)
|
||||||
],
|
],
|
||||||
volumes: [
|
volumes: [
|
||||||
|
emptyDirVolume(mountPath: '/root/.azure', memory: false),
|
||||||
emptyDirVolume(mountPath: '/opt/sonar-scanner/.sonar/cache', memory: false)
|
emptyDirVolume(mountPath: '/opt/sonar-scanner/.sonar/cache', memory: false)
|
||||||
]
|
]
|
||||||
) {
|
) {
|
||||||
node(PIPELINE_ID) {
|
node(PIPELINE_ID) {
|
||||||
def props
|
def props
|
||||||
def imageTag = getImageTag()
|
def imageTag = getImageTag()
|
||||||
|
def manifest = "deploy.yaml"
|
||||||
|
def namespace
|
||||||
def sonarScannerHome = '/opt/sonar-scanner'
|
def sonarScannerHome = '/opt/sonar-scanner'
|
||||||
|
def MANIFEST_REPO = "https://github.com/cna-bootcamp/lifesub-manifest.git"
|
||||||
|
def MANIFEST_BRANCH = "main"
|
||||||
|
|
||||||
stage("Get Source") {
|
stage("Get Source") {
|
||||||
checkout scm
|
checkout scm
|
||||||
props = readProperties file: "deployment/deploy_env_vars"
|
props = readProperties file: "deployment/deploy_env_vars"
|
||||||
|
namespace = "${props.namespace}"
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Code Analysis & Quality Gate') {
|
stage('Code Analysis & Quality Gate') {
|
||||||
container('node') {
|
container('node') {
|
||||||
sh "npm install"
|
sh "npm install"
|
||||||
sh "npm test -- --coverage --passWithNoTests"
|
sh "npm test -- --coverage --passWithNoTests" //test code 없어도 통과되게 함
|
||||||
}
|
}
|
||||||
|
|
||||||
container('sonar-scanner') {
|
container('sonar-scanner') {
|
||||||
withSonarQubeEnv('SonarQube') {
|
withSonarQubeEnv('SonarQube') {
|
||||||
sh """
|
sh """
|
||||||
${sonarScannerHome}/bin/sonar-scanner \
|
${sonarScannerHome}/bin/sonar-scanner \
|
||||||
-Dsonar.projectKey=lifesub-web-dg0400 \
|
-Dsonar.projectKey=lifesub-web \
|
||||||
-Dsonar.sources=src \
|
-Dsonar.sources=src \
|
||||||
-Dsonar.tests=src \
|
-Dsonar.tests=src \
|
||||||
-Dsonar.test.inclusions=src/**/*.test.js,src/**/*.test.jsx \
|
-Dsonar.test.inclusions=src/**/*.test.js,src/**/*.test.jsx \
|
||||||
@ -56,6 +64,18 @@ podTemplate(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stage("Setup AKS") {
|
||||||
|
container('azure-cli') {
|
||||||
|
withCredentials([azureServicePrincipal('azure-credentials')]) {
|
||||||
|
sh """
|
||||||
|
az login --service-principal -u \$AZURE_CLIENT_ID -p \$AZURE_CLIENT_SECRET -t \$AZURE_TENANT_ID
|
||||||
|
az aks get-credentials --resource-group ictcoe-edu --name ${props.teamid}-aks --overwrite-existing
|
||||||
|
kubectl create namespace ${namespace} --dry-run=client -o yaml | kubectl apply -f -
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
stage('Build & Push Image') {
|
stage('Build & Push Image') {
|
||||||
container('podman') {
|
container('podman') {
|
||||||
withCredentials([usernamePassword(
|
withCredentials([usernamePassword(
|
||||||
@ -70,9 +90,12 @@ podTemplate(
|
|||||||
|
|
||||||
podman build \
|
podman build \
|
||||||
--build-arg PROJECT_FOLDER="." \
|
--build-arg PROJECT_FOLDER="." \
|
||||||
--build-arg BUILD_FOLDER="deployment/container" \
|
--build-arg REACT_APP_MEMBER_URL="${props.react_app_member_url}" \
|
||||||
|
--build-arg REACT_APP_MYSUB_URL="${props.react_app_mysub_url}" \
|
||||||
|
--build-arg REACT_APP_RECOMMEND_URL="${props.react_app_recommend_url}" \
|
||||||
|
--build-arg BUILD_FOLDER="deployment" \
|
||||||
--build-arg EXPORT_PORT="${props.export_port}" \
|
--build-arg EXPORT_PORT="${props.export_port}" \
|
||||||
-f deployment/container/Dockerfile-lifesub-web \
|
-f deployment/Dockerfile-lifesub-web \
|
||||||
-t ${imagePath} .
|
-t ${imagePath} .
|
||||||
|
|
||||||
podman push ${imagePath}
|
podman push ${imagePath}
|
||||||
@ -83,37 +106,27 @@ podTemplate(
|
|||||||
|
|
||||||
stage('Update Manifest Repository') {
|
stage('Update Manifest Repository') {
|
||||||
container('git') {
|
container('git') {
|
||||||
withCredentials([usernamePassword(
|
// 임시 디렉토리 생성
|
||||||
credentialsId: 'github-credentials-dg0400',
|
sh "mkdir -p /tmp/manifests"
|
||||||
usernameVariable: 'GIT_USERNAME',
|
|
||||||
passwordVariable: 'GIT_PASSWORD'
|
// Clone manifest repository
|
||||||
)]) {
|
withCredentials([usernamePassword(credentialsId: 'github-credentials', usernameVariable: 'GIT_USERNAME', passwordVariable: 'GIT_PASSWORD')]) {
|
||||||
sh """
|
sh """
|
||||||
# Git 설정
|
|
||||||
git config --global user.name "Jenkins"
|
|
||||||
git config --global user.email "jenkins@example.com"
|
git config --global user.email "jenkins@example.com"
|
||||||
|
git config --global user.name "Jenkins Pipeline"
|
||||||
|
|
||||||
# Manifest Repository Clone
|
git clone https://${GIT_USERNAME}:${GIT_PASSWORD}@github.com/cna-bootcamp/lifesub-manifest.git /tmp/manifests
|
||||||
git clone https://\$GIT_USERNAME:\$GIT_PASSWORD@github.com/cna-bootcamp/lifesub-manifest.git
|
cd /tmp/manifests
|
||||||
cd lifesub-manifest
|
|
||||||
|
|
||||||
# Frontend 이미지 태그 업데이트
|
# Update frontend image tag
|
||||||
echo "Updating lifesub-web deployment with image tag: ${imageTag}"
|
if [ -f lifesub-web/deployments/lifesub-web-deployment.yaml ]; then
|
||||||
|
sed -i "s|image: ${props.registry}/${props.image_org}/lifesub-web:.*|image: ${props.registry}/${props.image_org}/lifesub-web:${imageTag}|g" lifesub-web/deployments/lifesub-web-deployment.yaml
|
||||||
if [ -f "lifesub-web/frontend-deployment.yaml" ]; then
|
|
||||||
# 기존 이미지 태그를 새 태그로 교체
|
|
||||||
sed -i "s|image: ${props.registry}/${props.image_org}/lifesub-web:.*|image: ${props.registry}/${props.image_org}/lifesub-web:${imageTag}|g" lifesub-web/frontend-deployment.yaml
|
|
||||||
|
|
||||||
echo "Updated frontend deployment file:"
|
|
||||||
cat lifesub-web/frontend-deployment.yaml | grep "image:"
|
|
||||||
else
|
|
||||||
echo "Warning: Frontend deployment file not found"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 변경사항 커밋 및 푸시
|
# Commit and push changes
|
||||||
git add .
|
git add .
|
||||||
git commit -m "Update frontend service image tag to ${imageTag}" || echo "No changes to commit"
|
git commit -m "Update frontend image tag to ${imageTag}" || true
|
||||||
git push origin main
|
git push
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,26 +1,9 @@
|
|||||||
# ConfigMap
|
# Frontend Deployment
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: frontend-config
|
|
||||||
namespace: ${namespace}
|
|
||||||
data:
|
|
||||||
runtime-env.js: |
|
|
||||||
window.__runtime_config__ = {
|
|
||||||
MEMBER_URL: 'http://${ingress_host}/api/auth',
|
|
||||||
MYSUB_URL: 'http://${ingress_host}/api/mysub',
|
|
||||||
RECOMMEND_URL: 'http://${ingress_host}/api/recommend'
|
|
||||||
};
|
|
||||||
|
|
||||||
---
|
|
||||||
# Deployment
|
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: lifesub-web
|
name: lifesub-web
|
||||||
namespace: ${namespace}
|
namespace: ${namespace}
|
||||||
labels:
|
|
||||||
app: lifesub-web
|
|
||||||
spec:
|
spec:
|
||||||
replicas: ${replicas}
|
replicas: ${replicas}
|
||||||
selector:
|
selector:
|
||||||
@ -31,8 +14,6 @@ spec:
|
|||||||
labels:
|
labels:
|
||||||
app: lifesub-web
|
app: lifesub-web
|
||||||
spec:
|
spec:
|
||||||
imagePullSecrets:
|
|
||||||
- name: acr-secret
|
|
||||||
containers:
|
containers:
|
||||||
- name: lifesub-web
|
- name: lifesub-web
|
||||||
image: ${lifesub_web_image_path}
|
image: ${lifesub_web_image_path}
|
||||||
@ -46,29 +27,18 @@ spec:
|
|||||||
limits:
|
limits:
|
||||||
cpu: ${resources_limits_cpu}
|
cpu: ${resources_limits_cpu}
|
||||||
memory: ${resources_limits_memory}
|
memory: ${resources_limits_memory}
|
||||||
volumeMounts:
|
|
||||||
- name: runtime-config
|
|
||||||
mountPath: /usr/share/nginx/html/runtime-env.js
|
|
||||||
subPath: runtime-env.js
|
|
||||||
volumes:
|
|
||||||
- name: runtime-config
|
|
||||||
configMap:
|
|
||||||
name: frontend-config
|
|
||||||
|
|
||||||
---
|
---
|
||||||
# Service
|
# Frontend Service
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
name: lifesub-web-service
|
name: lifesub-web
|
||||||
namespace: ${namespace}
|
namespace: ${namespace}
|
||||||
labels:
|
|
||||||
app: lifesub-web
|
|
||||||
spec:
|
spec:
|
||||||
type: LoadBalancer
|
selector:
|
||||||
|
app: lifesub-web
|
||||||
ports:
|
ports:
|
||||||
- port: 80
|
- port: 80
|
||||||
targetPort: ${export_port}
|
targetPort: ${export_port}
|
||||||
protocol: TCP
|
type: LoadBalancer
|
||||||
selector:
|
|
||||||
app: lifesub-web
|
|
||||||
|
|||||||
@ -1,16 +1,20 @@
|
|||||||
# Team Settings
|
# Team Settings
|
||||||
teamid=dg0400
|
teamid=unicorn
|
||||||
root_project=lifesub-web
|
root_project=lifesub-web
|
||||||
namespace=dg0400-lifesub-ns
|
namespace=unicorn-lifesub-ns
|
||||||
|
|
||||||
# Container Registry Settings
|
# Container Registry Settings
|
||||||
registry=acrdigitalgarage01.azurecr.io
|
registry=unicorncr.azurecr.io
|
||||||
image_org=dg0400
|
image_org=lifesub
|
||||||
|
|
||||||
# Application Settings
|
# Application Settings
|
||||||
replicas=1
|
replicas=1
|
||||||
export_port=18080
|
export_port=18080
|
||||||
ingress_host=dg0400.20.214.196.128.nip.io
|
|
||||||
|
# Backend Service URLs
|
||||||
|
react_app_member_url=http://20.249.205.95/member
|
||||||
|
react_app_mysub_url=http://20.249.205.95/mysub
|
||||||
|
react_app_recommend_url=http://20.249.205.95/recommend
|
||||||
|
|
||||||
# Resource Settings
|
# Resource Settings
|
||||||
resources_requests_cpu=256m
|
resources_requests_cpu=256m
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: frontend-config
|
|
||||||
namespace: dg0400-lifesub-ns
|
|
||||||
data:
|
|
||||||
runtime-env.js: |
|
|
||||||
window.__runtime_config__ = {
|
|
||||||
MEMBER_URL: 'http://dg0400.20.214.196.128.nip.io/api/auth',
|
|
||||||
MYSUB_URL: 'http://dg0400.20.214.196.128.nip.io/api/mysub',
|
|
||||||
RECOMMEND_URL: 'http://dg0400.20.214.196.128.nip.io/api/recommend'
|
|
||||||
};
|
|
||||||
@ -1,96 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: frontend-config
|
|
||||||
namespace: dg0400-lifesub-ns
|
|
||||||
data:
|
|
||||||
runtime-env.js: |
|
|
||||||
window.__runtime_config__ = {
|
|
||||||
MEMBER_URL: 'http://dg0400.20.214.196.128.nip.io/api/auth',
|
|
||||||
MYSUB_URL: 'http://dg0400.20.214.196.128.nip.io/api/mysub',
|
|
||||||
RECOMMEND_URL: 'http://dg0400.20.214.196.128.nip.io/api/recommend'
|
|
||||||
};
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: lifesub-web
|
|
||||||
namespace: dg0400-lifesub-ns
|
|
||||||
labels:
|
|
||||||
app: lifesub-web
|
|
||||||
spec:
|
|
||||||
replicas: 2
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: lifesub-web
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: lifesub-web
|
|
||||||
spec:
|
|
||||||
imagePullSecrets:
|
|
||||||
- name: acr-secret
|
|
||||||
containers:
|
|
||||||
- name: lifesub-web
|
|
||||||
image: dg0400cr.azurecr.io/dg0400/lifesub-web:1.0.0
|
|
||||||
imagePullPolicy: Always
|
|
||||||
ports:
|
|
||||||
- containerPort: 18080
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: 256m
|
|
||||||
memory: 256Mi
|
|
||||||
limits:
|
|
||||||
cpu: 1024m
|
|
||||||
memory: 1024Mi
|
|
||||||
volumeMounts:
|
|
||||||
- name: runtime-config
|
|
||||||
mountPath: /usr/share/nginx/html/runtime-env.js
|
|
||||||
subPath: runtime-env.js
|
|
||||||
volumes:
|
|
||||||
- name: runtime-config
|
|
||||||
configMap:
|
|
||||||
name: frontend-config
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: lifesub-web-service
|
|
||||||
namespace: dg0400-lifesub-ns
|
|
||||||
labels:
|
|
||||||
app: lifesub-web
|
|
||||||
spec:
|
|
||||||
type: ClusterIP
|
|
||||||
ports:
|
|
||||||
- port: 80
|
|
||||||
targetPort: 18080
|
|
||||||
protocol: TCP
|
|
||||||
selector:
|
|
||||||
app: lifesub-web
|
|
||||||
|
|
||||||
---
|
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: Ingress
|
|
||||||
metadata:
|
|
||||||
name: frontend-ingress
|
|
||||||
namespace: dg0400-lifesub-ns
|
|
||||||
annotations:
|
|
||||||
kubernetes.io/ingress.class: nginx
|
|
||||||
nginx.ingress.kubernetes.io/rewrite-target: /
|
|
||||||
spec:
|
|
||||||
rules:
|
|
||||||
- host: dg0400.20.249.113.122.nip.io
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- path: /
|
|
||||||
pathType: Prefix
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: lifesub-web-service
|
|
||||||
port:
|
|
||||||
number: 80
|
|
||||||
|
|
||||||
@ -1,10 +1,8 @@
|
|||||||
|
# lifesub-web/deployment/manifest/deployments/hiondal-web-deployment.yaml
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: lifesub-web
|
name: lifesub-web
|
||||||
namespace: dg0400-lifesub-ns
|
|
||||||
labels:
|
|
||||||
app: lifesub-web
|
|
||||||
spec:
|
spec:
|
||||||
replicas: 1
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
@ -16,10 +14,10 @@ spec:
|
|||||||
app: lifesub-web
|
app: lifesub-web
|
||||||
spec:
|
spec:
|
||||||
imagePullSecrets:
|
imagePullSecrets:
|
||||||
- name: acr-secret
|
- name: dockerhub
|
||||||
containers:
|
containers:
|
||||||
- name: lifesub-web
|
- name: lifesub-web
|
||||||
image: acrdigitalgarage01.azurecr.io/dg0400/lifesub-web:1.0.0
|
image: dg0200cr.azurecr.io/lifesub/lifesub-web:1.0.0
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 18080
|
- containerPort: 18080
|
||||||
@ -30,11 +28,4 @@ spec:
|
|||||||
limits:
|
limits:
|
||||||
cpu: 1024m
|
cpu: 1024m
|
||||||
memory: 1024Mi
|
memory: 1024Mi
|
||||||
volumeMounts:
|
|
||||||
- name: runtime-config
|
|
||||||
mountPath: /usr/share/nginx/html/runtime-env.js
|
|
||||||
subPath: runtime-env.js
|
|
||||||
volumes:
|
|
||||||
- name: runtime-config
|
|
||||||
configMap:
|
|
||||||
name: frontend-config
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: lifesub-web-service
|
|
||||||
namespace: dg0400-lifesub-ns
|
|
||||||
labels:
|
|
||||||
app: lifesub-web
|
|
||||||
spec:
|
|
||||||
type: LoadBalancer
|
|
||||||
ports:
|
|
||||||
- port: 80
|
|
||||||
targetPort: 18080
|
|
||||||
protocol: TCP
|
|
||||||
selector:
|
|
||||||
app: lifesub-web
|
|
||||||
12
deployment/manifest/services/lifesub-web-service.yaml
Normal file
12
deployment/manifest/services/lifesub-web-service.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# lifesub-web/deployment/manifest/services/lifesub-web-service.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: lifesub-web
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: lifesub-web
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 18080
|
||||||
|
type: LoadBalancer
|
||||||
Loading…
x
Reference in New Issue
Block a user