This commit is contained in:
hiondal 2025-04-09 12:32:48 +09:00
commit 6e36841dbc
47 changed files with 430 additions and 233 deletions

View File

@ -1,143 +1,274 @@
name: Backend CI/CD Pipeline
name: Backend Services CI/CD
# Temporarily disabled
# on:
# push:
# branches: [ k8s ]
env:
JAVA_VERSION: '21'
GRADLE_VERSION: '8.5'
on:
push:
#branches: [ cicd ]
paths:
- 'member/**'
- 'mysub/**'
- 'recommend/**'
- 'common/**'
- 'deployment/**'
- '.github/workflows/**'
jobs:
build:
name: Build Applications
name: Build and Test
runs-on: ubuntu-latest
outputs:
image_tag: ${{ steps.set_outputs.outputs.image_tag }}
steps:
- uses: actions/checkout@v4
- name: Check out code
uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v4
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: ${{ env.JAVA_VERSION }}
java-version: '21'
distribution: 'temurin'
cache: 'gradle'
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
with:
gradle-version: ${{ env.GRADLE_VERSION }}
- 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: |
chmod +x gradlew
./gradlew clean :member:build :mysub-infra:build :recommend:build
./gradlew :member:build :mysub-infra:build :recommend:build -x test
- name: Upload Build Artifacts
- 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: build-artifacts
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 Images
name: Build and Push Docker Images
needs: build
runs-on: ubuntu-latest
outputs:
image_tag: ${{ steps.set_tag.outputs.tag }}
steps:
- uses: actions/checkout@v4
- name: Check out code
uses: actions/checkout@v4
- name: Download Build Artifacts
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: build-artifacts
name: app-builds
- name: Load Environment Variables
run: source deployment/deploy_env_vars
- name: Generate Image Tag
id: set_tag
- name: Load environment variables
run: |
tag=$(date +'%Y%m%d%H%M%S')
echo "tag=${tag}" >> $GITHUB_OUTPUT
# 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: azure/docker-login@v1
uses: docker/login-action@v3
with:
login-server: ${{ env.registry }}
registry: ${{ env.registry }}
username: ${{ secrets.ACR_USERNAME }}
password: ${{ secrets.ACR_PASSWORD }}
- name: Build and Push Images
run: |
for service in member mysub recommend; do
build_dir=$([[ "$service" == "mysub" ]] && echo "mysub-infra" || echo "$service")
jar_file=$([[ "$service" == "mysub" ]] && echo "mysub.jar" || echo "${service}.jar")
docker build \
--build-arg BUILD_LIB_DIR="${build_dir}/build/libs" \
--build-arg ARTIFACTORY_FILE="${jar_file}" \
-f deployment/Dockerfile \
-t ${registry}/${image_org}/${service}:${{ steps.set_tag.outputs.tag }} .
docker push ${registry}/${image_org}/${service}:${{ steps.set_tag.outputs.tag }}
done
- 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 AKS
needs: release
name: Deploy to Kubernetes
needs: [build, release]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check out code
uses: actions/checkout@v4
- name: Load Environment Variables
run: source deployment/deploy_env_vars
- 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: Set AKS Context
uses: azure/aks-set-context@v1
with:
resource-group: ictcoe-edu
cluster-name: ${{ env.teamid }}-aks
- name: Setup kubectl
uses: azure/setup-kubectl@v3
- name: Create Namespace
- name: Get AKS Credentials
run: |
kubectl create namespace ${teamid}-${root_project}-ns --dry-run=client -o yaml | kubectl apply -f -
az aks get-credentials --resource-group ictcoe-edu --name ${{ env.teamid }}-aks --overwrite-existing
- name: Generate Manifest
- name: Create namespace
run: |
export namespace=${teamid}-${root_project}-ns
export allowed_origins=${allowed_origins}
export jwt_secret_key=${jwt_secret_key}
export postgres_user=${postgres_user}
export postgres_password=${postgres_password}
export replicas=${replicas}
export resources_requests_cpu=${resources_requests_cpu}
export resources_requests_memory=${resources_requests_memory}
export resources_limits_cpu=${resources_limits_cpu}
export resources_limits_memory=${resources_limits_memory}
export member_image_path=${registry}/${image_org}/member:${{ needs.release.outputs.image_tag }}
export mysub_image_path=${registry}/${image_org}/mysub:${{ needs.release.outputs.image_tag }}
export recommend_image_path=${registry}/${image_org}/recommend:${{ needs.release.outputs.image_tag }}
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: Deploy to AKS
- name: Apply Kubernetes manifest
run: |
kubectl apply -f deployment/deploy.yaml
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
- 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}')"

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
.gradle
build/
### IntelliJ IDEA ###
.idea/
### Mac OS ###
.DS_Store

Binary file not shown.

12
.idea/modules.xml generated
View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/modules/common/lifesub.common.main.iml" filepath="$PROJECT_DIR$/.idea/modules/common/lifesub.common.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/member/lifesub.member.main.iml" filepath="$PROJECT_DIR$/.idea/modules/member/lifesub.member.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/mysub-biz/lifesub.mysub-biz.main.iml" filepath="$PROJECT_DIR$/.idea/modules/mysub-biz/lifesub.mysub-biz.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/mysub-infra/lifesub.mysub-infra.main.iml" filepath="$PROJECT_DIR$/.idea/modules/mysub-infra/lifesub.mysub-infra.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/recommend/lifesub.recommend.main.iml" filepath="$PROJECT_DIR$/.idea/modules/recommend/lifesub.recommend.main.iml" />
</modules>
</component>
</project>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="AdditionalModuleElements">
<content url="file://$MODULE_DIR$/../../../common/build/generated/sources/annotationProcessor/java/main">
<sourceFolder url="file://$MODULE_DIR$/../../../common/build/generated/sources/annotationProcessor/java/main" isTestSource="false" generated="true" />
</content>
</component>
</module>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="AdditionalModuleElements">
<content url="file://$MODULE_DIR$/../../../member/build/generated/sources/annotationProcessor/java/main">
<sourceFolder url="file://$MODULE_DIR$/../../../member/build/generated/sources/annotationProcessor/java/main" isTestSource="false" generated="true" />
</content>
</component>
</module>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="AdditionalModuleElements">
<content url="file://$MODULE_DIR$/../../../mysub-biz/build/generated/sources/annotationProcessor/java/main">
<sourceFolder url="file://$MODULE_DIR$/../../../mysub-biz/build/generated/sources/annotationProcessor/java/main" isTestSource="false" generated="true" />
</content>
</component>
</module>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="AdditionalModuleElements">
<content url="file://$MODULE_DIR$/../../../mysub-infra/build/generated/sources/annotationProcessor/java/main">
<sourceFolder url="file://$MODULE_DIR$/../../../mysub-infra/build/generated/sources/annotationProcessor/java/main" isTestSource="false" generated="true" />
</content>
</component>
</module>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="AdditionalModuleElements">
<content url="file://$MODULE_DIR$/../../../recommend/build/generated/sources/annotationProcessor/java/main">
<sourceFolder url="file://$MODULE_DIR$/../../../recommend/build/generated/sources/annotationProcessor/java/main" isTestSource="false" generated="true" />
</content>
</component>
</module>

2
.idea/vcs.xml generated
View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="" vcs="Git" />
</component>
</project>

4
README.md Normal file
View File

@ -0,0 +1,4 @@
# 생활구독추천 서비스

View File

@ -92,6 +92,7 @@ subprojects {
}))
}
}
}
//-- Biz와 common
@ -133,3 +134,4 @@ configure(subprojects.findAll { it.name.endsWith('-biz') || it.name == 'common'
bootJar.enabled = false
jar.enabled = true
}

View File

@ -54,16 +54,16 @@ podTemplate(
}
}
stage('Build Applications') {
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'''
docker.host=unix:///run/podman/podman.sock
ryuk.container.privileged=true
testcontainers.reuse.enable=true'''
sh """
# TestContainers 설정
@ -71,11 +71,51 @@ podTemplate(
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
# 빌드 실행
chmod +x gradlew
./gradlew clean :member:build :mysub-infra:build :recommend:build
"""
// 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}"
}
}
}
@ -143,4 +183,5 @@ podTemplate(
}
}
}
}
}

View File

@ -10,43 +10,54 @@ podTemplate(
label: "${PIPELINE_ID}",
serviceAccount: 'jenkins',
containers: [
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: '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: 'git',
image: 'alpine:3.19',
command: 'cat',
ttyEnabled: true)
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 manifestRepo = 'cna-bootcamp/lifesub-manifest'
def manifestBranch = 'main'
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('Build Applications') {
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'
}
@ -63,11 +74,50 @@ testcontainers.reuse.enable=true'''
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
# 빌드 실행
chmod +x gradlew
./gradlew clean :member:build :mysub-infra:build :recommend:build
"""
// 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}"
}
}
}
@ -85,10 +135,10 @@ testcontainers.reuse.enable=true'''
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 \\
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}
@ -98,47 +148,35 @@ testcontainers.reuse.enable=true'''
}
}
stage('Update Manifests') {
stage('Update Manifest Repository') {
container('git') {
// git과 yq 설치
sh '''
apk add --no-cache git curl
curl -L https://github.com/mikefarah/yq/releases/download/v4.40.5/yq_linux_amd64 -o /usr/local/bin/yq
chmod +x /usr/local/bin/yq
'''
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"
'''
// 임시 디렉토리 생성
sh "mkdir -p /tmp/manifests"
// Clone manifest repository
withCredentials([usernamePassword(credentialsId: 'github-credentials', usernameVariable: 'GIT_USERNAME', passwordVariable: 'GIT_PASSWORD')]) {
sh """
rm -rf lifesub-manifest
#git clone https://oauth2:${GIT_PASSWORD}@github.com/${manifestRepo}.git
git clone https://${GIT_USERNAME}:${GIT_PASSWORD}@github.com/${manifestRepo}.git
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
"""
dir('lifesub-manifest') {
services.each { service ->
def imagePath = "${props.registry}/${props.image_org}/${service}:${imageTag}"
sh """
yq -i '.spec.template.spec.containers[0].image = "${imagePath}"' lifesub/deployments/${service}-deployment.yaml
"""
}
sh """
git add .
git diff-index --quiet HEAD || git commit -m "Update backend services images to ${imageTag}"
git push origin ${manifestBranch}
"""
}
}
}
}
}
}
}

View File

@ -306,4 +306,41 @@ spec:
ports:
- port: 80
targetPort: 8083
type: ClusterIP
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

View File

@ -8,8 +8,8 @@ registry=unicorncr.azurecr.io
image_org=lifesub
# Application Settings
replicas=2
allowed_origins=http://4.230.156.229
replicas=1
allowed_origins=http://20.214.113.12
# Security Settings
jwt_secret_key=8O2HQ13etL2BWZvYOiWsJ5uWFoLi6NBUG8divYVoCgtHVvlk3dqRksMl16toztDUeBTSIuOOPvHIrYq11G2BwQ

View File

@ -5,4 +5,8 @@ metadata:
data:
JPA_DDL_AUTO: update
JPA_SHOW_SQL: "true"
<<<<<<< HEAD
ALLOWED_ORIGINS: "http://localhost:18080,http://localhost:18081,http://20.249.184.6"
=======
ALLOWED_ORIGINS: "http://localhost:18080,http://localhost:18081,http://20.214.113.12"
>>>>>>> 1ea06faa5d92eae36ec9d161158eb1314eaff732

View File

@ -55,4 +55,4 @@ spec:
path: /actuator/health/readiness
port: 8082
initialDelaySeconds: 60
periodSeconds: 15
periodSeconds: 15

View File

@ -18,7 +18,6 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@ -40,7 +39,7 @@ public class MemberServiceImpl implements MemberService {
// 사용자의 권한 정보 생성
Collection<? extends GrantedAuthority> authorities = member.getRoles().stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
.toList();
return jwtTokenProvider.createToken(member, authorities);
}

View File

@ -7,7 +7,7 @@ spring:
datasource:
url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5432}/${POSTGRES_DB:member}
username: ${POSTGRES_USER:admin}
password: ${POSTGRES_PASSWORD:Passw0rd}
password: ${POSTGRES_PASSWORD:Hi5Jessica!}
driver-class-name: org.postgresql.Driver
# JPA 설정

View File

@ -7,7 +7,7 @@ spring:
datasource:
url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5432}/${POSTGRES_DB:mysub}
username: ${POSTGRES_USER:admin}
password: ${POSTGRES_PASSWORD:Passw0rd}
password: ${POSTGRES_PASSWORD:Hi5Jessica!}
driver-class-name: org.postgresql.Driver
# JPA 설정
jpa:

View File

@ -1 +0,0 @@
com.unicorn.lifesub.recommend.RecommendApplication

View File

@ -1,12 +0,0 @@
Manifest-Version: 1.0
Main-Class: org.springframework.boot.loader.launch.JarLauncher
Start-Class: com.unicorn.lifesub.recommend.RecommendApplication
Spring-Boot-Version: 3.4.0
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Build-Jdk-Spec: 21
Implementation-Title: recommend
Implementation-Version: 1.0.0

View File

@ -1,2 +0,0 @@
Manifest-Version: 1.0

View File

@ -7,7 +7,7 @@ spring:
datasource:
url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5432}/${POSTGRES_DB:recommend}
username: ${POSTGRES_USER:admin}
password: ${POSTGRES_PASSWORD:Passw0rd}
password: ${POSTGRES_PASSWORD:Hi5Jessica!}
driver-class-name: org.postgresql.Driver
# JPA 설정
jpa: