From 83a9e22dbedb43865c1ce8b6c06c05a1ac230e9a Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 19 Jun 2025 07:08:19 +0000 Subject: [PATCH] Convert smarketing-ai to GitOps: Jenkins CI + ArgoCD CD --- smarketing-ai/deployment/Jenkinsfile | 308 ++++++++++-------- smarketing-ai/deployment/Jenkinsfile_backup | 176 ++++++++++ ...l.template => deploy.yaml.template_backup} | 0 ...deploy_env_vars => deploy_env_vars_backup} | 0 4 files changed, 340 insertions(+), 144 deletions(-) create mode 100644 smarketing-ai/deployment/Jenkinsfile_backup rename smarketing-ai/deployment/{deploy.yaml.template => deploy.yaml.template_backup} (100%) rename smarketing-ai/deployment/{deploy_env_vars => deploy_env_vars_backup} (100%) diff --git a/smarketing-ai/deployment/Jenkinsfile b/smarketing-ai/deployment/Jenkinsfile index e55f855..8e07c93 100644 --- a/smarketing-ai/deployment/Jenkinsfile +++ b/smarketing-ai/deployment/Jenkinsfile @@ -1,3 +1,5 @@ +// smarketing-backend/smarketing-ai/deployment/Jenkinsfile + def PIPELINE_ID = "${env.BUILD_NUMBER}" def getImageTag() { @@ -11,166 +13,184 @@ podTemplate( serviceAccount: 'jenkins', containers: [ containerTemplate(name: 'podman', image: "mgoltzsche/podman", ttyEnabled: true, command: 'cat', privileged: 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: '/run/podman', 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 + + // Manifest Repository 설정 + def MANIFEST_REPO = 'https://github.com/won-ktds/smarketing-manifest.git' + def MANIFEST_CREDENTIAL_ID = 'github-credentials-smarketing' - stage("Get Source") { - checkout scm - props = readProperties file: "smarketing-ai/deployment/deploy_env_vars" - namespace = "${props.namespace}" + try { + stage("Get Source") { + checkout scm + + // smarketing-ai 하위에 있는 설정 파일 읽기 + props = readProperties file: "smarketing-ai/deployment/deploy_env_vars" - echo "Registry: ${props.registry}" - echo "Image Org: ${props.image_org}" - echo "Team ID: ${props.teamid}" - } - - stage("Check Changes") { - script { - def changes = sh( - script: "git diff --name-only HEAD~1 HEAD", - returnStdout: true - ).trim() - - echo "Changed files: ${changes}" - - if (!changes.contains("smarketing-ai/")) { - echo "No changes in smarketing-ai, skipping build" - currentBuild.result = 'SUCCESS' - error("Stopping pipeline - no changes detected") - } - - echo "Changes detected in smarketing-ai, proceeding with build" + echo "=== Build Information ===" + echo "Service: smarketing-ai" + echo "Image Tag: ${imageTag}" + echo "Registry: ${props.registry}" + echo "Image Org: ${props.image_org}" + echo "Team ID: ${props.teamid}" } - } - 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 rg-digitalgarage-02 --name aks-digitalgarage-02 --overwrite-existing - kubectl create namespace ${namespace} --dry-run=client -o yaml | kubectl apply -f - + stage("Check Changes") { + script { + def changes = sh( + script: "git diff --name-only HEAD~1 HEAD", + returnStdout: true + ).trim() + + echo "Changed files: ${changes}" + + if (!changes.contains("smarketing-ai/")) { + echo "No changes in smarketing-ai, skipping build" + currentBuild.result = 'SUCCESS' + error("Stopping pipeline - no changes detected") + } + + echo "Changes detected in smarketing-ai, proceeding with build" + } + } + + stage('Build & Push Docker Image') { + container('podman') { + sh 'podman system service -t 0 unix:///run/podman/podman.sock & sleep 2' + + withCredentials([usernamePassword( + credentialsId: 'acr-credentials', + usernameVariable: 'ACR_USERNAME', + passwordVariable: 'ACR_PASSWORD' + )]) { + sh """ + echo "==========================================" + echo "Building smarketing-ai Python Flask application" + echo "Image Tag: ${imageTag}" + echo "==========================================" + + # ACR 로그인 + echo \$ACR_PASSWORD | podman login ${props.registry} --username \$ACR_USERNAME --password-stdin + + # Docker 이미지 빌드 + podman build \\ + -f smarketing-ai/deployment/Dockerfile \\ + -t ${props.registry}/${props.image_org}/smarketing-ai:${imageTag} . + + # 이미지 푸시 + podman push ${props.registry}/${props.image_org}/smarketing-ai:${imageTag} + + echo "Successfully built and pushed: ${props.registry}/${props.image_org}/smarketing-ai:${imageTag}" + """ + } + } + } + + stage('Update Manifest Repository') { + container('git') { + script { + // Manifest Repository Clone + withCredentials([usernamePassword( + credentialsId: MANIFEST_CREDENTIAL_ID, + usernameVariable: 'GIT_USERNAME', + passwordVariable: 'GIT_PASSWORD' + )]) { + sh """ + echo "=== Git 설정 ===" + git config --global user.name "Jenkins CI" + git config --global user.email "jenkins@company.com" + + echo "=== Manifest Repository Clone ===" + rm -rf manifest-repo + git clone https://\$GIT_USERNAME:\$GIT_PASSWORD@github.com/won-ktds/smarketing-manifest.git manifest-repo + cd manifest-repo + """ + + def fullImageName = "${props.registry}/${props.image_org}/smarketing-ai:${imageTag}" + def deploymentFile = "smarketing-ai/deployments/smarketing-ai/smarketing-ai-deployment.yaml" + + sh """ + cd manifest-repo + + echo "=== smarketing-ai 이미지 태그 업데이트 ===" + if [ -f "${deploymentFile}" ]; then + # 이미지 태그 업데이트 (sed 사용) + sed -i 's|image: ${props.registry}/${props.image_org}/smarketing-ai:.*|image: ${fullImageName}|g' "${deploymentFile}" + echo "Updated ${deploymentFile} with new image: ${fullImageName}" + + # 변경사항 확인 + echo "=== 변경된 내용 확인 ===" + grep "image: ${props.registry}/${props.image_org}/smarketing-ai" "${deploymentFile}" || echo "이미지 태그 업데이트 확인 실패" + else + echo "Warning: ${deploymentFile} not found" + fi + """ + + sh """ + cd manifest-repo + + echo "=== Git 변경사항 확인 ===" + git status + git diff + + # 변경사항이 있으면 커밋 및 푸시 + if [ -n "\$(git status --porcelain)" ]; then + git add . + git commit -m "Update smarketing-ai to ${imageTag} - Build ${env.BUILD_NUMBER}" + git push origin main + echo "✅ Successfully updated manifest repository" + else + echo "ℹ️ No changes to commit" + fi + """ + } + } + } + } + + stage('Trigger ArgoCD Sync') { + script { + echo """ +🎯 smarketing-ai CI Pipeline 완료! + +📦 빌드된 이미지: +- ${props.registry}/${props.image_org}/smarketing-ai:${imageTag} + +🔄 ArgoCD 동작: +- ArgoCD가 manifest repository 변경사항을 자동으로 감지합니다 +- smarketing-ai Application이 새로운 이미지로 동기화됩니다 +- ArgoCD UI에서 배포 상태를 모니터링하세요 + +🌐 ArgoCD UI: [ArgoCD 접속 URL] +📁 Manifest Repo: ${MANIFEST_REPO} """ } } - } - stage('Build & Push Docker Image') { + // 성공 시 처리 + echo """ +✅ smarketing-ai CI Pipeline 성공! +🏷️ 새로운 이미지 태그: ${imageTag} +🔄 ArgoCD가 자동으로 배포를 시작합니다 + """ + + } catch (Exception e) { + // 실패 시 처리 + echo "❌ smarketing-ai CI Pipeline 실패: ${e.getMessage()}" + throw e + } finally { + // 정리 작업 (항상 실행) container('podman') { - sh 'podman system service -t 0 unix:///run/podman/podman.sock & sleep 2' - - withCredentials([usernamePassword( - credentialsId: 'acr-credentials', - usernameVariable: 'ACR_USERNAME', - passwordVariable: 'ACR_PASSWORD' - )]) { - sh """ - echo "==========================================" - echo "Building smarketing-ai Python Flask application" - echo "Image Tag: ${imageTag}" - echo "==========================================" - - # ACR 로그인 - echo \$ACR_PASSWORD | podman login ${props.registry} --username \$ACR_USERNAME --password-stdin - - # Docker 이미지 빌드 - podman build \ - -f smarketing-ai/deployment/Dockerfile \ - -t ${props.registry}/${props.image_org}/smarketing-ai:${imageTag} . - - # 이미지 푸시 - podman push ${props.registry}/${props.image_org}/smarketing-ai:${imageTag} - - echo "Successfully built and pushed: ${props.registry}/${props.image_org}/smarketing-ai:${imageTag}" - """ - } - } - } - - stage('Generate & Apply Manifest') { - container('envsubst') { - withCredentials([ - string(credentialsId: 'SECRET_KEY', variable: 'SECRET_KEY'), - string(credentialsId: 'CLAUDE_API_KEY', variable: 'CLAUDE_API_KEY'), - string(credentialsId: 'OPENAI_API_KEY', variable: 'OPENAI_API_KEY'), - string(credentialsId: 'AZURE_STORAGE_ACCOUNT_NAME', variable: 'AZURE_STORAGE_ACCOUNT_NAME'), - string(credentialsId: 'AZURE_STORAGE_ACCOUNT_KEY', variable: 'AZURE_STORAGE_ACCOUNT_KEY') - ]) { - sh """ - export namespace=${namespace} - export replicas=${props.replicas} - export resources_requests_cpu=${props.resources_requests_cpu} - export resources_requests_memory=${props.resources_requests_memory} - export resources_limits_cpu=${props.resources_limits_cpu} - export resources_limits_memory=${props.resources_limits_memory} - export upload_folder=${props.upload_folder} - export max_content_length=${props.max_content_length} - export allowed_extensions=${props.allowed_extensions} - export server_host=${props.server_host} - export server_port=${props.server_port} - export azure_storage_container_name=${props.azure_storage_container_name} - - # 이미지 경로 환경변수 설정 - export smarketing_image_path=${props.registry}/${props.image_org}/smarketing-ai:${imageTag} - - # Sensitive 환경변수 설정 (Jenkins Credentials에서) - export secret_key=\$SECRET_KEY - export claude_api_key=\$CLAUDE_API_KEY - export openai_api_key=\$OPENAI_API_KEY - export azure_storage_account_name=\$AZURE_STORAGE_ACCOUNT_NAME - export azure_storage_account_key=\$AZURE_STORAGE_ACCOUNT_KEY - - # manifest 생성 - envsubst < smarketing-ai/deployment/${manifest}.template > smarketing-ai/deployment/${manifest} - echo "Generated manifest file:" - cat smarketing-ai/deployment/${manifest} - """ - } - } - - container('azure-cli') { - sh """ - kubectl apply -f smarketing-ai/deployment/${manifest} - - echo "Waiting for smarketing deployment to be ready..." - kubectl -n ${namespace} wait --for=condition=available deployment/smarketing --timeout=300s - - echo "==========================================" - echo "Getting LoadBalancer External IP..." - - # External IP 확인 (최대 5분 대기) - for i in {1..30}; do - EXTERNAL_IP=\$(kubectl -n ${namespace} get service smarketing-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}') - if [ "\$EXTERNAL_IP" != "" ] && [ "\$EXTERNAL_IP" != "null" ]; then - echo "External IP assigned: \$EXTERNAL_IP" - break - fi - echo "Waiting for External IP... (attempt \$i/30)" - sleep 10 - done - - # 서비스 상태 확인 - kubectl -n ${namespace} get pods -l app=smarketing - kubectl -n ${namespace} get service smarketing-service - - echo "==========================================" - echo "Deployment Complete!" - echo "Service URL: http://\$EXTERNAL_IP:${props.server_port}" - echo "Health Check: http://\$EXTERNAL_IP:${props.server_port}/health" - echo "==========================================" - """ + sh 'podman system prune -f || true' } + sh 'rm -rf manifest-repo || true' } } -} \ No newline at end of file +} diff --git a/smarketing-ai/deployment/Jenkinsfile_backup b/smarketing-ai/deployment/Jenkinsfile_backup new file mode 100644 index 0000000..e55f855 --- /dev/null +++ b/smarketing-ai/deployment/Jenkinsfile_backup @@ -0,0 +1,176 @@ +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', + containers: [ + containerTemplate(name: 'podman', image: "mgoltzsche/podman", ttyEnabled: true, command: 'cat', privileged: true), + containerTemplate(name: 'azure-cli', image: 'hiondal/azure-kubectl:latest', command: 'cat', ttyEnabled: true), + containerTemplate(name: 'envsubst', image: "hiondal/envsubst", command: 'sleep', args: '1h') + ], + volumes: [ + emptyDirVolume(mountPath: '/run/podman', memory: false), + emptyDirVolume(mountPath: '/root/.azure', memory: false) + ] +) { + node(PIPELINE_ID) { + def props + def imageTag = getImageTag() + def manifest = "deploy.yaml" + def namespace + + stage("Get Source") { + checkout scm + props = readProperties file: "smarketing-ai/deployment/deploy_env_vars" + namespace = "${props.namespace}" + + echo "Registry: ${props.registry}" + echo "Image Org: ${props.image_org}" + echo "Team ID: ${props.teamid}" + } + + stage("Check Changes") { + script { + def changes = sh( + script: "git diff --name-only HEAD~1 HEAD", + returnStdout: true + ).trim() + + echo "Changed files: ${changes}" + + if (!changes.contains("smarketing-ai/")) { + echo "No changes in smarketing-ai, skipping build" + currentBuild.result = 'SUCCESS' + error("Stopping pipeline - no changes detected") + } + + echo "Changes detected in smarketing-ai, proceeding with build" + } + } + + 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 rg-digitalgarage-02 --name aks-digitalgarage-02 --overwrite-existing + kubectl create namespace ${namespace} --dry-run=client -o yaml | kubectl apply -f - + """ + } + } + } + + stage('Build & Push Docker Image') { + container('podman') { + sh 'podman system service -t 0 unix:///run/podman/podman.sock & sleep 2' + + withCredentials([usernamePassword( + credentialsId: 'acr-credentials', + usernameVariable: 'ACR_USERNAME', + passwordVariable: 'ACR_PASSWORD' + )]) { + sh """ + echo "==========================================" + echo "Building smarketing-ai Python Flask application" + echo "Image Tag: ${imageTag}" + echo "==========================================" + + # ACR 로그인 + echo \$ACR_PASSWORD | podman login ${props.registry} --username \$ACR_USERNAME --password-stdin + + # Docker 이미지 빌드 + podman build \ + -f smarketing-ai/deployment/Dockerfile \ + -t ${props.registry}/${props.image_org}/smarketing-ai:${imageTag} . + + # 이미지 푸시 + podman push ${props.registry}/${props.image_org}/smarketing-ai:${imageTag} + + echo "Successfully built and pushed: ${props.registry}/${props.image_org}/smarketing-ai:${imageTag}" + """ + } + } + } + + stage('Generate & Apply Manifest') { + container('envsubst') { + withCredentials([ + string(credentialsId: 'SECRET_KEY', variable: 'SECRET_KEY'), + string(credentialsId: 'CLAUDE_API_KEY', variable: 'CLAUDE_API_KEY'), + string(credentialsId: 'OPENAI_API_KEY', variable: 'OPENAI_API_KEY'), + string(credentialsId: 'AZURE_STORAGE_ACCOUNT_NAME', variable: 'AZURE_STORAGE_ACCOUNT_NAME'), + string(credentialsId: 'AZURE_STORAGE_ACCOUNT_KEY', variable: 'AZURE_STORAGE_ACCOUNT_KEY') + ]) { + sh """ + export namespace=${namespace} + export replicas=${props.replicas} + export resources_requests_cpu=${props.resources_requests_cpu} + export resources_requests_memory=${props.resources_requests_memory} + export resources_limits_cpu=${props.resources_limits_cpu} + export resources_limits_memory=${props.resources_limits_memory} + export upload_folder=${props.upload_folder} + export max_content_length=${props.max_content_length} + export allowed_extensions=${props.allowed_extensions} + export server_host=${props.server_host} + export server_port=${props.server_port} + export azure_storage_container_name=${props.azure_storage_container_name} + + # 이미지 경로 환경변수 설정 + export smarketing_image_path=${props.registry}/${props.image_org}/smarketing-ai:${imageTag} + + # Sensitive 환경변수 설정 (Jenkins Credentials에서) + export secret_key=\$SECRET_KEY + export claude_api_key=\$CLAUDE_API_KEY + export openai_api_key=\$OPENAI_API_KEY + export azure_storage_account_name=\$AZURE_STORAGE_ACCOUNT_NAME + export azure_storage_account_key=\$AZURE_STORAGE_ACCOUNT_KEY + + # manifest 생성 + envsubst < smarketing-ai/deployment/${manifest}.template > smarketing-ai/deployment/${manifest} + echo "Generated manifest file:" + cat smarketing-ai/deployment/${manifest} + """ + } + } + + container('azure-cli') { + sh """ + kubectl apply -f smarketing-ai/deployment/${manifest} + + echo "Waiting for smarketing deployment to be ready..." + kubectl -n ${namespace} wait --for=condition=available deployment/smarketing --timeout=300s + + echo "==========================================" + echo "Getting LoadBalancer External IP..." + + # External IP 확인 (최대 5분 대기) + for i in {1..30}; do + EXTERNAL_IP=\$(kubectl -n ${namespace} get service smarketing-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + if [ "\$EXTERNAL_IP" != "" ] && [ "\$EXTERNAL_IP" != "null" ]; then + echo "External IP assigned: \$EXTERNAL_IP" + break + fi + echo "Waiting for External IP... (attempt \$i/30)" + sleep 10 + done + + # 서비스 상태 확인 + kubectl -n ${namespace} get pods -l app=smarketing + kubectl -n ${namespace} get service smarketing-service + + echo "==========================================" + echo "Deployment Complete!" + echo "Service URL: http://\$EXTERNAL_IP:${props.server_port}" + echo "Health Check: http://\$EXTERNAL_IP:${props.server_port}/health" + echo "==========================================" + """ + } + } + } +} \ No newline at end of file diff --git a/smarketing-ai/deployment/deploy.yaml.template b/smarketing-ai/deployment/deploy.yaml.template_backup similarity index 100% rename from smarketing-ai/deployment/deploy.yaml.template rename to smarketing-ai/deployment/deploy.yaml.template_backup diff --git a/smarketing-ai/deployment/deploy_env_vars b/smarketing-ai/deployment/deploy_env_vars_backup similarity index 100% rename from smarketing-ai/deployment/deploy_env_vars rename to smarketing-ai/deployment/deploy_env_vars_backup