فهرست منبع

perf(uat): aliyun mirror, persistent m2, parallel harbor/deploy

dujian 1 هفته پیش
والد
کامیت
2934b86ef6
1فایلهای تغییر یافته به همراه227 افزوده شده و 99 حذف شده
  1. 227 99
      docs/jenkins/Jenkinsfile-uat-build-deploy.groovy

+ 227 - 99
docs/jenkins/Jenkinsfile-uat-build-deploy.groovy

@@ -4,15 +4,147 @@
  * Jenkins Job: Pipeline script from SCM
  * Script Path: docs/jenkins/Jenkinsfile-uat-build-deploy.groovy
  *
- * Harbor (153.68): when PUSH_TO_HARBOR=true, push e.g.
- *   39.105.153.68/alien_cloud/gateway:uat-build-<BUILD_NUMBER>
- * Production promote jobs use SOURCE_TAG=uat-build-<same number>.
+ * Harbor (153.68): when PUSH_TO_HARBOR=true, push uat-latest (+ archive prior as uat-build-<N>).
+ * Prod promote: SOURCE_TAG=uat-latest
  */
+
+def filterHarborPushScope(List allServices, String scope) {
+    def s = (scope ?: 'all-java-services').trim()
+    if (s == 'all-java-services') {
+        return allServices
+    }
+    if (s.endsWith('-only')) {
+        def repo = s.substring(0, s.length() - '-only'.length())
+        def picked = allServices.findAll { it.repo == repo }
+        if (picked.isEmpty()) {
+            error("Unknown HARBOR_PUSH_SCOPE: ${scope}")
+        }
+        return picked
+    }
+    error("Unknown HARBOR_PUSH_SCOPE: ${scope}")
+}
+
+/** POSIX sh only — Jenkins sh step uses /bin/sh, not bash. */
+def pruneHarborUatTags(def script, String reg, String proj, List repoNames, int keepCount, String tagPrefix, String currentBuildTag, String latestTag) {
+    if (repoNames == null || repoNames.isEmpty() || keepCount < 1) {
+        return
+    }
+    def repos = repoNames.join(' ')
+    script.sh """
+        set -e
+        REG='${reg}'
+        PROJ='${proj}'
+        KEEP=${keepCount}
+        PREFIX='${tagPrefix}'
+        CURRENT='${currentBuildTag}'
+        LATEST='${latestTag}'
+        if ! command -v jq >/dev/null 2>&1; then
+          echo '>>> Harbor prune skipped: jq not installed on Jenkins agent'
+          exit 0
+        fi
+        for repo in ${repos}; do
+          enc_repo=\$(printf '%s' "\${repo}" | jq -sRr @uri)
+          tags_file=\$(mktemp)
+          curl -fsS -u "\${HARBOR_USER}:\${HARBOR_PASS}" \\
+            "http://\${REG}/api/v2.0/projects/\${PROJ}/repositories/\${enc_repo}/artifacts?page_size=100" \\
+            | jq -r '.[] | .tags[]? | .name' | grep "^\${PREFIX}" | sort -t- -k3 -n > "\${tags_file}" || true
+          count=\$(wc -l < "\${tags_file}" | tr -d ' ')
+          echo ">>> prune \${repo}: \${count} tag(s) matching \${PREFIX}*"
+          if [ "\${count}" -le "\${KEEP}" ]; then
+            rm -f "\${tags_file}"
+            continue
+          fi
+          del_count=\$((count - KEEP))
+          deleted=0
+          while IFS= read -r t; do
+            [ -z "\${t}" ] && continue
+            if [ "\${t}" = "\${CURRENT}" ] || [ "\${t}" = "\${LATEST}" ]; then
+              continue
+            fi
+            if [ "\${deleted}" -ge "\${del_count}" ]; then
+              break
+            fi
+            echo ">>> DELETE Harbor tag \${repo}:\${t}"
+            if ! curl -fsS -X DELETE -u "\${HARBOR_USER}:\${HARBOR_PASS}" \\
+              "http://\${REG}/api/v2.0/projects/\${PROJ}/repositories/\${enc_repo}/artifacts/\${t}/tags/\${t}"; then
+              echo ">>> WARN: delete failed \${repo}:\${t}"
+            fi
+            deleted=\$((deleted + 1))
+          done < "\${tags_file}"
+          rm -f "\${tags_file}"
+        done
+    """
+}
+
+def pushOneHarborImage(String workspace, String dockerfile, String reg, String proj, Map svc, String latestTag, String buildTag) {
+    def jarName = "${svc.module}-1.0.0.jar"
+    def imageLatest = "${reg}/${proj}/${svc.repo}:${latestTag}"
+    def imageBuild = "${reg}/${proj}/${svc.repo}:${buildTag}"
+    def withLibFlag = svc.withLib ? 'true' : 'false'
+    sh """
+        set -e
+        export DOCKER_BUILDKIT=1
+        test -f ${workspace}/${svc.module}/target/${jarName}
+        cd ${workspace}/${svc.module}
+        rm -rf .jenkins_docker_ctx && mkdir -p .jenkins_docker_ctx/lib
+        cp -f target/${jarName} .jenkins_docker_ctx/${jarName}
+        if [ "${withLibFlag}" = "true" ] && [ -d target/lib ]; then
+          cp -rf target/lib/. .jenkins_docker_ctx/lib/
+        else
+          touch .jenkins_docker_ctx/lib/.keep
+        fi
+        cd .jenkins_docker_ctx
+        if docker pull ${imageLatest} 2>/dev/null; then
+          echo ">>> archive previous ${latestTag} -> ${buildTag}"
+          docker tag ${imageLatest} ${imageBuild}
+          docker push ${imageBuild}
+        fi
+        docker build -f ${workspace}/${dockerfile} \\
+          --build-arg BASE_IMAGE=${reg}/${proj}/base/openjdk8-ffmpeg:v1 \\
+          --build-arg JAR_FILE=${jarName} \\
+          --build-arg SERVER_PORT=${svc.port} \\
+          --build-arg WITH_LIB=${svc.withLib} \\
+          -t ${imageLatest} .
+        docker push ${imageLatest}
+        echo ">>> pushed ${imageLatest}"
+        docker rmi ${imageLatest} 2>/dev/null || true
+        cd ${workspace}/${svc.module}
+        rm -rf .jenkins_docker_ctx
+    """
+}
+
+def deployOneService(String workspace, String moduleName, String dirName) {
+    def sourceJar = "${workspace}/${moduleName}/target/${moduleName}-1.0.0.jar"
+    def sourceLib = "${workspace}/${moduleName}/target/lib"
+    def targetDir = "/app_deploy_uat/${dirName}"
+    sh """
+        set -e
+        echo ">>> Deploy module: ${moduleName}"
+        if [ -f "${sourceJar}" ]; then
+            mkdir -p "${targetDir}"
+            if [ -d "${sourceLib}" ]; then
+                rm -rf "${targetDir}/lib"
+                cp -rf "${sourceLib}" "${targetDir}"
+            fi
+            cp -f "${sourceJar}" "${targetDir}/"
+            if docker ps -a --format '{{.Names}}' | grep -wq "${dirName}"; then
+                docker restart "${dirName}"
+                echo ">>> [${dirName}] restarted"
+            else
+                echo ">>> [${dirName}] container missing, jar copied only"
+            fi
+        else
+            echo ">>> [${dirName}] jar missing, skip"
+        fi
+    """
+}
+
 pipeline {
     agent any
 
     options {
         buildDiscarder(logRotator(numToKeepStr: '15'))
+        disableConcurrentBuilds()
         timestamps()
         timeout(time: 90, unit: 'MINUTES')
     }
@@ -24,20 +156,32 @@ pipeline {
                 trim: true,
                 description: 'Git branch, must match remote (e.g. uat-20260202)'
         )
-        booleanParam(name: 'FORCE_UPDATE', defaultValue: true, description: 'mvn -U')
+        booleanParam(name: 'FORCE_UPDATE', defaultValue: false, description: 'mvn -U (leave unchecked for routine builds)')
         booleanParam(name: 'ALLOW_SNAPSHOTS', defaultValue: true, description: 'allow SNAPSHOT deps')
         booleanParam(
                 name: 'PUSH_TO_HARBOR',
-                defaultValue: false,
-                description: 'After Maven: docker build + push to 39.105.153.68/alien_cloud (tag uat-build-<BUILD_NUMBER>)'
+                defaultValue: true,
+                description: 'After Maven: docker build + push Harbor (uat-latest). Uncheck for jar-only deploy.'
         )
         choice(
                 name: 'HARBOR_PUSH_SCOPE',
-                choices: ['gateway-only', 'all-java-services'],
-                description: 'Only used when PUSH_TO_HARBOR=true'
+                choices: [
+                        'all-java-services',
+                        'gateway-only',
+                        'store-only',
+                        'second-only',
+                        'store-platform-only',
+                        'lawyer-only',
+                        'job-only',
+                        'dining-only',
+                ],
+                description: 'Only when PUSH_TO_HARBOR=true'
         )
         string(name: 'HARBOR_REGISTRY', defaultValue: '39.105.153.68', trim: true)
         string(name: 'HARBOR_PROJECT', defaultValue: 'alien_cloud', trim: true)
+        booleanParam(name: 'HARBOR_PRUNE_OLD_TAGS', defaultValue: true, description: 'Delete old uat-build-* tags (never uat-latest)')
+        string(name: 'HARBOR_KEEP_TAG_COUNT', defaultValue: '10', trim: true)
+        booleanParam(name: 'HARBOR_PUSH_PARALLEL', defaultValue: true, description: 'Build/push Harbor images in parallel (faster, uses more CPU/disk)')
     }
 
     environment {
@@ -46,8 +190,10 @@ pipeline {
         GIT_URL = 'http://8.152.195.41:3000/alien/alien_cloud'
         GIT_CREDENTIALS = 'zhanghaomimapingzheng'
         HARBOR_CREDENTIALS = 'harbor-robot-alien'
-        UAT_HARBOR_IMAGE_TAG = "uat-build-${env.BUILD_NUMBER}"
+        UAT_HARBOR_LATEST_TAG = 'uat-latest'
+        UAT_HARBOR_BUILD_TAG = "uat-build-${env.BUILD_NUMBER}"
         DOCKERFILE_JAVA = 'docs/jenkins/produ/docker/Dockerfile.java-service'
+        MAVEN_LOCAL_REPO = '/var/jenkins_home/.m2/repository'
     }
 
     stages {
@@ -75,34 +221,44 @@ pipeline {
 
         stage('Prepare Maven Settings') {
             steps {
-                script {
-                    writeFile file: 'settings.xml', text: """<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
+                writeFile file: 'settings.xml', text: """<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">
+    <mirrors>
+        <mirror>
+            <id>aliyunmaven</id>
+            <mirrorOf>central</mirrorOf>
+            <name>Aliyun Maven Central mirror</name>
+            <url>https://maven.aliyun.com/repository/central</url>
+        </mirror>
+        <mirror>
+            <id>aliyun-public</id>
+            <mirrorOf>*,!spring-milestones,!spring-snapshots</mirrorOf>
+            <name>Aliyun public</name>
+            <url>https://maven.aliyun.com/repository/public</url>
+        </mirror>
+    </mirrors>
     <profiles>
         <profile>
             <id>repo-mix</id>
             <repositories>
                 <repository>
                     <id>central</id>
-                    <name>Maven Central</name>
                     <url>https://repo.maven.apache.org/maven2</url>
-                    <releases><enabled>true</enabled><updatePolicy>always</updatePolicy></releases>
+                    <releases><enabled>true</enabled><updatePolicy>daily</updatePolicy></releases>
                     <snapshots><enabled>false</enabled></snapshots>
                 </repository>
                 <repository>
                     <id>spring-milestones</id>
-                    <name>Spring Milestones</name>
                     <url>https://repo.spring.io/milestone</url>
-                    <releases><enabled>true</enabled><updatePolicy>always</updatePolicy></releases>
+                    <releases><enabled>true</enabled><updatePolicy>daily</updatePolicy></releases>
                     <snapshots><enabled>false</enabled></snapshots>
                 </repository>
                 <repository>
                     <id>spring-snapshots</id>
-                    <name>Spring Snapshots</name>
                     <url>https://repo.spring.io/snapshot</url>
                     <releases><enabled>false</enabled></releases>
-                    <snapshots><enabled>true</enabled><updatePolicy>always</updatePolicy></snapshots>
+                    <snapshots><enabled>true</enabled><updatePolicy>daily</updatePolicy></snapshots>
                 </repository>
             </repositories>
             <pluginRepositories>
@@ -112,12 +268,6 @@ pipeline {
                     <releases><enabled>true</enabled></releases>
                     <snapshots><enabled>false</enabled></snapshots>
                 </pluginRepository>
-                <pluginRepository>
-                    <id>spring-milestones</id>
-                    <url>https://repo.spring.io/milestone</url>
-                    <releases><enabled>true</enabled></releases>
-                    <snapshots><enabled>false</enabled></snapshots>
-                </pluginRepository>
             </pluginRepositories>
         </profile>
     </profiles>
@@ -126,7 +276,6 @@ pipeline {
     </activeProfiles>
 </settings>
 """
-                }
             }
         }
 
@@ -139,12 +288,14 @@ pipeline {
                             set -e
                             mvn -version
                             unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY ALL_PROXY all_proxy no_proxy NO_PROXY || true
-                            export MAVEN_OPTS="-Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true -Dmaven.wagon.http.ssl.ignore.validity.dates=true"
-                            rm -rf /root/.m2/repository/org/springframework/cloud/spring-cloud-dependencies/Hoxton.SR1 || true
-                            rm -rf /root/.m2/repository/org/springframework/boot/spring-boot-dependencies/2.3.2.RELEASE || true
-                            rm -rf ${WORKSPACE}/.m2/repository/org/springframework/cloud/spring-cloud-dependencies/Hoxton.SR1 || true
-                            rm -rf ${WORKSPACE}/.m2/repository/org/springframework/boot/spring-boot-dependencies/2.3.2.RELEASE || true
-                            mvn clean package -DskipTests -s settings.xml ${updateFlag} -e -Dmaven.repo.local=${WORKSPACE}/.m2/repository
+                            export MAVEN_OPTS="-Xms512m -Xmx2048m -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true -Dmaven.wagon.http.ssl.ignore.validity.dates=true"
+                            mkdir -p ${MAVEN_LOCAL_REPO}
+                            du -sh ${MAVEN_LOCAL_REPO} 2>/dev/null || true
+                            echo ">>> Maven repo: ${MAVEN_LOCAL_REPO} (persists across builds; do NOT delete on disk cleanup)"
+                            mvn clean package -DskipTests -s settings.xml ${updateFlag} \\
+                              -T 1C \\
+                              -Dmaven.repo.local=${MAVEN_LOCAL_REPO} \\
+                              -Dmaven.artifact.threads=8
                         """
                     }
                 }
@@ -157,11 +308,12 @@ pipeline {
                 script {
                     def reg = params.HARBOR_REGISTRY.trim()
                     def proj = params.HARBOR_PROJECT.trim()
-                    def tag = env.UAT_HARBOR_IMAGE_TAG
-                    def baseImage = "${reg}/${proj}/base/openjdk8-ffmpeg:v1"
+                    def latestTag = env.UAT_HARBOR_LATEST_TAG
+                    def buildTag = env.UAT_HARBOR_BUILD_TAG
                     def dockerfile = env.DOCKERFILE_JAVA
+                    def workspace = env.WORKSPACE
 
-                    def harborServices = [
+                    def allHarborServices = [
                             [module: 'alien-gateway',        repo: 'gateway',        port: '8000',  withLib: false],
                             [module: 'alien-store',          repo: 'store',          port: '50014', withLib: true],
                             [module: 'alien-second',         repo: 'second',         port: '50015', withLib: false],
@@ -170,9 +322,8 @@ pipeline {
                             [module: 'alien-job',            repo: 'job',            port: '50108', withLib: false],
                             [module: 'alien-dining',         repo: 'dining',         port: '50019', withLib: false],
                     ]
-                    if (params.HARBOR_PUSH_SCOPE == 'gateway-only') {
-                        harborServices = harborServices.findAll { it.repo == 'gateway' }
-                    }
+                    def harborServices = filterHarborPushScope(allHarborServices, params.HARBOR_PUSH_SCOPE)
+                    echo ">>> HARBOR_PUSH_SCOPE=${params.HARBOR_PUSH_SCOPE} repos=${harborServices*.repo.join(',')}"
 
                     withCredentials([usernamePassword(
                             credentialsId: env.HARBOR_CREDENTIALS,
@@ -182,34 +333,33 @@ pipeline {
                         sh """
                             set -e
                             echo "\${HARBOR_PASS}" | docker login ${reg} -u "\${HARBOR_USER}" --password-stdin
+                            df -h / 2>/dev/null || true
+                            docker system prune -f --filter until=48h 2>/dev/null || true
                         """
-                        harborServices.each { svc ->
-                            def jarName = "${svc.module}-1.0.0.jar"
-                            def imageRef = "${reg}/${proj}/${svc.repo}:${tag}"
-                            sh """
-                                set -e
-                                test -f ${WORKSPACE}/${svc.module}/target/${jarName}
-                                cd ${WORKSPACE}/${svc.module}
-                                rm -rf .jenkins_docker_ctx && mkdir -p .jenkins_docker_ctx/lib
-                                cp -f target/${jarName} .jenkins_docker_ctx/${jarName}
-                                if [ -d target/lib ]; then
-                                  cp -rf target/lib/. .jenkins_docker_ctx/lib/
-                                else
-                                  touch .jenkins_docker_ctx/lib/.keep
-                                fi
-                                cd .jenkins_docker_ctx
-                                docker build -f ${WORKSPACE}/${dockerfile} \\
-                                  --build-arg BASE_IMAGE=${baseImage} \\
-                                  --build-arg JAR_FILE=${jarName} \\
-                                  --build-arg SERVER_PORT=${svc.port} \\
-                                  --build-arg WITH_LIB=${svc.withLib} \\
-                                  -t ${imageRef} .
-                                docker push ${imageRef}
-                                echo ">>> pushed ${imageRef}"
-                            """
+                        if (params.HARBOR_PUSH_PARALLEL && harborServices.size() > 1) {
+                            def branches = [:]
+                            harborServices.each { svc ->
+                                branches[svc.repo] = {
+                                    pushOneHarborImage(workspace, dockerfile, reg, proj, svc, latestTag, buildTag)
+                                }
+                            }
+                            parallel branches
+                        } else {
+                            harborServices.each { svc ->
+                                pushOneHarborImage(workspace, dockerfile, reg, proj, svc, latestTag, buildTag)
+                            }
+                        }
+                        if (params.HARBOR_PRUNE_OLD_TAGS == true) {
+                            def keepN = (params.HARBOR_KEEP_TAG_COUNT ?: '10').trim() as int
+                            catchError(buildResult: 'SUCCESS', stageResult: 'UNSTABLE') {
+                                pruneHarborUatTags(
+                                    this, reg, proj, harborServices*.repo,
+                                    keepN, 'uat-build-', buildTag, latestTag,
+                                )
+                            }
                         }
                     }
-                    echo ">>> Harbor tag for prod promote: SOURCE_TAG=${tag}"
+                    echo ">>> Prod promote: SOURCE_TAG=${env.UAT_HARBOR_LATEST_TAG}"
                 }
             }
         }
@@ -217,45 +367,23 @@ pipeline {
         stage('Deploy Services') {
             steps {
                 script {
+                    def workspace = env.WORKSPACE
                     def services = [
-                            'alien-gateway:gateway-uat',
-                            'alien-job:job-uat',
-                            'alien-lawyer:lawyer-uat',
-                            'alien-second:second-uat',
-                            'alien-store:store-uat',
-                            'alien-dining:dining-uat',
-                            'alien-store-platform:store-platform-uat',
+                            [module: 'alien-gateway',        dir: 'gateway-uat'],
+                            [module: 'alien-job',            dir: 'job-uat'],
+                            [module: 'alien-lawyer',         dir: 'lawyer-uat'],
+                            [module: 'alien-second',         dir: 'second-uat'],
+                            [module: 'alien-store',          dir: 'store-uat'],
+                            [module: 'alien-dining',         dir: 'dining-uat'],
+                            [module: 'alien-store-platform', dir: 'store-platform-uat'],
                     ]
-
-                    for (item in services) {
-                        def parts = item.split(':')
-                        def moduleName = parts[0]
-                        def dirName = parts[1]
-                        def sourceJar = "${env.WORKSPACE}/${moduleName}/target/${moduleName}-1.0.0.jar"
-                        def sourceLib = "${env.WORKSPACE}/${moduleName}/target/lib"
-                        def targetDir = "/app_deploy_uat/${dirName}"
-
-                        sh """
-                            set -e
-                            echo ">>> Deploy module: ${moduleName}"
-                            if [ -f "${sourceJar}" ]; then
-                                mkdir -p "${targetDir}"
-                                if [ -d "${sourceLib}" ]; then
-                                    rm -rf "${targetDir}/lib"
-                                    cp -rf "${sourceLib}" "${targetDir}"
-                                fi
-                                cp -f "${sourceJar}" "${targetDir}/"
-                                if docker ps -a --format '{{.Names}}' | grep -wq "${dirName}"; then
-                                    docker restart "${dirName}"
-                                    echo ">>> [${dirName}] restarted"
-                                else
-                                    echo ">>> [${dirName}] container missing, jar copied only"
-                                fi
-                            else
-                                echo ">>> [${dirName}] jar missing, skip"
-                            fi
-                        """
+                    def branches = [:]
+                    services.each { svc ->
+                        branches[svc.dir] = {
+                            deployOneService(workspace, svc.module, svc.dir)
+                        }
                     }
+                    parallel branches
                 }
             }
         }
@@ -268,8 +396,8 @@ pipeline {
         success {
             script {
                 if (params.PUSH_TO_HARBOR) {
-                    echo ">>> Harbor images tagged: ${env.UAT_HARBOR_IMAGE_TAG}"
-                    echo ">>> Prod promote: SOURCE_TAG=${env.UAT_HARBOR_IMAGE_TAG}"
+                    echo ">>> Harbor latest: ${env.UAT_HARBOR_LATEST_TAG}"
+                    echo ">>> Prod promote: SOURCE_TAG=${env.UAT_HARBOR_LATEST_TAG}"
                 }
             }
         }