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 "部署失敗,請檢查日志"}}
}