From 258bef0891ad1f2c72072032b1224787267e9f5f Mon Sep 17 00:00:00 2001 From: hjmoons Date: Thu, 30 Oct 2025 18:47:36 +0900 Subject: [PATCH] =?UTF-8?q?Jenkinsfile:=20Podman=20=EA=B8=B0=EB=B0=98=20Ku?= =?UTF-8?q?bernetes=20Pod=20=ED=85=9C=ED=94=8C=EB=A6=BF=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 주요 변경사항: - podTemplate 사용하여 Kubernetes Pod에서 실행 - 3개 컨테이너 사용: podman, gradle, git - mgoltzsche/podman 이미지로 Podman 빌드 - gradle:jdk21 이미지로 Gradle 빌드 - alpine/git으로 manifest 저장소 업데이트 컨테이너별 역할: - podman: Docker 이미지 빌드 및 ACR 푸시 - gradle: Gradle 빌드 및 JAR 생성 - git: Kustomize로 manifest 저장소 업데이트 리소스 최적화: - Pod 자동 정리 (idleMinutes: 1, terminationGracePeriodSeconds: 3) - 컨테이너별 리소스 제한 설정 - emptyDir 볼륨으로 Gradle 캐시 및 Podman 소켓 공유 Fix: Docker 대신 Podman 사용으로 Jenkins 환경 호환성 개선 --- Jenkinsfile | 342 ++++++++++++++++++++++++++-------------------------- 1 file changed, 170 insertions(+), 172 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 6e5560b..cdbdca7 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,216 +1,214 @@ -pipeline { - agent any +def PIPELINE_ID = "${env.BUILD_NUMBER}" - parameters { - choice( - name: 'ENVIRONMENT', - choices: ['dev', 'staging', 'prod'], - description: 'Target environment' +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: 1, + activeDeadlineSeconds: 3600, + podRetention: never(), + yaml: ''' + spec: + terminationGracePeriodSeconds: 3 + restartPolicy: Never + tolerations: + - effect: NoSchedule + key: dedicated + operator: Equal + value: cicd + ''', + 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: 'git', + image: 'alpine/git:latest', + command: 'cat', + ttyEnabled: true, + resourceRequestCpu: '100m', + resourceRequestMemory: '256Mi', + resourceLimitCpu: '300m', + resourceLimitMemory: '512Mi' ) - } + ], + volumes: [ + emptyDirVolume(mountPath: '/home/gradle/.gradle', memory: false), + emptyDirVolume(mountPath: '/run/podman', memory: false) + ] +) { + node(PIPELINE_ID) { + def imageTag = getImageTag() + def environment = params.ENVIRONMENT ?: 'dev' + def services = ['user', 'meeting', 'stt', 'ai', 'notification'] + def registry = 'acrdigitalgarage02.azurecr.io' + def imageOrg = 'hgzero' - environment { - // Container Registry - REGISTRY = 'acrdigitalgarage02.azurecr.io' - IMAGE_ORG = 'hgzero' + try { + stage("Get Source") { + checkout scm - // Azure - RESOURCE_GROUP = 'rg-digitalgarage-02' - AKS_CLUSTER = 'aks-digitalgarage-02' - NAMESPACE = 'hgzero' - - // Image Tag - IMAGE_TAG = "${new Date().format('yyyyMMddHHmmss')}" - - // Services - SERVICES = 'user meeting stt ai notification' - - // Credentials - ACR_CREDENTIALS = credentials('acr-credentials') - DOCKERHUB_CREDENTIALS = credentials('dockerhub-credentials') - GIT_CREDENTIALS = credentials('github-credentials-dg0506') - } - - stages { - stage('Checkout') { - steps { - script { - echo "🔄 Checking out code..." - checkout scm + // 환경 변수 로드 + def configFile = ".github/config/deploy_env_vars_${environment}" + if (fileExists(configFile)) { + echo "📋 Loading environment variables for ${environment}..." + def props = readProperties file: configFile + echo "Config loaded: ${props}" } } - } - stage('Setup Java') { - steps { - script { - echo "☕ Setting up Java 21..." - // JDK 21 설치 및 대기 - def jdkHome = tool name: 'JDK21', type: 'jdk' - def jdkPath = "${jdkHome}/jdk-21" - env.JAVA_HOME = jdkPath - env.PATH = "${jdkPath}/bin:${env.PATH}" - - // JDK 설치 완료 대기 및 확인 - sh """ - echo "Waiting for JDK installation..." - while [ ! -f ${jdkPath}/bin/java ]; do - echo "Waiting for JDK to be extracted..." - sleep 2 - done - echo "JDK installation completed" - ${jdkPath}/bin/java -version - """ - } - } - } - - stage('Load Environment Variables') { - steps { - script { - echo "📋 Loading environment variables for ${params.ENVIRONMENT}..." - - def configFile = ".github/config/deploy_env_vars_${params.ENVIRONMENT}" - if (fileExists(configFile)) { - def config = readFile(configFile) - config.split('\n').each { line -> - if (line && !line.startsWith('#')) { - def parts = line.split('=', 2) - if (parts.size() == 2) { - def key = parts[0].trim() - def value = parts[1].trim() - - if (key == 'resource_group') { - env.RESOURCE_GROUP = value - } else if (key == 'cluster_name') { - env.AKS_CLUSTER = value - } - } - } - } - } - - echo "Registry: ${env.REGISTRY}" - echo "Image Org: ${env.IMAGE_ORG}" - echo "Resource Group: ${env.RESOURCE_GROUP}" - echo "AKS Cluster: ${env.AKS_CLUSTER}" - } - } - } - - stage('Build with Gradle') { - steps { - script { + stage('Build') { + container('gradle') { echo "🔨 Building with Gradle..." - sh 'chmod +x gradlew' sh """ - export JAVA_HOME=${env.JAVA_HOME} - export PATH=\${JAVA_HOME}/bin:\${PATH} - java -version + chmod +x gradlew ./gradlew build -x test """ } } - } - stage('Archive Artifacts') { - steps { - script { - echo "📦 Archiving build artifacts..." - archiveArtifacts artifacts: '**/build/libs/*.jar', fingerprint: true - } + stage('Archive Artifacts') { + echo "📦 Archiving build artifacts..." + archiveArtifacts artifacts: '**/build/libs/*.jar', fingerprint: true } - } - stage('Docker Build & Push') { - steps { - script { - echo "🐳 Building and pushing Docker images..." + stage('Build & Push Images') { + timeout(time: 30, unit: 'MINUTES') { + container('podman') { + withCredentials([ + usernamePassword( + credentialsId: 'acr-credentials', + usernameVariable: 'ACR_USERNAME', + passwordVariable: 'ACR_PASSWORD' + ), + usernamePassword( + credentialsId: 'dockerhub-credentials', + usernameVariable: 'DOCKERHUB_USERNAME', + passwordVariable: 'DOCKERHUB_PASSWORD' + ) + ]) { + echo "🐳 Building and pushing Docker images..." - // Login to Docker Hub (prevent rate limit) - sh """ - echo ${DOCKERHUB_CREDENTIALS_PSW} | docker login -u ${DOCKERHUB_CREDENTIALS_USR} --password-stdin - """ + // Login to Docker Hub (prevent rate limit) + sh "podman login docker.io --username \$DOCKERHUB_USERNAME --password \$DOCKERHUB_PASSWORD" - // Login to Azure Container Registry - sh """ - echo ${ACR_CREDENTIALS_PSW} | docker login ${REGISTRY} -u ${ACR_CREDENTIALS_USR} --password-stdin - """ + // Login to Azure Container Registry + sh "podman login ${registry} --username \$ACR_USERNAME --password \$ACR_PASSWORD" - // Build and push each service - env.SERVICES.split().each { service -> - echo "Building ${service}..." + // Build and push each service + services.each { service -> + echo "Building ${service}..." - def imageTag = "${env.REGISTRY}/${env.IMAGE_ORG}/${service}:${params.ENVIRONMENT}-${env.IMAGE_TAG}" + def imageTagFull = "${registry}/${imageOrg}/${service}:${environment}-${imageTag}" - sh """ - docker build \ - --build-arg BUILD_LIB_DIR="${service}/build/libs" \ - --build-arg ARTIFACTORY_FILE="${service}.jar" \ - -f deployment/container/Dockerfile-backend \ - -t ${imageTag} . - """ + sh """ + podman build \\ + --build-arg BUILD_LIB_DIR="${service}/build/libs" \\ + --build-arg ARTIFACTORY_FILE="${service}.jar" \\ + -f deployment/container/Dockerfile-backend \\ + -t ${imageTagFull} . + """ - echo "Pushing ${service}..." - sh "docker push ${imageTag}" + echo "Pushing ${service}..." + sh "podman push ${imageTagFull}" - echo "✅ ${service} image pushed: ${imageTag}" + echo "✅ ${service} image pushed: ${imageTagFull}" + } + } } } } - } - stage('Update Manifest Repository') { - steps { - script { - echo "📝 Updating manifest repository..." + stage('Update Manifest Repository') { + container('git') { + withCredentials([usernamePassword( + credentialsId: 'github-credentials-dg0506', + usernameVariable: 'GIT_USERNAME', + passwordVariable: 'GIT_TOKEN' + )]) { + echo "📝 Updating manifest repository..." - // Clone manifest repository - sh """ - rm -rf manifest-repo - git clone https://${GIT_CREDENTIALS_USR}:${GIT_CREDENTIALS_PSW}@github.com/hjmoons/hgzero-manifest.git manifest-repo - """ - - dir('manifest-repo') { - // Install Kustomize sh """ - curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash + # 매니페스트 레포지토리 클론 + REPO_URL=\$(echo "https://github.com/hjmoons/hgzero-manifest.git" | sed 's|https://||') + git clone https://\${GIT_USERNAME}:\${GIT_TOKEN}@\${REPO_URL} manifest-repo + cd manifest-repo + + # Kustomize 설치 + curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | sh chmod +x kustomize - """ - // Update manifest - dir("hgzero-back/kustomize/overlays/${params.ENVIRONMENT}") { - env.SERVICES.split().each { service -> - sh """ - ../../../kustomize edit set image ${env.REGISTRY}/${env.IMAGE_ORG}/${service}:${params.ENVIRONMENT}-${env.IMAGE_TAG} - """ - } - } + # 각 서비스별 이미지 태그 업데이트 + cd hgzero-back/kustomize/overlays/${environment} - // Git commit and push - sh """ + services="user meeting stt ai notification" + for service in \$services; do + echo "Updating \$service image tag..." + ../../../kustomize edit set image ${registry}/${imageOrg}/\$service:${environment}-${imageTag} + done + + # Git 설정 및 푸시 + cd ../../../.. git config user.name "Jenkins" git config user.email "jenkins@hgzero.com" git add . - git commit -m "🚀 Update hgzero ${params.ENVIRONMENT} images to ${params.ENVIRONMENT}-${env.IMAGE_TAG}" + git commit -m "🚀 Update hgzero ${environment} images to ${environment}-${imageTag}" git push origin main + + echo "✅ 매니페스트 업데이트 완료. ArgoCD가 자동으로 배포합니다." """ } - - echo "✅ Manifest repository updated. ArgoCD will auto-deploy." } } - } - } - post { - success { - echo "✅ Pipeline completed successfully!" - echo "Environment: ${params.ENVIRONMENT}" - echo "Image Tag: ${env.IMAGE_TAG}" - } - failure { - echo "❌ Pipeline failed!" + 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 "Environment: ${environment}" + echo "Image Tag: ${imageTag}" + } else { + echo "❌ Pipeline failed with result: ${currentBuild.result}" + } + } + + } catch (Exception e) { + currentBuild.result = 'FAILURE' + echo "❌ Pipeline failed with exception: ${e.getMessage()}" + throw e + } finally { + echo "🧹 Cleaning up resources and preparing for pod termination..." + echo "Pod will be terminated in 3 seconds due to terminationGracePeriodSeconds: 3" } } }