全程使用docker构建,上文提供的案例不能说是企业级的因为还是在宿主机上面构建的,依赖性强,利用 Docker 插件或 K8s Agent,为每个 Stage 动态拉起一个包含所需工具的容器(Pod)。构建完即销毁。
优势:环境绝对干净,构建一致性高,不会因为"我本地是好的"这种问题扯皮。
当我们前后端仓库是分开的,那么企业级的构建流程是什么呢?
答案:子目录隔离(推荐)
为什么子目录隔离是企业级?
在工作空间下创建 frontend/ 和 backend/ 文件夹,分别存放代码。
- 物理隔离,逻辑统一
- 虽然它们在同一个 Jenkins 工作空间(Workspace)下,但互不干扰。
- 前端构建产生的
node_modules和后端的target目录完全分开,不会误删或混淆。
- 符合微服务/模块化思维
- 企业级项目通常是模块化的。即使现在是前后端分离,未来可能变成多后端服务。子目录结构清晰地表达了"这是两个独立的组件"。
- Docker 挂载更清晰
- 当启动 Docker 容器时,容器内的
/workspace对应宿主机的根目录。 - 在容器内执行
cd backend && mvn ...非常直观,不需要去猜测当前目录下到底有哪些文件。
- 当启动 Docker 容器时,容器内的
这是结合了 Docker 构建 、Nexus 上传 、子目录隔离 的最终版本(一)。
bash
pipeline {
// 全局代理:使用 Jenkins Slave 节点
agent {
node {
label 'jenkins-slave'
customWorkspace '/home/jenkins/docker-all-nexus'
}
}
// 全局选项
options {
// 在控制台输出时间戳,方便排查耗时
timestamps()
// 构建超时时间
timeout(time: 1, unit: 'HOURS')
// 保留最近 10 次构建记录
buildDiscarder(logRotator(numToKeepStr: '10'))
// 禁止并发构建,防止资源冲突
disableConcurrentBuilds()
}
// 环境变量
environment {
// --- Nexus 配置 ---
NEXUS_CREDENTIALS_ID = 'nexus-deploy-creds'
// 注意:这里只写 IP:PORT,协议单独配置
NEXUS_TOOL_URL = '172.20.10.6:8081'
NEXUS_PROTOCOL = 'http'
NEXUS_MAVEN_REPO = 'app-backend'
NEXUS_RAW_REPO = 'app-frontend-raw'
// --- Git 配置 ---
FRONTEND_GIT_URL = 'git@gitee.com:testpm/frontend-docker.git'
BACKEND_GIT_URL = 'git@gitee.com:testpm/backend-docker.git'
GIT_CREDENTIALS_ID = 'jenkins-slave-git'
// --- 镜像配置 ---
NODE_IMAGE = 'node:16'
// 使用具体的 Maven 版本
MAVEN_IMAGE = 'maven:3.9.9-eclipse-temurin-21'
// --- 版本控制 ---
// 使用构建号作为版本后缀,确保唯一性
BUILD_VERSION = "${BUILD_NUMBER}"
// --- 构建产物路径 实际工作中记得修改为自己的路径和包名---
BACKEND_JAR_PATH = 'backend/target/*.jar'
}
stages {
// ================== 阶段 1: 代码拉取 ==================
stage('📥 代码拉取') {
steps {
script {
echo "🚀 开始拉取代码..."
// 1. 拉取后端代码到 backend 目录
dir('backend') {
// 确保目录干净
deleteDir()
echo "📦 正在拉取后端代码..."
git branch: 'master',
credentialsId: "${GIT_CREDENTIALS_ID}",
url: "${BACKEND_GIT_URL}"
}
// 2. 拉取前端代码到 frontend 目录
dir('frontend') {
deleteDir()
echo "📦 正在拉取前端代码..."
git branch: 'master',
credentialsId: "${GIT_CREDENTIALS_ID}",
url: "${FRONTEND_GIT_URL}"
}
}
}
}
// ================== 阶段 2: 后端构建 (Maven) ==================
stage('🔨 后端构建') {
steps {
script {
// 增加重试机制:如果Maven下载依赖超时,自动重试最多3次
retry(3) {
//使用 inside 语法,强制指定工作目录
docker.image("${MAVEN_IMAGE}").inside("-u root:root -w ${WORKSPACE} -v /data/maven-cache:/root/.m2 --entrypoint=''") {
// 进入 backend 目录执行构建
dir('backend') {
sh '''
echo "🚀 当前容器内路径: $(pwd)"
echo "🚀 开始 Maven 编译..."
mvn clean package -DskipTests -B -U
'''
}
}
}
// 将验证和暂存移出 docker.inside 块(在 Jenkins 节点上执行)
def jarFiles = findFiles(glob: "${env.BACKEND_JAR_PATH}")
if (jarFiles == null || jarFiles.length == 0) {
error "❌ 后端构建失败:未找到JAR文件"
}
// 构建完成后,将产物存入暂存区,同时保存pom文件,方便上传nexus时可以读到pom文件中的版本/id等信息
stash name: 'backend-artifact', includes: 'backend/target/*.jar, backend/pom.xml'
}
}
}
// ================== 阶段 3: 前端构建 (Node.js) ==================
stage('🎨 前端构建') {
steps {
script {
// 增加重试机制:防止 npm install 网络波动
retry(3) {
//使用 inside 语法,强制指定工作目录,容器内执行npm打包命令
docker.image("${NODE_IMAGE}").inside("-u root:root -w ${WORKSPACE} -v /data/npm-cache:/root/.npm --entrypoint=''") {
// 进入frontend 目录
dir('frontend') {
sh '''
echo "🚀 开始 NPM 编译..."
npm config set registry https://registry.npmmirror.com
export NODE_OPTIONS=--max_old_space_size=1536
npm install --legacy-peer-deps
npm run build
'''
} // dir 块结束
} // docker.inside 块结束
} //retry(3) 块结束
// 2. 宿主机 (Jenkins Slave):打包文件
// 直接使用默认源安装 zip
dir('frontend') {
sh '''
# 安装 zip 工具
# apt-get update && apt-get install -y zip
# yum install -y zip
# 压缩构建产物
zip -r frontend-dist-${BUILD_VERSION}.zip dist/
'''
}
// 3. 验证并暂存
// 直接构造正确的文件路径,而不是使用环境变量
def expectedZipPath = "frontend/frontend-dist-${BUILD_VERSION}.zip"
def zipFiles = findFiles(glob: expectedZipPath)
if (zipFiles == null || zipFiles.length == 0) {
error "❌ 前端构建失败:未找到ZIP文件 ${expectedZipPath}"
}
// 暂存时也使用构造好的路径
stash name: 'frontend-artifact', includes: expectedZipPath
}
}
}
// ================== 阶段 4: 上传到 Nexus ==================
stage('☁️ 上传制品到 Nexus') {
// 回到主节点执行上传,因为 Nexus 插件通常在 Master/Agent 上运行,而不是 Docker 里
agent {
node {
label 'jenkins-slave'
}
}
steps {
script {
echo "🚀 开始上传制品..."
// --- 1. 上传后端 ---
// 恢复后端制品
unstash 'backend-artifact'
// 读取 pom.xml 获取 GroupId 和 ArtifactId
def pom = readMavenPom file: 'backend/pom.xml'
// 找到生成的 jar 文件
def jarFile = findFiles(glob: 'backend/target/*.jar')[0]
nexusArtifactUploader(
nexusVersion: 'nexus3',
protocol: "${NEXUS_PROTOCOL}",
nexusUrl: "${NEXUS_TOOL_URL}", // 注意:插件版本不同可能是 toolUrl 或 nexusUrl
groupId: "${pom.groupId}",
version: "${pom.version}",
repository: "${NEXUS_MAVEN_REPO}",
credentialsId: "${NEXUS_CREDENTIALS_ID}",
artifacts: [[
artifactId: "${pom.artifactId}",
classifier: '',
file: "${jarFile.path}",
type: 'jar'
]]
)
// --- 2. 上传前端 ---
// 恢复前端制品
unstash 'frontend-artifact'
def zipFile = findFiles(glob: "frontend/frontend-dist-${BUILD_VERSION}.zip")[0]
nexusArtifactUploader(
nexusVersion: 'nexus3',
protocol: "${NEXUS_PROTOCOL}",
nexusUrl: "${NEXUS_TOOL_URL}",
// 前端通常作为 Raw 类型上传,这里手动指定 GroupId
groupId: 'com.company.frontend',
version: "${BUILD_VERSION}",
repository: "${NEXUS_RAW_REPO}",
credentialsId: "${NEXUS_CREDENTIALS_ID}",
artifacts: [[
artifactId: 'frontend-dist',
classifier: '',
file: "${zipFile.path}",
type: 'zip'
]]
)
}
}
}
}
post {
success {
echo "✅ 构建及上传成功!"
}
failure {
echo "❌ 构建失败,请检查日志。"
}
always {
cleanWs()
}
}
}
这是结合了 Docker 构建 、Nexus 上传 、子目录隔离 的最终版本(二)。
压缩也放到容器内
ini
pipeline {
// 全局代理:使用 Jenkins Slave 节点
agent {
node {
label 'jenkins-slave'
customWorkspace '/home/jenkins/docker-all-nexus'
}
}
// 全局选项
options {
// 在控制台输出时间戳,方便排查耗时
timestamps()
// 构建超时时间
timeout(time: 1, unit: 'HOURS')
// 保留最近 10 次构建记录
buildDiscarder(logRotator(numToKeepStr: '10'))
// 禁止并发构建,防止资源冲突
disableConcurrentBuilds()
}
// 环境变量
environment {
// --- Nexus 配置 ---
NEXUS_CREDENTIALS_ID = 'nexus-deploy-creds'
// 注意:这里只写 IP:PORT,协议单独配置
NEXUS_TOOL_URL = '172.20.10.6:8081'
NEXUS_PROTOCOL = 'http'
NEXUS_MAVEN_REPO = 'app-backend'
NEXUS_RAW_REPO = 'app-frontend-raw'
// --- Git 配置 ---
FRONTEND_GIT_URL = 'git@gitee.com:testpm/frontend-docker.git'
BACKEND_GIT_URL = 'git@gitee.com:testpm/backend-docker.git'
GIT_CREDENTIALS_ID = 'jenkins-slave-git'
// --- 版本控制 ---
// 使用构建号作为版本后缀,确保唯一性
BUILD_VERSION = "${BUILD_NUMBER}"
// --- 镜像配置 ---
NODE_IMAGE = 'node:20'
// 使用具体的 Maven 版本
MAVEN_IMAGE = 'maven:3.9.9-eclipse-temurin-21'
// 明确定义产物名称,避免路径拼接错误
BACKEND_JAR_GLOB = 'backend/target/*.jar'
}
stages {
// ================== 阶段 1: 代码拉取 (手动控制目录) ==================
stage('📥 代码拉取') {
steps {
script {
echo "🚀 开始拉取代码..."
// 1. 拉取后端代码到 backend 目录
dir('backend') {
// 确保目录干净
deleteDir()
echo "正在拉取后端代码..."
git branch: 'master',
credentialsId: "${GIT_CREDENTIALS_ID}",
url: "${BACKEND_GIT_URL}"
}
// 2. 拉取前端代码到 frontend 目录
dir('frontend') {
deleteDir()
echo "正在拉取前端代码..."
git branch: 'master',
credentialsId: "${GIT_CREDENTIALS_ID}",
url: "${FRONTEND_GIT_URL}"
}
}
}
}
// ================== 阶段 2: 后端构建 (Maven) ==================
stage('🔨 后端构建') {
steps {
script {
// 针对 Maven 的网络波动,单独加重试
retry(3) {
// 使用 inside 语法,强制指定工作目录
docker.image("${MAVEN_IMAGE}").inside("-u root:root -w ${WORKSPACE} -v /data/maven-cache:/root/.m2 --rm --entrypoint=''") {
// 进入 backend 目录执行构建
dir('backend') {
sh '''
echo "🚀 当前容器内路径: $(pwd)"
echo "🚀 开始 Maven 编译..."
mvn clean package -DskipTests -B -U
'''
}
}
}
// 验证产物
def jars = findFiles(glob: "${BACKEND_JAR_GLOB}")
if (!jars) error "❌ 后端构建失败:未找到 Jar 包"
// 构建完成后,将产物存入暂存区 (加入构建号防止冲突)
stash name: "backend-artifact-${BUILD_NUMBER}", includes: 'backend/target/*.jar, backend/pom.xml'
}
}
}
// ================== 阶段 3: 前端构建 (Node.js) ==================
stage('🎨 前端构建') {
steps {
script {
// 使用 inside 语法,强制指定工作目录
docker.image("${NODE_IMAGE}").inside("-u root:root -w ${WORKSPACE} -v /data/npm-cache:/root/.npm --rm --entrypoint=''") {
dir('frontend') {
// 1. 下载依赖(网络不稳定,单独重试)
retry(3) {
sh '''
echo "📦 安装依赖..."
npm config set registry https://registry.npmmirror.com
export NODE_OPTIONS=--max_old_space_size=1536
npm install --legacy-peer-deps
'''
}
// 2. 编译
sh '''
echo "🚀 开始编译..."
npm run build
'''
// 3. 打包
sh '''
echo "📦 正在压缩前端产物..."
# 更新源并安装 zip
apt-get update || echo "警告: apt-get update 失败,尝试继续..."
apt-get install -y zip || echo "警告: zip 安装失败"
# 打包
zip -r frontend-dist-${BUILD_VERSION}.zip dist/
'''
}
}
}
// 构建完成后,将产物存入暂存区
stash name: "frontend-artifact-${BUILD_VERSION}", includes: "frontend/frontend-dist-${BUILD_VERSION}.zip"
}
}
// ================== 阶段 4: 上传到 Nexus ==================
stage('☁️ 上传制品到 Nexus') {
// 回到主节点执行上传
agent {
node {
label 'jenkins-slave'
}
}
steps {
script {
echo "🚀 开始上传制品..."
// --- 1. 处理并上传后端 ---
echo "📦 上传后端 Jar..."
// 恢复后端制品 (注意名字要对应)
unstash "backend-artifact-${BUILD_NUMBER}"
// 读取 pom.xml 获取 GroupId 和 ArtifactId
def pom = readMavenPom file: 'backend/pom.xml'
// 找到生成的 jar 文件
def jarFile = findFiles(glob: 'backend/target/*.jar')[0]
nexusArtifactUploader(
nexusVersion: 'nexus3',
protocol: "${NEXUS_PROTOCOL}",
nexusUrl: "${NEXUS_TOOL_URL}",
groupId: "${pom.groupId}",
version: "${pom.version}",
repository: "${NEXUS_MAVEN_REPO}",
credentialsId: "${NEXUS_CREDENTIALS_ID}",
artifacts: [
[
artifactId: "${pom.artifactId}",
classifier: '',
file: "${jarFile.path}",
type: 'jar'
]
]
)
// --- 2. 处理并上传前端 ---
echo "📦 上传前端 Zip..."
// 恢复前端制品
unstash "frontend-artifact-${BUILD_VERSION}"
def zipFile = findFiles(glob: "frontend/frontend-dist-${BUILD_VERSION}.zip")[0]
nexusArtifactUploader(
nexusVersion: 'nexus3',
protocol: "${NEXUS_PROTOCOL}",
nexusUrl: "${NEXUS_TOOL_URL}",
// 前端通常作为 Raw 类型上传,这里手动指定 GroupId
groupId: 'com.company.frontend',
version: "${BUILD_VERSION}",
repository: "${NEXUS_RAW_REPO}",
credentialsId: "${NEXUS_CREDENTIALS_ID}",
artifacts: [
[
artifactId: 'frontend-dist',
classifier: '',
file: "${zipFile.path}",
type: 'zip'
]
]
)
}
}
}
}
// 后置处理
post {
success {
echo "✅ 构建及上传成功!"
}
failure {
echo "❌ 构建失败,请检查日志。"
}
always {
// 清理工作空间,防止磁盘爆满
cleanWs()
}
}
}
未完待续