/** * OfferPie Web 前端蓝绿部署流水线 * * 架构:Jenkins 本地编译 → scp 镜像到目标机 → SSH 远程蓝绿切换 * 目标机目录:/opt/offerpie/web/ * * 内层 Nginx 职责: * - 蓝绿切换(前端静态文件 serve) * - /api/ 代理到 Java 后端(10202) * - /ai-api/ 代理到 Python AI(10502),rewrite 去掉前缀 */ pipeline { agent any parameters { choice(name: 'BRANCH', choices: ['master', 'pre', 'dev', 'test'], description: '选择要部署的分支') } environment { // 目标服务器配置 DEPLOY_HOST = '8.138.180.255' DEPLOY_PORT = '22' DEPLOY_USER = 'root' DEPLOY_PASS = 'sh.0807.' // 项目配置 IMAGE_NAME = 'offerpie-web' IMAGE_TAG = 'latest' CONTAINER_PREFIX = 'offerpie-web' REMOTE_DIR = '/opt/offerpie/web' HEALTH_URL = 'http://localhost:80/' // SSH 命令前缀 SSH_CMD = "sshpass -p '${DEPLOY_PASS}' ssh -o StrictHostKeyChecking=no -p ${DEPLOY_PORT} ${DEPLOY_USER}@${DEPLOY_HOST}" SCP_CMD = "sshpass -p '${DEPLOY_PASS}' scp -o StrictHostKeyChecking=no -P ${DEPLOY_PORT}" } stages { stage('环境检查') { steps { sh 'sshpass -V' } } stage('拉取代码') { steps { echo "拉取 ${params.BRANCH} 分支代码" git branch: "${params.BRANCH}", credentialsId: 'gitea-fab089c1-b55d-4b58-9fad', url: 'http://git.jianshixingqiu.com/offerpai/offerpai_web.git' } } stage('本地编译') { steps { echo "开始构建镜像" sh "docker build -t ${IMAGE_NAME}:${IMAGE_TAG} ." echo "导出镜像" sh "docker save -o ${IMAGE_NAME}.tar ${IMAGE_NAME}:${IMAGE_TAG}" } } stage('文件传输') { steps { echo "传输文件到目标服务器" sh "${SSH_CMD} 'mkdir -p ${REMOTE_DIR}'" sh "${SCP_CMD} ${IMAGE_NAME}.tar ${DEPLOY_USER}@${DEPLOY_HOST}:${REMOTE_DIR}/" sh "${SCP_CMD} proxy_nginx.conf ${DEPLOY_USER}@${DEPLOY_HOST}:${REMOTE_DIR}/proxy_nginx.conf" sh "${SCP_CMD} docker-compose.yml ${DEPLOY_USER}@${DEPLOY_HOST}:${REMOTE_DIR}/docker-compose.yml" } } stage('加载镜像') { steps { sh "${SSH_CMD} 'docker load < ${REMOTE_DIR}/${IMAGE_NAME}.tar'" } } stage('检测部署目标') { steps { script { def blueRunning = sh(script: "${SSH_CMD} 'docker ps -q -f name=${CONTAINER_PREFIX}-blue'", returnStdout: true).trim() def greenRunning = sh(script: "${SSH_CMD} 'docker ps -q -f name=${CONTAINER_PREFIX}-green'", returnStdout: true).trim() env.DEPLOY_TARGET = '' if (blueRunning && !greenRunning) { env.DEPLOY_TARGET = 'green' } if (greenRunning && !blueRunning) { env.DEPLOY_TARGET = 'blue' } if (!env.DEPLOY_TARGET) { echo "当前环境未部署服务或状态异常,默认部署 blue" env.DEPLOY_TARGET = 'blue' } env.OTHER_TARGET = (env.DEPLOY_TARGET == 'blue') ? 'green' : 'blue' echo "当前激活: ${env.OTHER_TARGET},即将部署: ${env.DEPLOY_TARGET}" } } } stage('启动新版本') { steps { script { def existingContainer = sh(script: "${SSH_CMD} 'docker ps -aq -f name=${CONTAINER_PREFIX}-${env.DEPLOY_TARGET}'", returnStdout: true).trim() if (existingContainer) { sh "${SSH_CMD} 'docker rm -f ${CONTAINER_PREFIX}-${env.DEPLOY_TARGET}'" } sh "${SSH_CMD} 'cd ${REMOTE_DIR} && docker compose up -d ${env.DEPLOY_TARGET}'" sh 'sleep 5' } } } stage('检查Nginx') { steps { script { def nginxExists = sh(script: "${SSH_CMD} 'docker ps -aq -f name=${CONTAINER_PREFIX}-nginx'", returnStdout: true).trim() if (!nginxExists) { echo "首次部署,初始化 Nginx 容器" sh "${SSH_CMD} 'cd ${REMOTE_DIR} && docker compose up -d nginx'" sh 'sleep 3' } else { def nginxRunning = sh(script: "${SSH_CMD} 'docker ps -q -f name=${CONTAINER_PREFIX}-nginx'", returnStdout: true).trim() if (!nginxRunning) { echo "Nginx 容器已停止,重新启动" sh "${SSH_CMD} 'docker start ${CONTAINER_PREFIX}-nginx'" sh 'sleep 2' } echo "Nginx 容器已存在且运行中" } } } } stage('健康检查') { steps { script { def maxRetries = 3 def retryCount = 0 def healthy = false while (retryCount < maxRetries && !healthy) { try { sh "${SSH_CMD} 'docker exec ${CONTAINER_PREFIX}-${env.DEPLOY_TARGET} curl -f ${HEALTH_URL}'" healthy = true } catch (Exception e) { retryCount++ if (retryCount < maxRetries) { echo "健康检查失败,5秒后重试 (${retryCount}/${maxRetries})" sleep 5 } } } if (!healthy) { error "健康检查失败,部署中止" } echo "✅ ${CONTAINER_PREFIX}-${env.DEPLOY_TARGET} 健康检查通过" } } } stage('切换流量') { steps { script { sh "${SSH_CMD} \"sed -i 's/proxy_pass http:\\/\\/\\(blue\\|green\\);/proxy_pass http:\\/\\/${env.DEPLOY_TARGET};/' ${REMOTE_DIR}/proxy_nginx.conf\"" sh "${SSH_CMD} 'docker exec ${CONTAINER_PREFIX}-nginx nginx -s reload'" echo "✅ 流量已切换到 ${env.DEPLOY_TARGET}" } } } stage('删除旧版本') { steps { script { def otherExists = sh(script: "${SSH_CMD} 'docker ps -aq -f name=${CONTAINER_PREFIX}-${env.OTHER_TARGET}'", returnStdout: true).trim() if (otherExists) { sh "${SSH_CMD} 'docker rm -f ${CONTAINER_PREFIX}-${env.OTHER_TARGET}'" echo "旧版本 ${env.OTHER_TARGET} 已删除" } } } } stage('清理') { steps { sh "${SSH_CMD} 'rm -f ${REMOTE_DIR}/${IMAGE_NAME}.tar'" sh "rm -f ${IMAGE_NAME}.tar" } } } post { success { echo "✅ 蓝绿部署成功!当前运行: ${env.DEPLOY_TARGET}" } failure { echo '❌ 部署失败,请检查日志' sh "rm -f ${IMAGE_NAME}.tar || true" } } }