From f02b7778c4ce0fdd34f639c5ccaf88b3ff5b7ef2 Mon Sep 17 00:00:00 2001 From: zk Date: Wed, 27 May 2026 14:04:25 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E6=89=93=E5=8C=85=E6=B5=81?= =?UTF-8?q?=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client-api/Dockerfile | 2 - client-api/Jenkinsfile | 156 +++++++++++++++++----------------- docker-compose.client-api.yml | 15 +--- 3 files changed, 83 insertions(+), 90 deletions(-) diff --git a/client-api/Dockerfile b/client-api/Dockerfile index 8f172b4..66906c5 100644 --- a/client-api/Dockerfile +++ b/client-api/Dockerfile @@ -1,5 +1,3 @@ -# syntax=docker/dockerfile:1 - # ==================== 第一阶段:编译 ==================== FROM maven:3.8-openjdk-17 AS builder WORKDIR /build diff --git a/client-api/Jenkinsfile b/client-api/Jenkinsfile index 684eae5..3fa9d4f 100644 --- a/client-api/Jenkinsfile +++ b/client-api/Jenkinsfile @@ -1,11 +1,8 @@ /** * OfferPie Backend Client-API 蓝绿部署流水线 * - * 工作目录说明: - * - Jenkins 会自动为每个项目创建独立的工作空间:/var/jenkins_home/workspace/<项目名>/ - * - docker-compose 文件在项目根目录,通过 -f 指定文件名 - * - Dockerfile 在 client-api/ 子目录下,build context 为项目根目录(因为需要编译 common 和 manager 模块) - * - nginx.conf 在 client-api/ 下,首次部署时 docker cp 进 nginx 容器 + * 架构:Jenkins 本地编译 → scp 镜像到目标机 → SSH 远程蓝绿切换 + * 目标机目录:/opt/offerpie/client-api/ */ pipeline { agent any @@ -15,15 +12,28 @@ pipeline { } environment { - COMPOSE_FILE = 'docker-compose.client-api.yml' // docker-compose 文件名 - CONTAINER_PREFIX = 'offerpie-backend-client' // 容器名前缀,拼接 -blue / -green / -nginx - HEALTH_URL = 'http://localhost:8080/api/public/actuator/health' // 应用健康检查地址 + // 目标服务器配置 + DEPLOY_HOST = '8.138.180.255' + DEPLOY_PORT = '22' + DEPLOY_USER = 'root' + DEPLOY_PASS = 'sh.0807.' + + // 项目配置 + IMAGE_NAME = 'offerpie-backend-client' + IMAGE_TAG = 'latest' + CONTAINER_PREFIX = 'offerpie-backend-client' + REMOTE_DIR = '/opt/offerpie/client-api' + HEALTH_URL = 'http://localhost:8080/api/public/actuator/health' + + // 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('开始提示') { + stage('环境检查') { steps { - echo "OfferPie Backend Client-API 开始构建" + sh 'which sshpass || (apt-get update && apt-get install -y sshpass)' } } @@ -36,18 +46,40 @@ pipeline { } } - /** - * 检测部署目标 - * - 检查 blue 和 green 容器的运行状态 - * - blue 在运行 → 部署 green;green 在运行 → 部署 blue - * - 都不在或都在 → 默认部署 blue - */ + stage('本地编译') { + steps { + echo "开始构建镜像" + sh "docker build -f client-api/Dockerfile -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}/" + // 传输 nginx.conf + sh "${SCP_CMD} client-api/nginx.conf ${DEPLOY_USER}@${DEPLOY_HOST}:${REMOTE_DIR}/nginx.conf" + // 传输 docker-compose.yml + sh "${SCP_CMD} docker-compose.client-api.yml ${DEPLOY_USER}@${DEPLOY_HOST}:${REMOTE_DIR}/docker-compose.yml" + } + } + + stage('加载镜像') { + steps { + sh "${SSH_CMD} 'docker load < ${REMOTE_DIR}/${IMAGE_NAME}.tar'" + } + } + stage('检测部署目标') { steps { - echo "检查当前容器状态" script { - def blueRunning = sh(script: "docker ps -q -f name=${CONTAINER_PREFIX}-blue", returnStdout: true).trim() - def greenRunning = sh(script: "docker ps -q -f name=${CONTAINER_PREFIX}-green", returnStdout: true).trim() + 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 = '' @@ -68,47 +100,34 @@ pipeline { } } - /** - * 构建新版本 - * - 先清理目标颜色的旧容器(如果残留) - * - docker-compose build 编译新镜像 - */ - stage('构建新版本') { + stage('启动新版本') { steps { script { - def existingContainer = sh(script: "docker ps -aq -f name=${CONTAINER_PREFIX}-${env.DEPLOY_TARGET}", returnStdout: true).trim() + // 清理目标色残留容器 + def existingContainer = sh(script: "${SSH_CMD} 'docker ps -aq -f name=${CONTAINER_PREFIX}-${env.DEPLOY_TARGET}'", returnStdout: true).trim() if (existingContainer) { - echo "清理已存在的容器: ${CONTAINER_PREFIX}-${env.DEPLOY_TARGET}" - sh "docker rm -f ${CONTAINER_PREFIX}-${env.DEPLOY_TARGET}" + sh "${SSH_CMD} 'docker rm -f ${CONTAINER_PREFIX}-${env.DEPLOY_TARGET}'" } - sh "docker-compose -f ${COMPOSE_FILE} build ${env.DEPLOY_TARGET}" + sh "${SSH_CMD} 'cd ${REMOTE_DIR} && docker compose up -d ${env.DEPLOY_TARGET}'" + sh 'sleep 35' } } } - /** - * 检查 Nginx - * - 检查 nginx 容器是否存在 - * - 不存在则启动并复制配置文件(首次部署) - * - 存在则确保容器运行中 - */ stage('检查Nginx') { steps { script { - def nginxExists = sh(script: "docker ps -aq -f name=${CONTAINER_PREFIX}-nginx", returnStdout: true).trim() + def nginxExists = sh(script: "${SSH_CMD} 'docker ps -aq -f name=${CONTAINER_PREFIX}-nginx'", returnStdout: true).trim() if (!nginxExists) { echo "首次部署,初始化 Nginx 容器" - sh "docker-compose -f ${COMPOSE_FILE} up -d nginx" - sh "sleep 3" - // 复制配置文件到容器内 - sh "docker cp client-api/nginx.conf ${CONTAINER_PREFIX}-nginx:/etc/nginx/nginx.conf" - sh "docker exec ${CONTAINER_PREFIX}-nginx nginx -s reload" + sh "${SSH_CMD} 'cd ${REMOTE_DIR} && docker compose up -d nginx'" + sh 'sleep 3' } else { - def nginxRunning = sh(script: "docker ps -q -f name=${CONTAINER_PREFIX}-nginx", returnStdout: true).trim() + def nginxRunning = sh(script: "${SSH_CMD} 'docker ps -q -f name=${CONTAINER_PREFIX}-nginx'", returnStdout: true).trim() if (!nginxRunning) { echo "Nginx 容器已停止,重新启动" - sh "docker start ${CONTAINER_PREFIX}-nginx" - sh "sleep 2" + sh "${SSH_CMD} 'docker start ${CONTAINER_PREFIX}-nginx'" + sh 'sleep 2' } echo "Nginx 容器已存在且运行中" } @@ -116,23 +135,6 @@ pipeline { } } - stage('启动新版本') { - steps { - script { - sh "docker-compose -f ${COMPOSE_FILE} up -d ${env.DEPLOY_TARGET}" - // 等待 Spring Boot 应用完全启动 - sh 'sleep 35' - } - } - } - - /** - * 健康检查 - * - 进入容器执行 curl 检测应用是否正常响应 - * - curl -f 参数:HTTP 错误码(404、500等)时返回失败 - * - 最多重试 3 次,每次间隔 5 秒 - * - 全部失败则终止部署流程 - */ stage('健康检查') { steps { script { @@ -142,7 +144,7 @@ pipeline { while (retryCount < maxRetries && !healthy) { try { - sh "docker exec ${CONTAINER_PREFIX}-${env.DEPLOY_TARGET} curl -f ${HEALTH_URL}" + sh "${SSH_CMD} 'docker exec ${CONTAINER_PREFIX}-${env.DEPLOY_TARGET} curl -f ${HEALTH_URL}'" healthy = true } catch (Exception e) { retryCount++ @@ -161,38 +163,36 @@ pipeline { } } - /** - * 切换流量 - * - 修改 nginx 配置中的 proxy_pass 指向新版本容器 - * - nginx 不挂载宿主机文件,直接 sed -i 修改容器内配置 - * - reload 使新配置生效,实现零停机切换 - */ stage('切换流量') { steps { script { - sh "docker exec ${CONTAINER_PREFIX}-nginx sed -i 's/proxy_pass http:\\/\\/\\(blue\\|green\\):8080;/proxy_pass http:\\/\\/${env.DEPLOY_TARGET}:8080;/' /etc/nginx/nginx.conf" - sh "docker exec ${CONTAINER_PREFIX}-nginx nginx -s reload" + sh "${SSH_CMD} \"sed -i 's/proxy_pass http:\\/\\/\\(blue\\|green\\):8080;/proxy_pass http:\\/\\/${env.DEPLOY_TARGET}:8080;/' ${REMOTE_DIR}/nginx.conf\"" + sh "${SSH_CMD} 'docker exec ${CONTAINER_PREFIX}-nginx nginx -s reload'" echo "✅ 流量已切换到 ${env.DEPLOY_TARGET}" } } } - /** - * 停止并删除旧版本 - * - 不能用 docker-compose down,会删除所有服务(包括 nginx) - * - 用 docker rm -f 只删除指定的旧颜色容器 - */ stage('删除旧版本') { steps { script { - def otherExists = sh(script: "docker ps -aq -f name=${CONTAINER_PREFIX}-${env.OTHER_TARGET}", returnStdout: true).trim() + def otherExists = sh(script: "${SSH_CMD} 'docker ps -aq -f name=${CONTAINER_PREFIX}-${env.OTHER_TARGET}'", returnStdout: true).trim() if (otherExists) { - sh "docker rm -f ${CONTAINER_PREFIX}-${env.OTHER_TARGET}" + 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 { @@ -201,6 +201,8 @@ pipeline { } failure { echo '❌ 部署失败,请检查日志' + // 失败时清理本地临时文件 + sh "rm -f ${IMAGE_NAME}.tar || true" } } } diff --git a/docker-compose.client-api.yml b/docker-compose.client-api.yml index 5fcd509..6cf69d3 100644 --- a/docker-compose.client-api.yml +++ b/docker-compose.client-api.yml @@ -5,9 +5,8 @@ services: restart: unless-stopped ports: - "10202:80" - depends_on: - - blue - - green + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf:ro healthcheck: test: ["CMD", "wget", "--spider", "-q", "http://localhost/health"] interval: 30s @@ -20,9 +19,7 @@ services: cpus: '1' blue: - build: - context: . - dockerfile: client-api/Dockerfile + image: offerpie-backend-client:latest container_name: offerpie-backend-client-blue restart: unless-stopped expose: @@ -46,9 +43,7 @@ services: cpus: '2' green: - build: - context: . - dockerfile: client-api/Dockerfile + image: offerpie-backend-client:latest container_name: offerpie-backend-client-green restart: unless-stopped expose: @@ -70,5 +65,3 @@ services: limits: memory: 2G cpus: '2' - -