Jenkins 部署

在项目正式部署上限的过程当中, 有时候需要用到项目 打包 -> 测试 -> 出包 流程,
这个流程一般是重复且复杂的, 所以就衍生 高效、可靠、可追溯的开发与交付体验 为目标的 流水线 部署流程.

目前 Jenkins 自动化构建流程基本支持前后端项目和容器化处理

推荐采用单独的设备来部署 Jenkins, 因为打包中心可能涉及到修改和暴露很多东西, 所以最好做环境方面隔离.

安装部署

推荐直接 Jenkins官网 去下载 LTS 版本,
虽然我是坚定的 apt 一键安装部署推崇者, 但是基于目前的国内网络原因更新下载速度及其离谱(有时候要更新一整晚);
所以最后不得不直接采用安装包来部署, 还能避免污染 apt 源更新(除非国内以后有镜像部署来加速).

不过这里还提供下 APT 部署流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 安装源证书
sudo wget -O /etc/apt/keyrings/jenkins-keyring.asc \
https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key

# 写入镜像源
echo "deb [signed-by=/etc/apt/keyrings/jenkins-keyring.asc]" \
https://pkg.jenkins.io/debian-stable binary/ | sudo tee \
/etc/apt/sources.list.d/jenkins.list > /dev/null

# 更新安装依赖组建, 注: JenkinsLTS 目前基于 Java17 构建, 热更版本是基于 Java21
sudo apt-get update
sudo apt-get install fontconfig openjdk-17-jre
sudo apt-get install jenkins

这里如果自带网络代理的时候, apt 作软件版本维护都挺不错,
可惜就国内网络基本上配置这个源会导致更新系统应用更加缓慢,
只能自己手动部署这个服务(不过一般这种都是作为长期服务启动不需要太频繁维护):

1
2
3
4
5
6
# 下载远程的 jenkins.war 包
# 官方地址: https://www.jenkins.io/download
cd /tmp && wget https://get.jenkins.io/war-stable/2.528.1/jenkins.war jenkins.war

# 默认直接按照以下方式启动即可, 但是这种方式太过粗糙需要自己编写成系统服务
java -jar jenkins.war

下载好之后目前需要就是编写成 systemctl 的服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 创建专属的用户组 jenkins:jenkins, 并且指定目录
sudo useradd -r -m -d /var/lib/jenkins -s /sbin/nologin jenkins

# 创建二进制启动目录
sudo -u jenkins mkdir /var/lib/jenkins/bin

# 将之前下载 jenkins.war 移动到程序启动目录并且赋予启动权限
sudo -u jenkins cp /tmp/jenkins.war /var/lib/jenkins/bin
sudo -u jenkins chmod +x /var/lib/jenkins/bin/jenkins.war

# 安装 jenkins 所需要的 Jre17
sudo apt install -y openjdk-17-jre

# 这里可以测试下启动服务, 确保后续服务编写无误:
# sudo -u jenkins /usr/bin/java -jar /var/lib/jenkins/bin/jenkins.war --logfile=/var/log/jenkins/jenkins.log

# 创建 jenkins.service 服务
sudo touch /etc/systemd/system/jenkins.service

# 创建外部的配置参数, 将配置文件放置于 /etc/jenkins/jenkins.conf 之中
sudo mkdir -p /etc/jenkins
sudo touch /etc/jenkins/jenkins.conf
sudo chmod 600 /etc/jenkins/jenkins.conf
sudo chown root:root /etc/jenkins/jenkins.conf

编写外部通用的配置参数文件( /etc/jenkins/jenkins.conf ):

1
2
3
4
5
# Jenkins 启动参数, 可以按照自己需要微调之后重启即可
# 后续需要修改端口或者地址可以直接在此修改重新启动即可
JENKINS_ARGS="\
--httpPort=8080 \
--httpListenAddress=0.0.0.0

这里就是服务单元配置编写流程( /etc/systemd/system/jenkins.service ):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[Unit]
Description=Jenkins Service (WAR deployment)
Documentation=https://www.jenkins.io/doc
After=network.target

[Service]
Type=simple
User=jenkins
Group=jenkins

# 加载根目录和读取配置
# EnvironmentFile 其实就是解析文件内容, 而内部就是将 JENKINS_ARGS 解析成环境变量通过 $XX 可以读取
WorkingDirectory=/var/lib/jenkins
EnvironmentFile=/etc/jenkins/jenkins.conf

# 启动配置
ExecStart=/usr/bin/java -jar /var/lib/jenkins/bin/jenkins.war $JENKINS_ARGS

# 停止命令
ExecStop=/bin/kill -TERM $MAINPID

# 重启策略
Restart=on-failure
RestartSec=5

# 限制资源
LimitNOFILE=65536
PrivateTmp=true

[Install]
WantedBy=multi-user.target

最后更新下系统服务服务并启动:

1
2
3
sudo systemctl daemon-reload # 更新系统服务
sudo systemctl start jenkins.service # 启动服务
sudo systemctl enable jenkins.service # 开机启动

访问默认服务地址要求解锁密码, 一般在 /var/lib/jenkins/.jenkins/secrets/initialAdminPassword 文件之中,
输入之后按照典型所需的配置插件即可(不过插件市场在国外, 没有配置国内源可能安装插件要很久)

一般磁盘空间不足需要将扩展的 /var/lib/jenkins 指定到扩展外部空间盘, 创建用户时候指定 useradd -r -m -d 【扩展盘地址】

一般不会有什么报错问题, 手动搭建其实也比较容易, 后面大部分就是卡在插件市场的访问速度问题.

配置插件市场源, 注意目前国内大部分都停止解析插件源(可能是基于成本和攻击考虑), 所以国内大部分插件源目前配置会提示离线无法获取

另外还需要注意的是 Java17 版本只维护支持到 2026年3月31日, 后续推荐采用 Java21 来保持运行从而达到官方最低推荐.

项目构建

这里基于以下工具来部署个 基于 svn 代码管理通过 ssh 发布到指定服务器 的项目部署:

这些插件浏览下是否在 jenkins 之中安装, 远程的正式服务器则是需要部署好需要的 Jre 等环境

现在就是创建专用部署的 svn 帐号密码, 确保 svn 代码服务器已经创建角色和密码, 之后按照以下流程导入:

  1. 首页 系统管理(Manage Jenkins)
  2. 找到 安全(Security)
  3. 选择 管理凭据(Credentials)
  4. 点击目前的全局凭据 全局系统(System)
  5. 进入到系统全局凭据就可以点击 全局凭证[Global credentials (unrestricted)] 进行管理
  6. 点击 添加凭证(Add Credentials) 进入新凭证创建页面
  7. 类型默认 Username with password 即可, 范围也是 全局(Global) 可用
  8. 用户名和密码就是创建的 svn 专用账号密码, 主要的标识配置就是 ID, 这是我们全局用到的凭证字段
  9. 这里我标识 ID 设定成 devops-svn, 日常的标识可以自定义, 描述按照自己需要编写就行了
  10. 点击 Create 就创建完成, 这就是我们的远程 SVN 拉取源代码凭证

之后就是需要配置远程的 SSH 服务器配置:

  1. 首页 系统管理(Manage Jenkins)
  2. 进入 系统配置(System Configuration) - 系统配置 页面
  3. 找到 Publish Over SSH 配置栏, 最好页面全局搜索下, 没有的话检测插件是否安装
  4. 首先配置密钥相关信息, 需要注意这里需要 Jenkins 和远程服务器都配置好 SSH 证书
    • Passphrase: 配置专属证书密码, 默认一般留空即可
    • Path to key: 加载本地的证书文件, 一般不会直接直接加载
    • Key: 主要证书内容, 一般是 Jenkins 服务器的私钥(提前将公钥放远程服务器 ~/.ssh/authorized_keys)
  5. 选择 SSH Servers - Add 创建自定义的远程服务器信息
    • Name:自定义名称(如 cloud-web-api)
    • Hostname: 目标服务器 IP 或域名
    • Username: 远程登录用户名(如 devops, 远程服务器最好也配置专属管理账号)
    • Remote Directory: 远程默认的根目录, 最好是配置到专属的扩展目录
    • 点击 Test Configuration 测试没问题就代表成功

至此没问题的画, SVNSSH 远程配置完成, 不过因为这里我是 Java 打包项目, 还需要额外配置 Maven 全局打包工具:

  1. 首页 系统管理(Manage Jenkins)
  2. 进入 系统配置(System Configuration) - 全局工具配置 页面
  3. 找到 Maven 安装 配置栏, 我这里项目采用 Maven 3.9.9 版本, 命名为 maven-3-9-9
  4. 保存就行了, 现在默认全局都是采用 Maven 3.9.9

最后就是创建 Jenkins 服务:

  1. 点击 Jenkins 首页 新建 Item
  2. 选择 自由风格的软件项目(Freestyle project)
  3. 输入项目名称, 这里我配置的是 Java 项目所以命名 java-deploy 创建
  4. 在源码管理里面选择对应代码版本: Git|Subversion, 我这里选择 Subversion, 之后就是仓库配置
    • Repository URL: 远程版本库地址
    • Credentials: 之前我们创建的 SVN 服务器凭证, 注意选择正确凭据
    • Local module directory: 本地模块打包目录, 服务其实推荐放置在扩展盘之中, 如果本身服务就是配置在扩展盘就不需要管
    • 其他保持默认即可
  5. 找到 Build Steps, 选择 增加构建步骤 下拉点击 调用顶层 Maven 目标(Invoke top-level Maven targets)
  6. 这里如果为空配置代表没有配置全局的 Maven 打包版本需要去全局配置, 这里选择之前全局的 maven-3-9-9
  7. 这里的 目标(Target) 就是 maven 打包命令, 按照以下需求填写就行:
    • clean package: 基础打包
    • clean package -Dmaven.test.skip=true: 跳过测试打包
    • clean install -Dmaven.test.skip=true: 打包并安装到本地仓库
  8. 打包成功后, 默认在工作空间的 target 目录下(如 target/my-project-1.0.jar)
  9. 找到 构建后操作, 选择 增加构建后操作步骤 下拉点击 远程发布SSH(Send build artifacts over SSH)
    • SSH Server: 选择之前配置的正式远程服务器
    • Source files: 填写 Jenkins 工作空间中 JAR 包的路径(如 target/*.jar, 支持通配符, 多项目打包需要对应目录)
    • Remove prefix: 填写需去除的路径前缀(如 target, 避免远程服务器生成 target 目录)
    • Remote directory: 填写远程部署目录(如 /www/my-java-project, 相对于 SSH 用户的家目录, 绝对路径需写全)
    • Exec command: 填写部署后启动和重启服务的命令, 一般就是指定服务停止重启的行为命令
    • 点击保存, 可以准备测试构建

构建后操作的路径是基于 SSH 配置的 Remote Directory, 如果 SSH 配置 /data 而构建后是 /www 最后为 /data/www

这里直接进入首页, 找到指定项目点击项目右边的绿色小箭头就会开始构建项目,
一般容易出问题就是打包的时候没有镜像源打包失败只需要在项目当中查看命令行窗口输出修复就行了.

文件部署

上面针对的是编译打包处理之后移交给正式服务器部署流程, 还有另外的情况就是静态文件部署(前端|PHP之类)的发布提交;
这里采用应用比较广的 PHP 部署, 静态文件也和 PHP 一般, 只是 PHP 多出了 Composer 的操作流程.

远程的正式服务器先确保已经安装配置 PHP|Nginx|SSH, 有需要可能要 zip|unzip 等用于解压处理,
其他 sshsvn 配置流程按照上面配置就行了

这里还是一样创建 自由风格项目(Freestyle project) 并配置源代码地址,
需要注意 PHP 项目通常不需要编译打包, 但需通过 Composer 安装依赖.

而传输到正式服务器的时候, 可以考虑压缩为 ZIP 包便于传输

所以在项目配置之中找到 Build Steps, 选择 增加构建步骤 添加的是 执行Shell(Execute shell), 内容如下:

1
2
3
4
5
6
7
8
9
# 进入项目目录(Jenkins 工作空间,默认无需手动 cd
# 安装依赖(忽略版本锁定文件的警告,或根据项目需求使用 --no-dev 排除开发依赖)
# 如果包含 vendor 目录可以直接传输核心第三方目录, 这样可以让远程服务器不需要安装 composer 直接就可以运行
composer install --no-dev --optimize-autoloader

# 这里就是考虑打包成压缩文件生成 zip
# 压缩项目文件(排除 .git、vendor 等不需要的目录,根据实际调整)
# 注意压缩的时候默认目录生成, 要排除具体包名避免重复打包
zip -r php-project.zip . -x "php-project.zip" ".env" "*.git*" "*.svn*" "vendor/*" "tests/*"

之后就是找到 构建后操作, 选择 增加构建后操作步骤, 这里就是区分是简单传输还是压缩传输;
如果简单传输文件的话(静态文件), 直接下拉选择 远程发布SSH(Send build artifacts over SSH):

  • SSH Server: 选择之前配置的正式远程服务器
  • Source files: 项目文件相对路径(如 **/* 表示所有文件, 排除可通过 -x 过滤)
  • Remove prefix: 无需移除前缀(或根据目录结构调整)
  • Remote directory: 填写远程部署目录(如 /www/my-java-project, 相对于 SSH 用户的家目录, 绝对路径需写全)
  • Exec command: 部署后执行的命令(如设置权限|重启 PHP-FPM 等)

而如果是采用 zip 压缩之后同步解压, 那么以下配置就有所不同:

  • Source files: 填写打包后的 ZIP 路径(如 php-project.zip)
  • Remove prefix: 留空(仅传输 ZIP 包)
  • Remote directory:目标服务器临时目录(如 /tmp )
  • Exec command: 核心命令, 负责解压和运行 composer

Exec command 配置内容一般如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 解压到部署目录
unzip -o /tmp/php-project.zip -d /var/www/php-project

# 如果远程排除了 vendor 提交, 那么正式服务器是需要执行 composer 命令
# 下面命令就是考虑正式环境是否要配置 composer 的关键
# 如果不想正式环境也安装 composer 可以在排除规则上将 vendor 目录移除
cd /var/www/php-project
composer install --no-dev --optimize-autoloader

# 设置权限
chown -R www-data:www-data /var/www/php-project
chmod -R 755 /var/www/php-project/storage

# 删除临时 ZIP 包
rm -f /tmp/php-project.zip

# 重启 PHP-FPM, 注意如果分配的专用账号权限不足以下命令是无法执行的
# PHP本身支持热更新, 不需要部署时候重启 php-fpm
# systemctl restart php-fpm

实际上不推荐直接 vendor 一起提交部署, 主要问题如下:

  • vendor 第三方依赖会导致压缩包体积更大, 传输更慢
  • 依赖包在目标服务器不是直接安装, 可能会与运行环境产生兼容问题

所以除非内网且完全断网的环境, 否则还是直接在远程正式服务器安装 composer 来直接环境安装

PHP 另外好处的一点就是直接文件即服务, 直接覆盖更新不需要重启过多服务(如果追加PHP依赖就需要手动重启下 PHP-FPM)

系统应用

这里回归到上面提出关于 Java 编译发布问题, 实际上有的公司会专门编写配置启动脚本让他通过 nohup 挂载启动;
这种启动方式其实也是很毒瘤方式, 脱离系统管理来让进程启动完全匿名的服务进程维护服务.

所以就需要采用 systemctl 来管理系统服务, 并且基于 systemctl 将应用权限控制到自定义的配置下.

一般启动系统都需要 sudo 应用组来手动输入密码之后才允许启动, 这里就是要单独做低权限 service

这里需要介绍和 /etc/systemd/system 相对的服务 /etc/systemd/user;
system 不一样, user 则是代表用户可以运行的权限, 可以通过创建这个服务来构建用户级别的 Jar 应用启动:

1
2
3
4
5
6
7
# 这里假设目前采用 jenkins 在正式服务器采用 devops 账号部署
# 部署账号不建议无法登陆(/sbin/nologin), 否则可能日常维护比较麻烦
sudo - devops -s /bin/bash

# 要求在正式服务器当中也必须要包含这个权限账户, 这个账户可以不加入 sudo
mkdir -p ~/.config/systemd/user/
vim ~/.config/systemd/user/[应用服务名].service

这里的服务内容如下, 可以修改下处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[Unit]
Description=My Java Application(用户级服务)
Documentation=https://example.com/myapp
After=network.target # 网络就绪后启动

[Service]
# 运行用户和用户组
User=devops
Group=devops

# Java 应用路径(确保 devops 有读取和执行权限)
# 以下内容可以按照个人习惯修改
WorkingDirectory=/home/devops/myapp # 应用工作目录
ExecStart=/usr/bin/java -jar /home/devops/myapp/app.jar --spring.profiles.active=prod
ExecStop=/bin/kill -s TERM $MAINPID # 停止命令

# 自动重启策略
Restart=on-failure # 失败时重启
RestartSec=5 # 重启间隔 5 秒

# 限制资源
LimitNOFILE=65536
PrivateTmp=true

[Install]
WantedBy=default.target # 用户会话启动时自动启动

这里启动方式也和 system 有所不同:

1
2
3
4
5
6
systemctl --user daemon-reload # 更新用户服务
systemctl --user start [自定义服务名].service # 启动用户服务

# 需要注意开启启动有所不同, 他需要双重enable
systemctl --user enable [自定义服务名].service # 用户登陆之后自动启动
sudo loginctl enable-linger devops # 指定 devops 用户服务在系统启动的跟随一起服务器启动

另外还需要注意的一点就是低权限用户应用无法监听低数值的端口号, 只能监听 1024-65535 范围的端口;
不过一般都是外部做端口转发 Nginx 之类引入进来的, 所以基本上没有太多影响.

这样对于 Jenkins 做发布的时候, 就可以如果要做远程服务部署, 可以在构建后的 Exec command 编写如下命令:

1
2
3
4
5
6
7
8
# 先停止用户级的服务, 避免 jar 被系统占用无法替换
systemctl --user stop [自定义服务名].service

# 复制指定应用到指定二进制执行目录
cp [打包出来的应用].jar /[自定义目录]/

# 再重新启动应用
systemctl --user start [自定义服务名].service