Jenkins 流水线模板

Jenkins 流水线模板,用于自动化构建、部署和验证应用。

一、Jenkins 流水线模板

1. 流水线模板 本机部署

groovy
pipeline {
    agent any
    environment {
        // 🐳 Docker配置
        DOCKER_REGISTRY = "xxx/xxx-dev"
        // 📁 Git配置
        GIT_REPOSITORY = "http://xxx.git"
        GIT_CREDENTIALS_ID = "35b228e2-0a11-45ca-b33f-7f521b70b440"
        // 🚀 项目配置
        PROJECT_NAME = 'xxx-web'
        APP_PORT = '3090'
        MEMORY_LIMIT = '1g'
        CPU_LIMIT = '1.0'
        BUILD_COMMAND = 'build:dev'
        // 📊 构建元数据
        BUILD_TIMESTAMP = sh(script: "TZ='Asia/Shanghai' date '+%Y%m%d-%H%M%S'", returnStdout: true).trim()
        IMAGE_TAG = "${BUILD_NUMBER}-${BUILD_TIMESTAMP}"
    }
    options {
        buildDiscarder(logRotator(
            numToKeepStr: '10',
            artifactNumToKeepStr: '5',
            daysToKeepStr: '14'
        ))
        timeout(time: 20, unit: 'MINUTES')
        timestamps()
        skipStagesAfterUnstable()
        retry(1)
    }
    parameters {
        string(
            name: 'BRANCH',
            defaultValue: 'dev',
            description: '🌿 输入要部署的分支名 (例如: dev, staging, main)'
        )
        booleanParam(
            name: 'FORCE_REBUILD',
            defaultValue: false,
            description: '🔄 强制重新构建Docker镜像 (忽略缓存)'
        )
        booleanParam(
            name: 'SKIP_CLEANUP',
            defaultValue: false,
            description: '🧹 跳过旧镜像清理'
        )
    }
    stages {
        stage('🚀 构建准备') {
            steps {
                script {
                    def buildInfo = """
==================== 构建信息 ====================
项目名称: ${PROJECT_NAME}
分支: ${params.BRANCH}
构建号: ${BUILD_NUMBER}
镜像标签: ${IMAGE_TAG}
Docker镜像: ${DOCKER_REGISTRY}/${PROJECT_NAME}:${IMAGE_TAG}
构建时间: ${BUILD_TIMESTAMP}
构建者: ${env.BUILD_USER ?: 'System'}
=============================================="""
                    echo buildInfo
                    // 设置构建显示名称
                    currentBuild.displayName = "#${BUILD_NUMBER}-${params.BRANCH}"
                    currentBuild.description = "🌿 ${params.BRANCH} | 🏷️ ${IMAGE_TAG}"
                }
            }
        }
        stage('🔍 环境检查') {
            parallel {
                stage('🔍 Docker检查') {
                    steps {
                        script {
                            sh '''
                                echo "🐳 检查Docker环境..."
                                docker --version
                                docker buildx version
                                echo "📊 Docker磁盘使用情况:"
                                docker system df
                            '''
                        }
                    }
                }
                stage('🔍 资源检查') {
                    steps {
                        script {
                            sh '''
                                echo "💻 检查系统资源..."
                                echo "📊 内存使用:"
                                free -h
                                echo "💾 磁盘使用:"
                                df -h
                                echo "🔄 CPU信息:"
                                nproc
                            '''
                        }
                    }
                }
            }
        }
        stage('📥 代码检出') {
            steps {
                script {
                    echo "🔄 正在检出代码,分支: ${params.BRANCH}"
                    try {
                        checkout([
                            $class: 'GitSCM',
                            branches: [[name: "*/${params.BRANCH}"]],
                            doGenerateSubmoduleConfigurations: false,
                            extensions: [
                                [$class: 'CleanBeforeCheckout'],
[$class: 'PruneStaleBranch'],
                                // [$class: 'CloneOption', depth: 1, noTags: false, shallow: true],
[$class: 'SubmoduleOption', disableSubmodules: false, parentCredentials: true]
                            ],
                            submoduleCfg: [],
                            userRemoteConfigs: [[
                                credentialsId: GIT_CREDENTIALS_ID,
                                url: GIT_REPOSITORY
                            ]]
                        ])
                        // 获取详细的Git信息
                        env.COMMIT_HASH = sh(
                            script: "git rev-parse --short HEAD",
                            returnStdout: true
                        ).trim()
                        env.COMMIT_MESSAGE = sh(
                            script: "git log -1 --pretty=format:'%s' | head -c 100",
                            returnStdout: true
                        ).trim()
                        env.COMMIT_AUTHOR = sh(
                            script: "git log -1 --pretty=format:'%an'",
                            returnStdout: true
                        ).trim()
                        env.COMMIT_DATE = sh(
                            script: "TZ='Asia/Shanghai' git log -1 --pretty=format:'%ci'",
                            returnStdout: true
                        ).trim()
                        env.BRANCH_NAME = params.BRANCH
                        def checkoutSuccess = """✅ 代码检出成功!
提交哈希: ${env.COMMIT_HASH}
提交作者: ${env.COMMIT_AUTHOR}
提交时间: ${env.COMMIT_DATE}
提交信息: ${env.COMMIT_MESSAGE}"""
                        echo checkoutSuccess
                    } catch (Exception e) {
                        error("❌ 代码检出失败: ${e.getMessage()}")
                    }
                }
            }
        }
        stage('🔨 构建Docker镜像') {
            steps {
                script {
                    def dockerImageTag = "${DOCKER_REGISTRY}/${PROJECT_NAME}:${IMAGE_TAG}"
                    def dockerImageLatest = "${DOCKER_REGISTRY}/${PROJECT_NAME}:latest"
                    echo "🐳 开始构建Docker镜像: ${dockerImageTag}"
                    try {
                        // 根据构建类型设置不同的构建参数
                        def buildArgs = [
                            "--build-arg BUILD_CMD=${BUILD_COMMAND}",
                            "--build-arg PORT=${APP_PORT}",
                            "--build-arg COMMIT_HASH=${env.COMMIT_HASH}",
                            "--build-arg BUILD_NUMBER=${BUILD_NUMBER}",
                            "--build-arg BUILD_TIMESTAMP=${BUILD_TIMESTAMP}"
                        ]
                        if (params.FORCE_REBUILD) {
                            buildArgs.add("--no-cache")
                            echo "🔄 强制重新构建 (无缓存)"
                        }
                        // 使用buildx构建
                        sh """
                            docker buildx build \\
                                --progress=plain \\
                                --tag ${dockerImageTag} \\
                                --tag ${dockerImageLatest} \\
                                ${buildArgs.join(' ')} \\
                                --file Dockerfile \\
                                --label "io.jenkins.build-number=${BUILD_NUMBER}" \\
                                --label "io.jenkins.build-url=${BUILD_URL}" \\
                                --label "git.commit=${env.COMMIT_HASH}" \\
                                --label "git.branch=${params.BRANCH}" \\
                                --label "build.timestamp=${BUILD_TIMESTAMP}" \\
                                .
                        """
                        echo "✅ Docker镜像构建成功: ${dockerImageTag}"
                        // 显示镜像详细信息
                        sh """
                            echo "📊 镜像信息:"
                            docker inspect ${dockerImageTag} --format='{{.Size}}' | numfmt --to=iec
                            echo "🏷️  镜像标签:"
                            docker images ${DOCKER_REGISTRY}/${PROJECT_NAME} --format 'table {{.Tag}}\\t{{.Size}}\\t{{.CreatedSince}}' | head -10
                        """
                    } catch (Exception e) {
                        error("❌ Docker镜像构建失败: ${e.getMessage()}")
                    }
                }
            }
        }
        stage('🚀 部署应用') {
            steps {
                script {
                    def dockerImageTag = "${DOCKER_REGISTRY}/${PROJECT_NAME}:${IMAGE_TAG}"
                    echo "🚀 开始部署应用..."
                    try {
                        // 优雅停止现有容器
                        def existingContainer = sh(
                            script: "docker ps -q -f name=^/${PROJECT_NAME}\$",
                            returnStdout: true
                        ).trim()
                        if (existingContainer) {
                            echo "🔄 发现运行中的容器,执行优雅停止: ${existingContainer}"
                            sh """
                                # 发送SIGTERM信号,给应用30秒优雅关闭
                                docker stop --time=30 ${PROJECT_NAME} || true
                                # 清理容器
                                docker rm ${PROJECT_NAME} || true
                            """
                            echo "✅ 旧容器已优雅停止并清理"
                        }
                        // 启动新容器
                        echo "🚀 启动新容器: ${dockerImageTag}"
                        def containerCmd = """
                            docker run -d \\
                                --name ${PROJECT_NAME} \\
                                --restart=unless-stopped \\
                                --memory ${MEMORY_LIMIT} \\
                                --cpus="${CPU_LIMIT}" \\
                                --security-opt no-new-privileges:true \\
                                --user 1001:1001 \\
                                -p ${APP_PORT}:${APP_PORT} \\
                                --label "project=${PROJECT_NAME}" \\
                                --label "build=${BUILD_NUMBER}" \\
                                --label "commit=${env.COMMIT_HASH}" \\
                                --label "branch=${params.BRANCH}" \\
                                --label "deploy-time=\$(date -Iseconds)" \\
                                --env PORT=${APP_PORT} \\
                                ${dockerImageTag}
                        """
                        sh containerCmd
                        // 智能等待容器启动
                        echo "⏳ 等待容器启动"
                        def maxRetries = 30
                        def retryCount = 0
                        def containerStarted = false
                        while (retryCount < maxRetries && !containerStarted) {
                            sleep(time: 1, unit: 'SECONDS')
                            retryCount++
                            def containerStatus = sh(
                                script: "docker inspect ${PROJECT_NAME} --format='{{.State.Status}}'",
                                returnStdout: true
                            ).trim()
                            if (containerStatus == 'running') {
                                echo "✅ 容器已启动 (${retryCount}/${maxRetries} 尝试)"
                                containerStarted = true
                            } else {
                                echo "⏳ 等待容器启动... (${retryCount}/${maxRetries}) 状态: ${containerStatus}"
                            }
                        }
                        if (!containerStarted) {
                            error("❌ 容器启动超时")
                        }
                        // 验证部署
                        def containerId = sh(
                            script: "docker ps -q -f name=^/${PROJECT_NAME}\$",
                            returnStdout: true
                        ).trim()
                        if (containerId) {
                            def deploySuccess = """✅ 部署成功!
容器ID: ${containerId}
镜像标签: ${IMAGE_TAG}
访问端口: ${APP_PORT}"""
                            echo deploySuccess
                            // 显示容器详细信息
                            sh """
                                echo "📊 容器详细信息:"
                                docker ps -f name=${PROJECT_NAME} --format 'table {{.Names}}\\t{{.Status}}\\t{{.Ports}}'
                                echo "💾 资源使用:"
                                docker stats --no-stream ${PROJECT_NAME}
                            """
                        } else {
                            error("❌ 部署验证失败: 容器未能正常启动")
                        }
                    } catch (Exception e) {
                        error("❌ 部署失败: ${e.getMessage()}")
                    }
                }
            }
        }
        stage('📊 部署验证') {
            steps {
                script {
                    echo "📊 开始部署验证..."
                    try {
                        def verificationReport = """
==================== 部署验证报告 ===================="""
                        echo verificationReport
                        // 容器日志检查
                        echo "📋 最新容器日志 (最后30行):"
                        sh "docker logs --tail 30 --timestamps ${PROJECT_NAME}"
                        // 容器资源使用情况
                        echo "📊 容器资源使用:"
                        sh "docker stats --no-stream --format 'table {{.Name}}\\t{{.CPUPerc}}\\t{{.MemUsage}}\\t{{.NetIO}}' ${PROJECT_NAME}"
                        // 网络连接检查
                        echo "🌐 网络连接检查:"
                        sh """
                            echo "容器网络信息:"
                            docker port ${PROJECT_NAME}
                        """
                        // 容器进程检查
                        echo "🔍 容器进程:"
                        sh "docker exec ${PROJECT_NAME} ps aux || echo '无法获取进程信息'"
                        echo "✅ 部署验证完成"
                    } catch (Exception e) {
                        echo "⚠️ 部署验证过程中出现问题: ${e.getMessage()}"
                        unstable("部署验证出现警告")
                    }
                }
            }
        }
    }
    post {
        success {
            script {
                echo "🎉 构建和部署成功!"
                // 智能清理策略
                if (!params.SKIP_CLEANUP) {
                    try {
                        echo "🧹 执行智能清理..."
                        // 保留最近的构建
                        def buildsToKeep = 5
                        def oldBuildNumber = BUILD_NUMBER.toInteger() - buildsToKeep
                        if (oldBuildNumber > 0) {
                            def oldImage = "${DOCKER_REGISTRY}/${PROJECT_NAME}:${oldBuildNumber}*"
                            sh """
                                # 清理旧版本镜像
                                docker images ${DOCKER_REGISTRY}/${PROJECT_NAME} --format '{{.Tag}}' | \\
                                grep -E '^[0-9]+-' | \\
                                sort -V | \\
                                head -n -${buildsToKeep} | \\
                                xargs -r -I {} docker rmi ${DOCKER_REGISTRY}/${PROJECT_NAME}:{} || true
                            """
                        }
                        // 清理无用资源
                        sh """
                            # 清理悬空镜像
                            docker image prune -f || true
                            # 清理停止的容器
                            docker container prune -f || true
                            # 清理无用的网络
                            docker network prune -f || true
                        """
                        echo "✅ 清理完成"
                    } catch (Exception e) {
                        echo "⚠️ 清理过程中出现问题: ${e.getMessage()}"
                    }
                }
                // 构建成功总结
                def successSummary = """
==================== 部署成功总结 ====================
项目: ${PROJECT_NAME}
分支: ${params.BRANCH}
构建号: ${BUILD_NUMBER}
镜像标签: ${IMAGE_TAG}
提交: ${env.COMMIT_HASH ?: 'N/A'}
镜像: ${DOCKER_REGISTRY}/${PROJECT_NAME}:${IMAGE_TAG}
端口: ${APP_PORT}
部署时间: ${new Date()}
构建链接: ${BUILD_URL}
=============================================="""
                echo successSummary
                // 发送通知 (示例)
                // slackSend(
                //     color: 'good',
                //     message: "✅ ${PROJECT_NAME} 部署成功! 分支: ${params.BRANCH}, 构建: #${BUILD_NUMBER}"
                // )
            }
        }
        failure {
            script {
                echo "❌ 构建或部署失败!"
                def failureInfo = """
==================== 部署失败信息 ====================
项目: ${PROJECT_NAME}
分支: ${params.BRANCH}
构建号: ${BUILD_NUMBER}
提交: ${env.COMMIT_HASH ?: 'N/A'}
失败阶段: ${env.STAGE_NAME ?: 'Unknown'}
失败时间: ${new Date()}
构建链接: ${BUILD_URL}
控制台日志: ${BUILD_URL}console
=============================================="""
                echo failureInfo
                // 收集故障排除信息
                try {
                    echo "🔍 收集故障排除信息..."
                    sh """
                        echo "=== Docker容器状态 ==="
                        docker ps -a | grep ${PROJECT_NAME} || echo "未找到相关容器"

                        echo "=== Docker镜像 ==="
                        docker images | grep ${PROJECT_NAME} || echo "未找到相关镜像"

                        echo "=== 系统资源 ==="
                        free -h
                        df -h

                        echo "=== 最近的容器日志 ==="
                        docker logs --tail 50 ${PROJECT_NAME} 2>/dev/null || echo "无法获取容器日志"
                    """
                } catch (Exception e) {
                    echo "⚠️ 收集故障信息失败: ${e.getMessage()}"
                }
                // 发送失败通知
                // slackSend(
                //     color: 'danger',
                //     message: "❌ ${PROJECT_NAME} 部署失败! 分支: ${params.BRANCH}, 构建: #${BUILD_NUMBER}\n详情: ${BUILD_URL}"
                // )
            }
        }
        always {
            script {
                echo "🧹 执行构建后清理..."
                try {
                    // 清理临时资源
                    sh """
                        # 清理临时容器
                        docker container prune -f --filter 'until=1h' || true

                        # 清理构建缓存 (可选,谨慎使用)
                        # docker builder prune -f --keep-storage 10GB || true
                    """
                    echo "✅ 构建后清理完成"
                    // 记录构建统计
                    def buildDuration = currentBuild.duration ?: 0
                    def buildResult = currentBuild.result ?: 'SUCCESS'
                    def buildStats = """
📊 构建统计:
持续时间: ${buildDuration}ms
结果: ${buildResult}
构建号: ${BUILD_NUMBER}"""
                    echo buildStats
                } catch (Exception e) {
                    echo "⚠️ 清理过程中出现问题: ${e.getMessage()}"
                }
            }
        }
        unstable {
            script {
                echo "⚠️ 构建完成但存在警告"
                // emailext(
                //     subject: "⚠️ ${PROJECT_NAME} 构建不稳定 - #${BUILD_NUMBER}",
                //     body: "构建完成但存在警告,请检查日志。",
                //     to: "${env.CHANGE_AUTHOR_EMAIL}"
                // )
            }
        }
        aborted {
            script {
                echo "🚫 构建被中止"
            }
        }
    }
}

2. 流水线模板 远程部署

groovy
pipeline {
    agent any
    environment {
        // 📊 构建元数据
        BUILD_TIMESTAMP = sh(script: "TZ='Asia/Shanghai' date '+%Y%m%d-%H%M%S'", returnStdout: true).trim()
        IMAGE_TAG = "${BUILD_NUMBER}-${BUILD_TIMESTAMP}"
        // 🚀 项目配置
        PROJECT_NAME = 'xxx-web'
        APP_PORT = '3090'
        MEMORY_LIMIT = '1g'
        CPU_LIMIT = '1.0'
        BUILD_COMMAND = 'build:dev'
        // 🐳 Docker配置
        DOCKER_REGISTRY = "xxx/xxx-dev"
        DOCKER_LOGIN_CREDENTIALS_ID = "47ea54fa-a957-4f67-97c6-c3bcebd863f6"
        DOCKER_IMAGE = "${DOCKER_REGISTRY}/${PROJECT_NAME}:${IMAGE_TAG}"
        // 🚀 部署配置
        DEPLOY_PROJECT_SSH_HOST = "170.187.173.192"
        DEPLOY_PROJECT_SSH_CREDENTIALS_ID = "84406c49-7110-44cf-8e8d-80927586ec56"
        // 📁 Git配置
        GIT_REPOSITORY = "http://xxx.git"
        GIT_CREDENTIALS_ID = "35b228e2-0a11-45ca-b33f-7f521b70b440"
    }
    options {
        buildDiscarder(logRotator(
            numToKeepStr: '10',
            artifactNumToKeepStr: '5',
            daysToKeepStr: '14'
        ))
        timeout(time: 8, unit: 'MINUTES')
        timestamps()
        skipStagesAfterUnstable()
        retry(1)
    }
    parameters {
        string(
            name: 'BRANCH',
            defaultValue: 'dev',
            description: '🌿 输入要部署的分支名 (例如: dev, staging, main)'
        )
        booleanParam(
            name: 'FORCE_REBUILD',
            defaultValue: false,
            description: '🔄 强制重新构建Docker镜像 (忽略缓存)'
        )
        booleanParam(
            name: 'SKIP_CLEANUP',
            defaultValue: false,
            description: '🧹 跳过旧镜像清理'
        )
    }
    stages {
        stage('🚀 构建准备') {
            steps {
                script {
                    def buildInfo = """
==================== 构建信息 ====================
项目名称: ${PROJECT_NAME}
分支: ${params.BRANCH}
构建号: ${BUILD_NUMBER}
镜像标签: ${IMAGE_TAG}
Docker镜像: ${DOCKER_IMAGE}
构建时间: ${BUILD_TIMESTAMP}
构建者: ${env.BUILD_USER ?: 'System'}
=============================================="""
                    echo buildInfo
                    // 设置构建显示名称
                    currentBuild.displayName = "#${BUILD_NUMBER}-${params.BRANCH}"
                    currentBuild.description = "🌿 ${params.BRANCH} | 🏷️ ${IMAGE_TAG}"
                }
            }
        }
        stage('🔍 环境检查') {
            parallel {
                stage('🔍 Docker检查') {
                    steps {
                        script {
                            sh '''
echo "🐳 检查Docker环境..."
docker --version
docker buildx version
echo "📊 Docker磁盘使用情况:"
docker system df
'''
                        }
                    }
                }
                stage('🔍 资源检查') {
                    steps {
                        script {
                            sh '''
echo "💻 检查系统资源..."
echo "📊 内存使用:"
free -h
echo "💾 磁盘使用:"
df -h
echo "🔄 CPU信息:"
nproc
'''
                        }
                    }
                }
            }
        }
        stage('📥 代码检出') {
            steps {
                script {
                    echo "🔄 正在检出代码,分支: ${params.BRANCH}"
                    try {
                        checkout([
                            $class: 'GitSCM',
                            branches: [[name: "*/${params.BRANCH}"]],
                            doGenerateSubmoduleConfigurations: false,
                            extensions: [
                                [$class: 'CleanBeforeCheckout'],
[$class: 'PruneStaleBranch'],
                                // [$class: 'CloneOption', depth: 1, noTags: false, shallow: true],
[$class: 'SubmoduleOption', disableSubmodules: false, parentCredentials: true]
                            ],
                            submoduleCfg: [],
                            userRemoteConfigs: [[
                                credentialsId: GIT_CREDENTIALS_ID,
                                url: GIT_REPOSITORY
                            ]]
                        ])
                        // 获取详细的Git信息
                        env.COMMIT_HASH = sh(
                            script: "git rev-parse --short HEAD",
                            returnStdout: true
                        ).trim()
                        env.COMMIT_MESSAGE = sh(
                            script: "git log -1 --pretty=format:'%s' | head -c 100",
                            returnStdout: true
                        ).trim()
                        env.COMMIT_AUTHOR = sh(
                            script: "git log -1 --pretty=format:'%an'",
                            returnStdout: true
                        ).trim()
                        env.COMMIT_DATE = sh(
                            script: "TZ='Asia/Shanghai' git log -1 --pretty=format:'%ci'",
                            returnStdout: true
                        ).trim()
                        env.BRANCH_NAME = params.BRANCH
                        def checkoutSuccess = """✅ 代码检出成功!
提交哈希: ${env.COMMIT_HASH}
提交作者: ${env.COMMIT_AUTHOR}
提交时间: ${env.COMMIT_DATE}
提交信息: ${env.COMMIT_MESSAGE}"""
                        echo checkoutSuccess
                    } catch (Exception e) {
                        error("❌ 代码检出失败: ${e.getMessage()}")
                    }
                }
            }
        }
        stage('🔨 构建Docker镜像') {
            steps {
                script {
                    def dockerImageTag = "${DOCKER_IMAGE}"
                    def dockerImageLatest = "${DOCKER_REGISTRY}/${PROJECT_NAME}:latest"
                    echo "🐳 开始构建Docker镜像: ${dockerImageTag}"
                    try {
                        // 根据构建类型设置不同的构建参数
                        def buildArgs = [
                            "--build-arg BUILD_CMD=${BUILD_COMMAND}",
                            "--build-arg PORT=${APP_PORT}",
                            "--build-arg COMMIT_HASH=${env.COMMIT_HASH}",
                            "--build-arg BUILD_NUMBER=${BUILD_NUMBER}",
                            "--build-arg BUILD_TIMESTAMP=${BUILD_TIMESTAMP}"
                        ]
                        if (params.FORCE_REBUILD) {
                            buildArgs.add("--no-cache")
                            echo "🔄 强制重新构建 (无缓存)"
                        }
                        // 使用buildx构建
                        sh """
                            docker buildx build \\
                                --progress=plain \\
                                --tag ${dockerImageTag} \\
                                --tag ${dockerImageLatest} \\
                                ${buildArgs.join(' ')} \\
                                --file Dockerfile \\
                                --label "io.jenkins.build-number=${BUILD_NUMBER}" \\
                                --label "io.jenkins.build-url=${BUILD_URL}" \\
                                --label "git.commit=${env.COMMIT_HASH}" \\
                                --label "git.branch=${params.BRANCH}" \\
                                --label "build.timestamp=${BUILD_TIMESTAMP}" \\
                                .
                        """
                        echo "✅ Docker镜像构建成功: ${dockerImageTag}"
                        // 显示镜像详细信息
                        sh """
                            echo "📊 镜像信息:"
                            docker inspect ${dockerImageTag} --format='{{.Size}}' | numfmt --to=iec
                            echo "🏷️ 镜像标签:"
                            docker images ${DOCKER_REGISTRY}/${PROJECT_NAME} --format 'table {{.Tag}}\\t{{.Size}}\\t{{.CreatedSince}}' | head -10
                        """
                    } catch (Exception e) {
                        error("❌ Docker镜像构建失败: ${e.getMessage()}")
                    }
                }
            }
        }
        stage('📤 推送镜像') {
            steps {
                script {
                    withCredentials([usernamePassword(credentialsId: DOCKER_LOGIN_CREDENTIALS_ID, usernameVariable: 'DOCKER_USERNAME', passwordVariable: 'DOCKER_PASSWORD')]) {
                        def dockerLoginCmd = "docker login -u ${DOCKER_USERNAME} -p ${DOCKER_PASSWORD} ${DOCKER_REGISTRY}"
                        sh dockerLoginCmd
                        sh "docker push ${DOCKER_IMAGE}"
                    }
                }
            }
        }
        stage('🚀 部署应用') {
            steps {
                script {
                    withCredentials([
                        usernamePassword(credentialsId: DOCKER_LOGIN_CREDENTIALS_ID, usernameVariable: 'DOCKER_USERNAME', passwordVariable: 'DOCKER_PASSWORD'),
                        sshUserPrivateKey(credentialsId: DEPLOY_PROJECT_SSH_CREDENTIALS_ID, keyFileVariable: 'SSH_KEY', usernameVariable: 'SSH_USER')
                    ]) {
                        def dockerImageTag = "${DOCKER_IMAGE}"
                        def dockerLoginCmd = "docker login -u ${DOCKER_USERNAME} -p ${DOCKER_PASSWORD} ${DOCKER_REGISTRY}"
                        echo "🚀 开始远程部署应用..."
                        try {
                            // 创建远程部署脚本(带错误处理)
                            def remoteDeployScript = """
                set -e  # 遇到错误立即退出

                echo "=== 开始部署调试信息 ==="
                echo "项目名称: ${PROJECT_NAME}"
                echo "镜像标签: ${dockerImageTag}"
                echo "应用端口: ${APP_PORT}"
                echo "============================"

                # Docker 登录
                echo "🔐 Docker 登录..."
                if ! ${dockerLoginCmd}; then
                    echo "❌ Docker 登录失败"
                    exit 1
                fi

                # 拉取镜像
                echo "📥 拉取镜像: ${dockerImageTag}"
                if ! docker pull ${dockerImageTag}; then
                    echo "❌ 镜像拉取失败"
                    exit 1
                fi

                # 优雅停止现有容器
                echo "🔍 检查现有容器..."
                EXISTING_CONTAINER=\$(docker ps -q -f name=${PROJECT_NAME} 2>/dev/null || true)
                if [ ! -z "\$EXISTING_CONTAINER" ]; then
                    echo "🔄 发现运行中的容器,执行优雅停止: \$EXISTING_CONTAINER"
                    docker stop --time=30 ${PROJECT_NAME} || true
                    docker rm ${PROJECT_NAME} || true
                    echo "✅ 旧容器已优雅停止并清理"
                else
                    echo "ℹ️  没有发现运行中的容器"
                fi

                # 清理可能存在的同名停止容器
                echo "🧹 清理停止的容器..."
                docker rm ${PROJECT_NAME} 2>/dev/null || true

                # 启动新容器
                echo "🚀 启动新容器: ${dockerImageTag}"
                CONTAINER_ID=\$(docker run -d \\
                    --name ${PROJECT_NAME} \\
                    --restart=unless-stopped \\
                    --memory ${MEMORY_LIMIT} \\
                    --cpus="${CPU_LIMIT}" \\
                    --security-opt no-new-privileges:true \\
                    --user 1001:1001 \\
                    -p ${APP_PORT}:${APP_PORT} \\
                    --label "project=${PROJECT_NAME}" \\
                    --label "build=${BUILD_NUMBER}" \\
                    --label "commit=${env.COMMIT_HASH}" \\
                    --label "branch=${params.BRANCH}" \\
                    --label "deploy-time=\$(date -Iseconds)" \\
                    --env PORT=${APP_PORT} \\
                    ${dockerImageTag})

                if [ -z "\$CONTAINER_ID" ]; then
                    echo "❌ 容器启动失败"
                    echo "📋 获取可能的错误信息..."
                    docker logs ${PROJECT_NAME} 2>&1 || echo "无法获取容器日志"
                    exit 1
                fi

                echo "✅ 容器已创建,ID: \$CONTAINER_ID"

                # 智能等待容器启动
                echo "⏳ 等待容器启动"
                MAX_RETRIES=30
                RETRY_COUNT=0
                CONTAINER_STARTED=false

                while [ \$RETRY_COUNT -lt \$MAX_RETRIES ] && [ "\$CONTAINER_STARTED" = "false" ]; do
                    sleep 1
                    RETRY_COUNT=\$((RETRY_COUNT + 1))

                    CONTAINER_STATUS=\$(docker inspect ${PROJECT_NAME} --format='{{.State.Status}}' 2>/dev/null || echo "unknown")

                    if [ "\$CONTAINER_STATUS" = "running" ]; then
                        echo "✅ 容器已启动 (\$RETRY_COUNT/\$MAX_RETRIES 尝试)"
                        CONTAINER_STARTED=true
                    else
                        echo "⏳ 等待容器启动... (\$RETRY_COUNT/\$MAX_RETRIES) 状态: \$CONTAINER_STATUS"
                        # 如果容器已经退出,获取日志
                        if [ "\$CONTAINER_STATUS" = "exited" ]; then
                            echo "📋 容器已退出,查看日志:"
                            docker logs ${PROJECT_NAME} --tail 10 2>&1 || echo "无法获取容器日志"
                        fi
                    fi
                done

                if [ "\$CONTAINER_STARTED" = "false" ]; then
                    echo "❌ 容器启动超时"
                    echo "📋 完整容器日志:"
                    docker logs ${PROJECT_NAME} 2>&1 || echo "无法获取容器日志"
                    echo "📋 容器详细状态:"
                    docker inspect ${PROJECT_NAME} --format='{{json .State}}' 2>/dev/null || echo "无法获取容器状态"
                    exit 1
                fi

                # 验证部署
                RUNNING_CONTAINER_ID=\$(docker ps -q -f name=${PROJECT_NAME} 2>/dev/null || true)
                if [ ! -z "\$RUNNING_CONTAINER_ID" ]; then
                    echo "✅ 部署成功!"
                    echo "容器ID: \$RUNNING_CONTAINER_ID"
                    echo "镜像标签: ${dockerImageTag}"
                    echo "访问端口: ${APP_PORT}"

                    echo "📊 容器详细信息:"
                    docker ps -f name=${PROJECT_NAME} --format 'table {{.Names}}\\t{{.Status}}\\t{{.Ports}}' 2>/dev/null || echo "无法获取容器信息"
                    echo "💾 资源使用:"
                    docker stats --no-stream ${PROJECT_NAME} 2>/dev/null || echo "无法获取资源使用信息"

                    echo "✅ 远程部署成功完成"
                    exit 0
                else
                    echo "❌ 部署验证失败: 容器未能正常启动"
                    exit 1
                fi
            """
                            // 将脚本写入临时文件
                            writeFile file: 'deploy_script.sh', text: remoteDeployScript
                            // 执行远程部署
                            def sshResult = sh(
                                script: """
                        chmod +x deploy_script.sh
                        scp -i "\${SSH_KEY}" -o StrictHostKeyChecking=no deploy_script.sh "\${SSH_USER}@\${DEPLOY_PROJECT_SSH_HOST}:/tmp/deploy_script.sh"
                        ssh -i "\${SSH_KEY}" -o StrictHostKeyChecking=no "\${SSH_USER}@\${DEPLOY_PROJECT_SSH_HOST}" 'bash /tmp/deploy_script.sh && rm /tmp/deploy_script.sh'
                        """,
                                returnStatus: true
                            )
                            // 如果失败,获取详细错误信息
                            if (sshResult != 0) {
                                echo "❌ 远程部署失败,尝试获取详细错误信息..."
                                try {
                                    sh """
                            ssh -i "\${SSH_KEY}" -o StrictHostKeyChecking=no "\${SSH_USER}@\${DEPLOY_PROJECT_SSH_HOST}" '
                                echo "=== 系统信息 ==="
                                docker --version
                                echo "=== 当前运行的容器 ==="
                                docker ps -a
                                echo "=== 磁盘空间 ==="
                                df -h
                                echo "=== 内存使用 ==="
                                free -h
                                echo "=== 最近的容器日志 ==="
                                docker logs ${PROJECT_NAME} --tail 20 2>&1 || echo "无容器日志"
                            '
                            """
                                } catch (Exception debugE) {
                                    echo "获取调试信息失败: ${debugE.getMessage()}"
                                }
                                // 清理临时文件
                                sh 'rm -f deploy_script.sh'
                            }
                            // 根据远程执行结果判断
                            if (sshResult == 0) {
                                echo "✅ 远程部署完成!"
                            } else {
                                echo "❌ 远程部署失败,退出码: ${sshResult}"
                                error("远程部署失败")
                            }
                        } catch (Exception e) {
                            echo "❌ SSH连接失败: ${e.getMessage()}"
                            error("远程部署连接失败")
                        }
                    }
                }
            }
        }
        stage('📊 部署验证') {
            steps {
                script {
                    withCredentials([
                        sshUserPrivateKey(credentialsId: DEPLOY_PROJECT_SSH_CREDENTIALS_ID, keyFileVariable: 'SSH_KEY', usernameVariable: 'SSH_USER')
                    ]) {
                        echo "📊 开始远程部署验证..."
                        try {
                            // 执行远程验证(分步骤执行,避免复杂脚本)
                            echo "🔍 检查容器状态..."
                            def containerCheckResult = sh(
                                script: """
                        ssh -i "\${SSH_KEY}" -o StrictHostKeyChecking=no "\${SSH_USER}@\${DEPLOY_PROJECT_SSH_HOST}" '
                            echo "==================== 部署验证报告 ===================="
                            echo "项目名称: ${PROJECT_NAME}"
                            echo "构建编号: ${BUILD_NUMBER}"
                            echo "验证时间: \$(date)"
                            echo "======================================================"

                            echo "🔍 检查容器状态..."
                            if docker ps -q -f name=${PROJECT_NAME} >/dev/null 2>&1; then
                                echo "✅ 容器正在运行"
                                docker ps -f name=${PROJECT_NAME} --format "table {{.Names}}\\t{{.Status}}\\t{{.Ports}}\\t{{.Image}}"
                            else
                                echo "❌ 容器未运行"
                                exit 1
                            fi
                        '
                        """,
                                returnStatus: true
                            )
                            if (containerCheckResult != 0) {
                                echo "❌ 容器状态检查失败"
                                error("容器未正常运行")
                            }
                            echo "📊 获取容器资源使用情况..."
                            try {
                                sh """
                        ssh -i "\${SSH_KEY}" -o StrictHostKeyChecking=no "\${SSH_USER}@\${DEPLOY_PROJECT_SSH_HOST}" '
                            echo "📊 容器资源使用:"
                            docker stats --no-stream --format "table {{.Name}}\\t{{.CPUPerc}}\\t{{.MemUsage}}\\t{{.NetIO}}" ${PROJECT_NAME} 2>/dev/null || echo "⚠️ 无法获取资源使用情况"

                            echo "🌐 网络连接检查:"
                            docker port ${PROJECT_NAME} 2>/dev/null || echo "⚠️ 无法获取端口信息"

                            echo "📋 容器日志 (最后20行):"
                            docker logs --tail 20 --timestamps ${PROJECT_NAME} 2>&1 || echo "⚠️ 无法获取容器日志"
                        '
                        """
                            } catch (Exception e) {
                                echo "⚠️ 获取容器详细信息时出现警告: ${e.getMessage()}"
                                unstable("部署验证过程中出现警告")
                            }
                            echo "✅ 远程部署验证完成"
                        } catch (Exception e) {
                            echo "❌ SSH连接失败: ${e.getMessage()}"
                            error("远程部署验证连接失败")
                        }
                    }
                }
            }
        }
    }
    post {
        success {
            script {
                echo "🎉 构建和部署成功!"
                // 智能清理策略
                if (!params.SKIP_CLEANUP) {
                    try {
                        echo "🧹 执行智能清理..."
                        // 保留最近的构建
                        def buildsToKeep = 5
                        def oldBuildNumber = BUILD_NUMBER.toInteger() - buildsToKeep
                        if (oldBuildNumber > 0) {
                            def oldImage = "${DOCKER_REGISTRY}/${PROJECT_NAME}:${oldBuildNumber}*"
                            sh """
                                # 清理旧版本镜像
                                docker images ${DOCKER_REGISTRY}/${PROJECT_NAME} --format '{{.Tag}}' | \\
                                grep -E '^[0-9]+-' | \\
                                sort -V | \\
                                head -n -${buildsToKeep} | \\
                                xargs -r -I {} docker rmi ${DOCKER_REGISTRY}/${PROJECT_NAME}:{} || true
                            """
                        }
                        // 清理无用资源
                        sh """
                            # 清理悬空镜像
                            docker image prune -f || true
                            # 清理停止的容器
                            docker container prune -f || true
                            # 清理无用的网络
                            docker network prune -f || true
                        """
                        echo "✅ 清理完成"
                    } catch (Exception e) {
                        echo "⚠️ 清理过程中出现问题: ${e.getMessage()}"
                    }
                }
                // 构建成功总结
                def successSummary = """
==================== 部署成功总结 ====================
项目: ${PROJECT_NAME}
分支: ${params.BRANCH}
构建号: ${BUILD_NUMBER}
镜像标签: ${IMAGE_TAG}
提交: ${env.COMMIT_HASH ?: 'N/A'}
镜像: ${DOCKER_IMAGE}
端口: ${APP_PORT}
部署时间: ${new Date()}
构建链接: ${BUILD_URL}
=============================================="""
                echo successSummary
                // 发送通知 (示例)
                // slackSend(
                //     color: 'good',
                //     message: "✅ ${PROJECT_NAME} 部署成功! 分支: ${params.BRANCH}, 构建: #${BUILD_NUMBER}"
                // )
            }
        }
        failure {
            script {
                echo "❌ 构建或部署失败!"
                def failureInfo = """
==================== 部署失败信息 ====================
项目: ${PROJECT_NAME}
分支: ${params.BRANCH}
构建号: ${BUILD_NUMBER}
提交: ${env.COMMIT_HASH ?: 'N/A'}
失败阶段: ${env.STAGE_NAME ?: 'Unknown'}
失败时间: ${new Date()}
构建链接: ${BUILD_URL}
控制台日志: ${BUILD_URL}console
=============================================="""
                echo failureInfo
                // 收集故障排除信息
                // try {
                //     echo "🔍 收集故障排除信息..."
                //     sh """
                //         echo "=== Docker容器状态 ==="
                //         docker ps -a | grep ${PROJECT_NAME} || echo "未找到相关容器"
                //         echo "=== Docker镜像 ==="
                //         docker images | grep ${PROJECT_NAME} || echo "未找到相关镜像"
                //         echo "=== 系统资源 ==="
                //         free -h
                //         df -h
                //         echo "=== 最近的容器日志 ==="
                //         docker logs --tail 50 ${PROJECT_NAME} 2>/dev/null || echo "无法获取容器日志"
                //     """
                // } catch (Exception e) {
                //     echo "⚠️ 收集故障信息失败: ${e.getMessage()}"
                // }
                // 发送失败通知
                // slackSend(
                //     color: 'danger',
                //     message: "❌ ${PROJECT_NAME} 部署失败! 分支: ${params.BRANCH}, 构建: #${BUILD_NUMBER}\n详情: ${BUILD_URL}"
                // )
            }
        }
        always {
            script {
                echo "🧹 执行构建后清理..."
                try {
                    // 清理临时资源
                    sh """
                        # 清理临时容器
                        docker container prune -f --filter 'until=1h' || true

                        # 清理构建缓存 (可选,谨慎使用)
                        # docker builder prune -f --keep-storage 10GB || true
                    """
                    echo "✅ 构建后清理完成"
                    // 记录构建统计
                    def buildDuration = currentBuild.duration ?: 0
                    def buildResult = currentBuild.result ?: 'SUCCESS'
                    def buildStats = """
📊 构建统计:
持续时间: ${buildDuration}ms
结果: ${buildResult}
构建号: ${BUILD_NUMBER}"""
                    echo buildStats
                } catch (Exception e) {
                    echo "⚠️ 清理过程中出现问题: ${e.getMessage()}"
                }
            }
        }
        unstable {
            script {
                echo "⚠️ 构建完成但存在警告"
                // emailext(
                //     subject: "⚠️ ${PROJECT_NAME} 构建不稳定 - #${BUILD_NUMBER}",
                //     body: "构建完成但存在警告,请检查日志。",
                //     to: "${env.CHANGE_AUTHOR_EMAIL}"
                // )
            }
        }
        aborted {
            script {
                echo "🚫 构建被中止"
            }
        }
    }
}
Docker 安装:脚本与 Ubuntu 生产实践
Mermaid图表绘制完全指南:用代码画出精美图表