在前面的文章里面介绍了自由风格和Pipeline方式(裸机部署,docker项目部署)的部署过程,也做出来了模板,目前遇到一个问题就是公司给的运维服务器有限,在构建的过程中发现项目并没有语言版本完全统一,比如有jdk17、jdk21、jdk8,node16,node18等等,这种多语言环境的情况如果混合部署在一台打包服务器上会出现各种奇奇怪怪的问题,所以这次采取了直接使用jenkins的容器节点来进行项目构建,这样能够减少多语言项目构建的冲突。
1. 构建打包容器
这里仅以一个版本作为示例,其他的语言版本可以按这个思路来进行构建,另外请注意 maven node go在打包前一定先把私服地址什么的设置好要不直接到国外服务器下载慢的一批
1.1 构建目录结构
ruby
# 目录结构(这套是带包的JDK21 Maven3.9 node-v22 sonar-5 docker-28.4)
root@ubuntu2204test99:~/cicd_images/cicd_jdk21_go_mvn3911_node22_sonarq5_docker284# tree -L 1
.
├── docker
├── Dockerfile
├── go
├── jdk-21.0.88cccaac7ca7e
├── maven-3.9.11
├── node-v22.19.0
└── sonar-scanner-5.0.2.4997
1.2 Dockerfile内容
bash
# Dockerfile构建内容 使用的公司私服
FROM harbor.xxxxxx.com/devops/cicd-ubuntu2404-base:v1
# 安装 Git 并清理缓存
RUN apt-get update && \
apt-get install -y git && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
COPY go /usr/local/go
COPY jdk-21.0.8 /usr/local/jdk-21.0.8
COPY maven-3.9.11 /usr/local/maven-3.9.11
COPY node-v22.19.0 /usr/local/node-v22.19.0
COPY sonar-scanner-5.0.2.4997 /usr/local/sonar-scanner-5.0.2.4997
COPY docker/docker /usr/bin/docker
# 设置环境变量
ENV JAVA_HOME=/usr/local/jdk-21.0.8
ENV SCANNER_HOME=/usr/local/sonar-scanner-5.0.2.4997
ENV MAVEN_HOME=/usr/local/maven-3.9.11
ENV NODE_HOME=/usr/local/node-v22.19.0
ENV GO_HOME=/usr/local/go
# 更新 PATH 和 CLASSPATH
ENV PATH=$PATH:$JAVA_HOME/bin:$MAVEN_HOME/bin:$NODE_HOME/bin:$GO_HOME/bin:$SCANNER_HOME/bin
ENV CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV NODE_OPTIONS=--max_old_space_size=2048
# 设置 Go 和 npm 的国内源
RUN go env -w GO111MODULE=on && \
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct && \
npm config set registry http://mirrors.cloud.tencent.com/npm/
# 使用 bash 启动容器
CMD ["/bin/bash"]
1.3 打包并推送至私服
ruby
root@ubuntu2204test99:~# docker build -t harbor.muscledog.top/devops/cicd-ubuntu2404-jdk21:v1 .
root@ubuntu2204test99:~# docker push harbor.muscledog.top/devops/cicd-ubuntu2404-jdk21:v1
2. 容器节点流水线
2.1 项目Dockerfile内容
bash
# 项目的dockerfile名称MutilBuildDockerfile,使用多段构建
FROM harbor.xxx.com/devops/cicd-ubuntu2404-jdk21:v1 AS builder
WORKDIR /build
COPY . .
RUN mvn clean package -DskipTests
FROM harbor.xxx.top/devops/ubuntu/jre:21-24.04
WORKDIR /app
COPY --from=builder /build/target/spring-boot-3-hello-world-1.0.0-SNAPSHOT.jar /app/spring-boot-3-hello-world-1.0.0-SNAPSHOT.jar
ENTRYPOINT ["java", "-jar", "spring-boot-3-hello-world-1.0.0-SNAPSHOT.jar"]
这里我将容器相关构建的代码片段输出到这里,全部的代码片段太多了而且和前面文章有些是一样的 没必要!
kotlin
def createVersion() {
// 创建了一个方法createVersion()
// 定义一个时间戳+构建ID作为版本号,为tag使用
return new Date().format('yyyyMMddHHmmss') + "_${env.BUILD_ID}"
}
pipeline {
agent { node { label 'node47' } }
environment {
GIT_REPO_URL = 'git@xxxx.com:xxxx/spring-boot-3-hello-world-jdk21.git'
RED = "\u001B[31m"
GREEN = "\u001B[32m" // 绿色
PURPLE = "\u001B[35m" // 紫色
RESET = "\u001B[0m" // 重置
REGISTRY_URL = 'harbor.xxxx.com'
HARBOR_URL = 'https://harbor.xxxx.com'
PROJECT_GROUP = 'devops'
PROJECT_NAME = 'hello-world-jdk21'
CONTAINER_NAME = 'hello-world-jdk21'
OPS_SHARE_LIBRARY = 'git@xxxx.com:xxxx/ops-share-librarya.git'
}
options {
timeout(time: 10, unit: 'MINUTES')
disableConcurrentBuilds()
timestamps()
}
parameters {
gitParameter(
name: 'BRANCH_TAG',
type: 'PT_BRANCH_TAG',
defaultValue: 'master',
description: '请选择你要部署的分支或Tag',
useRepository: 'git@xxxx.com:xxxx/spring-boot-3-hello-world-jdk21.git',
quickFilterEnabled: true
)
booleanParam(defaultValue: false, description: '是否进行项目回滚?', name: 'ROLLBACK_TAG')
}
stages {
stage('Check requirement') {
steps {
script {
if (params.ROLLBACK_TAG && env.BRANCH_TAG.startsWith('rel-')) {
echo "条件满足:ROLLBACK_TAG 为真,且 BRANCH_TAG 以 rel- 开头,继续执行后续步骤。"
} else if (!params.ROLLBACK_TAG && !env.BRANCH_TAG.startsWith('rel-')) {
echo "条件满足:ROLLBACK_TAG 为假,且BRANCH_TAG 不以 rel- 开头,继续执行后续步骤。"
} else {
echo "条件不满足,终止整个流程。"
error("构建中止:条件不满足。")
}
}
}
}
stage('Show info') {
steps {
script {
if (params.ROLLBACK_TAG) {
wrap([$class: 'BuildUser']) {
echo "Built by: ${env.BUILD_USER_ID}"
currentBuild.description = "Built by: ${env.BUILD_USER_ID}, Rollback: ${env.BRANCH_TAG}, tag: '项目回滚'}"
}
} else {
_tag = createVersion()
wrap([$class: 'BuildUser']) {
echo "Built by: ${env.BUILD_USER_ID}"
currentBuild.description = "Built by: ${env.BUILD_USER_ID}, Branch: ${env.BRANCH_TAG}, tag: ${_tag}"
}
}
}
}
}
stage('CleanWorkDir') {
steps {
cleanWs()
}
}
//这里开始使用容器进行构建
stage('SlaveDocker49Node'com
steps {
node('node47') {
script {
//这里使用先前构建好的打包容器进行项目构建,将构建的缓存映射到服务器目录当中加快以后构建的速度,同时将主机的docker进程映射到docker容器中
docker.image('harbor.xxx.com/devops/cicd-ubuntu2404-jdk21:v1').inside('-v /root/Cache/m2/:/usr/local/maven-3.9.11/m2 -v /var/run/docker.sock:/var/run/docker.sock -v /root/.ssh:/root/.ssh') {
stage('Checkout') {
script {
checkout([$class: 'GitSCM',
branches: [[name: params.BRANCH_TAG]],
userRemoteConfigs: [[url: "${GIT_REPO_URL}", credentialsId: "GiteeKey"]]])
}
}
stage('Build') {
script {
if (!params.BRANCH_TAG.startsWith('rel-')) {
ansiColor('xterm') {
echo "${GREEN}项目开始构建${RESET}"
echo "Building"
}
// 构建项目镜像,指定Dockerfile名称
def image = docker.build("${REGISTRY_URL}/${PROJECT_GROUP}/${PROJECT_NAME}:${_tag}", "-f MutilBuildDockerfile .")
// 构建成功后推送到私服当中
docker.withRegistry("${HARBOR_URL}", "Harbor") {
image.push()
}
// 删除已经推送完毕的镜像
sh "docker rmi ${REGISTRY_URL}/${PROJECT_GROUP}/${PROJECT_NAME}:${_tag}"
if (params.BRANCH_TAG.startsWith("rel-")) {
echo "选择的是标签,不打标签。"
} else if (params.BRANCH_TAG == "master" || params.BRANCH_TAG == "origin/master") {
sh "git tag rel-${_tag}"
sh "git push origin rel-${_tag}"
echo "为分支 ${params.BRANCH_TAG} 打标签 rel-${_tag}。"
} else {
echo "当前分支 ${params.BRANCH_TAG} 不是 master,不打标签。"
}
} else {
echo "跳过构建:BRANCH_TAG 以 'rel-' 开头。"
}
}
}
}
}
}
}
}
// 同样Ansible部署也使用了容器来对项目进行推送部署
stage('SlaveAnsible49Node') {
steps {
node('node47') {
script {
// 这里使用私服中的ansible镜像进行部署,将密钥挂载到容器当中
docker.image('harbor.xxx.com/devops/ansible:2.18.6').inside('-v /root/.ssh:/root/.ssh') {
stage('Checkout') {
script {
checkout([$class: 'GitSCM',
branches: [[name: '*/master']],
userRemoteConfigs: [[url: "${OPS_SHARE_LIBRARY}", credentialsId: "GiteeKey"]]])
}
}
stage('Ansible in Slave1') {
script {
// 定义私服和ansible的相关变量信息
withCredentials([usernamePassword(credentialsId: 'Harbor', usernameVariable: 'HARBOR_USERNAME', passwordVariable: 'HARBOR_PASSWORD')]) {
def extraVars = [
container_name: "${CONTAINER_NAME}",
docker_registry: "${HARBOR_URL}",
docker_username: "${HARBOR_USERNAME}",
docker_password: "${HARBOR_PASSWORD}"
].collectEntries { [(it.key): it.value] }
if (!env.BRANCH_TAG.startsWith('rel-')) {
extraVars['new_image_name'] = "${REGISTRY_URL}/${PROJECT_GROUP}/${PROJECT_NAME}:${_tag}"
ansiblePlaybook(
playbook: "./Ansible/HelloWorld-Pipeline/deploy.yml",
inventory: "./Ansible/HelloWorld-Pipeline/hosts",
extraVars: extraVars
)
} else {
def branchTag = env.BRANCH_TAG
def extractedValue = branchTag.replaceFirst(/^rel-/, '')
println "Extracted Value: ${extractedValue}"
extraVars['rel_image_name'] = "${REGISTRY_URL}/${PROJECT_GROUP}/${PROJECT_NAME}:${extractedValue}"
ansiblePlaybook(
playbook: "./Ansible/HelloWorld-Pipeline/rollback.yml",
inventory: "./Ansible/HelloWorld-Pipeline/hosts",
extraVars: extraVars
)
}
}
}
}
}
}
}
}
}
} // 结束 stages
post {
always {
script {
println("流水线结束后,经常做的事情")
}
}
success {
script {
def notificationText = []
notificationText.add("- 成功构建部署: ${JOB_NAME}项目!\n")
notificationText.add("- 版本: ${BRANCH_TAG}\n")
notificationText.add("- 持续时间: ${currentBuild.durationString}\n")
notificationText.add("- 任务: #${JOB_NAME}")
if (params.BRANCH_TAG == "master" || params.BRANCH_TAG == "origin/master") {
notificationText.add("- 标签: rel-${_tag}\n")
}
dingtalk (
robot: 'BuildBoy',
type: 'MARKDOWN',
title: "success: ${JOB_NAME}",
text: notificationText
)
}
}
failure {
dingtalk (
robot: 'BuildBoy',
type: 'MARKDOWN',
title: "fail: ${JOB_NAME}",
text: ["- 失败构建部署:${JOB_NAME}项目!\n- 版本:${BRANCH_TAG}\n- 持续时间:${currentBuild.durationString}\n- 任务:#${JOB_NAME}"]
)
}
aborted {
dingtalk (
robot: 'BuildBoy',
type: 'MARKDOWN',
title: "aborted: ${JOB_NAME}",
text: ["- 流水线被取消:${JOB_NAME}项目!\n- 版本:${BRANCH_TAG}\n- 持续时间:${currentBuild.durationString}\n- 任务:${JOB_NAME}"]
)
}
}
}
3.测试流水线

image-20250919143911634
这样整个构建的过程就再容器当中进行,而不会影响到你现在的系统环境,感觉这种方式还比较nice,如果文章帮助到了您记得来个赞哦!