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', slaveConnectTimeout: 300, idleMinutes: 30, activeDeadlineSeconds: 3600, podRetention: never(), // 파드 자동 정리 옵션: never(), onFailure(), always(), default() showRawYaml: false, // 디버깅용 YAML 출력 비활성화 yaml: ''' spec: tolerations: - effect: NoSchedule key: dedicated operator: Equal value: cicd activeDeadlineSeconds: 3600 restartPolicy: Never ''', containers: [ containerTemplate( name: 'podman', image: "mgoltzsche/podman", ttyEnabled: true, command: 'cat', privileged: true, resourceRequestCpu: '500m', resourceRequestMemory: '2Gi', resourceLimitCpu: '2000m', resourceLimitMemory: '4Gi' ), containerTemplate( name: 'gradle', image: 'gradle:jdk21', ttyEnabled: true, command: 'cat', resourceRequestCpu: '500m', resourceRequestMemory: '1Gi', resourceLimitCpu: '1000m', resourceLimitMemory: '2Gi', 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, resourceRequestCpu: '200m', resourceRequestMemory: '512Mi', resourceLimitCpu: '500m', resourceLimitMemory: '1Gi' ) ], 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 environment = params.ENVIRONMENT ?: 'dev' def services = ['api-gateway', 'user-service', 'bill-service', 'product-service', 'kos-mock'] 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 & SonarQube Analysis') { container('gradle') { withSonarQubeEnv('SonarQube') { sh """ chmod +x gradlew ./gradlew build -x test """ // 각 서비스별 테스트 및 SonarQube 분석 services.each { service -> sh """ ./gradlew :${service}:test :${service}:jacocoTestReport :${service}:sonar \\ -Dsonar.projectKey=phonebill-${service}-${environment} \\ -Dsonar.projectName=phonebill-${service}-${environment} \\ -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') { timeout(time: 30, unit: 'MINUTES') { container('podman') { withCredentials([usernamePassword( credentialsId: 'acr-credentials', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD' )]) { sh "podman login acrdigitalgarage01.azurecr.io --username \$USERNAME --password \$PASSWORD" services.each { service -> sh """ podman build \\ --build-arg BUILD_LIB_DIR="${service}/build/libs" \\ --build-arg ARTIFACTORY_FILE="${service}.jar" \\ -f deployment/container/Dockerfile-backend \\ -t acrdigitalgarage01.azurecr.io/phonebill/${service}:${environment}-${imageTag} . podman push acrdigitalgarage01.azurecr.io/phonebill/${service}:${environment}-${imageTag} """ } } } } } stage('Update Kustomize & Deploy') { container('azure-cli') { sh """ # Kustomize 설치 (sudo 없이 사용자 디렉토리에 설치) curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash mkdir -p \$HOME/bin mv kustomize \$HOME/bin/ export PATH=\$PATH:\$HOME/bin # 환경별 디렉토리로 이동 cd deployment/cicd/kustomize/overlays/${environment} # 이미지 태그 업데이트 kustomize edit set image acrdigitalgarage01.azurecr.io/phonebill/api-gateway:${environment}-${imageTag} kustomize edit set image acrdigitalgarage01.azurecr.io/phonebill/user-service:${environment}-${imageTag} kustomize edit set image acrdigitalgarage01.azurecr.io/phonebill/bill-service:${environment}-${imageTag} kustomize edit set image acrdigitalgarage01.azurecr.io/phonebill/product-service:${environment}-${imageTag} kustomize edit set image acrdigitalgarage01.azurecr.io/phonebill/kos-mock:${environment}-${imageTag} # 매니페스트 적용 kubectl apply -k . echo "Waiting for deployments to be ready..." kubectl -n phonebill-${environment} wait --for=condition=available deployment/${environment}-api-gateway --timeout=300s kubectl -n phonebill-${environment} wait --for=condition=available deployment/${environment}-user-service --timeout=300s kubectl -n phonebill-${environment} wait --for=condition=available deployment/${environment}-bill-service --timeout=300s kubectl -n phonebill-${environment} wait --for=condition=available deployment/${environment}-product-service --timeout=300s kubectl -n phonebill-${environment} wait --for=condition=available deployment/${environment}-kos-mock --timeout=300s """ } } // 파이프라인 완료 후 후처리 post { always { // 파드 정리는 Jenkins Kubernetes Plugin이 자동으로 처리 echo "🧹 Pipeline completed. Pod cleanup handled by Jenkins Kubernetes Plugin." // 성공/실패 여부 로깅 script { if (currentBuild.result == null || currentBuild.result == 'SUCCESS') { echo "✅ Pipeline completed successfully!" } else { echo "❌ Pipeline failed with result: ${currentBuild.result}" } } } } } }