用 Docker 在 Anolis OS 上搭 Jenkins,自动发布 Maven 多模块和 Vue 前端
后端和前端放在同一个 Git 仓库里,手工打包、停服务、传包、再启动,项目一多就容易漏步骤。这篇记录怎么用 Docker 跑 Jenkins,把四个项目收成四个流水线:三个 Java 服务,一个 Vue 管理端。
Jenkins 装在一台 Anolis OS 8.10 上。业务跑在另一台部署机上。打包在容器里完成,再通过 SSH 把产物推过去。JDK、Maven、Node 都放在 Jenkins 的数据卷里,不会碰到部署机上已经在跑的进程。
仓库地址写成 https://gitee.com/example/platform.git,分支 master。部署机写成 DEPLOY_HOST。按自己的地址替换即可。
四个项目的对应关系:
| 项目 | 模块目录 | 产物 | 部署目录 | 构建环境 |
|---|---|---|---|---|
| 用户中心 | user | user.jar | /home/user | JDK 8 |
| 订单中心 | order | order.jar | /home/order | JDK 17 |
| 内容中心 | content | content.jar | /home/content | JDK 17 |
| 管理端 | admin | dist/ | /data/wwwroot/admin | Node 22 |
用 Docker 启动 Jenkins
部署机上的 8080 通常已经有别的服务,宿主机端口改到 18080。镜像用 Jenkins LTS,时区设为上海。
/opt/jenkins/docker-compose.yml:
services:
jenkins:
image: docker.m.daocloud.io/jenkins/jenkins:2.580.1-lts-jdk21
container_name: jenkins
restart: unless-stopped
ports:
- "18080:8080"
volumes:
- jenkins_home:/var/jenkins_home
environment:
TZ: Asia/Shanghai
JAVA_OPTS: "-Duser.timezone=Asia/Shanghai -Xmx1g"
volumes:
jenkins_home:
cd /opt/jenkins
docker compose up -d
云服务器安全组放行 18080,浏览器打开 http://Jenkins机器公网IP:18080。首次解锁密码:
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
创建一个管理员,例如 admin。任务、历史记录和后面安装的 JDK、Maven、Node 都在 volume jenkins_home 里,容器重启后还在。
插件源用清华镜像:
https://mirrors.tuna.tsinghua.edu.cn/jenkins/plugins/
流水线需要这些插件及其依赖:workflow-aggregator、workflow-cps、workflow-job、git。依赖没装齐时,新建任务里看不到「流水线」,配置页的 Pipeline 下拉也是空的。
准备 JDK、Maven 和 Node
在「系统管理 → 全局工具配置」里添加四项,名字要和流水线里的完全一致:
jdk-8jdk-11jdk-17maven-3.9
JDK 从清华 Adoptium 镜像下载。个别镜像会返回一段 HTML,tar 解开会失败,换一个镜像源即可。
Node 直接放进数据卷,供前端流水线使用。官方包是 .tar.xz,Jenkins 镜像里没有 xz,会报 xz: Cannot exec。下载 .tar.gz:
docker exec -u root jenkins bash -lc '
set -eu
rm -rf /tmp/node-install
mkdir -p /tmp/node-install
cd /tmp/node-install
curl -fL -o node.tar.gz https://npmmirror.com/mirrors/node/v22.23.4/node-v22.23.4-linux-x64.tar.gz
tar -xzf node.tar.gz
rm -rf /var/jenkins_home/tools/node
mv node-v22.23.4-linux-x64 /var/jenkins_home/tools/node
chown -R jenkins:jenkins /var/jenkins_home/tools/node
/var/jenkins_home/tools/node/bin/node -v
'
输出 v22.23.4 即安装完成。Vite 6.4.3 接受 Node 18、20,以及 22 及以上,19 和 21 不在范围内。
配置两个凭据
拉代码,ID 为 git-gitee。 类型选 Username with password,密码填 Gitee 私人令牌,令牌需要有读取仓库的权限。流水线里的 credentialsId 必须和这个 ID 一致。令牌无效时,git fetch 返回 401。
发包,ID 为 deploy-ssh。 部署机上单独建一个 jenkins 用户,把公钥写入该用户的 ~/.ssh/authorized_keys。Jenkins 里新建「SSH Username with private key」,用户名填 jenkins,私钥整段贴进去,包括 BEGIN OPENSSH PRIVATE KEY 那两行。密钥对保存好。以后新机器继续用这把公钥,流水线不用改凭据。
后端流水线
三个服务都是根工程下的子模块。-pl 后面写仓库里的目录名。目录名写错时,Maven 报 Could not find the selected project in the reactor。
父 POM 要先装进本地仓库,否则子模块报 Non-resolvable parent POM:
mvn -B -N -DskipTests install
mvn -B -DskipTests -pl 模块目录 -am clean package
公共模块如果把编译目标设成 17,内容和订单中心就要选 jdk-17。用 8 或 11 去编它,会得到 invalid target release: 17。用户中心维持 JDK 8。
发布在部署机的项目目录里做三件事:执行 ./stop.sh,把当前 jar 复制成 原名.jar.back.当天日期,再上传新 jar 并启动。备份放在项目目录下,例如 user-center.jar.back.20261027。
远程多行命令要用 heredoc 交给对方的 bash。不加 heredoc 时,换行会被吃掉,主机名和后面的命令粘在一起。
用户中心的启动脚本是 ./start,内容中心是 ./start.sh,以部署目录里的真实文件名为准。
pipeline {
agent any
tools {
jdk 'jdk-8'
maven 'maven-3.9'
}
options {
timestamps()
disableConcurrentBuilds()
buildDiscarder(logRotator(numToKeepStr: '20'))
}
stages {
stage('拉取') {
steps {
git branch: 'master',
url: 'https://gitee.com/example/platform.git',
credentialsId: 'git-gitee'
}
}
stage('打包') {
steps {
sh '''
set -eu
mvn -B -N -DskipTests install
mvn -B -DskipTests -pl user -am clean package
jar=$(ls user/target/*.jar | grep -v sources | grep -v javadoc | grep -v '.original' | head -1)
cp "$jar" user/target/user.jar
ls -l user/target/user.jar
'''
}
}
stage('发布') {
steps {
withCredentials([sshUserPrivateKey(credentialsId: 'deploy-ssh', keyFileVariable: 'KEY')]) {
sh '''
set -eu
ssh -i "$KEY" -o StrictHostKeyChecking=accept-new jenkins@DEPLOY_HOST bash -s << 'REMOTE'
cd /home/user
./stop.sh || true
if [ -f user.jar ]; then
cp -a user.jar user.jar.back.$(date +%Y%m%d)
fi
REMOTE
scp -i "$KEY" -o StrictHostKeyChecking=accept-new user/target/user.jar jenkins@DEPLOY_HOST:/home/user/user.jar
ssh -i "$KEY" -o StrictHostKeyChecking=accept-new jenkins@DEPLOY_HOST bash -s << 'REMOTE'
cd /home/user
./start
REMOTE
'''
}
}
}
}
post {
success {
archiveArtifacts artifacts: 'user/target/user.jar', fingerprint: true
}
}
}
订单中心把模块、jar、目录换成 order、order.jar、/home/order,工具换成 jdk-17。内容中心换成 content、content.jar、/home/content,JDK 同样是 17,启动命令写成 ./start.sh`。
第一次构建要下载 Maven 依赖,会比以后慢。成功后,构建记录里可以直接下载 jar。容器内还有两份:
/var/jenkins_home/workspace/任务名/模块/target/xxx.jar
/var/jenkins_home/jobs/任务名/builds/构建号/archive/...
把目录交给 jenkins 用户
服务如果原来是 root 启动的,jenkins 停不掉进程,也覆盖不了 jar。日志里会看到 kill: Operation not permitted,以及 scp: Permission denied。
在部署机上用 root 做一次交接:
cd /home/content && ./stop.sh || true
cd /home/order && ./stop.sh || true
ps -ef | grep -E 'content|order' | grep -v grep
chown -R jenkins:jenkins /home/content /home/order
chmod 755 /home/content/stop.sh /home/content/start.sh
chmod 755 /home/order/stop.sh /home/order/start.sh
ps 里如果还有进程,用 root 杀掉,再执行一次 chown。然后回到 Jenkins 重新构建。新进程的属主是 jenkins,以后的 stop.sh 才能生效。交接之后,不要再在部署机上用 root 启动这些服务。
前端流水线
frontend-admin 是 Vue 3 + Vite 6,产物是静态文件,发布时不用停进程。先把整个站点复制到同级目录 /data/wwwroot/admin.back.当天日期,再清空原目录,写入新的 dist。同一天多次发布,只保留当天最后一份备份。
部署机上先授权:
mkdir -p /data/wwwroot/admin
chown jenkins:jenkins /data/wwwroot
chown -R jenkins:jenkins /data/wwwroot/admin
pipeline {
agent any
options {
timestamps()
disableConcurrentBuilds()
buildDiscarder(logRotator(numToKeepStr: '20'))
}
stages {
stage('拉取') {
steps {
git branch: 'master',
url: 'https://gitee.com/example/platform.git',
credentialsId: 'git-gitee'
}
}
stage('打包') {
steps {
sh '''
set -eu
export PATH="/var/jenkins_home/tools/node/bin:$PATH"
node -v
npm -v
cd frontend-admin
npm config set registry https://registry.npmmirror.com
if [ -f package-lock.json ]; then
npm ci
else
npm install
fi
npm run build
test -d dist
ls dist
'''
}
}
stage('发布') {
steps {
withCredentials([sshUserPrivateKey(credentialsId: 'deploy-ssh', keyFileVariable: 'KEY')]) {
sh '''
set -eu
ssh -i "$KEY" -o StrictHostKeyChecking=accept-new jenkins@DEPLOY_HOST bash -s << 'REMOTE'
set -eu
mkdir -p /data/wwwroot/admin
if [ -n "$(ls -A /data/wwwroot/admin 2>/dev/null)" ]; then
rm -rf /data/wwwroot/admin.back.$(date +%Y%m%d)
cp -a /data/wwwroot/admin /data/wwwroot/admin.back.$(date +%Y%m%d)
fi
find /data/wwwroot/admin -mindepth 1 -maxdepth 1 -exec rm -rf {} +
REMOTE
tar -C frontend-admin/dist -czf - . | ssh -i "$KEY" -o StrictHostKeyChecking=accept-new jenkins@DEPLOY_HOST 'tar -C /data/wwwroot/admin -xzf -'
'''
}
}
}
}
}
有 package-lock.json 时用 npm ci。npm 走 registry.npmmirror.com。构建完成后,部署目录里应有 index.html。
日常发布
打开 Jenkins,进入对应任务,点击「立即构建」。后端流水线会停掉旧进程、按日期备份 jar、上传新包并启动。前端流水线会备份整个站点目录,再覆盖静态文件。每个任务保留最近 20 次构建。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/wen_3370/article/details/169263665



