Jenkins实战——以Spring Boot和Vue的Hello World应用为例

1. 从背景讲起

Jenkins实战需要有能够公网访问的服务器,可以是阿里/腾讯/华为的云服务器,也可以是自己搭的服务器/个人电脑+DDNS/FRP实现公网访问

最近,我正着手于鱼皮OJ项目的更新(打下广告哈~),前端使用 Vue3,后端基于 Spring Boot。为了方便展示技术成果,我希望把项目部署到自己搭建的服务器上(自建云服务教程),但随着功能的逐步完善,我发现每次改动都要伴随着大量"重复性劳动" ,例如:

  • 前端改个样式/修复个小BUG,需要手动build后上传到宝塔服务器;
  • 后端添加一个接口,需要重新打包jar,登录服务器上传文件并重启服务;
  • 多次改动后难以追踪版本、没有构建记录,部署出错也不易排查。

最初我的解决方案是:专门写一个文档,记录前后端部署的流程 ,每次更新就照着文档做一遍。但随着项目更新愈发频繁,我开始体会到手动交付流程的不可靠与低效

我之前就对Jenkins有所耳闻,大致知道它是一套自动化测试、构建、部署(CI/CD)的工具 ,但是一直没有去实践。这两天,我通过阅读Jenkins的官方文档对Jenkins进行了学习,最终实现了本地代码push,Jenkins自动拉取代码,构建并部署的效果,大大降低了重新部署项目的劳动负担。

下面,我就以Spring Boot和Vue的Hello World应用为例,记录下Jenkins的部署、使用的流程,希望对有同样痛点的小伙伴们有所帮助。

2. Jenkins的安装

推荐使用Docker来运行Jenkins

2.1 Jenkins-BlueOcean

Jenkins官方建议使用Blue Ocean镜像而非纯净的jenkins/jenkinsBlue Ocean版本开箱即用、UI现代简洁。以下是官方文档中手动build Blue Ocean镜像的Dockerfile:

bash 复制代码
FROM jenkins/jenkins:2.504.1-jdk21
USER root
RUN apt-get update && apt-get install -y lsb-release ca-certificates curl && \
    install -m 0755 -d /etc/apt/keyrings && \
    curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc && \
    chmod a+r /etc/apt/keyrings/docker.asc && \
    echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
    https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
    | tee /etc/apt/sources.list.d/docker.list > /dev/null && \
    apt-get update && apt-get install -y docker-ce-cli && \
    apt-get clean && rm -rf /var/lib/apt/lists/*
USER jenkins
RUN jenkins-plugin-cli --plugins "blueocean docker-workflow json-path-api"
复制代码
docker build -t myjenkins-blueocean:2.504.1-1

2.2 Jenkins容器的运行

Jenkins可以根据我们预设的触发条件,运行设置好的构建任务或流水线(编译、打包、测试、部署等),然而,Jenkins本身并不包含运行这些任务的环境(如maven、nodejs)等,如果想对从Git仓库拉取的源代码进行构建,主要通过三种策略:

  1. 使用Jenkins的工具与插件功能:这相当于在Jenkins容器的内部,安装maven等环境
  2. 使用宿主机的docker:让Jenkins操纵宿主机的docker,并在宿主机上临时创建用于构建的(maven、nodejs)容器,当构建任务完成后,删除容器
  3. 使用docker:dind:再单独创建一个docker-in-docker容器,让Jenkins操纵该docker(而非宿主机docker)

对比:

类型 优点 缺点
Jenkins的工具与插件 简单易上手 污染Jenkins容器内的环境,他人需要保持跟你相同的Jenkins容器环境才能复现
宿主机docker 构建所需的环境(maven、nodejs)可以直接写在Jenkinsfile配置文件中,与Jenkins容器无关,别人容易复现 让Jenkins容器直接操纵宿主机Docker可能存在一定安全风险
docker-in-docker 易复现,安全 需要创建Jenkins和docker-in-docker两个容器,配置最麻烦

尽管Jenkins的官方教程中推荐docker-in-docker这种方式,但我们这里仅作入门,所以使用前两种方式。下面是使用宿主机docker这种方式(不影响后续在Jenkins中使用工具与插件),运行Jenkins容器的指令:

javascript 复制代码
docker run -d \
    --name jenkins \
    --restart=unless-stopped \
    -u root \
    -p 8080:8080 -p 50000:50000 \
    -v /docker/jenkins/home:/var/jenkins_home \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v /root/.ssh:/root/.ssh \
    myjenkins-blueocean:2.504.1-1

-u root防止调用宿主机docker过程中的权限问题

-v /var/run/docker.sock:/var/run/docker.sock挂载宿主机的docker

-v /root/.ssh:/root/.ssh挂载宿主机的.ssh,确保后续Jenkins可以正常访问GitHub并拉取你的代码。.ssh文件夹下应该包含至少3个文件:id_rsaid_rsa.pubknown_hosts,其中id_rsa.pub应该已经添加至GitHub/Setting/SSH Keys。如果你没有,可以参考教程GitHub配置SSH Key的步骤,在你的服务器上执行里面的指令。当然,你也可以不挂载.ssh,不过后续还是要在Jenkins中配置凭据,更麻烦点,我这里就不提供教程了

接下来打开http://{你的服务器地址}:8080来访问Jenkins(前提是服务器防火墙放行8080端口),根据页面的指引,输入/docker/jenkins/home/secrets(跟你挂载卷的设置有关)下的初始密码,安装推荐插件并创建第一个用户


3. 基于Freestyle Job的Spring Boot Hello World应用自动构建部署

3.1 创建Spring Boot项目

首先创建一个简单的Spring Boot项目,其中提供一个/hello接口,返回一个Hello World字符串

kotlin 复制代码
@RestController
public class HelloController {
​
    @GetMapping("/hello")
    public String hello() {
        return "Hello World!";
    }
}

将该项目托管到GitHub。大家可以去我的仓库fork这个入门Spring Boot项目:qk-antares/jenkins-hello-world-backend

3.2 添加插件与工具

我们希望最终达到的效果是:当本地向远程仓库(GitHub)push代码时,Jenkins能自动拉取代码,打jar包并部署到指定的服务器上。要使Jenkins具备打jar包的能力,它需要maven环境,所以我们打开系统管理/全局工具配置,添加一个maven环境:

点几下很快就完成,这里我们需要记住设置的Name,后面要用到

3.3 添加SSH Server

我们需要提前配置项目将要部署的服务器。当Freestyle Job执行完打jar包的操作,可以通过ssh连接该服务器,上传jar包,并执行java -jar来运行项目

打开系统管理/系统配置,添加一个SSH Server:

  • 由于我要部署的服务器和Jenkins容器的宿主机本质是一台机器,所以你可以看到我这里的Hostname172.17.0.1这样一个Docker的bridge网关,Jenkins容器可以通过该地址访问到宿主机。如果你们的Jenkins容器和要部署的服务器不在一台机器上,这里就填实际的部署服务器的地址
  • Username是部署服务器上的一个真实用户,比如叫debian好了,那么部署服务器的/home/debian/.ssh下就要有对应的密钥,你可以拷贝前面提到的密钥(用一套密钥,就不用重新生成了)。也可以用root用户,同理/root/.ssh下应该有对应的密钥
  • /software/app是连上服务器后访问的目录,我们把build的产物上传到这里

3.4 创建Freestyle Job

在主页新建一个自由风格(Freestyle)的软件项目,然后对其进行配置:

  1. 源码管理设置仓库地址以及分支:

    如果你仓库这里报错了,证明Jenkins无法访问该仓库,检查前面提到的SSH设置

  2. Triggers 选择GitHub hook trigger for GITScm polling,并在GitHub的仓库中配置Webhooks:打开Settings/Webhooks,添加一个Webhook,配置如下(Payload URL需要填写你自己的Jenkins地址,需要能公网访问):

    这里的逻辑是,当你进行了一次push后,GitHub会通知Jenkins执行构建(打jar包,部署等任何你定义的步骤),所以说你的Jenkins需要能够公网访问

  3. Build Steps 添加调用顶层 Maven 目标,选择你上一步创建的maven环境,并配置命令clean package

  4. 构建后操作 添加Send build artifacts over SSH,选择前面创建的SSH Server,要上传的文件并写一个shell脚本来运行jar包:

    Exec command为:

    bash 复制代码
    #!/bin/bash
    cd /software/app/backend
    APP_NAME=$(ls *.jar | head -n 1)
    PORT=8180
    ​
    # kill old process
    PID=$(lsof -ti tcp:$PORT)
    if [ ! -z "$PID" ]; then
      echo "Killing old process $PID"
      kill $PID
    fi
    ​
    # start new app
    nohup /software/jdk-21.0.5/bin/java -jar $APP_NAME --server.port=$PORT > jenkins-hello-world-backend.log 2>&1 &

    8180我的Spring Boot Hello World项目的运行端口

    /software/jdk-21.0.5是我的JAVA_HOME,这里需要你的部署服务器安装了Java并根据实际情况进行配置

总结一下,Freestyle Job中配置了这么几件事:

  1. 源代码放在哪里,即Jenkins去哪里拉取最新的源代码
  2. 触发这个Job的时机是什么
  3. 拉取完源代码,要怎么build来得到一些产物(例如jar)
  4. build完了还要执行些什么操作(把产物上传,执行运行项目的脚本)

3.5 Job的触发

3.5.1 手动触发

创建完上述的Freestyle Job,我们可以点击立即构建来手动触发一次Job:

此时我们便可在build的日志中查看此次Job执行的控制台信息:

如果Job失败可以根据这里的控制台输出来Debug(git连接不上、SSH Server连接不上、maven环境、JDK环境...)

如果Job成功,我们可以尝试访问http://{你的服务器地址}:8180/hello来验证项目是否自动部署成功

3.5.2 自动触发

本地代码仓库的push行为也会触发Freestyle Job的执行,例如,我们可以修改hello接口的响应:

kotlin 复制代码
@RestController
public class HelloController {
​
    @GetMapping("/hello")
    public String hello() {
        return "Hello, Jenkins updated!";
    }
}

然后将最新的代码push到GitHub,我们可以在Settings/Webhooks/Recent Deliveries中查看到GitHub向Jenkins发送请求的信息:

同时在Jenkins中也重新执行了一次构建、部署的流程,访问http://{你的服务器地址}:8180/hello,我们会发现接口的响应变成了Hello, Jenkins updated!


4. 基于Pipeline Job的Vue Hello World应用自动构建部署

我们发现,Freestyle Job的整个配置过程都是在Jenkins的Web界面完成的,这虽然简单上手,但同时也有一些缺陷:

类型 优点 缺点
Freestyle 配置图形界面简单直观,几乎不用写脚本 难以维护复杂逻辑,如果有多个构建步骤,配置会很混乱 不能对构建的整个流程做版本控制,构建的配置保存在 Jenkins 中,无法保存为代码,可读性差,不适合团队协作 插件依赖强,不同 Jenkins 环境迁移麻烦
Pipeline 代码即配置(Jenkinsfile) ,支持配置本身的版本控制、审查、共享 更强的控制能力:条件构建、循环、分支、并发 可以实现与插件解耦,便于迁移、重建环境 需要对Jenkinsfile的语法结构有所了解

而Pipeline Job将构建的整个流程以Jenkinsfile配置文件的形式进行声明,Jenkinsfile一般保存在项目的根目录下,Jenkins读取它并执行整个构建过程。使用Pipeline Job可以实现构建流程本身的版本控制,对于一个开源项目来讲,也方便他人复现你项目的构建、部署流程

4.1 创建Vue3项目

首先创建一个简单的Vue3项目,使用Vue官方脚手架创建的模板项目就可以,大家也可以去我的仓库fork:qk-antares/jenkins-hello-world-frontend

4.2 添加插件与工具

尽管Pipeline Job支持调用宿主机的Docker,实现在Docker容器里build项目,从而达到完全不依赖Jenkins的插件/工具,但这里为了简单入门,还是像之前那样使用Jenkins的插件/工具。大家可以通过Jenkins官方文档的入门案例来学习如何在Docker容器里build项目(Creating your first Pipeline),其实主要就是Jenkinsfile使用agent:docker还是使用tools的区别

类似于前面添加maven环境那样,这次我们需要添加nodejs环境:

  • 记住这里的别名nodejs 18,后面会用到

  • pnpm作为一个全局的npm包进行安装

  • 如果你这里没有NodeJS环境的选项,需要去插件中安装NodeJS Plugin

4.3 创建Pipeline Job

在完成SSH Server的配置之后(参考3.3节),我们首先在宝塔面板中对部署服务器的Nginx进行配置,添加一个PHP项目,网站目录指向我们将要把前端构建产物上传到的目录:

接下来创建Pipeline Job。Pipeline Job的配置很简单:

  • 类似Freestyle Job,在Triggers中配置Pipeline触发的时机:GitHub hook trigger for GITScm polling。同时在Git仓库的Settings/Webhooks添加Webhook(参考3.4节)

  • 在流水线中配置Git地址:

4.4 Jenkinsfile

接下来,我们需要在Vue3项目中写一个Jenkinsfile,它以代码的形式配置构建的整个流程:

php 复制代码
pipeline {
    agent any
​
    // 使用Jenkins上的nodejs 18工具
    tools {
        nodejs 'nodejs 18'
    }
​
    stages {
        stage('Install Dependencies') {
            steps {
                sh 'pnpm install'
            }
        }
​
        stage('Build') {
            steps {
                sh 'pnpm run build'
            }
        }
​
        stage('Deploy') {
            steps {
                sshPublisher(publishers: [
                    sshPublisherDesc(
                        configName: 'host-deploy', // 名称要和你 SSH 配置里的 Name 完全一致!
                        transfers: [
                            sshTransfer(
                                sourceFiles: 'dist/**',
                                removePrefix: 'dist', // 删除路径前缀,保留 dist 下结构
                                remoteDirectory: './frontend/jenkins-hello-world-frontend', // 远程路径
                                execCommand: '''
                                    echo "✅ 文件上传完成,开始执行部署任务"
                                '''
                            )
                        ],
                        usePromotionTimestamp: false,
                        verbose: true
                    )
                ])
            }
        }
    }
​
    post {
        success {
            echo '部署成功 ✅'
        }
        failure {
            echo '部署失败 ❌'
        }
    }
}

总体来说,整个Jenkinsfile定义了3个stage,分别是依赖安装(sh 'pnpm install'),打包(sh 'pnpm run build')以及dist目录下产物的上传。我们无需在Jenkinsfile中配置git clone,因为Jenkins会自动帮我们做这件事

4.5 Job的触发

和Freestyle Job相同,可以进行手动触发,或者本地代码push到Git,会自动触发(参考3.5节)。

可以查看Pipeline Overview来对整个构建流程进行Debug:

如果Pipeline执行成功,可以通过4.3节配置的站点网址进行访问。

相关推荐
星释1 小时前
如何编写GitLab-CI配置文件
ci/cd·gitlab
It's Q16 小时前
从测试角度看待CI/CD,敏捷开发
ci/cd·自动化·敏捷流程
t198751281 天前
jenkins结合gitlab实现CI
ci/cd·gitlab·jenkins
星释2 天前
Golang持续集成与自动化测试和部署
开发语言·ci/cd·golang
程序员Bears2 天前
云部署实战:基于AWS EC2/Aliyun ECS与GitHub Actions的CI/CD全流程指南
ci/cd·github·aws
迢迢星万里灬3 天前
Java求职者面试指南:DevOps技术栈深度解析
java·ci/cd·docker·kubernetes·jenkins·devops
茶本无香3 天前
Jenkins:自动化流水线的基石,开启 DevOps 新时代
自动化·jenkins·devops
m0_555762903 天前
GitLab CI、GitHub Actions和Jenkins进行比较
ci/cd·gitlab·github