diff --git a/.github/kustomize/base/kustomization.yaml b/.github/kustomize/base/kustomization.yaml index 9a34038..86fb5de 100644 --- a/.github/kustomize/base/kustomization.yaml +++ b/.github/kustomize/base/kustomization.yaml @@ -6,7 +6,6 @@ metadata: resources: # Common resources - - namespace.yaml - common/ingress.yaml - common/cm-common.yaml - common/secret-common.yaml diff --git a/.github/kustomize/base/namespace.yaml b/.github/kustomize/base/namespace.yaml deleted file mode 100644 index e0edb58..0000000 --- a/.github/kustomize/base/namespace.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: phonebill - labels: - name: phonebill \ No newline at end of file diff --git a/.github/workflows/backend-cicd_ArgoCD.yaml b/.github/workflows/backend-cicd_ArgoCD.yaml index 086cb0f..8dcf1d0 100644 --- a/.github/workflows/backend-cicd_ArgoCD.yaml +++ b/.github/workflows/backend-cicd_ArgoCD.yaml @@ -1,13 +1,36 @@ -name: Backend CI/CD Pipeline - +name: Backend Services CI/CD (ArgoCD) on: push: - branches: - - main - - develop + branches: [ main, develop ] + paths: + - 'api-gateway/**' + - 'user-service/**' + - 'bill-service/**' + - 'product-service/**' + - 'kos-mock/**' + - 'common/**' + - '.github/**' pull_request: - branches: - - main + branches: [ main ] + workflow_dispatch: + inputs: + ENVIRONMENT: + description: 'Target environment' + required: true + default: 'dev' + type: choice + options: + - dev + - staging + - prod + SKIP_SONARQUBE: + description: 'Skip SonarQube Analysis' + required: false + default: 'true' + type: choice + options: + - 'true' + - 'false' env: REGISTRY: acrdigitalgarage01.azurecr.io @@ -20,132 +43,170 @@ jobs: name: Build and Test runs-on: ubuntu-latest outputs: - image_tag: ${{ steps.set_env.outputs.image_tag }} - environment: ${{ steps.set_env.outputs.environment }} + image_tag: ${{ steps.set_outputs.outputs.image_tag }} + environment: ${{ steps.set_outputs.outputs.environment }} + steps: - name: Check out code uses: actions/checkout@v4 - - name: Set Environment - id: set_env - run: | - if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then - echo "environment=prod" >> $GITHUB_OUTPUT - elif [[ "${{ github.ref }}" == "refs/heads/develop" ]]; then - echo "environment=staging" >> $GITHUB_OUTPUT - else - echo "environment=dev" >> $GITHUB_OUTPUT - fi - - IMAGE_TAG=$(date '+%Y%m%d%H%M%S') - echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT - - - name: Setup JDK 21 - uses: actions/setup-java@v4 + - name: Set up JDK 21 + uses: actions/setup-java@v3 with: java-version: '21' distribution: 'temurin' + cache: 'gradle' - - name: Cache Gradle packages - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- + - name: Determine environment + id: determine_env + run: | + # Use input parameter or default to 'dev' + ENVIRONMENT="${{ github.event.inputs.ENVIRONMENT || 'dev' }}" + echo "environment=$ENVIRONMENT" >> $GITHUB_OUTPUT + + - name: Load environment variables + id: env_vars + run: | + ENV=${{ steps.determine_env.outputs.environment }} + + # Initialize variables with defaults + REGISTRY="acrdigitalgarage01.azurecr.io" + IMAGE_ORG="phonebill" + RESOURCE_GROUP="rg-digitalgarage-01" + AKS_CLUSTER="aks-digitalgarage-01" + + # Read environment variables from .github/config file + if [[ -f ".github/config/deploy_env_vars_${ENV}" ]]; then + 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-) + + # Override defaults if found in config + case "$key" in + "resource_group") RESOURCE_GROUP="$value" ;; + "cluster_name") AKS_CLUSTER="$value" ;; + esac + done < ".github/config/deploy_env_vars_${ENV}" + fi + + # Export for other jobs + echo "REGISTRY=$REGISTRY" >> $GITHUB_ENV + echo "IMAGE_ORG=$IMAGE_ORG" >> $GITHUB_ENV + echo "RESOURCE_GROUP=$RESOURCE_GROUP" >> $GITHUB_ENV + echo "AKS_CLUSTER=$AKS_CLUSTER" >> $GITHUB_ENV - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build with Gradle - run: ./gradlew build -x test + run: | + ./gradlew build -x test - - name: Run tests - run: ./gradlew test - - - name: Generate test report - run: ./gradlew jacocoTestReport - - - name: SonarQube Scan - if: github.event_name != 'pull_request' + - name: SonarQube Analysis & Quality Gate env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} run: | - services=("api-gateway" "user-service" "bill-service" "product-service" "kos-mock") + # Check if SonarQube should be skipped + SKIP_SONARQUBE="${{ github.event.inputs.SKIP_SONARQUBE || 'true' }}" + + if [[ "$SKIP_SONARQUBE" == "true" ]]; then + echo "⏭️ Skipping SonarQube Analysis (SKIP_SONARQUBE=$SKIP_SONARQUBE)" + exit 0 + fi + + # Define services array + services=(api-gateway user-service bill-service product-service kos-mock) + + # Run tests, coverage reports, and SonarQube analysis for each service for service in "${services[@]}"; do - ./gradlew :${service}:sonar \ - -Dsonar.projectKey=phonebill-${service}-${{ steps.set_env.outputs.environment }} \ - -Dsonar.projectName=phonebill-${service}-${{ steps.set_env.outputs.environment }} \ + ./gradlew :$service:test :$service:jacocoTestReport :$service:sonar \ + -Dsonar.projectKey=phonebill-$service-${{ steps.determine_env.outputs.environment }} \ + -Dsonar.projectName=phonebill-$service-${{ steps.determine_env.outputs.environment }} \ + -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 \ -Dsonar.exclusions=**/config/**,**/entity/**,**/dto/**,**/*Application.class,**/exception/** done + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: app-builds + path: | + api-gateway/build/libs/*.jar + user-service/build/libs/*.jar + bill-service/build/libs/*.jar + product-service/build/libs/*.jar + kos-mock/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 + echo "environment=${{ steps.determine_env.outputs.environment }}" >> $GITHUB_OUTPUT + release: - name: Build and Push Images + name: Build and Push Docker Images needs: build runs-on: ubuntu-latest - if: github.event_name == 'push' + steps: - name: Check out code uses: actions/checkout@v4 - - name: Set image tag environment variable + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: app-builds + + - name: Set environment variables from build job run: | - echo "IMAGE_TAG=${{ needs.build.outputs.image_tag }}" >> $GITHUB_ENV + echo "REGISTRY=${{ env.REGISTRY }}" >> $GITHUB_ENV + echo "IMAGE_ORG=${{ env.IMAGE_ORG }}" >> $GITHUB_ENV echo "ENVIRONMENT=${{ needs.build.outputs.environment }}" >> $GITHUB_ENV - - - name: Setup JDK 21 - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: 'temurin' - - - name: Cache Gradle packages - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - - name: Build with Gradle - run: ./gradlew build -x test + echo "IMAGE_TAG=${{ needs.build.outputs.image_tag }}" >> $GITHUB_ENV - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Log in to Azure Container Registry + - name: Login to Docker Hub (prevent rate limit) + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - 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 images + - name: Build and push Docker images for all services run: | - services=("api-gateway" "user-service" "bill-service" "product-service" "kos-mock") + # Define services array + services=(api-gateway user-service bill-service product-service kos-mock) + # Build and push each service image for service in "${services[@]}"; do echo "Building and pushing $service..." - docker build \ - --build-arg BUILD_LIB_DIR="${service}/build/libs" \ - --build-arg ARTIFACTORY_FILE="${service}.jar" \ + --build-arg BUILD_LIB_DIR="$service/build/libs" \ + --build-arg ARTIFACTORY_FILE="$service.jar" \ -f deployment/container/Dockerfile-backend \ - -t ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/${service}:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }} . + -t ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/$service:${{ needs.build.outputs.environment }}-${{ needs.build.outputs.image_tag }} . - docker push ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/${service}:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }} - - echo "✅ Successfully built and pushed ${service}:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }}" + docker push ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/$service:${{ needs.build.outputs.environment }}-${{ needs.build.outputs.image_tag }} done update-manifest: @@ -154,37 +215,37 @@ jobs: runs-on: ubuntu-latest steps: - - name: Set image tag environment variable - run: | - echo "IMAGE_TAG=${{ needs.build.outputs.image_tag }}" >> $GITHUB_ENV - echo "ENVIRONMENT=${{ needs.build.outputs.environment }}" >> $GITHUB_ENV + - name: Set image tag environment variable + run: | + echo "IMAGE_TAG=${{ needs.build.outputs.image_tag }}" >> $GITHUB_ENV + echo "ENVIRONMENT=${{ needs.build.outputs.environment }}" >> $GITHUB_ENV - - name: Update Manifest Repository - run: | - # 매니페스트 레포지토리 클론 - REPO_URL=$(echo "https://github.com/cna-bootcamp/phonebill-manifest.git" | sed 's|https://||') - git clone https://${{ secrets.GIT_USERNAME }}:${{ secrets.GIT_PASSWORD }}@${REPO_URL} manifest-repo - cd manifest-repo + - name: Update Manifest Repository + run: | + # 매니페스트 레포지토리 클론 + REPO_URL=$(echo "https://github.com/cna-bootcamp/phonebill-manifest.git" | sed 's|https://||') + git clone https://${{ secrets.GIT_USERNAME }}:${{ secrets.GIT_PASSWORD }}@${REPO_URL} manifest-repo + cd manifest-repo - # Kustomize 설치 - curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash - sudo mv kustomize /usr/local/bin/ + # Kustomize 설치 + curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash + sudo mv kustomize /usr/local/bin/ - # 매니페스트 업데이트 - cd phonebill/kustomize/overlays/${{ env.ENVIRONMENT }} + # 매니페스트 업데이트 + cd phonebill/kustomize/overlays/${{ env.ENVIRONMENT }} - # 각 서비스별 이미지 태그 업데이트 - services="api-gateway user-service bill-service product-service kos-mock" - for service in $services; do - kustomize edit set image acrdigitalgarage01.azurecr.io/phonebill/$service:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }} - done + # 각 서비스별 이미지 태그 업데이트 + services="api-gateway user-service bill-service product-service kos-mock" + for service in $services; do + kustomize edit set image acrdigitalgarage01.azurecr.io/phonebill/$service:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }} + done - # Git 설정 및 푸시 - cd ../../../.. - git config user.name "GitHub Actions" - git config user.email "actions@github.com" - git add . - git commit -m "🚀 Update phonebill ${{ env.ENVIRONMENT }} images to ${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }}" - git push origin main + # Git 설정 및 푸시 + cd ../../../.. + git config user.name "GitHub Actions" + git config user.email "actions@github.com" + git add . + git commit -m "🚀 Update phonebill ${{ env.ENVIRONMENT }} images to ${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }}" + git push origin main - echo "✅ 매니페스트 업데이트 완료. ArgoCD가 자동으로 배포합니다." \ No newline at end of file + echo "✅ 매니페스트 업데이트 완료. ArgoCD가 자동으로 배포합니다." \ No newline at end of file diff --git a/deployment/cicd/Jenkinsfile_ArgoCD b/deployment/cicd/Jenkinsfile_ArgoCD index 204f7bb..80cab0f 100644 --- a/deployment/cicd/Jenkinsfile_ArgoCD +++ b/deployment/cicd/Jenkinsfile_ArgoCD @@ -25,10 +25,10 @@ podTemplate( ''', containers: [ containerTemplate( - name: 'podman', - image: "mgoltzsche/podman", - ttyEnabled: true, - command: 'cat', + name: 'podman', + image: "mgoltzsche/podman", + ttyEnabled: true, + command: 'cat', privileged: true, resourceRequestCpu: '500m', resourceRequestMemory: '2Gi', @@ -51,14 +51,24 @@ podTemplate( ] ), containerTemplate( - name: 'azure-cli', - image: 'hiondal/azure-kubectl:latest', - command: 'cat', + name: 'azure-cli', + image: 'hiondal/azure-kubectl:latest', + command: 'cat', ttyEnabled: true, resourceRequestCpu: '200m', resourceRequestMemory: '512Mi', resourceLimitCpu: '500m', resourceLimitMemory: '1Gi' + ), + containerTemplate( + name: 'git', + image: 'alpine/git:latest', + command: 'cat', + ttyEnabled: true, + resourceRequestCpu: '100m', + resourceRequestMemory: '256Mi', + resourceLimitCpu: '300m', + resourceLimitMemory: '512Mi' ) ], volumes: [ @@ -73,25 +83,13 @@ podTemplate( def environment = params.ENVIRONMENT ?: 'dev' def skipSonarQube = (params.SKIP_SONARQUBE?.toLowerCase() == 'true') def services = ['api-gateway', 'user-service', 'bill-service', 'product-service', 'kos-mock'] - + try { stage("Get Source") { checkout scm props = readProperties file: "deployment/cicd/config/deploy_env_vars_${environment}" } - 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 ${props.resource_group} --name ${props.cluster_name} --overwrite-existing - kubectl create namespace phonebill-${environment} --dry-run=client -o yaml | kubectl apply -f - - """ - } - } - } - stage('Build') { container('gradle') { sh """ @@ -118,7 +116,7 @@ podTemplate( -Dsonar.exclusions=**/config/**,**/entity/**,**/dto/**,**/*Application.class,**/exception/** """ } - + // Quality Gate 확인 timeout(time: 10, unit: 'MINUTES') { def qg = waitForQualityGate() @@ -142,13 +140,13 @@ podTemplate( ), usernamePassword( credentialsId: 'dockerhub-credentials', - usernameVariable: 'DOCKERHUB_USERNAME', + usernameVariable: 'DOCKERHUB_USERNAME', passwordVariable: 'DOCKERHUB_PASSWORD' ) ]) { // Docker Hub 로그인 (rate limit 해결) sh "podman login docker.io --username \$DOCKERHUB_USERNAME --password \$DOCKERHUB_PASSWORD" - + // ACR 로그인 sh "podman login acrdigitalgarage01.azurecr.io --username \$ACR_USERNAME --password \$ACR_PASSWORD" @@ -169,7 +167,7 @@ podTemplate( } stage('Update Manifest Repository') { - container('azure-cli') { + container('git') { withCredentials([usernamePassword( credentialsId: 'github-credentials-dg0500', usernameVariable: 'GIT_USERNAME', @@ -208,19 +206,20 @@ podTemplate( } } } - + // 파이프라인 완료 로그 (Scripted Pipeline 방식) stage('Pipeline Complete') { echo "🧹 Pipeline completed. Pod cleanup handled by Jenkins Kubernetes Plugin." - + // 성공/실패 여부 로깅 if (currentBuild.result == null || currentBuild.result == 'SUCCESS') { echo "✅ Pipeline completed successfully!" + echo "✅ 매니페스트가 업데이트되었습니다. ArgoCD에서 배포를 확인하세요." } else { echo "❌ Pipeline failed with result: ${currentBuild.result}" } } - + } catch (Exception e) { currentBuild.result = 'FAILURE' echo "❌ Pipeline failed with exception: ${e.getMessage()}" diff --git a/deployment/cicd/kustomize/base/kustomization.yaml b/deployment/cicd/kustomize/base/kustomization.yaml index e6ec7ef..e4c3872 100644 --- a/deployment/cicd/kustomize/base/kustomization.yaml +++ b/deployment/cicd/kustomize/base/kustomization.yaml @@ -5,9 +5,7 @@ metadata: name: phonebill-base resources: - # Namespace - - namespace.yaml - + # Common resources - common/cm-common.yaml - common/secret-common.yaml diff --git a/deployment/cicd/kustomize/base/namespace.yaml b/deployment/cicd/kustomize/base/namespace.yaml deleted file mode 100644 index 3900a62..0000000 --- a/deployment/cicd/kustomize/base/namespace.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: placeholder \ No newline at end of file