Просмотр исходного кода

ci(jenkins): 去参数化,环境从GIT_BRANCH自动推断

- 删除 parameters 块,手动构建不再弹窗(Build Now 直接跑)
- 不再依赖 DEPLOY_ENV 全局变量(避免污染 Jenkins 其他 Job)
- APP_ENV 从 env.GIT_BRANCH 自动推断(Branch Specifier 决定)
- 三台机器 Jenkinsfile 内容完全一致,只靠 Job 的 Branch Specifier 区分环境
- 顺便去掉 Push Images stage(同机构建直接 run,不需要 Registry)
- 增加 disableConcurrentBuilds 避免并发部署冲突

Co-authored-by: Cursor <cursoragent@cursor.com>
天空之城 2 недель назад
Родитель
Сommit
b6d9b6527b
1 измененных файлов с 72 добавлено и 123 удалено
  1. 72 123
      Jenkinsfile

+ 72 - 123
Jenkinsfile

@@ -5,73 +5,54 @@ pipeline {
     buildDiscarder(logRotator(numToKeepStr: '10'))
     timestamps()
     ansiColor('xterm')
+    disableConcurrentBuilds()
   }
 
-  // 构建时手选环境与分支:一份 Jenkinsfile 服务 dev / sit / uat 三个环境。
-  // 物理隔离架构下:每台 Jenkins 上设全局环境变量 DEPLOY_ENV=dev/sit/uat,
-  // Validate Deploy Target stage 会强制 APP_ENV 必须等于 DEPLOY_ENV,
-  // 避免把错误环境的代码部署到错误的机器上。
-  parameters {
-    choice(
-      name: 'APP_ENV',
-      choices: ['dev', 'sit', 'uat'],
-      description: '部署到哪个环境(物理隔离下:必须与本机 DEPLOY_ENV 一致,否则构建失败)'
-    )
-    string(
-      name: 'BRANCH',
-      defaultValue: 'dev',
-      description: '要部署的 Git 分支(一般 dev 环境用 dev 分支,sit 用 sit,uat 用 uat;只有一条主干时统一填 main)'
-    )
-  }
-
-  environment {
-    // ---------- Dockerfile 路径 ----------
-    DOCKERFILE_STORE    = "alien_store/Dockerfile"
-    DOCKERFILE_GATEWAY  = "alien_gateway/Dockerfile"
-    DOCKERFILE_CONTRACT = "alien_contract/Dockerfile"
-
-    // ---------- 镜像仓库(如不推远端 Registry 就留空) ----------
-    REGISTRY      = ""
-    REGISTRY_CRED = "registry-cred-id"
-
-    // ---------- 三环境共用的命名规则:全部以 ${APP_ENV} 区分 ----------
-    IMAGE_TAG               = "${params.APP_ENV}-${env.BUILD_NUMBER}"
-    IMAGE_STORE             = "${REGISTRY ? REGISTRY + '/' : ''}alien_store:${IMAGE_TAG}"
-    IMAGE_GATEWAY           = "${REGISTRY ? REGISTRY + '/' : ''}alien_gateway:${IMAGE_TAG}"
-    IMAGE_CONTRACT          = "${REGISTRY ? REGISTRY + '/' : ''}alien_contract:${IMAGE_TAG}"
-
-    CONTAINER_NAME_STORE    = "alien_store_py-${params.APP_ENV}"
-    CONTAINER_NAME_GATEWAY  = "alien_gateway_py-${params.APP_ENV}"
-    CONTAINER_NAME_CONTRACT = "alien_contract_py-${params.APP_ENV}"
-
-    DOCKER_NET              = "alien_net_${params.APP_ENV}"
-    LOG_ROOT                = "/docker/python-${params.APP_ENV}/logs"
-  }
+  // 设计原则:
+  // - 无 parameters 块,所以"Build Now"直接跑、不弹窗
+  // - 环境(APP_ENV) 与 分支(BRANCH) 都从 Jenkins 自动注入的 GIT_BRANCH 推断
+  //   GIT_BRANCH 来自每个 Job 的 SCM 配置(Branch Specifier *​/dev、*​/sit、*​/uat 之一)
+  // - 因此每台机器上 Job 唯一要配的就是 Branch Specifier,三台机器的 Jenkinsfile 内容完全一致
 
   stages {
 
-    stage('Validate Deploy Target') {
-      // 物理隔离守护:本机的 DEPLOY_ENV 必须与请求的 APP_ENV 一致
-      // 在 dev 服务器误选 sit/uat 时,立即失败,避免把 uat 代码部到 dev 机
-      // 配置方式:在每台机器的 Jenkins "Global properties → Environment variables"
-      //   设置 DEPLOY_ENV 为 dev / sit / uat 之一。
-      //   未配置 DEPLOY_ENV 时(如本地调试),跳过校验。
+    stage('Resolve Environment') {
+      // 从 Jenkins 注入的 GIT_BRANCH 推断 APP_ENV
+      // GIT_BRANCH 形如 "origin/uat" / "uat" / "refs/heads/uat",统一去前缀
       steps {
         script {
-          def expected = env.DEPLOY_ENV
-          if (expected && expected != params.APP_ENV) {
+          def raw = env.GIT_BRANCH ?: env.BRANCH_NAME ?: ''
+          def branch = raw.replaceFirst('^origin/', '').replaceFirst('^refs/heads/', '').trim()
+
+          if (!(branch in ['dev', 'sit', 'uat'])) {
             error """
-              ============ 部署环境与本机定位不匹配 ============
-              本机被定位为: DEPLOY_ENV=${expected}
-              你请求部署的: APP_ENV=${params.APP_ENV}
-              本次构建已终止,避免错误地把 ${params.APP_ENV} 部署到 ${expected} 机器上。
-              如果你确实想改本机定位,请联系运维修改 Jenkins 全局环境变量 DEPLOY_ENV。
-              =================================================
+              ============ 无法识别当前部署环境 ============
+              Jenkins 注入的 GIT_BRANCH = '${env.GIT_BRANCH}'
+              解析后的分支名             = '${branch}'
+              期望分支必须是 dev / sit / uat 之一。
+
+              请检查 Job 配置中的 Branch Specifier 是否正确:
+                DEV 服务器  → */dev
+                SIT 服务器  → */sit
+                UAT 服务器  → */uat
+              =============================================
             """.stripIndent()
           }
-          if (!expected) {
-            echo "提示: 本机 Jenkins 未设置 DEPLOY_ENV,跳过部署目标校验。"
-          }
+
+          // 把推断结果写回 env,后续 stage 用 env.APP_ENV 而不是 params.APP_ENV
+          env.APP_ENV = branch
+          env.BRANCH  = branch
+
+          // 派生命名规则(容器名/网络/日志/镜像 TAG 全部以 APP_ENV 区分)
+          env.IMAGE_TAG               = "${env.APP_ENV}-${env.BUILD_NUMBER}"
+          env.IMAGE_STORE             = "alien_store:${env.IMAGE_TAG}"
+          env.IMAGE_GATEWAY           = "alien_gateway:${env.IMAGE_TAG}"
+          env.IMAGE_CONTRACT          = "alien_contract:${env.IMAGE_TAG}"
+          env.CONTAINER_NAME_STORE    = "alien_store_py-${env.APP_ENV}"
+          env.CONTAINER_NAME_GATEWAY  = "alien_gateway_py-${env.APP_ENV}"
+          env.CONTAINER_NAME_CONTRACT = "alien_contract_py-${env.APP_ENV}"
+          env.DOCKER_NET              = "alien_net_${env.APP_ENV}"
+          env.LOG_ROOT                = "/docker/python-${env.APP_ENV}/logs"
         }
       }
     }
@@ -79,33 +60,20 @@ pipeline {
     stage('Show Build Info') {
       steps {
         echo "============================================================"
-        echo " 部署环境  : ${params.APP_ENV}"
-        echo " 部署分支  : ${params.BRANCH}"
-        echo " 镜像 TAG  : ${IMAGE_TAG}"
-        echo " 网络  : ${DOCKER_NET}"
-        echo " 日志根目录: ${LOG_ROOT}"
+        echo " 部署环境  : ${env.APP_ENV}    (来自 GIT_BRANCH=${env.GIT_BRANCH})"
+        echo " 部署分支  : ${env.BRANCH}"
+        echo " 镜像 TAG  : ${env.IMAGE_TAG}"
+        echo " 容器网络  : ${env.DOCKER_NET}"
+        echo " 日志根目录: ${env.LOG_ROOT}"
         echo "============================================================"
       }
     }
 
-    // 注意:使用 "Pipeline script from SCM" 模式时 Jenkins 已自动 checkout
-    // 但因为我们参数化了 BRANCH,这里显式 checkout 指定分支
-    stage('Checkout') {
-      steps {
-        checkout([
-          $class: 'GitSCM',
-          branches: [[name: "*/${params.BRANCH}"]],
-          userRemoteConfigs: scm.userRemoteConfigs
-        ])
-      }
-    }
-
     stage('Load Env Port Mapping') {
       // 从 .env.${APP_ENV} 解析 GATEWAY_PORT,作为 docker -p 宿主端口映射用
-      // (容器内监听端口同样靠 Dockerfile 默认 ARG/ENV 与 .env 配合)
       steps {
         script {
-          def envFile = ".env.${params.APP_ENV}"
+          def envFile = ".env.${env.APP_ENV}"
           if (!fileExists(envFile)) {
             error "缺少环境配置文件: ${envFile}(仓库里应该有这份;请检查 .gitignore 是否误屏蔽)"
           }
@@ -125,29 +93,10 @@ pipeline {
     stage('Build Images') {
       steps {
         script {
-          def buildArgs = "--build-arg APP_ENV=${params.APP_ENV}"
-          if (env.REGISTRY?.trim()) {
-            withDockerRegistry(credentialsId: env.REGISTRY_CRED, url: "") {
-              sh "docker build ${buildArgs} -f ${DOCKERFILE_STORE}    -t ${IMAGE_STORE}    ."
-              sh "docker build ${buildArgs} -f ${DOCKERFILE_GATEWAY}  -t ${IMAGE_GATEWAY}  ."
-              sh "docker build ${buildArgs} -f ${DOCKERFILE_CONTRACT} -t ${IMAGE_CONTRACT} ."
-            }
-          } else {
-            sh "docker build ${buildArgs} -f ${DOCKERFILE_STORE}    -t ${IMAGE_STORE}    ."
-            sh "docker build ${buildArgs} -f ${DOCKERFILE_GATEWAY}  -t ${IMAGE_GATEWAY}  ."
-            sh "docker build ${buildArgs} -f ${DOCKERFILE_CONTRACT} -t ${IMAGE_CONTRACT} ."
-          }
-        }
-      }
-    }
-
-    stage('Push Images') {
-      when { expression { return env.REGISTRY?.trim() as boolean } }
-      steps {
-        withDockerRegistry(credentialsId: env.REGISTRY_CRED, url: "") {
-          sh "docker push ${IMAGE_STORE}"
-          sh "docker push ${IMAGE_GATEWAY}"
-          sh "docker push ${IMAGE_CONTRACT}"
+          def buildArgs = "--build-arg APP_ENV=${env.APP_ENV}"
+          sh "docker build ${buildArgs} -f alien_store/Dockerfile    -t ${env.IMAGE_STORE}    ."
+          sh "docker build ${buildArgs} -f alien_gateway/Dockerfile  -t ${env.IMAGE_GATEWAY}  ."
+          sh "docker build ${buildArgs} -f alien_contract/Dockerfile -t ${env.IMAGE_CONTRACT} ."
         }
       }
     }
@@ -155,42 +104,42 @@ pipeline {
     stage('Deploy') {
       steps {
         script {
-          echo ">>> [${params.APP_ENV}] 部署镜像: ${IMAGE_STORE} / ${IMAGE_GATEWAY} / ${IMAGE_CONTRACT}"
+          echo ">>> [${env.APP_ENV}] 部署镜像: ${env.IMAGE_STORE} / ${env.IMAGE_GATEWAY} / ${env.IMAGE_CONTRACT}"
           sh """
             set -e
 
-            docker network create ${DOCKER_NET} 2>/dev/null || true
-            mkdir -p ${LOG_ROOT}/store ${LOG_ROOT}/gateway ${LOG_ROOT}/contract
+            docker network create ${env.DOCKER_NET} 2>/dev/null || true
+            mkdir -p ${env.LOG_ROOT}/store ${env.LOG_ROOT}/gateway ${env.LOG_ROOT}/contract
 
-            docker rm -f ${CONTAINER_NAME_STORE} ${CONTAINER_NAME_GATEWAY} ${CONTAINER_NAME_CONTRACT} 2>/dev/null || true
+            docker rm -f ${env.CONTAINER_NAME_STORE} ${env.CONTAINER_NAME_GATEWAY} ${env.CONTAINER_NAME_CONTRACT} 2>/dev/null || true
 
             # 1) 下游:store
-            #    APP_ENV=${params.APP_ENV} 让 config.py 加载镜像内的 .env.${params.APP_ENV}
-            docker run -d --name ${CONTAINER_NAME_STORE} \\
-              --network ${DOCKER_NET} \\
-              -e APP_ENV=${params.APP_ENV} \\
-              -v ${LOG_ROOT}/store:/app/common/logs/alien_store \\
+            #    APP_ENV=${env.APP_ENV} 让 config.py 加载镜像内的 .env.${env.APP_ENV}
+            docker run -d --name ${env.CONTAINER_NAME_STORE} \\
+              --network ${env.DOCKER_NET} \\
+              -e APP_ENV=${env.APP_ENV} \\
+              -v ${env.LOG_ROOT}/store:/app/common/logs/alien_store \\
               --restart unless-stopped \\
-              ${IMAGE_STORE}
+              ${env.IMAGE_STORE}
 
             # 2) 下游:contract
-            docker run -d --name ${CONTAINER_NAME_CONTRACT} \\
-              --network ${DOCKER_NET} \\
-              -e APP_ENV=${params.APP_ENV} \\
-              -v ${LOG_ROOT}/contract:/app/common/logs/alien_contract \\
+            docker run -d --name ${env.CONTAINER_NAME_CONTRACT} \\
+              --network ${env.DOCKER_NET} \\
+              -e APP_ENV=${env.APP_ENV} \\
+              -v ${env.LOG_ROOT}/contract:/app/common/logs/alien_contract \\
               --restart unless-stopped \\
-              ${IMAGE_CONTRACT}
+              ${env.IMAGE_CONTRACT}
 
             # 3) 网关:gateway(唯一对外端口,并用 -e 把下游地址覆盖为容器名)
-            docker run -d --name ${CONTAINER_NAME_GATEWAY} \\
-              --network ${DOCKER_NET} \\
+            docker run -d --name ${env.CONTAINER_NAME_GATEWAY} \\
+              --network ${env.DOCKER_NET} \\
               -p ${env.GATEWAY_PORT}:${env.GATEWAY_PORT} \\
-              -e APP_ENV=${params.APP_ENV} \\
-              -e STORE_BASE_URL=http://${CONTAINER_NAME_STORE}:8001 \\
-              -e CONTRACT_BASE_URL=http://${CONTAINER_NAME_CONTRACT}:8002 \\
-              -v ${LOG_ROOT}/gateway:/app/common/logs/alien_gateway \\
+              -e APP_ENV=${env.APP_ENV} \\
+              -e STORE_BASE_URL=http://${env.CONTAINER_NAME_STORE}:8001 \\
+              -e CONTRACT_BASE_URL=http://${env.CONTAINER_NAME_CONTRACT}:8002 \\
+              -v ${env.LOG_ROOT}/gateway:/app/common/logs/alien_gateway \\
               --restart unless-stopped \\
-              ${IMAGE_GATEWAY}
+              ${env.IMAGE_GATEWAY}
           """
         }
       }
@@ -214,10 +163,10 @@ pipeline {
 
   post {
     success {
-      echo "[${params.APP_ENV}] 环境部署成功!gateway: http://<host>:${env.GATEWAY_PORT}"
+      echo "[${env.APP_ENV}] 环境部署成功!gateway: http://<host>:${env.GATEWAY_PORT}"
     }
     failure {
-      echo "[${params.APP_ENV}] 环境部署失败,请检查上面日志。"
+      echo "[${env.APP_ENV}] 环境部署失败,请检查上面日志。"
     }
     always {
       cleanWs()