文章目录
- 引言
- 一、结论
- [二、service 文件常见问题分析](#二、service 文件常见问题分析)
- [三、方案 A:Jenkins Pipeline 脚本组装 deb(✅ 推荐)](#三、方案 A:Jenkins Pipeline 脚本组装 deb(✅ 推荐))
-
- 核心思路
- 项目目录结构
- `deb/lib/systemd/system/sight-manage.service`(修正版)
- [`deb/opt/sight/start.sh`(exec 前台运行)](#
deb/opt/sight/start.sh(exec 前台运行)) - [`scripts/build-deb.sh`(Jenkins 调用)](#
scripts/build-deb.sh(Jenkins 调用)) - [`Jenkinsfile` 片段](#
Jenkinsfile片段)
- [四、方案 B:maven-antrun-plugin 组装 deb](#四、方案 B:maven-antrun-plugin 组装 deb)
-
- 什么时候用?
- [`pom.xml` 配置](#
pom.xml配置) - [antrun 方案的致命缺点](#antrun 方案的致命缺点)
- 五、两种方案对比总结
- 六、最终推荐架构
- [七、 service 文件的修正对照](#七、 service 文件的修正对照)
- [see also](#see also)
-
- [Jenkins Pipeline](#Jenkins Pipeline)
- Jenkinsfile
- Fat-JAR(胖JAR)
引言
生产环境推荐使用 Jenkins Pipeline 脚本构建 deb 包,相比 Maven Antrun 插件,其可维护性、调试体验与 CI/CD 灵活性显著更优。方案核心为:Maven 打包 fat-jar,Shell 脚本组装 deb 目录结构,动态配置 JVM 内存并集成 systemd 服务管理。关键优化包括移除无效参数、拆分启动脚本、合理设置内存上限,并通过 Jenkinsfile 实现自动化构建与上传至 APT 仓库,确保部署高效可靠。
一、结论
| 维度 | maven-antrun-plugin | Jenkins Pipeline 脚本 |
|---|---|---|
| 推荐度 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| 复杂度 | 高(XML 里写 shell) | 低(直接 bash) |
| 可维护性 | 差(XML 转义地狱) | 好(独立脚本文件) |
| 调试体验 | 差(mvn 日志里翻) | 好(Jenkins 直接看) |
| 与 deb 工具集成 | 勉强 | 原生 |
| CI/CD 灵活性 | 低 | 高 |
| 本地也能打包 | ✅ | ❌(依赖 CI 环境) |
结论:生产环境用 Jenkins 脚本方案。maven-antrun 只适合"本地开发时快速验证 deb 结构"。
二、service 文件常见问题分析
| 参数 | 问题 |
|---|---|
User=root / Group=root |
前面讨论过,能用但不够安全 |
ExecStart= /usr/bin/java 前面有空格 |
systemd 会解析失败 |
-XX:MaxNewSize=640m + -XX:SurvivorRatio=16 |
ZGC 下全部无效 |
-XX:+UseNUMA |
RK3588 ARM 不是 NUMA |
-Xms2048m -Xmx2048m |
2GB 对 manage 服务偏多,RK3588 总共 8GB |
ExecStart 一行太长 |
不可维护,改参数要改整个文件 |
三、方案 A:Jenkins Pipeline 脚本组装 deb(✅ 推荐)
核心思路
Jenkinsfile
→ mvn package(产出 fat-jar)
→ bash scripts/build-deb.sh(组装 deb 目录结构)
→ dpkg-deb --build
→ rsync 到 APT 仓库
项目目录结构
mes-manage/
├── src/
│ ├── main/java/
│ └── resources/
│ └── application-prod.yml
├── deb/
│ ├── DEBIAN/
│ │ ├── control
│ │ ├── postinst
│ │ ├── prerm
│ │ └── postrm
│ ├── opt/
│ │ └── sight/
│ │ ├── start.sh
│ │ └── conf/
│ │ └── application-prod.yml
│ └── lib/
│ └── systemd/
│ └── system/
│ └── sight-manage.service
├── scripts/
│ └── build-deb.sh
├── pom.xml
└── Jenkinsfile
deb/lib/systemd/system/sight-manage.service(修正版)
ini
[Unit]
Description=sight Manage API (Spring Boot)
After=network.target mysql.service
Wants=mysql.service
[Service]
Type=simple
User=sight
Group=sight
WorkingDirectory=/opt/sight
# JVM 参数拆到 start.sh 里,service 只管调脚本
ExecStart=/opt/sight/start.sh
ExecStop=/bin/kill -SIGTERM $MAINPID
TimeoutStopSec=30
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=sight-manage
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/opt/sight/logs /var/log/sight /var/lib/sight
[Install]
WantedBy=multi-user.target
deb/opt/sight/start.sh(exec 前台运行)
bash
#!/bin/bash
# sight-manage start script
# 注意:不用 nohup,不用 &,用 exec 让 systemd 管控 java
set -e
JAR="/opt/sight/sight-manage.jar"
JAVA="/usr/bin/java"
if [ ! -x "${JAVA}" ]; then
echo "ERROR: java not found at ${JAVA}" >&2
exit 1
fi
if [ ! -f "${JAR}" ]; then
echo "ERROR: ${JAR} not found" >&2
exit 1
fi
# 动态内存:物理内存的 1/4,上限 2048m
MEM_KB=$(grep MemTotal /proc/meminfo | awk '{print $2}')
HEAP_MB=$(( MEM_KB / 1024 / 4 ))
[ ${HEAP_MB} -gt 2048 ] && HEAP_MB=2048
[ ${HEAP_MB} -lt 512 ] && HEAP_MB=512
# exec:用 java 替换当前 shell 进程
exec "${JAVA}" \
-Xms${HEAP_MB}m \
-Xmx${HEAP_MB}m \
-XX:+UseZGC \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/sight/heapdump.hprof \
-Duser.timezone=GMT+08 \
-Dfile.encoding=UTF-8 \
-Dspring.profiles.active=prod \
-Dspring.config.location=file:/opt/sight/conf/application-prod.yml \
-jar "${JAR}"
scripts/build-deb.sh(Jenkins 调用)
bash
#!/bin/bash
set -e
VERSION="${1:-$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)}"
BUILD_DIR="build/deb/sight-manage_${VERSION}_all"
echo "=== Building deb v${VERSION} ==="
# 清理 & 创建目录
rm -rf build/deb
mkdir -p ${BUILD_DIR}/DEBIAN
mkdir -p ${BUILD_DIR}/opt/sight/conf
mkdir -p ${BUILD_DIR}/opt/sight/logs
mkdir -p ${BUILD_DIR}/lib/systemd/system
# 1. 复制 jar
cp target/sight-manage.jar ${BUILD_DIR}/opt/sight/
# 2. 复制配置文件
cp deb/opt/sight/conf/application-prod.yml ${BUILD_DIR}/opt/sight/conf/
cp deb/opt/sight/start.sh ${BUILD_DIR}/opt/sight/
chmod +x ${BUILD_DIR}/opt/sight/start.sh
# 3. 复制 systemd service
cp deb/lib/systemd/system/sight-manage.service ${BUILD_DIR}/lib/systemd/system/
# 4. 复制 DEBIAN 控制文件
cp deb/DEBIAN/control ${BUILD_DIR}/DEBIAN/
cp deb/DEBIAN/postinst ${BUILD_DIR}/DEBIAN/
cp deb/DEBIAN/prerm ${BUILD_DIR}/DEBIAN/
cp deb/DEBIAN/postrm ${BUILD_DIR}/DEBIAN/
# 替换版本号
sed -i "s/^Version:.*/Version: ${VERSION}/" ${BUILD_DIR}/DEBIAN/control
# 设置脚本权限
chmod 755 ${BUILD_DIR}/DEBIAN/postinst
chmod 755 ${BUILD_DIR}/DEBIAN/prerm
chmod 755 ${BUILD_DIR}/DEBIAN/postrm
# 打包
dpkg-deb --build --root-owner-group ${BUILD_DIR}
echo "=== Done: sight-manage_${VERSION}_all.deb ==="
ls -lh sight-manage_${VERSION}_all.deb
Jenkinsfile 片段
groovy
stage('Build Fat-Jar') {
steps {
sh 'mvn clean package -DskipTests'
}
}
stage('Assemble DEB') {
steps {
sh 'bash scripts/build-deb.sh'
}
}
stage('Verify DEB') {
steps {
sh '''
dpkg-deb -I sight-manage_*_all.deb
dpkg-deb -c sight-manage_*_all.deb | head -20
'''
}
}
stage('Upload to APT Repo') {
steps {
sshagent(credentials: ['apt-repo-rsync']) {
sh '''
VERSION=$(dpkg-deb -f sight-manage_*_all.deb Version)
rsync -avz sight-manage_${VERSION}_all.deb \
aptly@192.168.20.100:/srv/incoming/
ssh aptly@192.168.20.100 \
"aptly repo add mes-stable /srv/incoming/sight-manage_${VERSION}_all.deb && \
aptly publish switch stable mes-stable-$(date +%Y%m%d-%H%M%S)"
'''
}
}
}
四、方案 B:maven-antrun-plugin 组装 deb
什么时候用?
- 本地开发想
mvn package直接出 deb - 没有 Jenkins 环境
- 快速验证 deb 结构
pom.xml 配置
xml
<build>
<finalName>sight-manage</finalName>
<plugins>
<!-- Spring Boot -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
</configuration>
</plugin>
<!-- 复制 deb 资源 -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
<executions>
<execution>
<id>copy-deb-resources</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/deb-build</outputDirectory>
<resources>
<resource>
<directory>deb</directory>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<!-- antrun: 组装 deb 目录 + dpkg-deb 打包 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>build-deb</id>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<!-- 创建目录结构 -->
<mkdir dir="${project.build.directory}/deb-build/sight-manage_${project.version}_all/DEBIAN"/>
<mkdir dir="${project.build.directory}/deb-build/sight-manage_${project.version}_all/opt/sight/conf"/>
<mkdir dir="${project.build.directory}/deb-build/sight-manage_${project.version}_all/opt/sight/logs"/>
<mkdir dir="${project.build.directory}/deb-build/sight-manage_${project.version}_all/lib/systemd/system"/>
<!-- 复制 jar -->
<copy file="${project.build.directory}/sight-manage.jar"
tofile="${project.build.directory}/deb-build/sight-manage_${project.version}_all/opt/sight/sight-manage.jar"/>
<!-- 复制 service 文件 -->
<copy file="deb/lib/systemd/system/sight-manage.service"
todir="${project.build.directory}/deb-build/sight-manage_${project.version}_all/lib/systemd/system/"/>
<!-- 复制 start.sh -->
<copy file="deb/opt/sight/start.sh"
tofile="${project.build.directory}/deb-build/sight-manage_${project.version}_all/opt/sight/start.sh"/>
<chmod file="${project.build.directory}/deb-build/sight-manage_${project.version}_all/opt/sight/start.sh" perm="755"/>
<!-- 复制 DEBIAN 脚本 -->
<copy file="deb/DEBIAN/postinst"
todir="${project.build.directory}/deb-build/sight-manage_${project.version}_all/DEBIAN/"/>
<copy file="deb/DEBIAN/prerm"
todir="${project.build.directory}/deb-build/sight-manage_${project.version}_all/DEBIAN/"/>
<copy file="deb/DEBIAN/postrm"
todir="${project.build.directory}/deb-build/sight-manage_${project.version}_all/DEBIAN/"/>
<!-- 生成 control 文件(替换版本号) -->
<copy file="deb/DEBIAN/control"
tofile="${project.build.directory}/deb-build/sight-manage_${project.version}_all/DEBIAN/control"/>
<!-- 设置权限 -->
<chmod perm="755">
<fileset dir="${project.build.directory}/deb-build/sight-manage_${project.version}_all/DEBIAN">
<include name="postinst"/>
<include name="prerm"/>
<include name="postrm"/>
</fileset>
</chmod>
<!-- 执行 dpkg-deb 打包 -->
<exec executable="dpkg-deb" failonerror="true">
<arg value="--build"/>
<arg value="--root-owner-group"/>
<arg value="${project.build.directory}/deb-build/sight-manage_${project.version}_all"/>
<arg value="${project.build.directory}/sight-manage_${project.version}_all.deb"/>
</exec>
<echo message="DEB package created: ${project.build.directory}/sight-manage_${project.version}_all.deb"/>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
antrun 方案的致命缺点
| 问题 | 说明 |
|---|---|
| XML 里写 shell | < 要转义为 <,& 要转义为 & |
| 调试困难 | mvn 日志混在一起,看不到中间状态 |
| 条件逻辑弱 | 想判断架构、判断文件存在?ant 条件语法反人类 |
| 不可复用 | 这段 XML 绑死在 pom 里,Jenkins 没法单独调 |
| 打包工具耦合 | dpkg-deb 必须在本地有,Windows 开发机直接挂 |
| 版本管理 | control 文件版本号替换要用 filter,容易出错 |
五、两种方案对比总结
| 维度 | Jenkins 脚本 | maven-antrun |
|---|---|---|
| 学习成本 | 低(bash 谁都会) | 高(ant XML 语法) |
| 可调试 | ✅ bash -x |
❌ mvn 日志翻 |
| 可复用 | ✅ 脚本可独立运行 | ❌ 绑死 Maven |
| 跨平台 | ❌ 依赖 Linux | ❌ 依赖 dpkg-deb |
| CI 集成 | ✅ 原生 | ⚠️ 能跑但不优雅 |
| 适合生产 | ✅ | ❌ |
| 适合本地验证 | ⚠️ 要装 dpkg-deb | ✅ mvn package 一条命令 |
六、最终推荐架构
开发阶段:mvn package → 本地手动 dpkg-deb -b 验证结构
CI 阶段:Jenkins → build-deb.sh → dpkg-deb → APT 仓库
生产部署:终端 apt update && apt install sight-manage
maven-antrun 不要用在 CI 里 ,它的定位是"本地开发时 mvn package 顺带出个 deb 方便联调",不是生产打包方案。
七、 service 文件的修正对照
| 原来 | 修正后 | 原因 |
|---|---|---|
User=root |
User=sight |
最小权限 |
ExecStart= /usr/bin/java 前有空格 |
去掉空格 | systemd 解析 |
| JVM 参数全堆 ExecStart 一行 | 拆到 start.sh |
可维护 |
-XX:MaxNewSize + -XX:SurvivorRatio |
删除 | ZGC 无效 |
-XX:+UseNUMA |
删除 | ARM 无效 |
-Xms2048m -Xmx2048m |
动态计算 | 适配不同硬件 |
start.sh 用 nohup & |
exec java |
systemd 管控 |
see also
Jenkins Pipeline
是Jenkins 2.x版本引入的核心持续交付功能,通过Jenkinsfile将构建、测试、部署等流程编码为可版本控制的脚本,实现"流水线即代码"。相比传统自由风格任务,它具备版本可追溯、多分支隔离、失败可重入等优势,适用于微服务并行构建、多环境部署等复杂场景。Jenkins Pipeline分为声明式(结构化、易维护)和脚本式(灵活但需掌握Groovy)两种语法,生产中推荐优先使用声明式。其关键特性包括:通过agent配置动态分配执行节点(如Docker容器或Kubernetes Pod)、使用共享库复用逻辑、集成凭据管理实现安全构建。典型应用包括多阶段CI/CD流程自动化,如代码检出、并行测试、镜像构建及部署。
Jenkinsfile
是 Jenkins 流水线的代码化配置文件,使用 Groovy 语法定义 CI/CD 流程,支持声明式和脚本式两种写法。它将构建、测试、部署等步骤以代码形式管理,可提交到版本控制系统(如 Git)实现版本化和团队协作。典型结构包含 agent(执行节点)、stages(阶段)、steps(步骤)等元素,可配置环境变量、条件分支、循环等复杂逻辑。在生产环境中,Jenkinsfile 通过Pipeline 插件实现自动化构建,相比 Maven Antrun 等方案具有更高的可维护性和灵活性,尤其适合动态编排复杂流水线任务。
Fat-JAR(胖JAR)
是一种包含项目所有依赖的可执行JAR文件,通过Maven插件(如Assembly或Shade)将主类和依赖打包到一个文件中,适合独立部署。Spring Boot默认使用嵌套结构的Fat-JAR,依赖存放在BOOT-INF/lib目录,通过自定义类加载器解决类路径问题。Maven配置示例:使用Assembly插件指定主类
<mainClass>com.example.MainClass</mainClass>,执行
mvn package生成带依赖的JAR。其优势是部署简单,但需注意依赖冲突和文件体积。