mirror of
https://github.com/cna-bootcamp/lifesub.git
synced 2025-12-06 08:06:24 +00:00
Merge branch 'cicd'
add testcode
This commit is contained in:
commit
6095e1ac25
274
.github/workflows/cicd.yaml
vendored
Normal file
274
.github/workflows/cicd.yaml
vendored
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
name: Backend Services CI/CD
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
#branches: [ cicd ]
|
||||||
|
paths:
|
||||||
|
- 'member/**'
|
||||||
|
- 'mysub/**'
|
||||||
|
- 'recommend/**'
|
||||||
|
- 'common/**'
|
||||||
|
- 'deployment/**'
|
||||||
|
- '.github/workflows/**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build and Test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
image_tag: ${{ steps.set_outputs.outputs.image_tag }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up JDK 21
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
java-version: '21'
|
||||||
|
distribution: 'temurin'
|
||||||
|
cache: 'gradle'
|
||||||
|
|
||||||
|
- name: Load environment variables
|
||||||
|
id: env_vars
|
||||||
|
run: |
|
||||||
|
# Read environment variables from file
|
||||||
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||||
|
# Skip comments and empty lines
|
||||||
|
[[ "$line" =~ ^#.*$ ]] && continue
|
||||||
|
[[ -z "$line" ]] && continue
|
||||||
|
|
||||||
|
# Extract key-value pairs
|
||||||
|
key=$(echo "$line" | cut -d '=' -f1)
|
||||||
|
value=$(echo "$line" | cut -d '=' -f2-)
|
||||||
|
|
||||||
|
# Set GitHub environment variables
|
||||||
|
echo "$key=$value" >> $GITHUB_ENV
|
||||||
|
done < deployment/deploy_env_vars
|
||||||
|
|
||||||
|
- name: Grant execute permission for gradlew
|
||||||
|
run: chmod +x gradlew
|
||||||
|
|
||||||
|
- name: Build with Gradle
|
||||||
|
run: |
|
||||||
|
./gradlew :member:build :mysub-infra:build :recommend:build -x test
|
||||||
|
|
||||||
|
- name: Test with Gradle
|
||||||
|
run: |
|
||||||
|
./gradlew :member:test :member:jacocoTestReport
|
||||||
|
./gradlew :mysub-infra:test :mysub-infra:jacocoTestReport
|
||||||
|
./gradlew :recommend:test :recommend:jacocoTestReport
|
||||||
|
|
||||||
|
- name: SonarQube Analysis
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
|
||||||
|
run: |
|
||||||
|
./gradlew :member:sonar \
|
||||||
|
-Dsonar.projectKey=lifesub-member \
|
||||||
|
-Dsonar.projectName=lifesub-member \
|
||||||
|
-Dsonar.host.url=$SONAR_HOST_URL \
|
||||||
|
-Dsonar.token=$SONAR_TOKEN \
|
||||||
|
-Dsonar.java.binaries=build/classes/java/main \
|
||||||
|
-Dsonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml
|
||||||
|
|
||||||
|
./gradlew :mysub-infra:sonar \
|
||||||
|
-Dsonar.projectKey=lifesub-mysub \
|
||||||
|
-Dsonar.projectName=lifesub-mysub \
|
||||||
|
-Dsonar.host.url=$SONAR_HOST_URL \
|
||||||
|
-Dsonar.token=$SONAR_TOKEN \
|
||||||
|
-Dsonar.java.binaries=build/classes/java/main \
|
||||||
|
-Dsonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml
|
||||||
|
|
||||||
|
./gradlew :recommend:sonar \
|
||||||
|
-Dsonar.projectKey=lifesub-recommend \
|
||||||
|
-Dsonar.projectName=lifesub-recommend \
|
||||||
|
-Dsonar.host.url=$SONAR_HOST_URL \
|
||||||
|
-Dsonar.token=$SONAR_TOKEN \
|
||||||
|
-Dsonar.java.binaries=build/classes/java/main \
|
||||||
|
-Dsonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml
|
||||||
|
|
||||||
|
- name: Upload build artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: app-builds
|
||||||
|
path: |
|
||||||
|
member/build/libs/*.jar
|
||||||
|
mysub-infra/build/libs/*.jar
|
||||||
|
recommend/build/libs/*.jar
|
||||||
|
|
||||||
|
- name: Set outputs
|
||||||
|
id: set_outputs
|
||||||
|
run: |
|
||||||
|
# Generate timestamp for image tag
|
||||||
|
IMAGE_TAG=$(date +%Y%m%d%H%M%S)
|
||||||
|
echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
release:
|
||||||
|
name: Build and Push Docker Images
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Download build artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: app-builds
|
||||||
|
|
||||||
|
- name: Load environment variables
|
||||||
|
run: |
|
||||||
|
# Read environment variables from file
|
||||||
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||||
|
# Skip comments and empty lines
|
||||||
|
[[ "$line" =~ ^#.*$ ]] && continue
|
||||||
|
[[ -z "$line" ]] && continue
|
||||||
|
|
||||||
|
# Extract key-value pairs
|
||||||
|
key=$(echo "$line" | cut -d '=' -f1)
|
||||||
|
value=$(echo "$line" | cut -d '=' -f2-)
|
||||||
|
|
||||||
|
# Set GitHub environment variables
|
||||||
|
echo "$key=$value" >> $GITHUB_ENV
|
||||||
|
done < deployment/deploy_env_vars
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to Azure Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.registry }}
|
||||||
|
username: ${{ secrets.ACR_USERNAME }}
|
||||||
|
password: ${{ secrets.ACR_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Build and push Member service image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: deployment/Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.registry }}/${{ env.image_org }}/member:${{ needs.build.outputs.image_tag }}
|
||||||
|
build-args: |
|
||||||
|
BUILD_LIB_DIR=member/build/libs
|
||||||
|
ARTIFACTORY_FILE=member.jar
|
||||||
|
|
||||||
|
- name: Build and push MySub service image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: deployment/Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.registry }}/${{ env.image_org }}/mysub:${{ needs.build.outputs.image_tag }}
|
||||||
|
build-args: |
|
||||||
|
BUILD_LIB_DIR=mysub-infra/build/libs
|
||||||
|
ARTIFACTORY_FILE=mysub.jar
|
||||||
|
|
||||||
|
- name: Build and push Recommend service image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: deployment/Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.registry }}/${{ env.image_org }}/recommend:${{ needs.build.outputs.image_tag }}
|
||||||
|
build-args: |
|
||||||
|
BUILD_LIB_DIR=recommend/build/libs
|
||||||
|
ARTIFACTORY_FILE=recommend.jar
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
name: Deploy to Kubernetes
|
||||||
|
needs: [build, release]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Load environment variables
|
||||||
|
run: |
|
||||||
|
# Read environment variables from file
|
||||||
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||||
|
# Skip comments and empty lines
|
||||||
|
[[ "$line" =~ ^#.*$ ]] && continue
|
||||||
|
[[ -z "$line" ]] && continue
|
||||||
|
|
||||||
|
# Extract key-value pairs
|
||||||
|
key=$(echo "$line" | cut -d '=' -f1)
|
||||||
|
value=$(echo "$line" | cut -d '=' -f2-)
|
||||||
|
|
||||||
|
# Set GitHub environment variables
|
||||||
|
echo "$key=$value" >> $GITHUB_ENV
|
||||||
|
done < deployment/deploy_env_vars
|
||||||
|
|
||||||
|
- name: Set image tag environment variable
|
||||||
|
run: |
|
||||||
|
echo "IMAGE_TAG=${{ needs.build.outputs.image_tag }}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
# Azure CLI 설치 단계 수정
|
||||||
|
- name: Install Azure CLI
|
||||||
|
run: |
|
||||||
|
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
|
||||||
|
|
||||||
|
- name: Azure Login
|
||||||
|
uses: azure/login@v1
|
||||||
|
with:
|
||||||
|
creds: ${{ secrets.AZURE_CREDENTIALS }}
|
||||||
|
|
||||||
|
- name: Setup kubectl
|
||||||
|
uses: azure/setup-kubectl@v3
|
||||||
|
|
||||||
|
- name: Get AKS Credentials
|
||||||
|
run: |
|
||||||
|
az aks get-credentials --resource-group ictcoe-edu --name ${{ env.teamid }}-aks --overwrite-existing
|
||||||
|
|
||||||
|
- name: Create namespace
|
||||||
|
run: |
|
||||||
|
kubectl create namespace ${{ env.namespace }} --dry-run=client -o yaml | kubectl apply -f -
|
||||||
|
|
||||||
|
- name: Install envsubst
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y gettext-base
|
||||||
|
|
||||||
|
- name: Generate Kubernetes manifest
|
||||||
|
run: |
|
||||||
|
# Set environment variables for the deployment template
|
||||||
|
export namespace=${{ env.namespace }}
|
||||||
|
export allowed_origins=${{ env.allowed_origins }}
|
||||||
|
export jwt_secret_key=${{ env.jwt_secret_key }}
|
||||||
|
export postgres_user=${{ env.postgres_user }}
|
||||||
|
export postgres_password=${{ env.postgres_password }}
|
||||||
|
export replicas=${{ env.replicas }}
|
||||||
|
export resources_requests_cpu=${{ env.resources_requests_cpu }}
|
||||||
|
export resources_requests_memory=${{ env.resources_requests_memory }}
|
||||||
|
export resources_limits_cpu=${{ env.resources_limits_cpu }}
|
||||||
|
export resources_limits_memory=${{ env.resources_limits_memory }}
|
||||||
|
|
||||||
|
# Set image paths with the dynamic tag
|
||||||
|
export member_image_path=${{ env.registry }}/${{ env.image_org }}/member:${{ env.IMAGE_TAG }}
|
||||||
|
export mysub_image_path=${{ env.registry }}/${{ env.image_org }}/mysub:${{ env.IMAGE_TAG }}
|
||||||
|
export recommend_image_path=${{ env.registry }}/${{ env.image_org }}/recommend:${{ env.IMAGE_TAG }}
|
||||||
|
|
||||||
|
# Generate the manifest file using envsubst
|
||||||
|
envsubst < deployment/deploy.yaml.template > deployment/deploy.yaml
|
||||||
|
|
||||||
|
# Print manifest for debugging
|
||||||
|
echo "Generated Kubernetes manifest:"
|
||||||
|
cat deployment/deploy.yaml
|
||||||
|
|
||||||
|
- name: Apply Kubernetes manifest
|
||||||
|
run: |
|
||||||
|
kubectl apply -f deployment/deploy.yaml
|
||||||
|
|
||||||
|
- name: Wait for deployments to be ready
|
||||||
|
run: |
|
||||||
|
kubectl -n ${{ env.namespace }} wait --for=condition=available deployment/member --timeout=300s
|
||||||
|
kubectl -n ${{ env.namespace }} wait --for=condition=available deployment/mysub --timeout=300s
|
||||||
|
kubectl -n ${{ env.namespace }} wait --for=condition=available deployment/recommend --timeout=300s
|
||||||
|
|
||||||
|
- name: Get service information
|
||||||
|
run: |
|
||||||
|
echo "Ingress IP: $(kubectl -n ${{ env.namespace }} get ingress lifesub -o jsonpath='{.status.loadBalancer.ingress[0].ip}')"
|
||||||
BIN
.gradle/8.10/checksums/checksums.lock
Normal file
BIN
.gradle/8.10/checksums/checksums.lock
Normal file
Binary file not shown.
BIN
.gradle/8.10/checksums/md5-checksums.bin
Normal file
BIN
.gradle/8.10/checksums/md5-checksums.bin
Normal file
Binary file not shown.
BIN
.gradle/8.10/checksums/sha1-checksums.bin
Normal file
BIN
.gradle/8.10/checksums/sha1-checksums.bin
Normal file
Binary file not shown.
BIN
.gradle/8.10/executionHistory/executionHistory.bin
Normal file
BIN
.gradle/8.10/executionHistory/executionHistory.bin
Normal file
Binary file not shown.
BIN
.gradle/8.10/executionHistory/executionHistory.lock
Normal file
BIN
.gradle/8.10/executionHistory/executionHistory.lock
Normal file
Binary file not shown.
BIN
.gradle/8.10/fileHashes/fileHashes.bin
Normal file
BIN
.gradle/8.10/fileHashes/fileHashes.bin
Normal file
Binary file not shown.
BIN
.gradle/8.10/fileHashes/fileHashes.lock
Normal file
BIN
.gradle/8.10/fileHashes/fileHashes.lock
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,2 +1,2 @@
|
|||||||
#Wed Feb 12 15:21:47 KST 2025
|
#Sat Feb 15 20:31:45 KST 2025
|
||||||
gradle.version=8.4
|
gradle.version=8.10
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
9
.idea/compiler.xml
generated
9
.idea/compiler.xml
generated
@ -8,13 +8,18 @@
|
|||||||
<processorPath useClasspath="false">
|
<processorPath useClasspath="false">
|
||||||
<entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.36/5a30490a6e14977d97d9c73c924c1f1b5311ea95/lombok-1.18.36.jar" />
|
<entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.36/5a30490a6e14977d97d9c73c924c1f1b5311ea95/lombok-1.18.36.jar" />
|
||||||
</processorPath>
|
</processorPath>
|
||||||
|
<module name="lifesub.recommend.test" />
|
||||||
|
<module name="lifesub.common.main" />
|
||||||
|
<module name="lifesub.mysub-infra.test" />
|
||||||
<module name="lifesub.mysub-infra.main" />
|
<module name="lifesub.mysub-infra.main" />
|
||||||
|
<module name="lifesub.common.test" />
|
||||||
|
<module name="lifesub.member.test" />
|
||||||
<module name="lifesub.mysub-biz.main" />
|
<module name="lifesub.mysub-biz.main" />
|
||||||
<module name="lifesub.recommend.main" />
|
<module name="lifesub.recommend.main" />
|
||||||
<module name="lifesub.common.main" />
|
|
||||||
<module name="lifesub.member.main" />
|
<module name="lifesub.member.main" />
|
||||||
|
<module name="lifesub.mysub-biz.test" />
|
||||||
</profile>
|
</profile>
|
||||||
</annotationProcessing>
|
</annotationProcessing>
|
||||||
<bytecodeTargetLevel target="17" />
|
<bytecodeTargetLevel target="21" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
3
.idea/dictionaries/hiond.xml
generated
Normal file
3
.idea/dictionaries/hiond.xml
generated
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<component name="ProjectDictionaryState">
|
||||||
|
<dictionary name="hiond" />
|
||||||
|
</component>
|
||||||
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
|
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||||
<component name="GradleSettings">
|
<component name="GradleSettings">
|
||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
|
|||||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK" />
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" project-jdk-name="23" project-jdk-type="JavaSDK" />
|
||||||
</project>
|
</project>
|
||||||
88
build.gradle
88
build.gradle
@ -2,6 +2,8 @@ plugins {
|
|||||||
id 'org.springframework.boot' version '3.4.0' apply false
|
id 'org.springframework.boot' version '3.4.0' apply false
|
||||||
//id 'io.spring.dependency-management' version '1.1.6' apply false
|
//id 'io.spring.dependency-management' version '1.1.6' apply false
|
||||||
id 'java'
|
id 'java'
|
||||||
|
|
||||||
|
id "org.sonarqube" version "5.0.0.4638" apply false //apply false 해야 서브 프로젝트에 제대로 적용됨
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
@ -14,26 +16,83 @@ subprojects {
|
|||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
apply plugin: 'org.springframework.boot'
|
apply plugin: 'org.springframework.boot'
|
||||||
apply plugin: 'io.spring.dependency-management'
|
apply plugin: 'io.spring.dependency-management'
|
||||||
|
|
||||||
|
apply plugin: 'org.sonarqube'
|
||||||
|
apply plugin: 'jacoco' // 서브 프로젝트에 JaCoCo 플러그인 적용
|
||||||
|
|
||||||
|
jacoco {
|
||||||
|
toolVersion = "0.8.11" // JaCoCo 최신 버전 사용
|
||||||
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// Spring Boot Starters
|
||||||
implementation 'org.springframework.boot:spring-boot-starter'
|
implementation 'org.springframework.boot:spring-boot-starter'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-aop' // AOP: 로깅 처리 자동화를 위해 사용
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
implementation 'com.google.code.gson:gson'
|
||||||
|
|
||||||
// Lombok
|
// Lombok
|
||||||
compileOnly 'org.projectlombok:lombok'
|
compileOnly 'org.projectlombok:lombok'
|
||||||
annotationProcessor 'org.projectlombok:lombok'
|
annotationProcessor 'org.projectlombok:lombok'
|
||||||
|
|
||||||
// Test
|
// Test Dependencies
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
|
testImplementation 'org.junit.jupiter:junit-jupiter-api'
|
||||||
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
|
||||||
|
testImplementation 'org.mockito:mockito-core'
|
||||||
|
testImplementation 'org.mockito:mockito-junit-jupiter'
|
||||||
|
|
||||||
|
// Lombok for Tests
|
||||||
|
testCompileOnly 'org.projectlombok:lombok'
|
||||||
|
testAnnotationProcessor 'org.projectlombok:lombok'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test Configuration
|
||||||
|
sourceSets {
|
||||||
|
test {
|
||||||
|
java {
|
||||||
|
srcDirs = ['src/test/java']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
|
include '**/*Test.class'
|
||||||
|
testLogging {
|
||||||
|
events "passed", "skipped", "failed"
|
||||||
|
}
|
||||||
|
finalizedBy jacocoTestReport // 테스트 후 JaCoCo 리포트 생성
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jacocoTestReport {
|
||||||
|
dependsOn test
|
||||||
|
reports {
|
||||||
|
xml.required = true // SonarQube 분석을 위해 XML 형식 필요
|
||||||
|
csv.required = false
|
||||||
|
html.required = true
|
||||||
|
html.outputLocation = layout.buildDirectory.dir("jacocoHtml").get().asFile
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEvaluate {
|
||||||
|
classDirectories.setFrom(files(classDirectories.files.collect {
|
||||||
|
fileTree(dir: it, exclude: [
|
||||||
|
"**/config/**", // 설정 클래스 제외
|
||||||
|
"**/entity/**", // 엔티티 클래스 제외
|
||||||
|
"**/dto/**", // DTO 클래스 제외
|
||||||
|
"**/*Application.class", // 메인 애플리케이션 클래스 제외
|
||||||
|
"**/exception/**" // 예외 클래스 제외
|
||||||
|
])
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//-- Biz와 common 모듈이 아닌 경우 인프라 관련 라이브러리 추가
|
//-- Biz와 common 모듈이 아닌 경우 인프라 관련 라이브러리 추가
|
||||||
@ -42,6 +101,10 @@ configure(subprojects.findAll { !it.name.endsWith('-biz') && it.name != 'common'
|
|||||||
// Spring Boot
|
// Spring Boot
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||||
|
|
||||||
|
// Actuator 추가
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||||
|
|
||||||
// data
|
// data
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||||
// JWT
|
// JWT
|
||||||
@ -50,10 +113,19 @@ configure(subprojects.findAll { !it.name.endsWith('-biz') && it.name != 'common'
|
|||||||
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
|
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
|
||||||
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
|
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
|
||||||
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
|
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
|
||||||
// AOP: 로깅 처리 자동화를 위해 사용
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-aop'
|
|
||||||
// Swagger
|
// Swagger
|
||||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0'
|
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0'
|
||||||
|
|
||||||
|
//-- spring security test
|
||||||
|
testImplementation 'org.springframework.security:spring-security-test'
|
||||||
|
|
||||||
|
// Test Containers
|
||||||
|
testImplementation 'org.testcontainers:postgresql'
|
||||||
|
testImplementation 'org.testcontainers:junit-jupiter'
|
||||||
|
|
||||||
|
// WebFlux for WebMvc Testing
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-webflux'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
25
deployment/Dockerfile
Normal file
25
deployment/Dockerfile
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Build stage
|
||||||
|
FROM openjdk:23-oraclelinux8 AS builder
|
||||||
|
ARG BUILD_LIB_DIR
|
||||||
|
ARG ARTIFACTORY_FILE
|
||||||
|
COPY ${BUILD_LIB_DIR}/${ARTIFACTORY_FILE} app.jar
|
||||||
|
|
||||||
|
# Run stage
|
||||||
|
FROM openjdk:23-slim
|
||||||
|
ENV USERNAME k8s
|
||||||
|
ENV ARTIFACTORY_HOME /home/${USERNAME}
|
||||||
|
ENV JAVA_OPTS=""
|
||||||
|
|
||||||
|
# Add a non-root user
|
||||||
|
RUN adduser --system --group ${USERNAME} && \
|
||||||
|
mkdir -p ${ARTIFACTORY_HOME} && \
|
||||||
|
chown ${USERNAME}:${USERNAME} ${ARTIFACTORY_HOME}
|
||||||
|
|
||||||
|
WORKDIR ${ARTIFACTORY_HOME}
|
||||||
|
COPY --from=builder app.jar app.jar
|
||||||
|
RUN chown ${USERNAME}:${USERNAME} app.jar
|
||||||
|
|
||||||
|
USER ${USERNAME}
|
||||||
|
|
||||||
|
ENTRYPOINT [ "sh", "-c" ]
|
||||||
|
CMD ["java ${JAVA_OPTS} -jar app.jar"]
|
||||||
187
deployment/Jenkinsfile
vendored
Normal file
187
deployment/Jenkinsfile
vendored
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
def PIPELINE_ID = "${env.BUILD_NUMBER}"
|
||||||
|
|
||||||
|
def getImageTag() {
|
||||||
|
def dateFormat = new java.text.SimpleDateFormat('yyyyMMddHHmmss')
|
||||||
|
def currentDate = new Date()
|
||||||
|
return dateFormat.format(currentDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
podTemplate(
|
||||||
|
label: "${PIPELINE_ID}",
|
||||||
|
serviceAccount: 'jenkins',
|
||||||
|
containers: [
|
||||||
|
containerTemplate(name: 'podman', image: "mgoltzsche/podman", ttyEnabled: true, command: 'cat', privileged: true),
|
||||||
|
containerTemplate(name: 'gradle',
|
||||||
|
image: 'gradle:jdk17',
|
||||||
|
ttyEnabled: true,
|
||||||
|
command: 'cat',
|
||||||
|
envVars: [
|
||||||
|
envVar(key: 'DOCKER_HOST', value: 'unix:///run/podman/podman.sock'),
|
||||||
|
envVar(key: 'TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE', value: '/run/podman/podman.sock'),
|
||||||
|
envVar(key: 'TESTCONTAINERS_RYUK_DISABLED', value: 'true')
|
||||||
|
]),
|
||||||
|
containerTemplate(name: 'azure-cli', image: 'hiondal/azure-kubectl:latest', command: 'cat', ttyEnabled: true),
|
||||||
|
containerTemplate(name: 'envsubst', image: "hiondal/envsubst", command: 'sleep', args: '1h')
|
||||||
|
],
|
||||||
|
volumes: [
|
||||||
|
emptyDirVolume(mountPath: '/home/gradle/.gradle', memory: false),
|
||||||
|
emptyDirVolume(mountPath: '/root/.azure', memory: false),
|
||||||
|
emptyDirVolume(mountPath: '/run/podman', memory: false)
|
||||||
|
]
|
||||||
|
) {
|
||||||
|
node(PIPELINE_ID) {
|
||||||
|
def props
|
||||||
|
def imageTag = getImageTag()
|
||||||
|
def manifest = "deploy.yaml"
|
||||||
|
def namespace
|
||||||
|
def services = ['member', 'mysub', 'recommend']
|
||||||
|
|
||||||
|
stage("Get Source") {
|
||||||
|
checkout scm
|
||||||
|
props = readProperties file: "deployment/deploy_env_vars"
|
||||||
|
namespace = "${props.namespace}"
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Applications & SonarQube Analysis') {
|
||||||
|
container('podman') {
|
||||||
|
sh 'podman system service -t 0 unix:///run/podman/podman.sock & sleep 2'
|
||||||
|
}
|
||||||
|
|
||||||
|
container('gradle') {
|
||||||
|
def testContainersConfig = '''docker.client.strategy=org.testcontainers.dockerclient.UnixSocketClientProviderStrategy
|
||||||
|
docker.host=unix:///run/podman/podman.sock
|
||||||
|
ryuk.container.privileged=true
|
||||||
|
testcontainers.reuse.enable=true'''
|
||||||
|
|
||||||
|
sh """
|
||||||
|
# TestContainers 설정
|
||||||
|
mkdir -p member/src/test/resources mysub-infra/src/test/resources recommend/src/test/resources
|
||||||
|
echo '${testContainersConfig}' > member/src/test/resources/testcontainers.properties
|
||||||
|
echo '${testContainersConfig}' > mysub-infra/src/test/resources/testcontainers.properties
|
||||||
|
echo '${testContainersConfig}' > recommend/src/test/resources/testcontainers.properties
|
||||||
|
"""
|
||||||
|
|
||||||
|
// Member 서비스 빌드 및 SonarQube 분석
|
||||||
|
withSonarQubeEnv('SonarQube') {
|
||||||
|
sh """
|
||||||
|
chmod +x gradlew
|
||||||
|
|
||||||
|
# 빌드 실행
|
||||||
|
./gradlew :member:build :mysub-infra:build :recommend:build -x test
|
||||||
|
|
||||||
|
# Member 서비스
|
||||||
|
./gradlew :member:test :member:jacocoTestReport :member:sonar \
|
||||||
|
-Dsonar.projectKey=lifesub-member \
|
||||||
|
-Dsonar.projectName=lifesub-member \
|
||||||
|
-Dsonar.java.binaries=build/classes/java/main \
|
||||||
|
-Dsonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml \
|
||||||
|
-Dsonar.exclusions=**/config/**,**/entity/**,**/dto/**,**/*Application.class,**/exception/**
|
||||||
|
|
||||||
|
# Recommend 서비스
|
||||||
|
./gradlew :recommend:test :recommend:jacocoTestReport :recommend:sonar \
|
||||||
|
-Dsonar.projectKey=lifesub-recommend \
|
||||||
|
-Dsonar.projectName=lifesub-recommend \
|
||||||
|
-Dsonar.java.binaries=build/classes/java/main \
|
||||||
|
-Dsonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml \
|
||||||
|
-Dsonar.exclusions=**/config/**,**/entity/**,**/dto/**,**/*Application.class,**/exception/**
|
||||||
|
|
||||||
|
# Mysub 서비스 (biz & infra 구조)
|
||||||
|
./gradlew :mysub-infra:test :mysub-infra:jacocoTestReport :mysub-infra:sonar \
|
||||||
|
-Dsonar.projectKey=lifesub-mysub \
|
||||||
|
-Dsonar.projectName=lifesub-mysub \
|
||||||
|
-Dsonar.java.binaries=build/classes/java/main \
|
||||||
|
-Dsonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml \
|
||||||
|
-Dsonar.exclusions=**/config/**,**/entity/**,**/dto/**,**/*Application.class,**/exception/**
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Quality Gate') {
|
||||||
|
timeout(time: 10, unit: 'MINUTES') {
|
||||||
|
def qg = waitForQualityGate()
|
||||||
|
if (qg.status != 'OK') {
|
||||||
|
error "Pipeline aborted due to quality gate failure: ${qg.status}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Build & Push Images') {
|
||||||
|
container('podman') {
|
||||||
|
withCredentials([usernamePassword(
|
||||||
|
credentialsId: 'acr-credentials',
|
||||||
|
usernameVariable: 'USERNAME',
|
||||||
|
passwordVariable: 'PASSWORD'
|
||||||
|
)]) {
|
||||||
|
sh "podman login ${props.registry} --username \$USERNAME --password \$PASSWORD"
|
||||||
|
|
||||||
|
services.each { service ->
|
||||||
|
def buildDir = service == 'mysub' ? 'mysub-infra' : service
|
||||||
|
def jarFile = service == 'mysub' ? 'mysub.jar' : "${service}.jar"
|
||||||
|
|
||||||
|
sh """
|
||||||
|
podman build \
|
||||||
|
--build-arg BUILD_LIB_DIR="${buildDir}/build/libs" \
|
||||||
|
--build-arg ARTIFACTORY_FILE="${jarFile}" \
|
||||||
|
-f deployment/Dockerfile \
|
||||||
|
-t ${props.registry}/${props.image_org}/${service}:${imageTag} .
|
||||||
|
|
||||||
|
podman push ${props.registry}/${props.image_org}/${service}:${imageTag}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Generate & Apply Manifest') {
|
||||||
|
container('envsubst') {
|
||||||
|
sh """
|
||||||
|
export namespace=${namespace}
|
||||||
|
export allowed_origins=${props.allowed_origins}
|
||||||
|
export jwt_secret_key=${props.jwt_secret_key}
|
||||||
|
export postgres_user=${props.postgres_user}
|
||||||
|
export postgres_password=${props.postgres_password}
|
||||||
|
export replicas=${props.replicas}
|
||||||
|
export resources_requests_cpu=${props.resources_requests_cpu}
|
||||||
|
export resources_requests_memory=${props.resources_requests_memory}
|
||||||
|
export resources_limits_cpu=${props.resources_limits_cpu}
|
||||||
|
export resources_limits_memory=${props.resources_limits_memory}
|
||||||
|
|
||||||
|
# 이미지 경로 환경변수 설정
|
||||||
|
export member_image_path=${props.registry}/${props.image_org}/member:${imageTag}
|
||||||
|
export mysub_image_path=${props.registry}/${props.image_org}/mysub:${imageTag}
|
||||||
|
export recommend_image_path=${props.registry}/${props.image_org}/recommend:${imageTag}
|
||||||
|
|
||||||
|
# manifest 생성
|
||||||
|
envsubst < deployment/${manifest}.template > deployment/${manifest}
|
||||||
|
cat deployment/${manifest}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
container('azure-cli') {
|
||||||
|
sh """
|
||||||
|
kubectl apply -f deployment/${manifest}
|
||||||
|
|
||||||
|
echo "Waiting for deployments to be ready..."
|
||||||
|
kubectl -n ${namespace} wait --for=condition=available deployment/member --timeout=300s
|
||||||
|
kubectl -n ${namespace} wait --for=condition=available deployment/mysub --timeout=300s
|
||||||
|
kubectl -n ${namespace} wait --for=condition=available deployment/recommend --timeout=300s
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
182
deployment/Jenkinsfile_ArgoCD
Normal file
182
deployment/Jenkinsfile_ArgoCD
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
def PIPELINE_ID = "${env.BUILD_NUMBER}"
|
||||||
|
|
||||||
|
def getImageTag() {
|
||||||
|
def dateFormat = new java.text.SimpleDateFormat('yyyyMMddHHmmss')
|
||||||
|
def currentDate = new Date()
|
||||||
|
return dateFormat.format(currentDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
podTemplate(
|
||||||
|
label: "${PIPELINE_ID}",
|
||||||
|
serviceAccount: 'jenkins',
|
||||||
|
containers: [
|
||||||
|
containerTemplate(name: 'podman', image: "mgoltzsche/podman", ttyEnabled: true, command: 'cat', privileged: true),
|
||||||
|
containerTemplate(name: 'gradle',
|
||||||
|
image: 'gradle:jdk17',
|
||||||
|
ttyEnabled: true,
|
||||||
|
command: 'cat',
|
||||||
|
envVars: [
|
||||||
|
envVar(key: 'DOCKER_HOST', value: 'unix:///run/podman/podman.sock'),
|
||||||
|
envVar(key: 'TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE', value: '/run/podman/podman.sock'),
|
||||||
|
envVar(key: 'TESTCONTAINERS_RYUK_DISABLED', value: 'true')
|
||||||
|
]),
|
||||||
|
containerTemplate(name: 'azure-cli', image: 'hiondal/azure-kubectl:latest', command: 'cat', ttyEnabled: true),
|
||||||
|
containerTemplate(name: 'envsubst', image: "hiondal/envsubst", command: 'sleep', args: '1h'),
|
||||||
|
containerTemplate(name: 'git', image: 'alpine/git:latest', command: 'cat', ttyEnabled: true)
|
||||||
|
],
|
||||||
|
volumes: [
|
||||||
|
emptyDirVolume(mountPath: '/home/gradle/.gradle', memory: false),
|
||||||
|
emptyDirVolume(mountPath: '/root/.azure', memory: false),
|
||||||
|
emptyDirVolume(mountPath: '/run/podman', memory: false)
|
||||||
|
]
|
||||||
|
) {
|
||||||
|
node(PIPELINE_ID) {
|
||||||
|
def props
|
||||||
|
def imageTag = getImageTag()
|
||||||
|
def manifest = "deploy.yaml"
|
||||||
|
def namespace
|
||||||
|
def services = ['member', 'mysub', 'recommend']
|
||||||
|
def MANIFEST_REPO = "https://github.com/cna-bootcamp/lifesub-manifest.git"
|
||||||
|
def MANIFEST_BRANCH = "main"
|
||||||
|
|
||||||
|
stage("Get Source") {
|
||||||
|
checkout scm
|
||||||
|
props = readProperties file: "deployment/deploy_env_vars"
|
||||||
|
namespace = "${props.namespace}"
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Applications & SonarQube Analysis') {
|
||||||
|
container('podman') {
|
||||||
|
sh 'podman system service -t 0 unix:///run/podman/podman.sock & sleep 2'
|
||||||
|
}
|
||||||
|
|
||||||
|
container('gradle') {
|
||||||
|
def testContainersConfig = '''docker.client.strategy=org.testcontainers.dockerclient.UnixSocketClientProviderStrategy
|
||||||
|
docker.host=unix:///run/podman/podman.sock
|
||||||
|
ryuk.container.privileged=true
|
||||||
|
testcontainers.reuse.enable=true'''
|
||||||
|
|
||||||
|
sh """
|
||||||
|
# TestContainers 설정
|
||||||
|
mkdir -p member/src/test/resources mysub-infra/src/test/resources recommend/src/test/resources
|
||||||
|
echo '${testContainersConfig}' > member/src/test/resources/testcontainers.properties
|
||||||
|
echo '${testContainersConfig}' > mysub-infra/src/test/resources/testcontainers.properties
|
||||||
|
echo '${testContainersConfig}' > recommend/src/test/resources/testcontainers.properties
|
||||||
|
"""
|
||||||
|
|
||||||
|
// Member 서비스 빌드 및 SonarQube 분석
|
||||||
|
withSonarQubeEnv('SonarQube') {
|
||||||
|
sh """
|
||||||
|
chmod +x gradlew
|
||||||
|
|
||||||
|
# 빌드 실행
|
||||||
|
./gradlew :member:build :mysub-infra:build :recommend:build -x test
|
||||||
|
|
||||||
|
# Member 서비스
|
||||||
|
./gradlew :member:test :member:jacocoTestReport :member:sonar \
|
||||||
|
-Dsonar.projectKey=lifesub-member \
|
||||||
|
-Dsonar.projectName=lifesub-member \
|
||||||
|
-Dsonar.java.binaries=build/classes/java/main \
|
||||||
|
-Dsonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml \
|
||||||
|
-Dsonar.exclusions=**/config/**,**/entity/**,**/dto/**,**/*Application.class,**/exception/**
|
||||||
|
|
||||||
|
# Recommend 서비스
|
||||||
|
./gradlew :recommend:test :recommend:jacocoTestReport :recommend:sonar \
|
||||||
|
-Dsonar.projectKey=lifesub-recommend \
|
||||||
|
-Dsonar.projectName=lifesub-recommend \
|
||||||
|
-Dsonar.java.binaries=build/classes/java/main \
|
||||||
|
-Dsonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml \
|
||||||
|
-Dsonar.exclusions=**/config/**,**/entity/**,**/dto/**,**/*Application.class,**/exception/**
|
||||||
|
|
||||||
|
# Mysub 서비스 (biz & infra 구조)
|
||||||
|
./gradlew :mysub-infra:test :mysub-infra:jacocoTestReport :mysub-infra:sonar \
|
||||||
|
-Dsonar.projectKey=lifesub-mysub \
|
||||||
|
-Dsonar.projectName=lifesub-mysub \
|
||||||
|
-Dsonar.java.binaries=build/classes/java/main \
|
||||||
|
-Dsonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml \
|
||||||
|
-Dsonar.exclusions=**/config/**,**/entity/**,**/dto/**,**/*Application.class,**/exception/**
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Quality Gate') {
|
||||||
|
timeout(time: 10, unit: 'MINUTES') {
|
||||||
|
def qg = waitForQualityGate()
|
||||||
|
if (qg.status != 'OK') {
|
||||||
|
error "Pipeline aborted due to quality gate failure: ${qg.status}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Build & Push Images') {
|
||||||
|
container('podman') {
|
||||||
|
withCredentials([usernamePassword(
|
||||||
|
credentialsId: 'acr-credentials',
|
||||||
|
usernameVariable: 'USERNAME',
|
||||||
|
passwordVariable: 'PASSWORD'
|
||||||
|
)]) {
|
||||||
|
sh "podman login ${props.registry} --username \$USERNAME --password \$PASSWORD"
|
||||||
|
|
||||||
|
services.each { service ->
|
||||||
|
def buildDir = service == 'mysub' ? 'mysub-infra' : service
|
||||||
|
def jarFile = service == 'mysub' ? 'mysub.jar' : "${service}.jar"
|
||||||
|
|
||||||
|
sh """
|
||||||
|
podman build \
|
||||||
|
--build-arg BUILD_LIB_DIR="${buildDir}/build/libs" \
|
||||||
|
--build-arg ARTIFACTORY_FILE="${jarFile}" \
|
||||||
|
-f deployment/Dockerfile \
|
||||||
|
-t ${props.registry}/${props.image_org}/${service}:${imageTag} .
|
||||||
|
|
||||||
|
podman push ${props.registry}/${props.image_org}/${service}:${imageTag}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Update Manifest Repository') {
|
||||||
|
container('git') {
|
||||||
|
// 임시 디렉토리 생성
|
||||||
|
sh "mkdir -p /tmp/manifests"
|
||||||
|
|
||||||
|
// Clone manifest repository
|
||||||
|
withCredentials([usernamePassword(credentialsId: 'github-credentials', usernameVariable: 'GIT_USERNAME', passwordVariable: 'GIT_PASSWORD')]) {
|
||||||
|
sh """
|
||||||
|
git config --global user.email "jenkins@example.com"
|
||||||
|
git config --global user.name "Jenkins Pipeline"
|
||||||
|
|
||||||
|
git clone https://${GIT_USERNAME}:${GIT_PASSWORD}@github.com/cna-bootcamp/lifesub-manifest.git /tmp/manifests
|
||||||
|
cd /tmp/manifests
|
||||||
|
|
||||||
|
# Update image tags in the appropriate YAML files
|
||||||
|
for service in ${services.join(' ')}; do
|
||||||
|
# 백엔드 서비스 이미지 태그 업데이트
|
||||||
|
if [ -f lifesub/deployments/\${service}-deployment.yaml ]; then
|
||||||
|
sed -i "s|image: ${props.registry}/${props.image_org}/\${service}:.*|image: ${props.registry}/${props.image_org}/\${service}:${imageTag}|g" lifesub/deployments/\${service}-deployment.yaml
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Commit and push changes
|
||||||
|
git add .
|
||||||
|
git commit -m "Update backend service image tags to ${imageTag}" || true
|
||||||
|
git push
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
68
deployment/database/deploy_db.sh
Normal file
68
deployment/database/deploy_db.sh
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 사용법 함수 정의
|
||||||
|
usage() {
|
||||||
|
echo "Usage: $0 <namespace>"
|
||||||
|
echo "Example: $0 myapp-ns"
|
||||||
|
echo "This script creates PostgreSQL databases for member, mysub, and recommend services in the specified namespace."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 파라미터 체크
|
||||||
|
if [ $# -ne 1 ]; then
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
NAMESPACE=$1
|
||||||
|
|
||||||
|
# Namespace 존재 여부 확인 후 생성
|
||||||
|
if ! kubectl get namespace ${NAMESPACE} &> /dev/null; then
|
||||||
|
echo "Creating namespace: ${NAMESPACE}"
|
||||||
|
kubectl create namespace ${NAMESPACE}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Namespace 전환
|
||||||
|
echo "Switching to namespace: ${NAMESPACE}"
|
||||||
|
kubens ${NAMESPACE}
|
||||||
|
|
||||||
|
# 각 서비스별 설치
|
||||||
|
for service in member mysub recommend; do
|
||||||
|
echo "Installing PostgreSQL for ${service} service..."
|
||||||
|
|
||||||
|
# Helm으로 PostgreSQL 설치 - heredoc으로 직접 values 전달
|
||||||
|
helm upgrade -i ${service} bitnami/postgresql --version 14.3.2 --values - <<EOF
|
||||||
|
architecture: standalone
|
||||||
|
global:
|
||||||
|
postgresql:
|
||||||
|
auth:
|
||||||
|
postgresPassword: "Passw0rd"
|
||||||
|
replicationPassword: "Passw0rd"
|
||||||
|
database: "${service}"
|
||||||
|
username: "admin"
|
||||||
|
password: "Passw0rd"
|
||||||
|
storageClass: "managed"
|
||||||
|
primary:
|
||||||
|
persistence:
|
||||||
|
enabled: true
|
||||||
|
storageClass: "managed"
|
||||||
|
size: 10Gi
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: "1Gi"
|
||||||
|
cpu: "1"
|
||||||
|
requests:
|
||||||
|
memory: "0.5Gi"
|
||||||
|
cpu: "0.5"
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
postgresql: 5432
|
||||||
|
securityContext:
|
||||||
|
enabled: true
|
||||||
|
fsGroup: 1001
|
||||||
|
runAsUser: 1001
|
||||||
|
EOF
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Installation completed successfully in namespace: ${NAMESPACE}"
|
||||||
346
deployment/deploy.yaml.template
Normal file
346
deployment/deploy.yaml.template
Normal file
@ -0,0 +1,346 @@
|
|||||||
|
# ConfigMap
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: common-config
|
||||||
|
namespace: ${namespace}
|
||||||
|
data:
|
||||||
|
ALLOWED_ORIGINS: ${allowed_origins}
|
||||||
|
JPA_DDL_AUTO: update
|
||||||
|
JPA_SHOW_SQL: 'true'
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: member-config
|
||||||
|
namespace: ${namespace}
|
||||||
|
data:
|
||||||
|
POSTGRES_DB: member
|
||||||
|
POSTGRES_HOST: member-postgresql
|
||||||
|
POSTGRES_PORT: '5432'
|
||||||
|
SERVER_PORT: '8081'
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: mysub-config
|
||||||
|
namespace: ${namespace}
|
||||||
|
data:
|
||||||
|
FEE_LEVEL_ADDICT: '100000'
|
||||||
|
FEE_LEVEL_COLLECTOR: '50000'
|
||||||
|
POSTGRES_DB: mysub
|
||||||
|
POSTGRES_HOST: mysub-postgresql
|
||||||
|
POSTGRES_PORT: '5432'
|
||||||
|
SERVER_PORT: '8082'
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: recommend-config
|
||||||
|
namespace: ${namespace}
|
||||||
|
data:
|
||||||
|
POSTGRES_DB: recommend
|
||||||
|
POSTGRES_HOST: recommend-postgresql
|
||||||
|
POSTGRES_PORT: '5432'
|
||||||
|
SERVER_PORT: '8083'
|
||||||
|
|
||||||
|
---
|
||||||
|
# Secrets
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: common-secret
|
||||||
|
namespace: ${namespace}
|
||||||
|
stringData:
|
||||||
|
JWT_SECRET_KEY: ${jwt_secret_key}
|
||||||
|
type: Opaque
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: member-secret
|
||||||
|
namespace: ${namespace}
|
||||||
|
stringData:
|
||||||
|
JWT_ACCESS_TOKEN_VALIDITY: '3600000'
|
||||||
|
JWT_REFRESH_TOKEN_VALIDITY: '86400000'
|
||||||
|
POSTGRES_PASSWORD: ${postgres_password}
|
||||||
|
POSTGRES_USER: ${postgres_user}
|
||||||
|
type: Opaque
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: mysub-secret
|
||||||
|
namespace: ${namespace}
|
||||||
|
stringData:
|
||||||
|
POSTGRES_PASSWORD: ${postgres_password}
|
||||||
|
POSTGRES_USER: ${postgres_user}
|
||||||
|
type: Opaque
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: recommend-secret
|
||||||
|
namespace: ${namespace}
|
||||||
|
stringData:
|
||||||
|
POSTGRES_PASSWORD: ${postgres_password}
|
||||||
|
POSTGRES_USER: ${postgres_user}
|
||||||
|
type: Opaque
|
||||||
|
|
||||||
|
---
|
||||||
|
# Deployments
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: member
|
||||||
|
namespace: ${namespace}
|
||||||
|
spec:
|
||||||
|
replicas: ${replicas}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: member
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: member
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: member
|
||||||
|
image: ${member_image_path}
|
||||||
|
imagePullPolicy: Always
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: common-config
|
||||||
|
- configMapRef:
|
||||||
|
name: member-config
|
||||||
|
- secretRef:
|
||||||
|
name: common-secret
|
||||||
|
- secretRef:
|
||||||
|
name: member-secret
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: ${resources_requests_cpu}
|
||||||
|
memory: ${resources_requests_memory}
|
||||||
|
limits:
|
||||||
|
cpu: ${resources_limits_cpu}
|
||||||
|
memory: ${resources_limits_memory}
|
||||||
|
ports:
|
||||||
|
- containerPort: 8081
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health
|
||||||
|
port: 8081
|
||||||
|
failureThreshold: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health
|
||||||
|
port: 8081
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
periodSeconds: 15
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health/readiness
|
||||||
|
port: 8081
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 5
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: mysub
|
||||||
|
namespace: ${namespace}
|
||||||
|
spec:
|
||||||
|
replicas: ${replicas}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: mysub
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: mysub
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: mysub
|
||||||
|
image: ${mysub_image_path}
|
||||||
|
imagePullPolicy: Always
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: common-config
|
||||||
|
- configMapRef:
|
||||||
|
name: mysub-config
|
||||||
|
- secretRef:
|
||||||
|
name: common-secret
|
||||||
|
- secretRef:
|
||||||
|
name: mysub-secret
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: ${resources_requests_cpu}
|
||||||
|
memory: ${resources_requests_memory}
|
||||||
|
limits:
|
||||||
|
cpu: ${resources_limits_cpu}
|
||||||
|
memory: ${resources_limits_memory}
|
||||||
|
ports:
|
||||||
|
- containerPort: 8082
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health
|
||||||
|
port: 8082
|
||||||
|
failureThreshold: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health
|
||||||
|
port: 8082
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
periodSeconds: 15
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health/readiness
|
||||||
|
port: 8082
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 5
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: recommend
|
||||||
|
namespace: ${namespace}
|
||||||
|
spec:
|
||||||
|
replicas: ${replicas}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: recommend
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: recommend
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: recommend
|
||||||
|
image: ${recommend_image_path}
|
||||||
|
imagePullPolicy: Always
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: common-config
|
||||||
|
- configMapRef:
|
||||||
|
name: recommend-config
|
||||||
|
- secretRef:
|
||||||
|
name: common-secret
|
||||||
|
- secretRef:
|
||||||
|
name: recommend-secret
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: ${resources_requests_cpu}
|
||||||
|
memory: ${resources_requests_memory}
|
||||||
|
limits:
|
||||||
|
cpu: ${resources_limits_cpu}
|
||||||
|
memory: ${resources_limits_memory}
|
||||||
|
ports:
|
||||||
|
- containerPort: 8083
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health
|
||||||
|
port: 8083
|
||||||
|
failureThreshold: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health
|
||||||
|
port: 8083
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
periodSeconds: 15
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health/readiness
|
||||||
|
port: 8083
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 5
|
||||||
|
|
||||||
|
---
|
||||||
|
# Services
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: member
|
||||||
|
namespace: ${namespace}
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: member
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8081
|
||||||
|
type: ClusterIP
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: mysub
|
||||||
|
namespace: ${namespace}
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: mysub
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8082
|
||||||
|
type: ClusterIP
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: recommend
|
||||||
|
namespace: ${namespace}
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: recommend
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8083
|
||||||
|
type: ClusterIP
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: lifesub
|
||||||
|
namespace: ${namespace}
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/ingress.class: nginx
|
||||||
|
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||||
|
nginx.ingress.kubernetes.io/use-regex: "true"
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
rules:
|
||||||
|
- http:
|
||||||
|
paths:
|
||||||
|
- path: /member(/|$)(.*)
|
||||||
|
pathType: ImplementationSpecific
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: member
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
- path: /mysub(/|$)(.*)
|
||||||
|
pathType: ImplementationSpecific
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: mysub
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
- path: /recommend(/|$)(.*)
|
||||||
|
pathType: ImplementationSpecific
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: recommend
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
23
deployment/deploy_env_vars
Normal file
23
deployment/deploy_env_vars
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Team Settings
|
||||||
|
teamid=unicorn
|
||||||
|
root_project=lifesub
|
||||||
|
namespace=unicorn-lifesub-ns
|
||||||
|
|
||||||
|
# Container Registry Settings
|
||||||
|
registry=unicorncr.azurecr.io
|
||||||
|
image_org=lifesub
|
||||||
|
|
||||||
|
# Application Settings
|
||||||
|
replicas=1
|
||||||
|
allowed_origins=http://4.230.147.248
|
||||||
|
|
||||||
|
# Security Settings
|
||||||
|
jwt_secret_key=8O2HQ13etL2BWZvYOiWsJ5uWFoLi6NBUG8divYVoCgtHVvlk3dqRksMl16toztDUeBTSIuOOPvHIrYq11G2BwQ
|
||||||
|
postgres_user=admin
|
||||||
|
postgres_password=Passw0rd
|
||||||
|
|
||||||
|
# Resource Settings
|
||||||
|
resources_requests_cpu=256m
|
||||||
|
resources_requests_memory=256Mi
|
||||||
|
resources_limits_cpu=1024m
|
||||||
|
resources_limits_memory=1024Mi
|
||||||
9
deployment/manifest/configmaps/common-config.yaml
Normal file
9
deployment/manifest/configmaps/common-config.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# lifesub/deployment/manifest/configmaps/common-config.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: common-config
|
||||||
|
data:
|
||||||
|
JPA_DDL_AUTO: update
|
||||||
|
JPA_SHOW_SQL: "true"
|
||||||
|
ALLOWED_ORIGINS: "http://localhost:18080,http://localhost:18081,http://20.214.113.12"
|
||||||
10
deployment/manifest/configmaps/member-config.yaml
Normal file
10
deployment/manifest/configmaps/member-config.yaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# lifesub/deployment/manifest/configmaps/member-config.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: member-config
|
||||||
|
data:
|
||||||
|
SERVER_PORT: "8081"
|
||||||
|
POSTGRES_HOST: "member-postgresql"
|
||||||
|
POSTGRES_PORT: "5432"
|
||||||
|
POSTGRES_DB: "member"
|
||||||
13
deployment/manifest/configmaps/mysub-config.yaml
Normal file
13
deployment/manifest/configmaps/mysub-config.yaml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
# lifesub/deployment/manifest/configmaps/mysub-config.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: mysub-config
|
||||||
|
data:
|
||||||
|
SERVER_PORT: "8082"
|
||||||
|
POSTGRES_HOST: "mysub-postgresql"
|
||||||
|
POSTGRES_PORT: "5432"
|
||||||
|
POSTGRES_DB: "mysub"
|
||||||
|
FEE_LEVEL_COLLECTOR: "50000"
|
||||||
|
FEE_LEVEL_ADDICT: "100000"
|
||||||
10
deployment/manifest/configmaps/recommend-config.yaml
Normal file
10
deployment/manifest/configmaps/recommend-config.yaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# lifesub/deployment/manifest/configmaps/recommend-config.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: recommend-config
|
||||||
|
data:
|
||||||
|
SERVER_PORT: "8083"
|
||||||
|
POSTGRES_HOST: "recommend-postgresql"
|
||||||
|
POSTGRES_PORT: "5432"
|
||||||
|
POSTGRES_DB: "recommend"
|
||||||
56
deployment/manifest/deployments/member-deployment.yaml
Normal file
56
deployment/manifest/deployments/member-deployment.yaml
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# lifesub/deployment/manifest/deployments/member-deployment.yaml
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: member
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: member
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: member
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: member
|
||||||
|
image: dg0200cr.azurecr.io/lifesub/member:1.0.0
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 8081
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 256m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: 1024m
|
||||||
|
memory: 1024Mi
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: common-config
|
||||||
|
- configMapRef:
|
||||||
|
name: member-config
|
||||||
|
- secretRef:
|
||||||
|
name: common-secret
|
||||||
|
- secretRef:
|
||||||
|
name: member-secret
|
||||||
|
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
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health/liveness
|
||||||
|
port: 8081
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
periodSeconds: 15
|
||||||
56
deployment/manifest/deployments/mysub-deployment.yaml
Normal file
56
deployment/manifest/deployments/mysub-deployment.yaml
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# lifesub/deployment/manifest/deployments/mysub-deployment.yaml
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: mysub
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: mysub
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: mysub
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: mysub
|
||||||
|
image: dg0200cr.azurecr.io/lifesub/mysub:1.0.0
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 8082
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 256m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: 1024m
|
||||||
|
memory: 1024Mi
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: common-config
|
||||||
|
- configMapRef:
|
||||||
|
name: mysub-config
|
||||||
|
- secretRef:
|
||||||
|
name: common-secret
|
||||||
|
- secretRef:
|
||||||
|
name: mysub-secret
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health
|
||||||
|
port: 8082
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 30
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health/readiness
|
||||||
|
port: 8082
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 5
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health/readiness
|
||||||
|
port: 8082
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
periodSeconds: 15
|
||||||
56
deployment/manifest/deployments/recommend-deployment.yaml
Normal file
56
deployment/manifest/deployments/recommend-deployment.yaml
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# lifesub/deployment/manifest/deployments/recommend-deployment.yaml
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: recommend
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: recommend
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: recommend
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: recommend
|
||||||
|
image: dg0200cr.azurecr.io/lifesub/recommend:1.0.0
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 8083
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 256m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: 1024m
|
||||||
|
memory: 1024Mi
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: common-config
|
||||||
|
- configMapRef:
|
||||||
|
name: recommend-config
|
||||||
|
- secretRef:
|
||||||
|
name: common-secret
|
||||||
|
- secretRef:
|
||||||
|
name: recommend-secret
|
||||||
|
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
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health/liveness
|
||||||
|
port: 8083
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
periodSeconds: 15
|
||||||
34
deployment/manifest/ingresses/ingress.yaml
Normal file
34
deployment/manifest/ingresses/ingress.yaml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: backend-ingress
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/ingress.class: nginx
|
||||||
|
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||||
|
nginx.ingress.kubernetes.io/use-regex: "true"
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
rules:
|
||||||
|
- http:
|
||||||
|
paths:
|
||||||
|
- path: /member(/|$)(.*)
|
||||||
|
pathType: ImplementationSpecific
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: member
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
- path: /mysub(/|$)(.*)
|
||||||
|
pathType: ImplementationSpecific
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: mysub
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
- path: /recommend(/|$)(.*)
|
||||||
|
pathType: ImplementationSpecific
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: recommend
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
8
deployment/manifest/secrets/common-secret.yaml
Normal file
8
deployment/manifest/secrets/common-secret.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# lifesub/deployment/manifest/secrets/common-secret.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: common-secret
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
JWT_SECRET_KEY: "8O2HQ13etL2BWZvYOiWsJ5uWFoLi6NBUG8divYVoCgtHVvlk3dqRksMl16toztDUeBTSIuOOPvHIrYq11G2BwQ"
|
||||||
11
deployment/manifest/secrets/member-secret.yaml
Normal file
11
deployment/manifest/secrets/member-secret.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# lifesub/deployment/manifest/secrets/member-secret.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: member-secret
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
POSTGRES_USER: "admin"
|
||||||
|
POSTGRES_PASSWORD: "Passw0rd"
|
||||||
|
JWT_ACCESS_TOKEN_VALIDITY: "3600000"
|
||||||
|
JWT_REFRESH_TOKEN_VALIDITY: "86400000"
|
||||||
9
deployment/manifest/secrets/mysub-secret.yaml
Normal file
9
deployment/manifest/secrets/mysub-secret.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# lifesub/deployment/manifest/secrets/mysub-secret.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: mysub-secret
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
POSTGRES_USER: "admin"
|
||||||
|
POSTGRES_PASSWORD: "Passw0rd"
|
||||||
9
deployment/manifest/secrets/recommend-secret.yaml
Normal file
9
deployment/manifest/secrets/recommend-secret.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# lifesub/deployment/manifest/secrets/recommend-secret.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: recommend-secret
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
POSTGRES_USER: "admin"
|
||||||
|
POSTGRES_PASSWORD: "Passw0rd"
|
||||||
12
deployment/manifest/services/member-service.yaml
Normal file
12
deployment/manifest/services/member-service.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# lifesub/deployment/manifest/services/member-service.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: member
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: member
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8081
|
||||||
|
type: ClusterIP
|
||||||
12
deployment/manifest/services/mysub-service.yaml
Normal file
12
deployment/manifest/services/mysub-service.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# lifesub/deployment/manifest/services/mysub-service.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: mysub
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: mysub
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8082
|
||||||
|
type: ClusterIP
|
||||||
12
deployment/manifest/services/recommend-service.yaml
Normal file
12
deployment/manifest/services/recommend-service.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# lifesub/deployment/manifest/services/recommend-service.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: recommend
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: recommend
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8083
|
||||||
|
type: ClusterIP
|
||||||
82
design/Common 클래스설계서
Normal file
82
design/Common 클래스설계서
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
!theme mono
|
||||||
|
title Common Module - Class Diagram
|
||||||
|
|
||||||
|
package "com.unicorn.lifesub.common" {
|
||||||
|
package "dto" {
|
||||||
|
class ApiResponse<T> {
|
||||||
|
-status: int
|
||||||
|
-message: String
|
||||||
|
-data: T
|
||||||
|
-timestamp: LocalDateTime
|
||||||
|
+ApiResponse(status: int, message: String, data: T)
|
||||||
|
+{static} success(data: T): ApiResponse<T>
|
||||||
|
+{static} error(errorCode: ErrorCode): ApiResponse<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
class JwtTokenDTO {
|
||||||
|
-accessToken: String
|
||||||
|
-refreshToken: String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
package "exception" {
|
||||||
|
class BusinessException {
|
||||||
|
-errorCode: ErrorCode
|
||||||
|
+BusinessException(errorCode: ErrorCode)
|
||||||
|
+getErrorCode(): ErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
class InfraException {
|
||||||
|
-errorCode: ErrorCode
|
||||||
|
+InfraException(errorCode: ErrorCode)
|
||||||
|
+getErrorCode(): ErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ErrorCode {
|
||||||
|
INVALID_INPUT_VALUE(100, "Invalid input value")
|
||||||
|
INTERNAL_SERVER_ERROR(110, "Internal server error")
|
||||||
|
MEMBER_NOT_FOUND(200, "Member not found")
|
||||||
|
INVALID_CREDENTIALS(210, "Invalid credentials")
|
||||||
|
TOKEN_EXPIRED(220, "Token expired")
|
||||||
|
SIGNATURE_VERIFICATION_EXCEPTION(230, "서명 검증 실패")
|
||||||
|
ALGORITHM_MISMATCH_EXCEPTION(240, "알고리즘 불일치")
|
||||||
|
INVALID_CLAIM_EXCEPTION(250, "유효하지 않은 클레임")
|
||||||
|
SUBSCRIPTION_NOT_FOUND(300, "Subscription not found")
|
||||||
|
ALREADY_SUBSCRIBED(310, "Already subscribed to this service")
|
||||||
|
NO_SPENDING_DATA(400, "No spending data found")
|
||||||
|
NO_RECOMMENDATION_DATA(410, "추천 구독 카테고리 없음")
|
||||||
|
UNDIFINED_ERROR(0, "정의되지 않은 에러")
|
||||||
|
--
|
||||||
|
-status: int
|
||||||
|
-message: String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
package "entity" {
|
||||||
|
abstract class BaseTimeEntity {
|
||||||
|
-createdAt: LocalDateTime
|
||||||
|
-updatedAt: LocalDateTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
package "aop" {
|
||||||
|
class LoggingAspect {
|
||||||
|
-gson: Gson
|
||||||
|
+logMethodStart(joinPoint: JoinPoint): void
|
||||||
|
+logMethodEnd(joinPoint: JoinPoint, result: Object): void
|
||||||
|
+logMethodException(joinPoint: JoinPoint, exception: Exception): void
|
||||||
|
-getArgumentString(args: Object[]): String
|
||||||
|
-getResultString(result: Object): String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
package "config" {
|
||||||
|
class JpaConfig {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
' Relationships
|
||||||
|
BusinessException --> ErrorCode
|
||||||
|
InfraException --> ErrorCode
|
||||||
|
LoggingAspect ..> ApiResponse : uses
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
7
gradlew
vendored
7
gradlew
vendored
@ -15,6 +15,8 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
@ -55,7 +57,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@ -84,7 +86,8 @@ done
|
|||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||||
|
' "$PWD" ) || exit
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
|
|||||||
22
gradlew.bat
vendored
22
gradlew.bat
vendored
@ -13,6 +13,8 @@
|
|||||||
@rem See the License for the specific language governing permissions and
|
@rem See the License for the specific language governing permissions and
|
||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
|
|||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user