Docker方式部署Jenkins
部署自定义Docker网络
部署Docker网络的作用:
- 隔离性
- 便于同一网络内容器相互通信
# 创建名为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 等。
配置环境
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下载链接
上传到容器
先将 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 。
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安装路径
# 进入容器内部
$ 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目标服务器
可以有两种方式配置:
- 账号密码方式
- 公私钥方式
这里介绍一下 公私钥方式(密码方式直接输入相关密码即可)
生成公私钥
############ 在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控制台不会打印日志。
pipeline {agent anyparameters {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指纹凭证IDSSH_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 1echo '当前目录:' && pwdecho '开始执行启动脚本...'echo 1 | sudo -S ./run.sh ${VERSIONED_JAR}""")])])}}}stage('输出服务日志') {steps {script {def result = sshPublisher(publishers: [sshPublisherDesc(configName: "${SSH_SERVER}", // SSH 服务器名称transfers: [sshTransfer(execCommand: """sleep 5secho '===== 开始获取服务日志 ====='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: trueecho "构建流程结束 - ${currentBuild.result}"}success {echo "部署成功! 版本 ${params.VERSION} 已发布"}failure {echo "部署失败,请检查日志"}}
}
所以有了第二版:使用 sshCommand 命令,这一版用到了 ssh Server 凭证,使用这个命令就 可以在Jenkins控制台看到日志 了。
ps:使用 sshCommand 命令需要安装插件:SSH Pipeline Steps
pipeline {agent anyparameters {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指纹凭证IDSSH_SERVER = 'test_server' // 这个配置在这里就没什么实际作用了,只是一个名称SSH_HOST = "192.168.100.102" // 远程服务器的IP地址SSH_CREDENTIALS_ID = "xxx" // SSH凭证IDDEPLOY_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 1echo '当前目录:' && pwdecho '开始执行启动脚本...'echo 1 | sudo -S ./run.sh ${env.VERSIONED_JAR}"""}}}}}post {always {archiveArtifacts artifacts: "*.jar", allowEmptyArchive: trueecho "构建流程结束 - ${currentBuild.result}"}success {echo "部署成功! 版本 ${params.VERSION} 已发布"}failure {echo "部署失败,请检查日志"}}
}
集成Git WebHook实现事件触发流水线
第一步:在Git上配置WebHook
这里用到的是 Gogs 类型,其他类型根据情况修改
token 在Linux中可以通过一下命令自动生成:
$ openssl rand -hex 16`
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 anyenvironment {BASE_JAR_NAME = "jenkins-study" // 根据你的实际项目修改GIT_REPO = 'https://xxx.git' // 替换为你的Git仓库地址GIT_CRE_ID = "xxx" // git凭证idSSH_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}// 如果不是版本发布事件 则结束 Pipelinedef 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 1echo '当前目录:' && pwdecho '开始执行启动脚本...'echo 1 | sudo -S ./run.sh ${env.VERSIONED_JAR}"""}}}}}post {always {archiveArtifacts artifacts: "*.jar", allowEmptyArchive: trueecho "构建流程结束 - ${currentBuild.result}"}success {echo "部署成功! 版本 ${VERSION} 已发布"}failure {echo "部署失败,请检查日志"}}
}