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/jenkins
,Blue 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仓库拉取的源代码进行构建,主要通过三种策略:
- 使用Jenkins的工具与插件功能:这相当于在Jenkins容器的内部,安装maven等环境
- 使用宿主机的docker:让Jenkins操纵宿主机的docker,并在宿主机上临时创建用于构建的(maven、nodejs)容器,当构建任务完成后,删除容器
- 使用
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_rsa
、id_rsa.pub
、known_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容器的宿主机本质是一台机器,所以你可以看到我这里的
Hostname
是172.17.0.1
这样一个Docker的bridge网关,Jenkins容器可以通过该地址访问到宿主机。如果你们的Jenkins容器和要部署的服务器不在一台机器上,这里就填实际的部署服务器的地址 Username
是部署服务器上的一个真实用户,比如叫debian
好了,那么部署服务器的/home/debian/.ssh
下就要有对应的密钥,你可以拷贝前面提到的密钥(用一套密钥,就不用重新生成了)。也可以用root
用户,同理/root/.ssh
下应该有对应的密钥/software/app
是连上服务器后访问的目录,我们把build的产物上传到这里
3.4 创建Freestyle Job
在主页新建一个自由风格(Freestyle)的软件项目,然后对其进行配置:
-
源码管理设置仓库地址以及分支:
如果你仓库这里报错了,证明Jenkins无法访问该仓库,检查前面提到的SSH设置
-
Triggers 选择GitHub hook trigger for GITScm polling,并在GitHub的仓库中配置Webhooks:打开
Settings/Webhooks
,添加一个Webhook,配置如下(Payload URL需要填写你自己的Jenkins地址,需要能公网访问):这里的逻辑是,当你进行了一次push后,GitHub会通知Jenkins执行构建(打jar包,部署等任何你定义的步骤),所以说你的Jenkins需要能够公网访问
-
Build Steps 添加
调用顶层 Maven 目标
,选择你上一步创建的maven环境,并配置命令clean package
: -
构建后操作 添加
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中配置了这么几件事:
- 源代码放在哪里,即Jenkins去哪里拉取最新的源代码
- 触发这个Job的时机是什么
- 拉取完源代码,要怎么build来得到一些产物(例如jar)
- 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节配置的站点网址进行访问。