运维|devops|Jenkins|pipeline结合docker构建应用
基于 jenkins Version 2.541.3 Pipeline 实操案例
部署前置条件
Jenkins容器是否挂载宿主机Docker,本案例都是基于docker部署的Jenkins
检查当前 Jenkins 容器启动参数
docker inspect jenkins | grep -A 10 "Mounts"
停止并删除当前容器
docker stop jenkins
docker rm jenkins
使用新命令启动容器(新增挂载 /usr/bin/docker)
docker run -d \
--name jenkins \
--restart always \
-p 8080:8080 \
-p 50000:50000 \
-v /data/jenkins:/var/jenkins_home \
-v /etc/localtime:/etc/localtime:ro \
-v /usr/local/apache-maven-3.9.6:/usr/local/maven \
-v /usr/local/jdk-21.0.11:/usr/local/jdk21 \
-v /data/maven_repo:/root/.m2/repository \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /usr/bin/docker:/usr/bin/docker \
-v /etc/docker/daemon.json:/etc/docker/daemon.json \
jenkins/jenkins:lts-jdk17
其他前置条件
配置Nexus 仓库凭证

Docker Pipeline 插件安装

nexus 允许匿名推送镜像

相关权限开通
- 临时开通(docker所在服务器和目标应用服务器)
sudo chmod 666 /var/run/docker.sock
- 永久开通
编辑 /etc/systemd/system/docker.service.d/override.conf(如果不存在则创建)
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H unix:///var/run/docker.sock --group=docker --exec-opt native.cgroupdriver=systemd
sudo systemctl daemon-reload
sudo systemctl restart docker
目标应用服务器
- sudo vi /etc/docker/daemon.json
{
"registry-mirrors": [
"https://docker.xuanyuan.me",
"https://docker.1ms.run",
"https://docker.m.daocloud.io"
],
"insecure-registries": ["192.168.0.88:8881"]
}
- 重启
sudo systemctl daemon-reload
sudo systemctl restart docker
- 检查
docker info | grep -A 2 "Insecure Registries"
- 默认登录下(保证有登录信息)
docker login 192.168.0.88:8881
相关脚本
docker
# 使用轻量级 JRE 镜像(基于 Alpine,体积更小)
FROM eclipse-temurin:21-jre-alpine
LABEL maintainer="your-email@example.com"
WORKDIR /app
# 复制 JAR 包(使用通配符,适应版本变化)
COPY target/*.jar app.jar
# 暴露应用端口
EXPOSE 6086
# 设置 JVM 参数(可通过环境变量覆盖)
ENV JAVA_OPTS="-Xms256m -Xmx512m"
# 健康检查(需应用暴露 /actuator/health 端点)
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:6086/actuator/health || exit 1
# 启动命令(支持传入 JAVA_OPTS)
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
jenkins
pipeline {
agent any
options {
disableConcurrentBuilds()
timeout(time: 20, unit: 'MINUTES')
timestamps()
buildDiscarder(logRotator(numToKeepStr: '10'))
}
parameters {
booleanParam(
name: 'AUTO_DEPLOY',
defaultValue: false,
description: '构建完成自动推送镜像并重启远程服务'
)
string(
name: 'IMAGE_TAG',
defaultValue: '',
description: '镜像标签,留空则使用 Jenkins 构建号'
)
}
environment {
SERVICE_DIR = 'app'
SERVICE_NAME = 'app'
JAR_VERSION = '1.0.0-SNAPSHOT'
JAR_FILE = "${SERVICE_NAME}-${JAR_VERSION}.jar"
LOCAL_JAR_PATH = "${SERVICE_DIR}/target/${JAR_FILE}"
JAVA_HOME = '/usr/local/jdk21'
M2_HOME = '/usr/local/maven'
REGISTRY = '192.168.0.20:8881'
IMAGE_NAME = "${REGISTRY}/${SERVICE_NAME}"
IMAGE_TAG = "${params.IMAGE_TAG ?: env.BUILD_ID}"
REMOTE_HOST = '192.168.0.19'
REMOTE_USER = 'test'
SSH_CREDENTIALS_ID = 'test'
CONTAINER_NAME = "${SERVICE_NAME}"
HOST_PORT = '6086'
CONTAINER_PORT = '6086'
CONFIG_HOST_PATH = '/data/test/config'
CONFIG_CONTAINER_PATH = '/data/test/config'
}
stages {
stage('1. 环境信息 & Git提交日志打印') {
steps {
sh '''#!/bin/bash
echo "==================== Git 最新提交信息 ===================="
echo "短提交号: $(git rev-parse --short HEAD)"
echo "完整commit: $(git rev-parse HEAD)"
echo "提交人姓名: $(git log -1 --pretty=%an)"
echo "提交备注:"
git log -1 --pretty=%B
echo "=========================================================="
'''
echo "【构建信息】服务名:${env.SERVICE_NAME}|镜像:${env.IMAGE_NAME}:${env.IMAGE_TAG}"
echo "本地JAR路径:${env.LOCAL_JAR_PATH}"
echo "远端主机:${env.REMOTE_HOST}"
}
}
stage('2. Maven编译打包') {
steps {
sh '''#!/bin/bash
test -d ${JAVA_HOME} || { echo "❌ JAVA_HOME目录不存在"; exit 1; }
test -d ${M2_HOME} || { echo "❌ M2_HOME目录不存在"; exit 1; }
${JAVA_HOME}/bin/java -version
${M2_HOME}/bin/mvn -v
cd ${SERVICE_DIR}
${M2_HOME}/bin/mvn clean package -DskipTests
'''
}
}
stage('3. 归档构建产物(可选)') {
steps {
archiveArtifacts artifacts: "${env.LOCAL_JAR_PATH}", fingerprint: true, onlyIfSuccessful: true
}
}
stage('4. 构建并推送 Docker 镜像') {
steps {
script {
docker.withRegistry("http://${env.REGISTRY}", 'nexus-credentials') {
def image = docker.build("${env.IMAGE_NAME}:${env.IMAGE_TAG}", "-f ${env.SERVICE_DIR}/Dockerfile ${env.SERVICE_DIR}")
image.push()
echo "✅ 镜像推送成功: ${env.IMAGE_NAME}:${env.IMAGE_TAG}"
}
}
}
}
stage('5. 远程发布部署') {
when {
expression { params.AUTO_DEPLOY }
}
steps {
echo "开始远程部署新镜像到 ${env.REMOTE_HOST}"
withCredentials([
sshUserPrivateKey(
credentialsId: "${env.SSH_CREDENTIALS_ID}",
keyFileVariable: 'SSH_KEY',
usernameVariable: 'SSH_USER'
),
usernamePassword(
credentialsId: 'nexus-credentials',
usernameVariable: 'NEXUS_USER',
passwordVariable: 'NEXUS_PWD'
)
]) {
sh """
#!/bin/bash
set -e -x
ssh -i \$SSH_KEY -o StrictHostKeyChecking=no ${REMOTE_USER}@${REMOTE_HOST} << 'EOF'
set -e -x
# -------- 写入 Docker 认证信息(使用 shell 生成 base64) --------
echo "【0】配置 Docker 认证"
mkdir -p ~/.docker
AUTH=\$(echo -n "${NEXUS_USER}:${NEXUS_PWD}" | base64)
cat > ~/.docker/config.json <<EOL
{
"auths": {
"${REGISTRY}": {
"auth": "\${AUTH}"
}
}
}
EOL
# -------- 拉取最新镜像 --------
echo "【1】拉取镜像 ${IMAGE_NAME}:${IMAGE_TAG}"
docker pull ${IMAGE_NAME}:${IMAGE_TAG}
# -------- 停止并删除旧容器(若有) --------
echo "【2】停止并删除旧容器 ${CONTAINER_NAME}"
docker stop ${CONTAINER_NAME} 2>/dev/null || true
docker rm ${CONTAINER_NAME} 2>/dev/null || true
# -------- 启动新容器(挂载配置目录) --------
echo "【3】启动新容器,映射端口 ${HOST_PORT}:${CONTAINER_PORT}"
docker run -d \\
--name ${CONTAINER_NAME} \\
--restart=always \\
-p ${HOST_PORT}:${CONTAINER_PORT} \\
-v ${CONFIG_HOST_PATH}:${CONFIG_CONTAINER_PATH} \\
${IMAGE_NAME}:${IMAGE_TAG}
# -------- 等待并检查状态 --------
sleep 5
echo "【4】检查容器状态"
docker ps --filter "name=${CONTAINER_NAME}" --format "table {{.Names}}\t{{.Status}}\t{{.Image}}"
echo "========== 容器启动日志(尾部50行) =========="
docker logs --tail 50 ${CONTAINER_NAME}
echo "================================================"
echo "✅ 部署完成"
EOF
"""
}
echo "远程部署流程执行完毕"
}
}
}
post {
always {
echo "【Post钩子】流水线构建结束"
}
success {
cleanWs()
echo "🎉 流水线全部执行成功!"
}
failure {
echo "💥 流水线执行失败,请查看日志排查问题!"
}
}
}
部署结果

