Git集成Jenkins通过Pipeline方式实现一键部署

Docker方式部署Jenkins

部署自定义Docker网络

部署Docker网络的作用:

  1. 隔离性
  2. 便于同一网络内容器相互通信
bash 复制代码
# 创建名为jenkins的docker网络
docker network create --subnet 172.18.0.0/16 --gateway 172.18.0.1 jenkins

# 查看docker网络列表
docker network ls

# 查看名为jenkins的docker网络详情
docker network inspect jenkins

部署Jenkins

拉取镜像

bash 复制代码
docker pull hub.rat.dev/jenkins/jenkins:lts-jdk17

运行容器

bash 复制代码
docker run \
  --name jenkins \
  --restart=on-failure \
  --detach \
  --network jenkins \
  --env DOCKER_HOST=tcp://docker:2376 \
  --env DOCKER_CERT_PATH=/certs/client \
  --env DOCKER_TLS_VERIFY=1 \
  --publish 8088:8080 \
  --publish 50000:50000 \
  --volume jenkins-data:/var/jenkins_home \
  --volume jenkins-docker-certs:/certs/client:ro \
    hub.rat.dev/jenkins/jenkins:lts-jdk17

参数解析

  • --name jenkins
    • 为容器指定一个名称,这里是 jenkins。
  • --restart=on-failure
    • 配置容器的重启策略。如果容器因错误退出(非正常退出),Docker 将自动重启它。
  • --detach
    • 在后台运行容器,而不是在前台运行。这使得容器在后台持续运行,而不会阻塞终端。
  • --network jenkins
    • 将容器连接到一个名为 jenkins 的 Docker 网络。这通常用于容器之间的通信。
  • --env DOCKER_HOST=tcp://docker:2376:
    • 设置环境变量 DOCKER_HOST,指定 Docker 守护进程的地址。这里指向 docker 服务的 2376 端口。
  • --env DOCKER_CERT_PATH=/certs/client
    • 设置环境变量 DOCKER_CERT_PATH,指定 Docker 客户端证书的路径。证书用于 TLS 验证。
  • --env DOCKER_TLS_VERIFY=1
    • 设置环境变量 DOCKER_TLS_VERIFY,启用 TLS 验证。这确保了与 Docker 守护进程的通信是安全的。
  • --publish 8088:8080
    • 将容器的 8080 端口映射到宿主机的 8088 端口。Jenkins 的 Web 界面通常运行在 8080 端口。
  • --publish 50000:50000
    • 将容器的 50000 端口映射到宿主机的 50000 端口。这是 Jenkins 用于与代理节点通信的端口。
  • --volume jenkins-data:/var/jenkins_home
    • 将宿主机的 jenkins-data 卷挂载到容器的 /var/jenkins_home 目录。这是 Jenkins 的主目录,用于存储配置文件、插件和构建历史。
  • --volume jenkins-docker-certs:/certs/client:ro
    • 将宿主机的 jenkins-docker-certs 卷挂载到容器的 /certs/client 目录,并设置为只读(ro)。这个卷包含用于 TLS 验证的客户端证书。
  • hub.rat.dev/jenkins/jenkins:lts-jdk17
    • 指定要运行的 Docker 镜像。这里是 hub.rat.dev/jenkins/jenkins 镜像的 lts-jdk17 版本。

这段命令的作用是:

  • 启动一个名为 jenkins 的 Jenkins 容器。
  • 配置了重启策略、网络连接、环境变量、端口映射和卷挂载。
  • 使用了 TLS 验证来确保与 Docker 守护进程的安全通信。
  • 将 Jenkins 数据持久化到宿主机的卷中,以便在容器重启后数据不会丢失。
  • 这个配置通常用于在 Docker 环境中运行 Jenkins,并确保其能够安全地与 Docker 守护进程通信。

访问Jenkins

打开浏览器,输入URL:http://${server_url}:8088,安装Jenkins推荐插件,例如:Git、Publish Over SSH 等。

配置环境

Java

查询自带JDK

Docker方式安装Jenkins,容器内部已经自带了Java环境,可以进入容器内部查看

bash 复制代码
jenkins@192.168.100.102:~$ sudo docker exec -it jenkins /bin/bash
[sudo] password for jenkins:*****(输入密码)
jenkins@cc89b70ab9ae:/$ java -version
openjdk version "17.0.15" 2025-04-15
OpenJDK Runtime Environment Temurin-17.0.15+6 (build 17.0.15+6)
OpenJDK 64-Bit Server VM Temurin-17.0.15+6 (build 17.0.15+6, mixed mode)

配置其他JDK版本

如果想要使用其他JDK版本,可以在 Manage Jenkins -> Tools 中配置(这里演示配置 JDK1.8版本)。

下载JDK

JDK下载链接

上传到容器

先将 jdk 上传到服务器(例如:上传到了 /home/jdk),然后拷贝到容器中(这里也可以直接拷贝到挂载目录下)

bash 复制代码
cd /home/jdk
# 拷贝到jenkins容器,目录自定义
$ sudo docker cp jdk-8u202-linux-x64.tar.gz jenkins:/var/jenkins_home/tools/hudson.model.JDK/JDK1.8
# 进入容器内部
$ sudo docker exec -it jenkins /bin/bash
# 解压
$ cd /var/jenkins_home/tools/hudson.model.JDK/JDK1.8
$ tar -zxvf jdk-8u202-linux-x64.tar.gz
$ mv jdk-8u202-linux-x64/* ./
$ rm jdk-8u202-linux-x64.tar.gz

在 Jenkins 控制台配置

进入 Manage Jenkins -> Tools

Git

Docker方式安装Jenkins,容器内部已经自带了Git环境,这里就不配置其他版本了。

查询自带Git

可以进入容器内部查看

bash 复制代码
jenkins@192.168.100.102:~$ sudo docker exec -it jenkins /bin/bash
[sudo] password for jenkins:*****(输入密码)
jenkins@cc89b70ab9ae:/$ git --version
git version 2.39.5

Maven

下载Maven

Maven需要自己安装,这里演示安装Maven 3.9.10 版本。同样也是在 Manage Jenkins -> Tools 中配置

查看Maven安装路径

bash 复制代码
# 进入容器内部
$ sudo docker exec -it jenkins /bin/bash
jenkins@cc89b70ab9ae:~$ cd /var/jenkins_home/tools/hudson.tasks.Maven_MavenInstallation
jenkins@cc89b70ab9ae:~/tools/hudson.tasks.Maven_MavenInstallation$ ls
Maven_3.9.10
jenkins@cc89b70ab9ae:~/tools/hudson.tasks.Maven_MavenInstallation/Maven_3.9.10$ ls Maven_3.9.10
LICENSE  NOTICE  README.txt  bin  boot  conf  lib

配置SSH Server目标服务器

可以有两种方式配置:

  1. 账号密码方式
  2. 公私钥方式

这里介绍一下 公私钥方式(密码方式直接输入相关密码即可)

生成公私钥

bash 复制代码
############ 在Jenkins容器中生成公私钥 ################
# 进入容器内部
$ docker exec -it jenkins /bin/bash
# 生成路径:/var/jenkins_home/.ssh
jenkins@cc89b70ab9ae:~$ ssh-keygen -t rsa -b 4096
jenkins@cc89b70ab9ae:~$ cd .ssh
jenkins@cc89b70ab9ae:~/.ssh$ ls
id_rsa  id_rsa.pub

############# 拷贝公钥内容,将其放在目标服务器的 ~/.ssh/authorized_keys  文件下 ##############
$ cd ~/.ssh
# 目录不存在直接新建即可,然后将公钥拷贝进去
$ vim authorized_keys

Jenkins控制台配置SSH Server

进入 Manage Jenkins >> System >> Publish over SSH

配置凭证

Jenkins控制台 >> Manage Jenkins >> Credentials >> System >> Global credentials (unrestricted) 中配置

配置Git仓库指纹凭证

后续从Git仓库拉取代码会需要用到

配置SSH Server凭证

与前面提到的 配置SSH Server目标服务器 相比,可以理解为:有些SSH插件可以用上面那种,这种是通用的

部署Pipeline任务

项目部署到目标服务器上,以系统服务方式运行

在这里编写 Pipeline Script

在Jenkins控制台手动触发

第一版使用了 sshPublisher 命令,这个命令用到了 Publish over SSH 配置,但是这个命令在 Jenkins控制台不会打印日志

bash 复制代码
pipeline {
    agent any
    
    parameters {
        string(name: 'VERSION', description: '请输入jar包版本号 (例如: 1.0.0)')
        string(name: 'GIT_BRANCH', description: '请输入部署分支 (例如: master)')
    }
    
    environment {
        BASE_JAR_NAME = "jenkins-study" // jar包名称,根据实际项目修改
        GIT_REPO = 'https://xxx.git' // 替换为Git仓库地址
        GIT_CRE_ID = "xxx" // git指纹凭证ID
        SSH_SERVER = 'test_server'  // ssh server名称
        DEPLOY_PATH = '/home/projects/xxx' // 替换为目标服务器的项目部署路径
        // jar包以系统服务方式运行
        MDM_SYSTEM_SVC = 'xxxx.service'
    }

    tools {
        jdk 'JDK1.8'    // jdk版本号
        maven 'Maven 3.9.10' // 使用全局工具配置中定义的 Maven
    }
    
    stages {
        stage('拉取代码') {
            steps {
                git branch: "${params.GIT_BRANCH}", url: "${GIT_REPO}", credentialsId: "${GIT_CRE_ID}"
                echo "代码拉取完成"
            }
        }
        
        stage('编译打包') {
            steps {
                dir("code") {   // 进入code目录下,我的代码仓是因为实际项目代码在code目录下(如果不需要去掉这一行)
                    // 1. 先执行打包
                    sh "mvn clean package"

                    script {    // 将jar修改为指定的版本号,并移动到根目录下
                        def jarFiles = sh(script: 'ls target/${BASE_JAR_NAME}-*.jar', returnStdout: true).trim().split('\n')
                        if (jarFiles.size() == 0 || jarFiles[0].contains('No such file')) {
                            error "未找到JAR包"
                        }
                        // 取第一个匹配的 JAR 文件
                        def originalJarPath = jarFiles[0]
                        env.VERSIONED_JAR = "${BASE_JAR_NAME}-v${params.VERSION}.jar"
                        // 将jar包移动到根目录下
                        sh """
                            mv ${originalJarPath} ../${VERSIONED_JAR}
                        """
                    }
                }
            }
        }
        
        stage('上传到目标服务器') {
            steps {
                script {
                    sshPublisher(
                        publishers: [
                            sshPublisherDesc(
                                configName: "${SSH_SERVER}", 
                                transfers: [
                                    sshTransfer(
                                        sourceFiles: "${VERSIONED_JAR}",
                                        remoteDirectory: "${DEPLOY_PATH}",
                                        remoteDirectorySDF: false,
                                        flatten: false,
                                        execCommand: """
                                            echo '已上传 ${VERSIONED_JAR} 到服务器'
                                            # 确保目标目录存在
                                            mkdir -p ${DEPLOY_PATH}
                                        """
                                    )
                                ]
                            )
                        ]
                    )
                }
            }
        }
        
        stage('执行启动脚本') {
            steps {
                script {
                    sshPublisher(
                        publishers: [
                            sshPublisherDesc(
                                configName: "${SSH_SERVER}", 
                                transfers: [
                                    sshTransfer(
                                        execCommand: """
                                            cd ${DEPLOY_PATH} || exit 1
                                            echo '当前目录:' && pwd
                                            echo '开始执行启动脚本...'
                                            echo 1 | sudo -S ./run.sh ${VERSIONED_JAR}
                                        """
                                    )
                                ]
                            )
                        ]
                    )
                }
            }
        }
        
        stage('输出服务日志') {
            steps {
                script {
                    def result = sshPublisher(
                        publishers: [
                            sshPublisherDesc(
                                configName: "${SSH_SERVER}", // SSH 服务器名称
                                transfers: [
                                    sshTransfer(
                                        execCommand: """
                                            sleep 5s
                                            echo '===== 开始获取服务日志 ====='
                                            echo '服务名称: ${MDM_SYSTEM_SVC}'
                                            echo "当前时间: \$(date '+%Y-%m-%d %H:%M:%S')"
                                            echo '---------------------------'
                                            journalctl -u ${MDM_SYSTEM_SVC} -n 50 --no-pager || {
                                                echo '错误:无法获取服务日志'
                                                exit 1
                                            }
                                            echo '===== 日志获取结束 ====='
                                        """
                                    )
                                ]
                            )
                        ]
                    )
                    
                    echo "服务最新50条日志:"
                    echo "${result}"
                }
            }
        }
    }
    
    post {
        always {
            archiveArtifacts artifacts: "*.jar", allowEmptyArchive: true
            echo "构建流程结束 - ${currentBuild.result}"
        }
        success {
            echo "部署成功! 版本 ${params.VERSION} 已发布"
        }
        failure {
            echo "部署失败,请检查日志"
        }
    }
}

所以有了第二版:使用 sshCommand 命令,这一版用到了 ssh Server 凭证,使用这个命令就 可以在Jenkins控制台看到日志 了。

ps:使用 sshCommand 命令需要安装插件:SSH Pipeline Steps

bash 复制代码
pipeline {
    agent any
    
    parameters {
        string(name: 'VERSION', description: '请输入jar包版本号 (例如: 1.0.0)')
        string(name: 'GIT_BRANCH', description: '请输入部署分支 (例如: master)')
    }

    environment {
        BASE_JAR_NAME = "jenkins-study" // jar包名称,根据实际项目修改
        GIT_REPO = 'https://xxx.git' // 替换为Git仓库地址
        GIT_CRE_ID = "xxx" // git指纹凭证ID
        SSH_SERVER = 'test_server'  // 这个配置在这里就没什么实际作用了,只是一个名称
        SSH_HOST = "192.168.100.102" // 远程服务器的IP地址
        SSH_CREDENTIALS_ID = "xxx" // SSH凭证ID
        DEPLOY_PATH = '/home/projects/xxx' // 替换为目标服务器的项目部署路径
        // jar包以系统服务方式运行
        MDM_SYSTEM_SVC = 'xxxx.service'
    }

    tools {
        jdk 'JDK1.8'    // jdk版本号
        maven 'Maven 3.9.10' // 使用全局工具配置中定义的 Maven
    }
    
    stages {
        stage('拉取代码') {
            steps {
                git branch: "${params.GIT_BRANCH}", url: "${GIT_REPO}", credentialsId: "${GIT_CRE_ID}"
                echo "代码拉取完成"
            }
        }
        
        stage('编译打包') {
            steps {
                dir("code") {
                    sh "mvn clean package"
                    script {
                        def jarFiles = sh(script: 'ls target/${BASE_JAR_NAME}-*.jar', returnStdout: true).trim().split('\n')
                        if (jarFiles.size() == 0 || jarFiles[0].contains('No such file')) {
                            error "未找到JAR包"
                        }
                        def originalJarPath = jarFiles[0]
                        env.VERSIONED_JAR = "${BASE_JAR_NAME}-v${params.VERSION}.jar"
                        sh "mv ${originalJarPath} ../${VERSIONED_JAR}"
                    }
                }
            }
        }
        
        stage('上传到目标服务器') {
            steps {
                script {
                    withCredentials([sshUserPrivateKey(
                        credentialsId: "${env.SSH_CREDENTIALS_ID}", 
                        keyFileVariable: 'identity', 
                        passphraseVariable: '', 
                        usernameVariable: 'userName'
                    )]) {
                        // 定义 remote 对象
                        def remote = [
                            name: "${env.SSH_SERVER}",
                            host: "${env.SSH_HOST}",
                            allowAnyHosts: true,
                            user: userName,
                            identityFile: identity
                        ]
                        
                        sshCommand remote: remote, command: """
                            echo '已上传 ${env.VERSIONED_JAR} 到服务器'
                            mkdir -p ${env.DEPLOY_PATH}
                        """
                        sshPut remote: remote, from: "${env.VERSIONED_JAR}", into: "${env.DEPLOY_PATH}"
                    }
                }
            }
        }
        
        stage('执行启动脚本') {
            steps {
                script {
                    withCredentials([sshUserPrivateKey(
                        credentialsId: "${env.SSH_CREDENTIALS_ID}", 
                        keyFileVariable: 'identity', 
                        passphraseVariable: '', 
                        usernameVariable: 'userName'
                    )]) {
                        // 定义 remote 对象
                        def remote = [
                            name: "${env.SSH_SERVER}",
                            host: "${env.SSH_HOST}",
                            allowAnyHosts: true,
                            user: userName,
                            identityFile: identity
                        ]
                        
                        sshCommand remote: remote, command: """
                            cd ${env.DEPLOY_PATH} || exit 1
                            echo '当前目录:' && pwd
                            echo '开始执行启动脚本...'
                            echo 1 | sudo -S ./run.sh ${env.VERSIONED_JAR}
                        """
                    }
                }
            }
        }
    }
    
    post {
        always {
            archiveArtifacts artifacts: "*.jar", allowEmptyArchive: true
            echo "构建流程结束 - ${currentBuild.result}"
        }
        success {
            echo "部署成功! 版本 ${params.VERSION} 已发布"
        }
        failure {
            echo "部署失败,请检查日志"
        }
    }
}

集成Git WebHook实现事件触发流水线

第一步:在Git上配置WebHook

这里用到的是 Gogs 类型 ,其他类型根据情况修改

token 在Linux中可以通过一下命令自动生成:

bash 复制代码
$ openssl rand -hex 16`

Jenkins通过触发器接收Git WebHook事件

bash 复制代码
// webhook触发(git)
    triggers {
        GenericTrigger(
            genericVariables: [
                [key: "RELEASE_ACTION", value: '$.action'],
                [key: "VERSION", value: '$.release.tag_name'],
                [key: "GIT_BRANCH", value: '$.release.target_commitish']
            ],
            token: "f935f042906c5432950f01359cb35d52",
            causeString: "Triggered by Gogs Release",
            printPostContent: true,      // 调试:打印Webhook原始数据
            printContributedVariables: true // 调试:打印解析后的变量
        )
    }

完整脚本

bash 复制代码
pipeline {
    agent any
    
    environment {
        BASE_JAR_NAME = "jenkins-study" // 根据你的实际项目修改
        GIT_REPO = 'https://xxx.git' // 替换为你的Git仓库地址
        GIT_CRE_ID = "xxx"  // git凭证id
        SSH_SERVER = 'test_server'  // ssh server名称,在这里没什么用
        DEPLOY_PATH = '/home/project' // 替换为目标服务器的部署路径
        MDM_SYSTEM_SVC = 'xxx.service' // jar包以系统服务方式运行
        SSH_HOST = "192.168.100.102" // 远程服务器的IP地址
        SSH_CREDENTIALS_ID = "xxx" // SSH凭证ID
    }

    tools {
        jdk 'JDK1.8'    // jdk版本号
        maven 'Maven 3.9.10' // 使用全局工具配置中定义的 Maven
    }

    // webhook触发(git)
    triggers {
        GenericTrigger(
            genericVariables: [
                [key: "RELEASE_ACTION", value: '$.action'],
                [key: "VERSION", value: '$.release.tag_name'],
                [key: "GIT_BRANCH", value: '$.release.target_commitish']
            ],
            token: "xxx",	// 跟git上面配置的保持一致
            causeString: "Triggered by Gogs Release",
            printPostContent: true,      // 调试:打印Webhook原始数据
            printContributedVariables: true // 调试:打印解析后的变量
        )
    }
    
    stages {
        stage('Handle Release') {
            steps {
                script {
                    // 检查变量是否存在
                    if (!env.VERSION?.trim()) {
                        error "未检测到有效的标签名称"
                        currentBuild.result = 'NOT_BUILT'
                        return
                    }
                    if (!env.GIT_BRANCH?.trim()) {
                        error "未检测到有效的部署推送分支"
                        currentBuild.result = 'NOT_BUILT'
                        return
                    }

                    // 如果不是版本发布事件 则结束 Pipeline
                    def isReleaseEvent = env.RELEASE_ACTION == "released"
                    if (!isReleaseEvent) {
                        echo "当前触发方式不是版本发布事件,直接结束 Pipeline。"
                        currentBuild.result = 'NOT_BUILT'
                        return
                    }
                    echo "发布标签: ${env.VERSION}"
                    echo "目标分支: ${env.TARGET_BRANCH}"
                }
            }
        }


        stage('拉取代码') {
            steps {
                git branch: "${GIT_BRANCH}", url: "${GIT_REPO}", credentialsId: "${GIT_CRE_ID}"
                echo "代码拉取完成"
            }
        }
        
        stage('编译打包') {
            steps {
                dir("code") {
                    sh "mvn clean package"
                    script {
                        def jarFiles = sh(script: 'ls target/${BASE_JAR_NAME}-*.jar', returnStdout: true).trim().split('\n')
                        if (jarFiles.size() == 0 || jarFiles[0].contains('No such file')) {
                            error "未找到JAR包"
                        }
                        def originalJarPath = jarFiles[0]
                        env.VERSIONED_JAR = "${BASE_JAR_NAME}-v${VERSION}.jar"
                        sh "mv ${originalJarPath} ../${VERSIONED_JAR}"
                    }
                }
            }
        }
        
        stage('上传到目标服务器') {
            steps {
                script {
                    withCredentials([sshUserPrivateKey(
                        credentialsId: "${env.SSH_CREDENTIALS_ID}", 
                        keyFileVariable: 'identity', 
                        passphraseVariable: '', 
                        usernameVariable: 'userName'
                    )]) {
                        // 定义 remote 对象
                        def remote = [
                            name: "${env.SSH_SERVER}",
                            host: "${env.SSH_HOST}",
                            allowAnyHosts: true,
                            user: userName,
                            identityFile: identity
                        ]
                        
                        sshCommand remote: remote, command: """
                            echo '已上传 ${env.VERSIONED_JAR} 到服务器'
                            mkdir -p ${env.DEPLOY_PATH}
                        """
                        sshPut remote: remote, from: "${env.VERSIONED_JAR}", into: "${env.DEPLOY_PATH}"
                    }
                }
            }
        }
        
        stage('执行启动脚本') {
            steps {
                script {
                    withCredentials([sshUserPrivateKey(
                        credentialsId: "${env.SSH_CREDENTIALS_ID}", 
                        keyFileVariable: 'identity', 
                        passphraseVariable: '', 
                        usernameVariable: 'userName'
                    )]) {
                        // 定义 remote 对象
                        def remote = [
                            name: "${env.SSH_SERVER}",
                            host: "${env.SSH_HOST}",
                            allowAnyHosts: true,
                            user: userName,
                            identityFile: identity
                        ]
                        
                        sshCommand remote: remote, command: """
                            cd ${env.DEPLOY_PATH} || exit 1
                            echo '当前目录:' && pwd
                            echo '开始执行启动脚本...'
                            echo 1 | sudo -S ./run.sh ${env.VERSIONED_JAR}
                        """
                    }
                }
            }
        }
    }
    
    post {
        always {
            archiveArtifacts artifacts: "*.jar", allowEmptyArchive: true
            echo "构建流程结束 - ${currentBuild.result}"
        }
        success {
            echo "部署成功! 版本 ${VERSION} 已发布"
        }
        failure {
            echo "部署失败,请检查日志"
        }
    }
}
相关推荐
七夜zippoe2 小时前
CANN Runtime任务描述序列化与持久化源码深度解码
大数据·运维·服务器·cann
李少兄2 小时前
在 IntelliJ IDEA 中修改 Git 远程仓库地址
java·git·intellij-idea
Fcy6484 小时前
Linux下 进程(一)(冯诺依曼体系、操作系统、进程基本概念与基本操作)
linux·运维·服务器·进程
袁袁袁袁满4 小时前
Linux怎么查看最新下载的文件
linux·运维·服务器
代码游侠4 小时前
学习笔记——设备树基础
linux·运维·开发语言·单片机·算法
Harvey9034 小时前
通过 Helm 部署 Nginx 应用的完整标准化步骤
linux·运维·nginx·k8s
珠海西格电力科技5 小时前
微电网能量平衡理论的实现条件在不同场景下有哪些差异?
运维·服务器·网络·人工智能·云计算·智慧城市
释怀不想释怀6 小时前
Linux环境变量
linux·运维·服务器
zzzsde6 小时前
【Linux】进程(4):进程优先级&&调度队列
linux·运维·服务器
聆风吟º7 小时前
CANN开源项目实战指南:使用oam-tools构建自动化故障诊断与运维可观测性体系
运维·开源·自动化·cann