deployJar.groovy 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. #!/usr/bin/env groovy
  2. /**
  3. * 强验证发布 SpringBoot fat-jar
  4. * - 按端口杀进程
  5. * - 按 profile 做所有进程级校验
  6. */
  7. def call(Map args = [:]) {
  8. String jarName = args.jarName ?: env.JAR_NAME
  9. String jarPath = args.jarPath ?: env.JAR_PATH
  10. String remoteHost = args.remoteHost ?: env.REMOTE_HOST
  11. String remotePort = args.remotePort ?: env.REMOTE_PORT
  12. String remotePath = args.remotePath ?: env.REMOTE_PATH
  13. String profile = args.profile ?: env.PROFILE
  14. String jvmArgs = args.jvmArgs ?: env.JVM_ARGS ?: ''
  15. String remoteJar = "${remotePath}/${jarName}"
  16. String javaCmd = args.javaCmd ?: env.JAVA_CMD ?: 'java'
  17. PrintMes("开始发布 ${jarName} [profile=${profile}] 到 ${remoteHost}:${remotePort}", 'yellow')
  18. try {
  19. /* 1. 按端口杀旧进程,并确认端口已释放 */
  20. PrintMes("→ 1. 开始清理旧进程,确保端口 ${remotePort} 释放", 'yellow')
  21. sh """
  22. ssh -o StrictHostKeyChecking=no ${remoteHost} '
  23. OLD_PID=\$(lsof -ti:${remotePort} || true)
  24. if [ -n "\$OLD_PID" ]; then
  25. echo "杀掉占用端口 ${remotePort} 的进程 \$OLD_PID"
  26. kill -9 \$OLD_PID
  27. sleep 3
  28. fi
  29. if lsof -ti:${remotePort}; then
  30. echo "端口 ${remotePort} 仍未释放,杀进程失败"
  31. exit 1
  32. fi
  33. '
  34. """
  35. PrintMes("√ 1. 旧进程清理完成,端口 ${remotePort} 已释放", 'green')
  36. /* 2. 备份旧包 */
  37. PrintMes("→ 2. 开始备份旧 JAR 包 ${remoteJar}", 'yellow')
  38. sh """
  39. ssh -o StrictHostKeyChecking=no ${remoteHost} '
  40. test -f ${remoteJar} && mv ${remoteJar} ${remoteJar}-\$(date +%Y%m%d%H%M) || true
  41. '
  42. """
  43. PrintMes("√ 2. 旧 JAR 包备份完成", 'green')
  44. /* 3. 传包 + MD5 强校验 */
  45. PrintMes("→ 3. 开始传输 JAR 包并进行 MD5 强校验", 'yellow')
  46. // 1. 在本地计算 MD5
  47. String localMd5 = sh(script: "md5sum ${jarPath} | awk '{print \$1}'", returnStdout: true).trim()
  48. PrintMes(" 本地 MD5: ${localMd5}", 'yellow')
  49. // 2. 传输 JAR 包
  50. sh "scp -o StrictHostKeyChecking=no ${jarPath} ${remoteHost}:${remoteJar}"
  51. // 3. 远程计算 MD5 并返回
  52. String remoteMd5Output = sh(
  53. script: "ssh -o StrictHostKeyChecking=no ${remoteHost} 'md5sum ${remoteJar}'",
  54. returnStdout: true
  55. ).trim()
  56. // 4. 在 Jenkins Groovy 侧处理输出,提取 MD5 值
  57. String remoteMd5 = remoteMd5Output.split('\\s+')[0]
  58. PrintMes(" 远程 MD5: ${remoteMd5}", 'yellow')
  59. if (localMd5 != remoteMd5) {
  60. PrintMes("× 3. MD5 校验失败:本地 ${localMd5} != 远程 ${remoteMd5}", 'red')
  61. error("MD5 校验失败:本地 ${localMd5} != 远程 ${remoteMd5}")
  62. }
  63. PrintMes("√ 3. JAR 包传输和 MD5 校验成功", 'green')
  64. /* 4. 启动新进程 */
  65. PrintMes("→ 4. 启动新进程 (profile=${profile})", 'yellow')
  66. sh """ssh -o StrictHostKeyChecking=no ${remoteHost} "sh -c 'cd ${remotePath} && nohup ${javaCmd} ${jvmArgs} -jar ${jarName} --spring.profiles.active=${profile} > server.log 2>&1 &'" """
  67. PrintMes("√ 4. 新进程启动命令已发送", 'green')
  68. /* 5. 按 profile 精确校验:有且仅有 1 个进程 */
  69. PrintMes("→ 5. 校验进程数量 (期望 1 个 ${profile} 进程)", 'yellow')
  70. String procCount = sh(
  71. script: "ssh -o StrictHostKeyChecking=no ${remoteHost} 'ps -ef | grep \"java.*${jarName}.*--spring.profiles.active=${profile}\" | grep -v grep | wc -l'",
  72. returnStdout: true
  73. ).trim()
  74. PrintMes(" 实际找到 ${procCount} 个进程", 'yellow')
  75. if (procCount != "1") {
  76. PrintMes("× 5. 启动校验失败:期望 1 个 ${profile} 进程,实际 ${procCount} 个", 'red')
  77. error("启动校验失败:期望 1 个 ${profile} 进程,实际 ${procCount} 个")
  78. }
  79. PrintMes("√ 5. 进程数量校验成功", 'green')
  80. /* 6. 端口已被新进程占用 (加入等待重试机制) */
  81. def maxRetries = 15 // 最多重试 15 次
  82. def waitTimeSec = 5 // 每次间隔 5 秒
  83. String newPid = ""
  84. PrintMes("→ 6. 校验端口 ${remotePort} 是否已被新进程监听 (最大等待 ${maxRetries * waitTimeSec} 秒)", 'yellow')
  85. for (int i = 0; i < maxRetries; i++) {
  86. newPid = sh(script: "ssh -o StrictHostKeyChecking=no ${remoteHost} 'lsof -ti:${remotePort} || true'", returnStdout: true).trim()
  87. if (newPid) {
  88. PrintMes(" 端口已监听,PID: ${newPid}", 'yellow')
  89. break // 找到 PID,跳出循环
  90. }
  91. if (i < maxRetries - 1) {
  92. PrintMes(" 未监听,等待 ${waitTimeSec} 秒后重试... (${i + 1}/${maxRetries})", 'yellow')
  93. sleep(waitTimeSec) // Jenkins Pipeline 内置步骤
  94. }
  95. }
  96. if (!newPid) {
  97. PrintMes("× 6. 端口 ${remotePort} 在 ${maxRetries * waitTimeSec} 秒内未被新进程监听,启动可能失败", 'red')
  98. error("端口 ${remotePort} 未被新进程监听,启动超时失败")
  99. }
  100. PrintMes("√ 6. 端口监听校验成功", 'green')
  101. // 最终成功提示
  102. PrintMes("发布成功 ${jarName} [profile=${profile}, pid=${newPid}]", 'green')
  103. } catch (Exception e) {
  104. // 捕获异常,打印红色失败信息
  105. PrintMes("发布失败 ${jarName} : ${e.message}", 'red')
  106. error('deploySpringBoot failed')
  107. }
  108. }