Jenkins + Maven + Git 实现 Spring Boot 自动化部署

作者:old wang 发布时间: 2021-07-03 阅读量:3 评论数:0

在日常开发中,Spring Boot 项目部署通常会经历这些步骤:

拉取代码
本地打包
上传服务器
停止旧进程
启动新 Jar 包
查看日志
验证接口

如果每次都手动操作,不仅效率低,也容易出错。

本文记录一种基于 Jenkins、Maven、Git 和 SSH Publisher 的简单自动化部署流程。

整体流程如下:

开发者提交代码到 GitLab
        ↓
Jenkins 拉取代码
        ↓
Jenkins 使用 Maven 打包 Spring Boot 项目
        ↓
Jenkins 通过 SSH 将 Jar 包上传到测试服务器
        ↓
测试服务器清理旧进程和旧 Jar 包
        ↓
启动新的 Spring Boot 应用

一、环境准备

本文示例使用三台服务器:

服务器

IP

配置

用途

gitlab99

192.168.40.99

8C8G

GitLab

jenkins98

192.168.40.98

4C4G

Jenkins、JDK、Maven、Git

test97

192.168.40.97

2C2G

测试环境,运行 Spring Boot Jar

整体职责如下:

  • GitLab:保存项目代码;

  • Jenkins:负责拉代码、编译、打包、发布;

  • 测试服务器:负责运行最终的 Spring Boot 服务。

二、创建 GitLab 项目

先在 GitLab 中创建一个空白项目。

基本流程是:

创建 Group
创建 Project
获取远程仓库地址
创建 Access Token

Access Token 后续用于 IDEA 或 Jenkins 访问 GitLab 仓库。

创建完成后,可以拿到类似下面的仓库地址:

http://192.168.40.99/group-name/project-name.git

三、创建 Spring Boot 测试项目

在 IDEA 中创建一个简单的 Spring Boot 项目。

依赖选择:

Spring Web

配置端口:

server.port=8088

创建测试 Controller:

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/")
public class HelloController {

    @RequestMapping
    public String sayHello() {
        return "Hello dev";
    }
}

本地启动后访问:

http://127.0.0.1:8088

如果返回:

Hello dev

说明项目可以正常运行。

四、将代码推送到 GitLab

在 IDEA 中配置 Git。

路径大致为:

Settings -> Git -> Path to Git executable

然后将当前项目初始化为 Git 仓库:

Version Control -> Create Git Repository

配置远程仓库:

Git -> Manage Remotes

填入 GitLab 项目的 HTTP 地址。

之后执行:

Commit
Push

将代码推送到 GitLab。

如果 GitLab 使用分支保护或 Merge Request 流程,需要在 GitLab 页面完成代码合并。

五、Jenkins 安装 Maven 插件

Jenkins 需要使用 Maven 构建项目,因此需要安装 Maven 插件。

路径:

Dashboard -> Manage Jenkins -> Plugins -> Available plugins

搜索:

Maven

安装 Maven Integration 相关插件。

安装完成后,回到 Jenkins 首页。

六、创建 Jenkins 构建任务

新建 Jenkins 任务:

Dashboard -> New Item

任务类型可以选择 Maven 项目。

例如任务名称:

first

进入任务配置页面后,配置 Git 仓库地址和凭据。

七、配置 Git 仓库

在 Jenkins 任务配置中找到源码管理部分。

选择:

Git

填写仓库地址:

http://192.168.40.99/group-name/project-name.git

添加 GitLab 凭据。

如果使用 Access Token,可以将用户名和 Token 配置到 Jenkins 凭据中。

然后配置要构建的分支,例如:

*/main

或者:

*/master

具体取决于 GitLab 项目的默认分支。

八、配置 Maven

在 Jenkins 全局工具配置中配置 Maven。

路径:

Manage Jenkins -> Tools

配置 Maven 安装路径。

例如:

/usr/local/maven/apache-maven-3.x.x

在任务配置中,Root POM 通常保持默认:

pom.xml

如果项目是多模块结构,需要根据实际路径调整。

例如:

backend/pom.xml

Goals 可以配置为:

clean package -DskipTests

如果需要执行测试,可以去掉:

-DskipTests

九、测试 Jenkins 打包

保存任务配置后,点击:

Build Now

进入构建历史,查看控制台输出。

如果构建成功,Jenkins 会在 workspace 下生成 Jar 包。

示例路径:

/root/.jenkins/workspace/first/target/

可以在 Jenkins 服务器上测试运行:

java -jar jenkins-study-0.0.1-SNAPSHOT.jar

如果项目正常启动,说明 Jenkins 已经可以完成代码拉取和 Maven 打包。

十、常见打包问题

如果执行:

java -jar jenkins-study-0.0.1-SNAPSHOT.jar

报错:

no main manifest attribute

通常说明 Spring Boot Maven 插件没有正确参与打包。

需要检查 pom.xml 中是否配置了:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

如果配置中有 skip,需要确认是否被设置成了:

<skip>false</skip>

确保最终打出来的是可执行 Spring Boot Jar。

十一、安装 SSH Publisher 插件

Jenkins 要把 Jar 包上传到测试服务器,可以使用 SSH Publisher 插件。

插件安装路径:

Dashboard -> Manage Jenkins -> Plugins -> Available plugins

搜索:

SSH Publisher

安装完成后,进入系统配置页面。

十二、配置测试服务器 SSH 信息

路径:

Dashboard -> Manage Jenkins -> System

找到:

Publish over SSH

新增一台服务器。

示例配置:

Name: test97
Hostname: 192.168.40.97
Username: root
Remote Directory: /root

如果使用密码登录,就配置密码。

如果使用密钥登录,就配置私钥。

配置完成后点击:

Test Configuration

确认 Jenkins 能正常连接测试服务器。

十三、配置构建后上传 Jar 包

进入 Jenkins 任务配置:

Dashboard -> first -> Configuration

找到:

Post Steps

选择:

Send files or execute commands over SSH

配置上传文件。

示例:

Source files: target/jenkins*.jar
Remove prefix: target
Remote directory: /root/jenkins-study

含义:

  • Source files:从 Jenkins workspace 中匹配 Jar 包;

  • Remove prefix:去掉上传路径中的 target 目录;

  • Remote directory:上传到测试服务器的目标目录。

例如最终 Jar 包会上传到:

/root/jenkins-study/jenkins-study-0.0.1-SNAPSHOT.jar

十四、启动远程 Jar 包

在 SSH Publisher 的 Exec command 中配置启动命令:

nohup java -jar /root/jenkins-study/jenkins*.jar >> /root/jenkins-study/log.out 2>&1 &

这条命令的含义是:

nohup:忽略挂起信号,终端关闭后进程仍然运行
>>:将标准输出追加到 log.out 文件
2>&1:将错误输出重定向到标准输出
&:后台运行

构建完成后,可以在测试服务器执行:

jps

如果看到项目进程,说明启动成功。

十五、远程服务器找不到 Java 的问题

如果测试服务器日志中出现:

nohup: failed to run command ‘java’: No such file or directory

说明 Jenkins 通过 SSH 执行命令时,没有拿到正确的 Java 环境变量。

可以在测试服务器上配置 /etc/bashrc

export JAVA_HOME=/usr/local/jdk/jdk-17.0.12
export PATH=$PATH:$JAVA_HOME/bin
export CLASSPATH=.:$JAVA_HOME/lib/

然后执行:

source /etc/bashrc

也可以在 Jenkins 的启动命令中直接使用 Java 绝对路径,例如:

nohup /usr/local/jdk/jdk-17.0.12/bin/java -jar /root/jenkins-study/jenkins*.jar >> /root/jenkins-study/log.out 2>&1 &

这种方式更直接,也不依赖 Shell 环境变量。

十六、为什么需要清理旧进程

如果第一次构建已经启动了应用,后续再次构建时直接执行:

nohup java -jar /root/jenkins-study/jenkins*.jar >> /root/jenkins-study/log.out 2>&1 &

可能会启动失败。

原因是旧进程还在占用端口。

例如应用端口是:

8088

旧进程没有停止,新进程启动时就会报端口占用。

表现可能是:

jps 看到的进程 PID 一直没变
log.out 中出现端口被占用

所以每次部署前需要先清理旧进程和旧 Jar 包。

十七、编写清理脚本

在测试服务器 /root 目录下创建脚本:

vim /root/clean.sh

内容如下:

#!/bin/bash

appname=$1

if [ -z "$appname" ]; then
    echo "应用名称不能为空!"
    exit 1
else
    echo "应用名称为 $appname"
fi

# 清理旧版本 jar 包
rm -rf /root/$appname/${appname}*.jar

# 获取正在运行的项目 pid
pid=$(ps -ef | grep "$appname" | grep 'java -jar' | grep -v grep | awk '{print $2}')

echo "pid 为 $pid"

if [ -z "$pid" ]; then
    echo "$appname is not started"
else
    kill -9 $pid
    echo "$appname was stopped"
fi

赋予执行权限:

chmod +x /root/clean.sh

脚本参数说明:

$1:脚本第一个参数,也就是应用名称
-z:判断字符串是否为空
ps -ef:查看进程
grep:过滤应用进程
awk '{print $2}':取进程 PID

十八、配置 Jenkins Pre Steps

在 Jenkins 任务配置中添加 Pre Steps。

选择:

Send files or execute commands over SSH

执行清理命令:

/root/clean.sh jenkins-study

这样每次构建时会先执行:

停止旧进程
删除旧 Jar 包

然后再上传新 Jar 包并启动。

重新构建后,可以在测试服务器查看:

jps

如果 PID 发生变化,说明旧应用已经停止,新应用已经启动。

十九、测试部署结果

部署完成后访问测试服务器:

http://192.168.40.97:8088

如果返回:

Hello dev

说明 Jenkins 自动化部署流程已经跑通。

完整流程是:

GitLab 拉取代码
Maven 打包
SSH 上传 Jar 包
执行清理脚本
启动新应用
访问接口验证

二十、目前方案的问题

这套流程可以完成基础自动化部署,但仍然比较简化。

实际项目中还需要继续完善。

1. 不建议直接 kill -9

示例中使用:

kill -9 $pid

这是强制停止进程。

更稳妥的方式是先使用:

kill $pid

等待应用优雅关闭。

如果超时后还没退出,再考虑:

kill -9 $pid

这样可以给 Spring Boot 应用释放资源的机会。

2. 没有做版本备份

当前脚本会直接删除旧 Jar 包。

如果新版本启动失败,不方便快速回滚。

更好的方式是:

保留最近几个版本
部署失败时回滚上一个 Jar

3. 没有健康检查

当前启动后只是通过 jps 判断进程是否存在。

但进程存在不代表应用真正可用。

可以增加健康检查接口,例如:

/actuator/health

部署后自动请求健康检查接口。

只有返回正常,才认为部署成功。

4. 没有处理多环境配置

实际项目通常有:

dev
test
prod

不同环境需要不同配置。

可以通过启动参数指定:

--spring.profiles.active=test

例如:

nohup java -jar /root/jenkins-study/jenkins*.jar --spring.profiles.active=test >> /root/jenkins-study/log.out 2>&1 &

5. 凭据和 Token 需要安全管理

GitLab Token、SSH 密码、私钥等不要写在脚本或日志中。

应该统一放到 Jenkins Credentials 中管理。

二十一、可以继续优化的方向

后续可以继续改进:

  1. 使用 Jenkins Pipeline 管理构建流程;

  2. 使用 Git Tag 或 Commit ID 标记部署版本;

  3. 增加构建前单元测试;

  4. 增加部署后健康检查;

  5. 增加失败回滚脚本;

  6. 使用 systemd 管理 Spring Boot 进程;

  7. 使用 Docker 镜像部署;

  8. 使用 Webhook 实现代码提交后自动触发构建;

  9. 增加钉钉、企业微信或邮件通知;

  10. 区分测试环境和生产环境的部署权限。

结论

本文记录了一套简单的 Jenkins 自动化部署流程。

核心步骤是:

  1. GitLab 保存代码;

  2. Jenkins 拉取代码;

  3. Maven 构建 Spring Boot Jar;

  4. SSH Publisher 上传 Jar 到测试服务器;

  5. Pre Steps 执行清理脚本;

  6. Post Steps 启动新 Jar;

  7. 访问接口验证部署结果。

评论