Files
offerpai_python_ai/Jenkinsfile
T
2026-05-21 18:35:25 +08:00

205 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 Python AI 蓝绿部署流水线
*
* 工作目录说明:
* - Jenkins 会自动为每个项目创建独立的工作空间:/var/jenkins_home/workspace/<项目名>/
* - docker-compose.yml 和 Dockerfile 都在项目根目录,直接执行即可
* - nginx.conf 在项目根目录,首次部署时 docker cp 进 nginx 容器
*/
pipeline {
agent any
parameters {
choice(name: 'BRANCH', choices: ['master', 'pre', 'dev', 'test'], description: '选择要部署的分支')
}
environment {
CONTAINER_PREFIX = 'offerpie-ai' // 容器名前缀,拼接 -blue / -green / -nginx
HEALTH_URL = 'http://localhost:8000/health/' // 应用健康检查地址
}
stages {
stage('开始提示') {
steps {
echo "OfferPie Python AI 开始构建"
}
}
stage('拉取代码') {
steps {
echo "拉取 ${params.BRANCH} 分支代码"
git branch: "${params.BRANCH}",
credentialsId: 'ef5fffc1-9b35-403d-9ca6-e1b73eb0e45a',
url: 'https://codeup.aliyun.com/5f0ed3b9769820a3e817dee2/offerpie/offerpie_python_ai.git'
}
}
/**
* 检测部署目标
* - 检查 blue 和 green 容器的运行状态
* - blue 在运行 → 部署 greengreen 在运行 → 部署 blue
* - 都不在或都在 → 默认部署 blue
*/
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()
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}"
}
}
}
/**
* 构建新版本
* - 先清理目标颜色的旧容器(如果残留)
* - docker-compose build 编译新镜像
*/
stage('构建新版本') {
steps {
script {
def existingContainer = sh(script: "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 "docker-compose build ${env.DEPLOY_TARGET}"
}
}
}
/**
* 检查 Nginx
* - 检查 nginx 容器是否存在
* - 不存在则启动并复制配置文件(首次部署)
* - 存在则确保容器运行中
*/
stage('检查Nginx') {
steps {
script {
def nginxExists = sh(script: "docker ps -aq -f name=${CONTAINER_PREFIX}-nginx", returnStdout: true).trim()
if (!nginxExists) {
echo "首次部署,初始化 Nginx 容器"
sh "docker-compose up -d nginx"
sh "sleep 3"
// 复制配置文件到容器内
sh "docker cp nginx.conf ${CONTAINER_PREFIX}-nginx:/etc/nginx/nginx.conf"
sh "docker exec ${CONTAINER_PREFIX}-nginx nginx -s reload"
} else {
def nginxRunning = sh(script: "docker ps -q -f name=${CONTAINER_PREFIX}-nginx", returnStdout: true).trim()
if (!nginxRunning) {
echo "Nginx 容器已停止,重新启动"
sh "docker start ${CONTAINER_PREFIX}-nginx"
sh "sleep 2"
}
echo "Nginx 容器已存在且运行中"
}
}
}
}
stage('启动新版本') {
steps {
script {
sh "docker-compose up -d ${env.DEPLOY_TARGET}"
// 等待 FastAPI 应用启动
sh 'sleep 15'
}
}
}
/**
* 健康检查
* - 进入容器执行 curl 检测应用是否正常响应
* - curl -f 参数:HTTP 错误码(404、500等)时返回失败
* - 最多重试 3 次,每次间隔 5 秒
* - 全部失败则终止部署流程
*/
stage('健康检查') {
steps {
script {
def maxRetries = 3
def retryCount = 0
def healthy = false
while (retryCount < maxRetries && !healthy) {
try {
sh "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} 健康检查通过"
}
}
}
/**
* 切换流量
* - 修改 nginx 配置中的 proxy_pass 指向新版本容器
* - nginx 不挂载宿主机文件,直接 sed -i 修改容器内配置
* - reload 使新配置生效,实现零停机切换
*/
stage('切换流量') {
steps {
script {
sh "docker exec ${CONTAINER_PREFIX}-nginx sed -i 's/proxy_pass http:\\/\\/\\(blue\\|green\\):8000;/proxy_pass http:\\/\\/${env.DEPLOY_TARGET}:8000;/' /etc/nginx/nginx.conf"
sh "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()
if (otherExists) {
sh "docker rm -f ${CONTAINER_PREFIX}-${env.OTHER_TARGET}"
echo "旧版本 ${env.OTHER_TARGET} 已删除"
}
}
}
}
}
post {
success {
echo "✅ 蓝绿部署成功!当前运行: ${env.DEPLOY_TARGET}"
}
failure {
echo '❌ 部署失败,请检查日志'
}
}
}