|
|
@@ -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()
|