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 中需要转义为 >
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()}"
}
}