// ============================================================ // juwan-backend CD Pipeline — Harbor Webhook 模式 // 适用场景:生产环境,Harbor 推送镜像后主动通知 Jenkins // // 工作原理: // 1. Harbor 配置 Webhook,推送事件发送 POST 到 Jenkins // 2. Jenkins Generic Webhook Trigger 插件解析 payload // 3. 提取镜像名称,触发对应 Deployment 的滚动更新 // // 前置条件(见 docs/jenkins-cd/02-production-webhook-setup.md): // - Jenkins 插件:Generic Webhook Trigger // - Jenkins 凭据:kubeconfig-prod(Secret file,生产 kubeconfig) // - Harbor Webhook 配置指向本 Jenkins URL // ============================================================ pipeline { agent any // ── Generic Webhook Trigger 插件配置 ───────────────────── // Harbor push 事件 payload 结构: // { // "type": "PUSH_ARTIFACT", // "event_data": { // "resources": [{ // "resource_url": "harbor.example.com/juwan/user-api:abc1234" // }], // "repository": { "name": "user-api" } // } // } triggers { GenericTrigger( genericVariables: [ // 提取镜像仓库名(即服务名,如 user-api) [key: 'REPO_NAME', value: '$.event_data.repository.name'], // 提取完整镜像 URL(含 tag) [key: 'RESOURCE_URL', value: '$.event_data.resources[0].resource_url'], // 提取事件类型(只处理 PUSH_ARTIFACT) [key: 'EVENT_TYPE', value: '$.type'] ], // Webhook token,在 Harbor 配置 Webhook URL 时附加: // http://jenkins.example.com/generic-webhook-trigger/invoke?token=JUWAN_CD_TOKEN token: 'JUWAN_CD_TOKEN', // 只处理推送事件 regexpFilterText: '$EVENT_TYPE', regexpFilterExpression: 'PUSH_ARTIFACT', causeString: 'Harbor push: $REPO_NAME ($RESOURCE_URL)', printContributedVariables: true, printPostContent: true ) } parameters { string( name: 'HARBOR_REGISTRY', defaultValue: 'harbor.example.com', description: 'Harbor 镜像仓库地址' ) string( name: 'HARBOR_PROJECT', defaultValue: 'juwan', description: 'Harbor 项目名' ) string( name: 'K8S_NAMESPACE', defaultValue: 'juwan', description: 'Kubernetes 命名空间' ) string( name: 'KUBECONFIG_CREDENTIAL', defaultValue: 'kubeconfig-prod', description: 'Jenkins 中存储生产 kubeconfig 的凭据 ID' ) } environment { KUBECTL = 'kubectl' } stages { stage('Validate Trigger') { steps { script { echo "=== Harbor Webhook 触发 ===" echo "事件类型: ${env.EVENT_TYPE}" echo "仓库名称: ${env.REPO_NAME}" echo "镜像 URL: ${env.RESOURCE_URL}" if (!env.REPO_NAME?.trim()) { error("Webhook payload 中未找到 repository.name,请检查 Harbor Webhook 配置") } } } } stage('Deploy') { steps { script { def svc = env.REPO_NAME.trim() withCredentials([ file(credentialsId: params.KUBECONFIG_CREDENTIAL, variable: 'KUBECONFIG_FILE') ]) { // 验证 Deployment 存在 def exists = sh( script: """ KUBECONFIG=\${KUBECONFIG_FILE} ${KUBECTL} get deployment ${svc} \\ -n ${params.K8S_NAMESPACE} \\ --ignore-not-found \\ -o name 2>/dev/null || echo "" """, returnStdout: true ).trim() if (!exists) { error("Deployment ${svc} 不存在于命名空间 ${params.K8S_NAMESPACE}") } echo "触发滚动更新: ${svc}" // 用 image tag 中的 digest/sha 更新镜像,确保精确版本 // resource_url 格式: harbor.example.com/juwan/user-api:abc1234 def imageRef = env.RESOURCE_URL.trim() sh """ KUBECONFIG=\${KUBECONFIG_FILE} ${KUBECTL} set image deployment/${svc} \\ ${svc}=${imageRef} \\ -n ${params.K8S_NAMESPACE} """ def rolloutStatus = sh( script: """ KUBECONFIG=\${KUBECONFIG_FILE} ${KUBECTL} rollout status deployment/${svc} \\ -n ${params.K8S_NAMESPACE} \\ --timeout=300s """, returnStatus: true ) if (rolloutStatus == 0) { echo "✅ ${svc} 部署成功 → ${imageRef}" } else { echo "❌ ${svc} 部署失败,执行回滚..." sh """ KUBECONFIG=\${KUBECONFIG_FILE} ${KUBECTL} rollout undo deployment/${svc} \\ -n ${params.K8S_NAMESPACE} """ error("${svc} 部署失败,已回滚") } } } } } } post { success { echo "✅ ${env.REPO_NAME} 部署完成" } failure { echo "❌ ${env.REPO_NAME} 部署失败,请检查日志" } } }