pipeline { agent any options { buildDiscarder(logRotator(numToKeepStr: '5')) timestamps() } parameters { string(name: 'BRANCH', defaultValue: 'uat', description: '要部署到UAT的分支') } environment { // Dockerfile 路径(相对于代码根目录,即 Jenkins workspace) DOCKERFILE_STORE = "alien_store/Dockerfile" DOCKERFILE_GATEWAY = "alien_gateway/Dockerfile" DOCKERFILE_CONTRACT = "alien_contract/Dockerfile" // 如需推送远端 Registry,填写地址,否则留空 REGISTRY = "" REGISTRY_CRED = "registry-cred-id" // 镜像标签统一使用 uat IMAGE_STORE = "${REGISTRY ? REGISTRY + '/alien_store:uat-' + env.BUILD_NUMBER : 'alien_store:uat'}" IMAGE_GATEWAY = "${REGISTRY ? REGISTRY + '/alien_gateway:uat-' + env.BUILD_NUMBER : 'alien_gateway:uat'}" IMAGE_CONTRACT = "${REGISTRY ? REGISTRY + '/alien_contract:uat-' + env.BUILD_NUMBER : 'alien_contract:uat'}" // 容器名(store 不要再用 esign-uat,会和真正的 e签宝容器撞名) CONTAINER_NAME_STORE = "alien_store_py-uat" CONTAINER_NAME_GATEWAY = "alien_gateway_py-uat" CONTAINER_NAME_CONTRACT = "alien_contract_py-uat" // 容器内端口(必须与各服务 Dockerfile 中 uvicorn 监听端口一致) PORT_STORE = "8001" PORT_GATEWAY = "43333" PORT_CONTRACT = "8002" // 使用独立的 UAT 网络 DOCKER_NET = "alien_net_uat" // UAT 日志目录 LOG_ROOT = "/docker/python-uat/logs" } stages { // 注意:使用 "Pipeline script from SCM" 模式时, // Jenkins 会自动 checkout 代码到 workspace,不需要在 Jenkinsfile 里再写 Checkout stage。 stage('Build Images') { steps { script { // 直接在当前 workspace(Jenkins 已 checkout 的代码根)里构建 if (env.REGISTRY?.trim()) { withDockerRegistry(credentialsId: env.REGISTRY_CRED, url: "") { sh "docker build -f ${DOCKERFILE_STORE} -t ${IMAGE_STORE} ." sh "docker build -f ${DOCKERFILE_GATEWAY} -t ${IMAGE_GATEWAY} ." sh "docker build -f ${DOCKERFILE_CONTRACT} -t ${IMAGE_CONTRACT} ." } } else { sh "docker build -f ${DOCKERFILE_STORE} -t ${IMAGE_STORE} ." sh "docker build -f ${DOCKERFILE_GATEWAY} -t ${IMAGE_GATEWAY} ." sh "docker build -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}" } } } stage('Deploy') { steps { script { echo ">>> UAT 部署镜像: ${IMAGE_STORE} / ${IMAGE_GATEWAY} / ${IMAGE_CONTRACT}" sh """ # 创建 UAT 专用网络 docker network create ${DOCKER_NET} 2>/dev/null || true # 创建日志目录 mkdir -p ${LOG_ROOT}/store ${LOG_ROOT}/gateway ${LOG_ROOT}/contract # 把当前 workspace 的 .env.uat 复制到宿主机持久目录,供容器挂载使用 # 注意:先把可能被 docker 自动创建为目录的同名路径清理掉,确保它是一个普通文件 mkdir -p /docker/python-uat rm -rf /docker/python-uat/.env.uat cp .env.uat /docker/python-uat/.env.uat chmod 644 /docker/python-uat/.env.uat # 停止旧容器(不会误删 java 的 gateway-uat / store-uat / 真正的 esign-uat 等) docker rm -f ${CONTAINER_NAME_STORE} ${CONTAINER_NAME_GATEWAY} ${CONTAINER_NAME_CONTRACT} 2>/dev/null || true # 1) 先启动下游:store docker run -d --name ${CONTAINER_NAME_STORE} \\ --network ${DOCKER_NET} \\ -e APP_ENV=uat \\ -v /docker/python-uat/.env.uat:/app/.env.uat:ro \\ -v ${LOG_ROOT}/store:/app/common/logs/alien_store \\ --restart unless-stopped \\ ${IMAGE_STORE} # 2) 再启动下游:contract docker run -d --name ${CONTAINER_NAME_CONTRACT} \\ --network ${DOCKER_NET} \\ -e APP_ENV=uat \\ -v /docker/python-uat/.env.uat:/app/.env.uat:ro \\ -v ${LOG_ROOT}/contract:/app/common/logs/alien_contract \\ --restart unless-stopped \\ ${IMAGE_CONTRACT} # 3) 最后启动网关:gateway(依赖上面两个下游;唯一对外暴露的入口) # STORE_BASE_URL / CONTRACT_BASE_URL 通过 -e 覆盖 .env.uat 中的本地默认值 docker run -d --name ${CONTAINER_NAME_GATEWAY} \\ --network ${DOCKER_NET} \\ -p ${PORT_GATEWAY}:${PORT_GATEWAY} \\ -e APP_ENV=uat \\ -e STORE_BASE_URL=http://${CONTAINER_NAME_STORE}:${PORT_STORE} \\ -e CONTRACT_BASE_URL=http://${CONTAINER_NAME_CONTRACT}:${PORT_CONTRACT} \\ -v /docker/python-uat/.env.uat:/app/.env.uat:ro \\ -v ${LOG_ROOT}/gateway:/app/common/logs/alien_gateway \\ --restart unless-stopped \\ ${IMAGE_GATEWAY} """ } } } } post { success { echo "UAT 环境部署成功!" } failure { echo "UAT 环境部署失败,请检查日志。" } always { cleanWs() } } }