Kaynağa Gözat

流水线优化

dujian 1 hafta önce
ebeveyn
işleme
e38478533d

+ 85 - 15
docs/jenkins/produ/_shared/k8s-produ-lib.groovy

@@ -25,14 +25,13 @@ def promoteHarborImages(def script, List services, Map cfg) {
     def srcTag = requireSourceTag(script, cfg.sourceTag)
     def tgtTag = cfg.targetTag
     def dryRun = cfg.dryRun == true
+    def method = (cfg.promoteMethod ?: 'harbor-api').trim()
+    def repos = services*.prodDir.join(' ')
 
     services.each { s ->
         def src = "${regHost}/${proj}/${s.prodDir}:${srcTag}"
         def tgt = "${regHost}/${proj}/${s.prodDir}:${tgtTag}"
-        echo ">>> promote ${s.prodDir}: ${src} -> ${tgt}"
-        if (dryRun) {
-            return
-        }
+        echo ">>> promote ${s.prodDir}: ${src} -> ${tgt} (method=${method})"
     }
     if (dryRun) {
         return
@@ -45,21 +44,91 @@ def promoteHarborImages(def script, List services, Map cfg) {
     )]) {
         script.sh """
             set -e
-            echo "\${HARBOR_PASS}" | docker login ${regHost} -u "\${HARBOR_USER}" --password-stdin
+            REG='${regHost}'
+            PROJ='${proj}'
+            SRC='${srcTag}'
+            TGT='${tgtTag}'
+            METHOD='${method}'
+            if [ "\${METHOD}" != "docker-pull" ] && ! command -v jq >/dev/null 2>&1; then
+              echo '>>> jq missing, fallback to docker-pull'
+              METHOD=docker-pull
+            fi
+            if [ "\${METHOD}" = "docker-pull" ]; then
+              echo "\${HARBOR_PASS}" | docker login \${REG} -u "\${HARBOR_USER}" --password-stdin
+            fi
+            promote_repo() {
+              local repo="\$1"
+              local src_img="\${REG}/\${PROJ}/\${repo}:\${SRC}"
+              local tgt_img="\${REG}/\${PROJ}/\${repo}:\${TGT}"
+              if [ "\${METHOD}" = "docker-pull" ]; then
+                echo ">>> docker promote \${repo}"
+                docker pull "\${src_img}"
+                docker tag "\${src_img}" "\${tgt_img}"
+                docker push "\${tgt_img}"
+                docker rmi "\${tgt_img}" "\${src_img}" 2>/dev/null || true
+                return 0
+              fi
+              local enc_repo
+              enc_repo=\$(printf '%s' "\${repo}" | jq -sRr @uri)
+              echo ">>> Harbor API promote \${repo}: \${SRC} -> \${TGT}"
+              if curl -fsS -X POST -u "\${HARBOR_USER}:\${HARBOR_PASS}" \\
+                "http://\${REG}/api/v2.0/projects/\${PROJ}/repositories/\${enc_repo}/artifacts/\${SRC}/tags" \\
+                -H 'Content-Type: application/json' \\
+                -d "{\\"name\\":\\"\${TGT}\\"}"; then
+                return 0
+              fi
+              echo ">>> WARN: Harbor API failed for \${repo}, fallback docker"
+              docker pull "\${src_img}"
+              docker tag "\${src_img}" "\${tgt_img}"
+              docker push "\${tgt_img}"
+              docker rmi "\${tgt_img}" "\${src_img}" 2>/dev/null || true
+            }
+            if [ "\${METHOD}" != "docker-pull" ]; then
+              echo "\${HARBOR_PASS}" | docker login \${REG} -u "\${HARBOR_USER}" --password-stdin 2>/dev/null || true
+            fi
+            for repo in ${repos}; do
+              promote_repo "\${repo}"
+            done
         """
-        services.each { s ->
-            def src = "${regHost}/${proj}/${s.prodDir}:${srcTag}"
-            def tgt = "${regHost}/${proj}/${s.prodDir}:${tgtTag}"
-            script.sh """
-                set -e
-                docker pull ${src}
-                docker tag ${src} ${tgt}
-                docker push ${tgt}
-            """
-        }
     }
 }
 
+def deployServicesToAck(def script, List services, Map cfg) {
+    def parallelDeploy = cfg.parallelDeploy != false
+    def regHost = cfg.harborRegistry.trim()
+    def proj = cfg.harborProject.trim()
+    def tgtTag = cfg.targetTag
+    def strategy = cfg.deployStrategy ?: 'rolling'
+    def ns = cfg.k8sNamespace
+    def canaryWeight = (cfg.canaryWeight ?: '10').trim()
+    def kubeCreds = cfg.kubeCredentialsId
+
+    def runDeploy = { s ->
+        def imageRef = "${regHost}/${proj}/${s.prodDir}:${tgtTag}"
+        deployToAck(script, [
+            k8sNamespace: ns,
+            imageRef: imageRef,
+            deployStrategy: strategy == 'canary' ? 'canary' : 'rolling',
+            deploymentStable: s.deployName,
+            deploymentCanary: "${s.deployName}-canary",
+            ingressCanary: "${s.deployName}-canary",
+            canaryWeight: canaryWeight,
+            kubeCredentialsId: kubeCreds,
+        ])
+    }
+
+    if (!parallelDeploy || services.size() <= 1) {
+        services.each { runDeploy(it) }
+        return
+    }
+
+    def branches = [:]
+    services.each { s ->
+        branches[s.prodDir] = { runDeploy(s) }
+    }
+    script.parallel branches
+}
+
 def deployToAck(def script, Map cfg) {
     def ns = cfg.k8sNamespace
     def imageRef = cfg.imageRef
@@ -106,6 +175,7 @@ def promoteOneServiceToAck(def script, Map svc) {
         sourceTag: srcTag,
         targetTag: tgtTag,
         harborCredentialsId: script.env.HARBOR_CREDENTIALS,
+        promoteMethod: (script.params.PROMOTE_METHOD ?: 'harbor-api').trim(),
         dryRun: dryRun,
     ])
 

+ 2 - 0
docs/jenkins/produ/gateway/Jenkinsfile

@@ -35,6 +35,8 @@ pipeline {
             description: '留空则 produ-${BUILD_NUMBER}')
         string(name: 'HARBOR_REGISTRY', defaultValue: '39.105.153.68', trim: true)
         string(name: 'HARBOR_PROJECT', defaultValue: 'alien_cloud', trim: true)
+        choice(name: 'PROMOTE_METHOD', choices: ['harbor-api', 'docker-pull'],
+            description: 'harbor-api=服务端打 tag(快,默认)')
         choice(name: 'DEPLOY_STRATEGY', choices: ['rolling', 'canary', 'skip'],
             description: 'skip=只推 Harbor,不更新 ACK')
         string(name: 'CANARY_WEIGHT', defaultValue: '10', trim: true)

+ 17 - 32
docs/jenkins/produ/promote-image/Jenkinsfile

@@ -25,6 +25,7 @@ pipeline {
     options {
         buildDiscarder(logRotator(numToKeepStr: '20'))
         disableConcurrentBuilds()
+        skipDefaultCheckout(true)
         timestamps()
         timeout(time: 120, unit: 'MINUTES')
     }
@@ -43,6 +44,9 @@ pipeline {
         string(name: 'SOURCE_TAG', defaultValue: 'uat-latest', trim: true,
             description: 'Harbor UAT tag,默认 uat-latest;回滚填 uat-build-<N>')
         string(name: 'TARGET_TAG', defaultValue: '', trim: true)
+        choice(name: 'PROMOTE_METHOD', choices: ['harbor-api', 'docker-pull'],
+            description: 'harbor-api=服务端打 tag(快)')
+        booleanParam(name: 'PARALLEL_ACK_DEPLOY', defaultValue: true)
         string(name: 'HARBOR_REGISTRY', defaultValue: '39.105.153.68', trim: true)
         string(name: 'HARBOR_PROJECT', defaultValue: 'alien_cloud', trim: true)
         choice(name: 'DEPLOY_STRATEGY', choices: ['rolling', 'canary', 'skip'])
@@ -55,55 +59,36 @@ pipeline {
         KUBECONFIG_CREDENTIALS = 'ack-kubeconfig-alien'
     }
     stages {
-        stage('Plan') {
+        stage('Promote & Deploy') {
             steps {
                 script {
                     def (k8s, reg) = getProduLibs()
                     def services = reg.filterServices(reg.getServiceRegistry(), params)
                     env.SOURCE_TAG_RESOLVED = k8s.requireSourceTag(this, params.SOURCE_TAG)
-                    env.PROMOTE_LIST = services*.prodDir.join(',')
                     env.TARGET_TAG_RESOLVED = k8s.resolveTargetTag(this, params.TARGET_TAG)
+                    env.PROMOTE_LIST = services*.prodDir.join(',')
                     echo ">>> 服务=${env.PROMOTE_LIST} ${env.SOURCE_TAG_RESOLVED} → ${env.TARGET_TAG_RESOLVED}"
-                }
-            }
-        }
-        stage('Promote images') {
-            steps {
-                script {
-                    def (k8s, reg) = getProduLibs()
-                    def services = reg.filterServices(reg.getServiceRegistry(), params)
+
                     k8s.promoteHarborImages(this, services, [
                         harborRegistry: params.HARBOR_REGISTRY,
                         harborProject: params.HARBOR_PROJECT,
                         sourceTag: env.SOURCE_TAG_RESOLVED,
                         targetTag: env.TARGET_TAG_RESOLVED,
                         harborCredentialsId: env.HARBOR_CREDENTIALS,
+                        promoteMethod: params.PROMOTE_METHOD,
                         dryRun: params.DRY_RUN == true,
                     ])
-                }
-            }
-        }
-        stage('Deploy to ACK') {
-            when { expression { return params.DEPLOY_STRATEGY != 'skip' && !params.DRY_RUN } }
-            steps {
-                script {
-                    def (k8s, reg) = getProduLibs()
-                    def services = reg.filterServices(reg.getServiceRegistry(), params)
-                    def regHost = params.HARBOR_REGISTRY.trim()
-                    def proj = params.HARBOR_PROJECT.trim()
-                    def tgtTag = env.TARGET_TAG_RESOLVED
-                    def strategy = params.DEPLOY_STRATEGY
-                    services.each { s ->
-                        def imageRef = "${regHost}/${proj}/${s.prodDir}:${tgtTag}"
-                        k8s.deployToAck(this, [
+
+                    if (params.DEPLOY_STRATEGY != 'skip' && !params.DRY_RUN) {
+                        k8s.deployServicesToAck(this, services, [
+                            harborRegistry: params.HARBOR_REGISTRY,
+                            harborProject: params.HARBOR_PROJECT,
+                            targetTag: env.TARGET_TAG_RESOLVED,
+                            deployStrategy: params.DEPLOY_STRATEGY,
                             k8sNamespace: params.K8S_NAMESPACE,
-                            imageRef: imageRef,
-                            deployStrategy: strategy == 'canary' ? 'canary' : 'rolling',
-                            deploymentStable: s.deployName,
-                            deploymentCanary: "${s.deployName}-canary",
-                            ingressCanary: "${s.deployName}-canary",
-                            canaryWeight: (params.CANARY_WEIGHT ?: '10').trim(),
+                            canaryWeight: params.CANARY_WEIGHT,
                             kubeCredentialsId: env.KUBECONFIG_CREDENTIALS,
+                            parallelDeploy: params.PARALLEL_ACK_DEPLOY == true,
                         ])
                     }
                 }

+ 19 - 36
docs/jenkins/produ/whole/Jenkinsfile

@@ -1,8 +1,6 @@
 /**
  * 整体 / 单服务 / 多选:仅晋升 Harbor 镜像 + 可选 ACK。
  * Script Path:docs/jenkins/produ/whole/Jenkinsfile
- *
- * 注意:load / checkout 必须在 agent 的 stage 内执行,禁止在 pipeline 外顶层 load。
  */
 def sparseCheckoutProduShared() {
     checkout scm: [
@@ -18,7 +16,6 @@ def sparseCheckoutProduShared() {
     ]
 }
 
-/** 在 node 内稀疏检出并加载共享库(每个 stage 可重复调用) */
 def getProduLibs() {
     sparseCheckoutProduShared()
     def k8s = load 'docs/jenkins/produ/_shared/k8s-produ-lib.groovy'
@@ -31,6 +28,7 @@ pipeline {
     options {
         buildDiscarder(logRotator(numToKeepStr: '15'))
         disableConcurrentBuilds()
+        skipDefaultCheckout(true)
         timestamps()
         timeout(time: 120, unit: 'MINUTES')
     }
@@ -51,6 +49,10 @@ pipeline {
             description: 'Harbor UAT tag,默认 uat-latest;回滚填 uat-build-<N>')
         string(name: 'TARGET_TAG', defaultValue: '', trim: true,
             description: '留空则 produ-${BUILD_NUMBER}')
+        choice(name: 'PROMOTE_METHOD', choices: ['harbor-api', 'docker-pull'],
+            description: 'harbor-api=服务端打 tag(快);docker-pull=拉取再 push(慢,兼容)')
+        booleanParam(name: 'PARALLEL_ACK_DEPLOY', defaultValue: true,
+            description: 'whole/multi 时并行 kubectl rollout(显著缩短 ACK 阶段)')
         string(name: 'HARBOR_REGISTRY', defaultValue: '39.105.153.68', trim: true)
         string(name: 'HARBOR_PROJECT', defaultValue: 'alien_cloud', trim: true)
         choice(name: 'DEPLOY_STRATEGY', choices: ['rolling', 'canary', 'skip'],
@@ -64,56 +66,37 @@ pipeline {
         KUBECONFIG_CREDENTIALS = 'ack-kubeconfig-alien'
     }
     stages {
-        stage('Plan') {
+        stage('Promote & Deploy') {
             steps {
                 script {
                     def (k8s, reg) = getProduLibs()
                     def services = reg.filterServices(reg.getServiceRegistry(), params)
                     env.SOURCE_TAG_RESOLVED = k8s.requireSourceTag(this, params.SOURCE_TAG)
-                    env.PROMOTE_LIST = services*.prodDir.join(',')
                     env.TARGET_TAG_RESOLVED = k8s.resolveTargetTag(this, params.TARGET_TAG)
+                    env.PROMOTE_LIST = services*.prodDir.join(',')
                     echo ">>> DEPLOY_MODE=${params.DEPLOY_MODE} 服务=${env.PROMOTE_LIST}"
-                    echo ">>> ${env.SOURCE_TAG_RESOLVED} → ${env.TARGET_TAG_RESOLVED}"
-                }
-            }
-        }
-        stage('Promote images') {
-            steps {
-                script {
-                    def (k8s, reg) = getProduLibs()
-                    def services = reg.filterServices(reg.getServiceRegistry(), params)
+                    echo ">>> ${env.SOURCE_TAG_RESOLVED} → ${env.TARGET_TAG_RESOLVED} promote=${params.PROMOTE_METHOD}"
+
                     k8s.promoteHarborImages(this, services, [
                         harborRegistry: params.HARBOR_REGISTRY,
                         harborProject: params.HARBOR_PROJECT,
                         sourceTag: env.SOURCE_TAG_RESOLVED,
                         targetTag: env.TARGET_TAG_RESOLVED,
                         harborCredentialsId: env.HARBOR_CREDENTIALS,
+                        promoteMethod: params.PROMOTE_METHOD,
                         dryRun: params.DRY_RUN == true,
                     ])
-                }
-            }
-        }
-        stage('Deploy to ACK') {
-            when { expression { return params.DEPLOY_STRATEGY != 'skip' && !params.DRY_RUN } }
-            steps {
-                script {
-                    def (k8s, reg) = getProduLibs()
-                    def services = reg.filterServices(reg.getServiceRegistry(), params)
-                    def regHost = params.HARBOR_REGISTRY.trim()
-                    def proj = params.HARBOR_PROJECT.trim()
-                    def tgtTag = env.TARGET_TAG_RESOLVED
-                    def strategy = params.DEPLOY_STRATEGY
-                    services.each { s ->
-                        def imageRef = "${regHost}/${proj}/${s.prodDir}:${tgtTag}"
-                        k8s.deployToAck(this, [
+
+                    if (params.DEPLOY_STRATEGY != 'skip' && !params.DRY_RUN) {
+                        k8s.deployServicesToAck(this, services, [
+                            harborRegistry: params.HARBOR_REGISTRY,
+                            harborProject: params.HARBOR_PROJECT,
+                            targetTag: env.TARGET_TAG_RESOLVED,
+                            deployStrategy: params.DEPLOY_STRATEGY,
                             k8sNamespace: params.K8S_NAMESPACE,
-                            imageRef: imageRef,
-                            deployStrategy: strategy == 'canary' ? 'canary' : 'rolling',
-                            deploymentStable: s.deployName,
-                            deploymentCanary: "${s.deployName}-canary",
-                            ingressCanary: "${s.deployName}-canary",
-                            canaryWeight: (params.CANARY_WEIGHT ?: '10').trim(),
+                            canaryWeight: params.CANARY_WEIGHT,
                             kubeCredentialsId: env.KUBECONFIG_CREDENTIALS,
+                            parallelDeploy: params.PARALLEL_ACK_DEPLOY == true,
                         ])
                     }
                 }