name: Backend Services CI/CD on: push: branches: [ main, develop ] paths: - 'api-gateway/**' - 'user-service/**' - 'bill-service/**' - 'product-service/**' - 'kos-mock/**' - 'common/**' - '.github/**' pull_request: 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 IMAGE_ORG: phonebill RESOURCE_GROUP: rg-digitalgarage-01 AKS_CLUSTER: aks-digitalgarage-01 jobs: build: name: Build and Test runs-on: ubuntu-latest outputs: 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 up JDK 21 uses: actions/setup-java@v3 with: java-version: '21' distribution: 'temurin' cache: '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 - name: SonarQube Analysis & Quality Gate env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} run: | # 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: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 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: Set environment variables from build job run: | echo "REGISTRY=${{ env.REGISTRY }}" >> $GITHUB_ENV echo "IMAGE_ORG=${{ env.IMAGE_ORG }}" >> $GITHUB_ENV echo "ENVIRONMENT=${{ needs.build.outputs.environment }}" >> $GITHUB_ENV echo "IMAGE_TAG=${{ needs.build.outputs.image_tag }}" >> $GITHUB_ENV - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - 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 Docker images for all services run: | # 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" \ -f deployment/container/Dockerfile-backend \ -t ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/$service:${{ needs.build.outputs.environment }}-${{ needs.build.outputs.image_tag }} . docker push ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/$service:${{ needs.build.outputs.environment }}-${{ needs.build.outputs.image_tag }} done deploy: name: Deploy to Kubernetes needs: [build, release] runs-on: ubuntu-latest steps: - name: Check out code uses: actions/checkout@v4 - 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: 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 ${{ env.RESOURCE_GROUP }} --name ${{ env.AKS_CLUSTER }} --overwrite-existing - name: Create namespace run: | kubectl create namespace phonebill-${{ env.ENVIRONMENT }} --dry-run=client -o yaml | kubectl apply -f - - name: Install Kustomize run: | curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash sudo mv kustomize /usr/local/bin/ - name: Update Kustomize images and deploy run: | # 환경별 디렉토리로 이동 cd .github/kustomize/overlays/${{ env.ENVIRONMENT }} # 각 서비스별 이미지 태그 업데이트 kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/api-gateway:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }} kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/user-service:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }} kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/bill-service:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }} kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/product-service:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }} kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/kos-mock:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }} # 매니페스트 적용 kubectl apply -k . - name: Wait for deployments to be ready run: | echo "Waiting for deployments to be ready..." kubectl -n phonebill-${{ env.ENVIRONMENT }} wait --for=condition=available deployment/api-gateway --timeout=300s kubectl -n phonebill-${{ env.ENVIRONMENT }} wait --for=condition=available deployment/user-service --timeout=300s kubectl -n phonebill-${{ env.ENVIRONMENT }} wait --for=condition=available deployment/bill-service --timeout=300s kubectl -n phonebill-${{ env.ENVIRONMENT }} wait --for=condition=available deployment/product-service --timeout=300s kubectl -n phonebill-${{ env.ENVIRONMENT }} wait --for=condition=available deployment/kos-mock --timeout=300s