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

Docker方式部署Jenkins

部署自定义Docker网络

部署Docker网络的作用:

  1. 隔离性
  2. 便于同一网络内容器相互通信
# 创建名为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

拉取镜像

docker pull hub.rat.dev/jenkins/jenkins:lts-jdk17

运行容器

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 等。
访问Jenkins

配置环境

Java

查询自带JDK

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

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下载链接
下载JDK1.8-linux

上传到容器

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

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
配置其他JDK版本

Git

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

查询自带Git

可以进入容器内部查看

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

查看Maven安装路径

# 进入容器内部
$ 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. 公私钥方式

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

生成公私钥

############ 在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
配置SSH Server

配置凭证

Jenkins控制台 >> Manage Jenkins >> Credentials >> System >> Global credentials (unrestricted) 中配置
添加Git仓库凭证

配置Git仓库指纹凭证

后续从Git仓库拉取代码会需要用到
配置Git仓库指纹凭证

配置SSH Server凭证

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

部署Pipeline任务

项目部署到目标服务器上,以系统服务方式运行
新建Pipeline任务
新建Pipeline任务在这里编写 Pipeline Script
编写pipeline script

在Jenkins控制台手动触发

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

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

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中可以通过一下命令自动生成:

$ openssl rand -hex 16`

Git配置WebHook

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

// 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 // 调试:打印解析后的变量
        )
    }

完整脚本

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 "部署失败,请检查日志"
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值