Files
offerpai_web/Jenkinsfile
T

208 lines
7.9 KiB
Groovy
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* OfferPie Web 前端蓝绿部署流水线
*
* 架构:Jenkins 本地编译 → scp 镜像到目标机 → SSH 远程蓝绿切换
* 目标机目录:/opt/offerpie/web/
*
* 内层 Nginx 职责:
* - 蓝绿切换(前端静态文件 serve)
* - /api/ 代理到 Java 后端(10202
* - /ai-api/ 代理到 Python AI10502),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}/"
// 传输 proxy_nginx.conf(用 cat 写入保持 inode,避免 bind mount 失效)
sh "${SCP_CMD} proxy_nginx.conf ${DEPLOY_USER}@${DEPLOY_HOST}:${REMOTE_DIR}/proxy_nginx.conf.tmp"
sh "${SSH_CMD} 'cat ${REMOTE_DIR}/proxy_nginx.conf.tmp > ${REMOTE_DIR}/proxy_nginx.conf && rm -f ${REMOTE_DIR}/proxy_nginx.conf.tmp'"
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\\):80;/proxy_pass http:\\/\\/${env.DEPLOY_TARGET}:80;/' ${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"
}
}
}