jenkins使用pipeline实现滚动发布

jenkins使用pipeline实现滚动发布

需求:
1、服务有多个,发布的时候,不能影响用户正常使用

脚本如下:

pipeline 复制代码
pipeline {
    agent any
    
    // 定义全局变量,请根据你的实际环境修改
    environment {
        NACOS_SERVER = "http://nacos.com.cn"
        NACOS_USER = "nacos"
        NACOS_PASSWORD = "nacos"
        SERVICE_NAME = "bdo-pm-project"
        NACOS_NAMESPACE_ID = "a7a24094-1599-4126-a72c-161d5b05c955"
        
        // 服务部署信息(IP、端口、部署路径)
        SERVER_PORT = "11002"
        SERVER_1_IP = "10.17.10.67"
        SERVER_2_IP = "10.17.10.66"
        SERVER_PATH = "/home/bdo.dev"
        SERVER_USER = "username"
        
        // 本地项目路径和Git仓库
        GIT_REPO_URL = "https://github.com/**.git"
        JAVA_HOME = tool name: 'jdk8', type: 'hudson.model.JDK'
        WECOM_WEBHOOK_URL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key="
    }

    stages {
        // 阶段1:编译项目 (对应你的步骤2)
        stage('编译项目') {
            steps {
                echo "开始拉取代码并编译..."
                // 拉取代码
                git branch: 'main',
                    credentialsId: 'fang_gitlab',
                    url: 'https://git**.git'
                    
                echo "代码拉取完成,开始编译。。。"
                // 执行Maven编译
                sh '''
                    export PATH=$JAVA_HOME/bin:$PATH
                    mvn clean package -Dmaven.test.skip=true
                '''
                echo "编译完成"
            }
        }

        // 阶段2:滚动发布 - 服务1 (对应你的步骤1, 3)
        stage('滚动发布 - 服务1') {
            steps {
                script {
                    // 1. 调用Nacos接口,将服务1下线
                    deregisterFromNacos(SERVER_1_IP, SERVER_PORT)
                    
                    // 2. 替换jar包并重启服务1
                    deployAndRestartService(SERVER_USER, SERVER_1_IP)
                    
                    // 3. 等待服务1重新注册到Nacos并健康检查通过
                    waitForServiceUp(SERVER_1_IP, SERVER_PORT)
                }
            }
        }

        // 阶段3:滚动发布 - 服务2 (对应你的步骤4, 5)
        stage('滚动发布 - 服务2') {
            steps {
                script {
                    // 1. 调用Nacos接口,将服务2下线
                    deregisterFromNacos(SERVER_2_IP, SERVER_PORT)
                    
                    // 2. 替换jar包并重启服务2
                    deployAndRestartService(SERVER_USER, SERVER_2_IP)
                    
                    // 3. 等待服务2重新注册到Nacos并健康检查通过
                    waitForServiceUp(SERVER_2_IP, SERVER_PORT)
                }
            }
        }
    }
    
    post {
        success {
            echo "🎉 滚动发布全部完成!"
        }
        failure {
            echo "❌ 发布过程中出现异常,请检查日志!"
        }
        always {
            script {
                sendWeComNotification("1")
            }
        }
    }
}

// ================= 自定义函数区域 =================

// 函数:从 Nacos 注销指定实例
def deregisterFromNacos(String ip, String port) {
    echo "正在将实例 ${ip}:${port} 从 Nacos 下线..."
    // 获取 Nacos accessToken
    def authResponse = sh(script: "curl -X POST '${NACOS_SERVER}/nacos/v1/auth/users/login?username=${NACOS_USER}&password=${NACOS_PASSWORD}'", returnStdout: true).trim()
    // 简单的JSON解析获取token(实际生产环境建议使用 readJSON)
    def token = authResponse.substring(authResponse.indexOf('accessToken\":\"') + 14)
    token = token.substring(0, token.indexOf('\"'))
    
    // 调用注销接口
    sh """
        curl -X PUT '${NACOS_SERVER}/nacos/v1/ns/instance' \
        -d 'serviceName=${SERVICE_NAME}' \
        -d 'clusterName=DEFAULT' \
        -d 'groupName=DEFAULT_GROUP' \
        -d 'ip=${ip}' \
        -d 'port=${port}' \
        -d 'ephemeral=true' \
        -d 'weight=1' \
        -d 'enabled=false' \
        -d 'metadata={"region":"SH","env":"uat","preserved.register.source":"SPRING_CLOUD","version":"1.0"}' \
        -d 'namespaceId=${NACOS_NAMESPACE_ID}' \
        -d 'accessToken=${token}'
    """
    echo "实例 ${ip}:${port} 已请求下线,等待 Nacos 缓存刷新..."
    // 强制等待一段时间,确保调用方(如网关或其他微服务)的 Nacos 客户端缓存失效
    sleep time: 20, unit: 'SECONDS'
}

// 函数:远程替换 Jar 包并重启服务
def deployAndRestartService(String user, String serverIp) {
    echo "开始部署到服务器 ${serverIp}..."
    // 1. 传输 jar 包到目标服务器
    sh "scp bdo-pm-project-provider/target/bdo-pm-project-provider.jar ${user}@${serverIp}:${SERVER_PATH}/temp/"

    // 2. 远程执行重启命令 (这里使用简单的 kill 后启动,建议配合 start.sh 脚本)
    sh """
        ssh ${user}@${serverIp} "sh ${SERVER_PATH}/apps/restart.sh bdo-pm-project-provider.jar ${SERVER_PATH}"
    """
    echo "服务器 ${serverIp} 重启命令已发送。"
}

// 函数:等待服务启动并健康检查通过
def waitForServiceUp(String ip, String port) {
    echo "等待服务 ${ip}:${port} 启动并健康检查..."
    echo "等待30秒后再检测服务"
    sleep time: 30, unit: 'SECONDS'
    def maxRetries = 10
    def retryInterval = 5 // 秒
    def success = false
    
    for (int i = 0; i < maxRetries; i++) {
        // 这里假设你的微服务有 /actuator/health 健康检查接口
        def existCode = sh(script: "curl -s --connect-timeout 5 -o /dev/null -w '%{http_code}' http://${ip}:${port}/actuator/health", returnStatus: true)
        if (existCode == 0) {
            echo "✅ 服务 ${ip}:${port} 启动成功且健康检查通过!"
            success = true
            break
        }
        echo "服务尚未就绪,${retryInterval}秒后重试... (${i+1}/${maxRetries})"
        sleep time: retryInterval, unit: 'SECONDS'
    }
    
    if (!success) {
        error "❌ 服务 ${ip}:${port} 启动超时,发布失败!"
    }
}


// ================= 封装的企业微信通知方法 =================
def sendWeComNotification(String onlyFail) {
    // 获取当前构建的真实结果(SUCCESS 或 FAILURE)
    def buildStatus = currentBuild.currentResult
    // 根据构建状态,动态设置消息的 Emoji 图标、文字颜色和标题
    def statusEmoji = (buildStatus == 'SUCCESS') ? '🚀' : '❌'
    def statusText = (buildStatus == 'SUCCESS') ? '发布成功' : '发布失败'
    
    if (buildStatus == 'SUCCESS') {
        if (onlyFail == '1') {
            return
        }
    }
    
    def color = (buildStatus == 'SUCCESS') ? 'info' : 'warning' // 企业微信支持的颜色:info(绿色), warning(橙色)
    
    // 构造 Markdown 格式的消息内容
    // 注意:Groovy 三引号 """ """ 中的变量会自动替换,> 符号在企业微信 Markdown 中需要转义为 &gt;
    def content = """
### ${statusEmoji} ${SERVICE_NAME} ${statusText}
> **项目名称**:${env.JOB_NAME}
> **构建编号**:#${env.BUILD_NUMBER}
> **发布状态**:<font color='${color}'>${statusText}</font>
> **发布时间**:${new Date().format('yyyy-MM-dd HH:mm:ss')}
> **构建详情**:[点击查看完整日志](${currentBuild.absoluteUrl})
""".stripIndent().trim() // stripIndent() 去除每行前多余的缩进空格,trim() 去除首尾空行<websource>source_group_web_1</websource>

    // 构造企业微信机器人要求的 JSON 请求体
    def requestBody = """
{
    "msgtype": "markdown",
    "markdown": {
        "content": "${content}"
    }
}
"""
    // 发送 HTTP POST 请求到企业微信
    try {
        sh '''
            curl -X POST \
            -H "Content-Type: application/json" \
            -d \'''' + requestBody + '''\' \
            ''' + WECOM_WEBHOOK_URL + '''
        '''
        echo "✅ 企业微信通知发送成功!"
    } catch (Exception e) {
        echo "⚠️ 企业微信通知发送失败,请检查 Webhook 地址或网络连通性<websource>source_group_web_2</websource>。错误信息: ${e.getMessage()}"
    }
}
相关推荐
荣--16 小时前
一键部署不是为了省时间 —— 它是把"买来的 PaaS"变成"自己的平台"的拐点
运维·zabbix·工程化·一键部署·平台化·边界设计
江华森16 小时前
动手实战学 Docker — 从零到集群编排完全指南
运维
Avan_菜菜1 天前
FRP 内网穿透完整实战:从 HTTP 映射到 HTTPS 自签代理
运维·nginx·https
SelectDB2 天前
Litefuse 开源并推出单进程轻量模式,25 秒就能跑起来的 Agent 可观测与评估平台
运维·后端·自动化运维
XIAOHEZIcode4 天前
Linux系统鼠标偏移常见原因以及修复方案
linux·运维·游戏
用户0328472220704 天前
如何搭建本地yum源(上)
运维
大树887 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠7 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质7 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
Inhand陈工7 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信