Jenkins + Spring Boot + GitHub 实现自动化部署

2024 年 7 月 27 日 星期六(已编辑)
/ , ,
107
摘要
使用基于 docker 的 Jenkins 构建 Spring Boot 项目
这篇文章上次修改于 2024 年 9 月 2 日 星期一,可能部分内容已经不适用,如有疑问可询问作者。

阅读此文章之前,你可能需要首先阅读以下的文章才能更好的理解上下文。

Jenkins + Spring Boot + GitHub 实现自动化部署

环境:

  1. Jenkins 部署在 docker 中
  2. 本地开发 Spring Boot 的 Demo (配合 Dockerfile 进行构建)
  3. 服务器:腾讯云 Debian GNU/Linux 11 (bullseye)

步骤:

Docker 安装 Jenkins

ps:没安装的看这里有安装教程

在 Jenkins 容器中配置需要的环境

ps:也可以在 Jenkins 客户端配置 JDK 和 Maven ,但是第一次构建的时候会导致速度很慢,我就在本地安装了

(也可以把本机的 JDK 和 Maven 环境挂载到 Jenkins 容器中)

首先进入Jenkins

docker exec -it --user root <container-id> /bin/bash

安装 JDK

ps:安装前先检查(java -version),Jenkins 有的自带的有 JDK,如果有了就跳过这一步

  1. 安装
apt install default-jdk

ps:默认 openjdk:17 版本

  1. 检查安装:
java -version
  1. 查看环境变量
echo $PATH

ps:列出所有环境变量,保存 openjdk 的即可

示例输出:/root/.nvm/versions/node/v20.14.0/bin:/usr/java/jdk-17.0.11+9/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin jdk 就是/usr/java/jdk-17.0.11+9/bin

安装 Maven

  1. 执行命令
apt update && apt install maven
  1. 检查版本
mvn -v

输出如下:

root@9ae2b288441d:/# mvn -v
Apache Maven 3.8.7
Maven home: /usr/share/maven
Java version: 17.0.12, vendor: Eclipse Adoptium, runtime: /opt/java/openjdk
Default locale: en, platform encoding: UTF-8
OS name: "linux", version: "5.10.0-26-amd64", arch: "amd64", family: "unix"
  1. 保存 Maven 路径/usr/share/maven
  2. 配置阿里云镜像(一定要配!不然以我们自己使用的这种 2 核 2G 的服务器,下载速度会很慢!!!)

/usr/share/maven/conf/settings.xml文件中,<mirrors></mirrors>标签中间添加下面的代码

<mirror>
    <id>nexus-aliyun</id>
    <mirrorOf>central</mirrorOf>
    <name>Nexus aliyun</name>
    <url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>

配置 ssh 密钥

为了方便 Jenkins 从 GitHub 使用 SSH 拉取代码,如果不需要则可以不配置(如果 HTTPS 可以拉取就不用)

  1. 获取并添加 GitHub 主机密钥

防止出现Host key verification failed错误

# 切换到 Jenkins 用户(如果不能切换,退出容器,重新以普通角色进入,去掉 --user root 的命令即可)
su - jenkins

# 创建 .ssh 目录
mkdir -p ~/.ssh

# 设置 .ssh 目录权限
chmod 700 ~/.ssh

# 获取 GitHub 主机密钥并添加到 known_hosts
ssh-keyscan github.com >> ~/.ssh/known_hosts

# 设置 known_hosts 文件权限
chmod 644 ~/.ssh/known_hosts

# 测试连接 GitHub 仓库
ssh -T git@github.com
  1. 生成 SSH 密钥对

如果没有 SSH 密钥对,而直接以 SSH 方式拉取会报错:Permission denied (publickey)

如果有密钥对,可以跳过这一步。直接复制.ssh/id_rsa.pub的内容

# 生成密钥对,后面一直按空格即可
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
# 获取公钥内容,并复制
cat ~/.ssh/id_rsa.pub

然后在github中添加 SSH key

  • 进入 Settings > SSH and GPG keys > New SSH key
  • 将公钥粘贴到 SSH key 中并保存。

注意:

确认 SSH 密钥文件权限

chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_rsa
chmod 644 ~/.ssh/id_rsa.pub

测试 SSH 连接

ssh -T git@github.com

如果配置正确,你应该看到类似以下的信息:

Hi <username>! You've successfully authenticated, but GitHub does not provide shell access.

到此,Jenkins 就可以使用 ssh 拉取 github 的代码了

到 Jenkins 客户端配置

安装插件

ps:除了刚安装 Jenkins 时候的推荐安装插件,其余需要安装的如下:

  1. Maven Integration (Maven 集成插件,可以执行 Maven 的各种命令)
  2. SSH (这个是使用 SSH 协议来执行打包后的 shell 命令的,这个很多年没维护了,有漏洞,但是目前没找到平替的,只能委屈使用这个了)
  3. Publish Over SSH (通过 SSH 发送打包好的 jar 文件)
  4. SSH Server (这个忘了推荐安装里面有没有了,如果有的话就不用安装了)

全局工具配置

ps:这一步是为了创建 Maven 任务需要添加的配置

  1. Maven 配置使用默认即可
  2. Maven 安装,名称随便填即可,设置 Maven 目录,如上述输出/usr/share/maven
  3. JDK 安装,名称随便填即可,JAVA_HOME 从上述的环境变量中取出,删掉后面的 /bin 即可
  4. 最后记得保存

凭据管理

  1. github 用户凭证
  • 类型:Username with password
  • 范围:全局(Jenkins,nodes,items,all child items,etc)
  • 用户名:github 用户名
  • Treat username as secret:勾选
  • 密码:github 登录密码
  • ID:不填
  • 描述:github-login(随意填)
  1. 服务器登录凭证(参考上面)
  1. 最后记得保存

系统配置

  1. SSH Remote Hosts(SSH sites)
  • Hostname:服务器IP

  • Port:22
  • Credentials:选择服务器的登录凭证(上面添加的)
  • serverAliveInterval:100 (根据个人需求修改)
  • timeout:100(根据个人需求修改)
  1. SSH Server
  • Name:自定义即可
  • Hostname:服务器IP
  • Username:服务器登录用户名
  • Remote Directory:远程目录,随便设置即可(但是必须要保证目录存在)
  1. 最后记得保存

新建任务

这里罗列了两种不同的新建任务方式

  • Maven 任务只适用于基于 Maven 构建的项目
  • Pipeline 任务包含 Maven 任务,还可以实现更多其他的选项(推荐)

新建Maven项目

  1. 填写任务名称
  2. 本次测试是使用 Maven,选择构建一个Maven项目
  3. 勾选 Github 项目,填入项目 URL (SSH 即可)
  4. 源码管理 -> Git
    1. 输入 Repository URL (SSH 即可)
    2. 添加凭证(上述 GitHub 用户凭证)
    3. 指定分支:Branches to build - > */master
  5. 构建触发器
    1. 勾选Generic Webhook Trigger
    2. 添加 Token(所有的触发器前缀 URL 一样,添加 Token 来辨别不同的触发器
    3. 其余什么都不选(有需求可以自行添加)
  6. Build -> Goals and options:clean package
  7. Post Steps -> Run regardless of build result
    1. 选择执行 shell 命令
    2. 输入 shell 脚本(shell 示例内容在下面附录
  8. 保存即可

新建流水线项目(推荐)

  1. 输入任务名称
  2. 选择“流水线”
  3. 勾选自己使用的 SCM
  4. 填写仓库 URL
  5. 构建触发器参考上面
  6. 定义流水线(流水线示例代码在下面附录
  7. 保存即可

GitHub 配置 Webhook

ps:给仓库配置 webhook,和上面的触发器连接

步骤如下:

  1. 打开对应 GitHub 存储库
  2. 点击 settings,左侧 Webhooks
  3. 右上角 add webhook
  4. Payload URL:http://<Jenkins-Url>/generic-webhook-trigger/invoke?token=xxxx(token填上面的)
  5. 下面可以选择对应想要触发的操作(push,pull Requeest等)
  6. 点击保存即可。

附录

sh 脚本(基于Maven任务的)

ps:构建完成后的执行的脚本

#!/bin/bash

# 服务名称
SERVER_NAME=jenkins-demo1

# 工程所在路径(根据自己情况进行调整)
APP_HOME=/var/jenkins_home/workspace/$SERVER_NAME

# maven打包后的jar包名
JAR_NAME=demo1-0.0.1-SNAPSHOT.jar

# jar包的目录
JAR_PATH=${APP_HOME}/target

# 杀死之前的进程
PID_FILE="${APP_HOME}"/"${SERVER_NAME}".pid
if [ -f "${PID_FILE}" ];then
PID=`cat "$PID_FILE"` && kill -9 $PID && echo "kill process "${PID}" finished!"
fi

cd $JAR_PATH

# 修改文件权限
chmod 755 $JAR_NAME

echo "当前路径:"

pwd

# 启动服务
BUILD_ID=dontKillMe nohup java -jar $JAR_NAME &

echo "nohup java -jar ${JAR_NAME} >output.log &"

echo "java程序启动!!!"

# 将新进程ID写到文件中
JAVA_PID=$!
echo "${JAVA_PID}" > "${PID_FILE}"

pipeline 脚本

ps:pipeline 脚本实现了以下步骤

  • 拉取代码
  • 编译构建
  • 移除原有的镜像和容器
  • 构建新的镜像和容器并运行
pipeline {
    agent any
    environment {
        GIT_URL = 'git@github.com:yannqing/YanOJ-SandBox.git'
        APP_NAME = 'sandbox'
        APP_PROFILE = 'prod'
        APP_IMAGE = 'yanojsandbox:latest'
        APP_PORT = '8091:8080'
    }

    stages {
        stage('拉取代码') {
            steps {
                git branch: 'master', url: GIT_URL
            }
        }
        stage('编译构建') {
            steps {
                sh "mvn clean package"
            }
        }
        stage('移除镜像和容器') {
            steps {
                script {
                    // 检查容器是否存在
                    def containerExists = sh(script: 'docker ps -a --format "{{.Names}}" | grep -q "^${APP_NAME}"', returnStatus: true) == 0
                    // 检查镜像是否存在
                    def imageExists = sh(script: 'docker images --format "{{.Repository}}:{{.Tag}}" | grep -q "^${APP_IMAGE}$"', returnStatus: true) == 0

                    // 如果容器存在,则停止和移除
                    if (containerExists) {
                        echo "容器存在"
                        def isRunning = sh(script: 'docker ps --format "{{.Names}}" | grep -q "^${APP_NAME}"', returnStatus: true) == 0
                        if (isRunning) {
                            sh "docker stop ${APP_NAME}"
                        }
                        echo "删除容器"
                        sh "docker rm ${APP_NAME}"
                    } else {
                        echo "容器不存在"
                    }

                    // 如果镜像存在,则移除
                    if (imageExists) {
                        echo "删除镜像"
                        sh "docker rmi ${APP_IMAGE}"
                    } else {
                        echo "镜像不存在"
                    }
                }
            }
        }
        stage('构建镜像,创建并运行容器') {
            steps {
                // 基于 Dockerfile 进行构建
                sh "docker build -f Dockerfile.dockerfile -t ${APP_IMAGE} ."
                sh "docker run -it --name ${APP_NAME} -v /yannqing/${APP_NAME}:/yannqing/${APP_NAME} -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -p ${APP_PORT} -d ${APP_IMAGE}"
            }
        }
    }
}

Dockerfile 文件

# 使用 OpenJDK 17 slim 版本作为基础镜像
FROM openjdk:17-slim

# 设置维护者标签
LABEL maintainer="yannqing <yannqing.com>"
LABEL version="1.0"
LABEL description="YanOJ SandBox"

# 设置工作目录
WORKDIR /yannqing/sandbox/java

# 创建一个挂载点
VOLUME /yannqing/sandbox/logs

# 复制应用程序
COPY ./target/yanoj-code-sandbox-0.0.1-SNAPSHOT.jar /tmp/app.jar

# 暴露端口
EXPOSE 8091

# 启动命令
CMD ["java", "-jar", "/tmp/app.jar"]
  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...