天天看點

從0搭建屬于自己的jenkins持續內建平台

從0搭建屬于自己的jenkins持續內建平台

前言

  Jenkins在日常工作中占據了一個非常重要的角色,幫助我們節省了大量用于建構的時間。有些公司有運維大哥對Jenkins進行維護,如果沒有那隻能自己動手了。俗話說的好自己動手豐衣足食,是以本文就從0開始搭建屬于自己的Jenkins持續平台。主要包含,普通項目建構、流水線建構、多分支流水線建構并将建構結果輔以釘釘通知。

前期準備

  • centos7 伺服器一台

确認是否能安裝docker

 Docker要求CentOS系統的核心版本高于3.10.通過

uname -r

指令檢視你目前的核心版本。

[root@CentOS ~]# uname -r
3.10.0-1127.8.2.el7.x86_64           

更改yum源為阿裡雲

備份舊源

mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup           

下載下傳最新的源

wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo           

生成緩存

yum makecache           

更新

yum update           

安裝docker

官方安裝文檔
yum install -y yum-utils           

添加docker源

yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo           
yum install docker-ce           

啟動docker

systemctl start docker           

更改docker鏡像源

vim /etc/docker/daemon.json           

加入阿裡雲源位址

{
    "registry-mirrors":["https://6kx4zyno.mirror.aliyuncs.com"]
}           

重新讀取配置

systemctl daemon-reload            

重新開機docker

systemctl restart docker           

安裝jenkins

下載下傳jenkins鏡像

docker pull jenkins           

啟動jenkins

 設定端口為9090并映射

jenkins_home

到主控端

/home/jenkins_home

docker run -d --name jenkins -p 9090:8080 -v /home/jenkins_home:/var/jenkins_home jenkins           

 可以通過

docker ps

檢視運作的容器。

[root@CentOS home]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                               NAMES
ec6a4da6b83f        jenkins             "/bin/tini -- /usr/l…"   About a minute ago   Up About a minute   50000/tcp, 0.0.0.0:9090->8080/tcp   jenkins
[root@CentOS home]#           

把玩jenkins docker鏡像遇到的volume權限問題

 在運作啟動jenkins的指令時,可能會出現jenkins無法啟動情況。

[root@CentOS home]# docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS               NAMES
b571f16dafbf        jenkins             "/bin/tini -- /usr/l…"   8 minutes ago       Exited (1) 8 minutes ago                       jenkins
[root@CentOS home]#           

docker logs 鏡像名稱

檢視啟動日志。

[root@CentOS home]# docker logs jenkins
touch: cannot touch '/var/jenkins_home/copy_reference_file.log': Permission denied
Can not write to /var/jenkins_home/copy_reference_file.log. Wrong volume permissions?
[root@CentOS home]#           

 檢視輸出的日志,如果出現

Permission denied

類似的錯誤。需要删除舊容器重新運作。

docker rm jenkins           

 運作指令加入了

-u 0

重新運作。

docker run -d --name jenkins -p 9090:8080 -v /home/jenkins_home:/var/jenkins_home -u 0 jenkins           
參考 https://blog.csdn.net/minicto/article/details/73539986

Jenkins初始化

 啟動成功後輸入

http://

伺服器:9090/

 如果無法通路,請檢查一下防火牆端口是否開放,如果是雲伺服器還需要檢查安全組設定

從0搭建屬于自己的jenkins持續內建平台

  首次啟動jenkins需要輸入密碼,需要進入容器内擷取密碼。密碼位于

/var/jenkins_home/secrets/initialAdminPassword

進入容器

docker exec -it jenkins /bin/bash           

擷取密碼

cat /var/jenkins_home/secrets/initialAdminPassword           
[root@CentOS jenkins_home]# docker exec -it jenkins /bin/bash
root@ec6a4da6b83f:/# cat /var/jenkins_home/secrets/initialAdminPassword
68eed23ad39541949972468e4f2ce1fd
root@ec6a4da6b83f:/#           

  由于我們将

/var/jenkins_home

-- 挂載到-->

/home/jenkins_home

是以也可以直接

cat /home/jenkins_home/secrets/initialAdminPassword

擷取密碼。

  輸入密碼以後,安裝需要的插件,在安裝途中由于網絡原因會出現有些插件安裝失敗,這個可以不用理會。

從0搭建屬于自己的jenkins持續內建平台
從0搭建屬于自己的jenkins持續內建平台
從0搭建屬于自己的jenkins持續內建平台
從0搭建屬于自己的jenkins持續內建平台

設定jenkins的預設登入賬号和密碼

從0搭建屬于自己的jenkins持續內建平台
從0搭建屬于自己的jenkins持續內建平台

處理插件安裝失敗

  進入jenkins的首頁面右上角可能會出現一些報錯資訊,主要是提示jenkins 需要的某些插件沒有安裝,或者說jenkins版本太低了,插件無法使用這個時候我們需要先更新jenkins做一個更新。

從0搭建屬于自己的jenkins持續內建平台

自動更新

Jenkins提供了自動更新的方式

從0搭建屬于自己的jenkins持續內建平台

手動更新

 可以去Jenkins的官網下載下傳好最新jar包上傳到伺服器,也可以使用

wget

指令。

wget http://jenkins新版本的下載下傳位址
#目前最新2.239
wget http://updates.jenkins-ci.org/download/war/2.239/jenkins.war           

  Jenkins的更新主要是替換jenkins鏡像裡面的war包 ,我們可以把下載下傳好的war包使用

docker cp

直接進行複制指令如下:

docker cp jenkins.war jenkins:/usr/share/jenkins           

 重新啟動Jenkins即可完成更新。

docker restart jenkins           

更插件源

從0搭建屬于自己的jenkins持續內建平台
從0搭建屬于自己的jenkins持續內建平台
https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json           
  • 替換完源以後點選送出。
  • 然後進入插件管理頁面将出錯的插件重新安裝。
  • 及時更新插件。
從0搭建屬于自己的jenkins持續內建平台

安裝必要的插件

  • Localization: Chinese (Simplified) 1.0.14 漢化包 搜尋關鍵字 chinese
  • Publish Over SSH 1.20.1 搜尋關鍵字 ssh
  • DingTalk 釘釘通知 2.3.0

配置jenkins

全局工具配置

  主要配置 jdk、maven、git等常用環境。需要注意配置的别名,後續建構将會使用到。

配置jdk

  因為jenkins鏡像自帶jdk是以無需安裝直接使用即可,進入Jenkins容器,使用

java -verbose

檢視java安裝路徑。

docker exec -it jenkins /bin/bash           
java -verbose           
從0搭建屬于自己的jenkins持續內建平台
從0搭建屬于自己的jenkins持續內建平台
配置git

 進入容器内使用

whereis git

即可查詢到git安裝路徑。

root@6a9fbb129cbe:~# whereis git
git: /usr/bin/git /usr/share/man/man1/git.1.gz
root@6a9fbb129cbe:~#           
從0搭建屬于自己的jenkins持續內建平台
配置maven

 maven直接使用自動安裝即可。

從0搭建屬于自己的jenkins持續內建平台

系統設定

配置伺服器
從0搭建屬于自己的jenkins持續內建平台

點選新增即可添加伺服器,主要配置:

  • Name

    名稱 - 建構的時候将會用到
  • Hostname

    伺服器位址
  • Username

    使用者名
  • Remote Directory

    遠端目錄 - 上傳檔案的目錄 預設配置根目錄即可

    /

點選進階進行其他參數配置

  • 如果需要使用密碼登入,則選中

    Use password authentication, or use a different key

    複選框即可,如下圖所示。
從0搭建屬于自己的jenkins持續內建平台

  除了配置密碼還可以配置端口

Port

,跳闆機

Jump Host

的參數,可以根據實際情況配置。預設可以使用密碼。

  配置完成以後點選

Test Configuration

按鈕,如果配置正常會出現

Success

反之出現錯誤資訊,可以根據錯誤資訊,調整配置參數。

配置釘釘

  釘釘主要用于建構通知,在配置前需要在釘釘群内,添加自定義機器人。

從0搭建屬于自己的jenkins持續內建平台
從0搭建屬于自己的jenkins持續內建平台

自由風格的軟體項目

  以

https://gitee.com/huangxunhui/jenkins_demo.git

為例。

建立項目

從0搭建屬于自己的jenkins持續內建平台

設定項目簡介

從0搭建屬于自己的jenkins持續內建平台

源碼管理

  • 配置倉庫位址。
  • 配置憑證-主要用于拉取代碼。
  • 配置需要建構的分支。
從0搭建屬于自己的jenkins持續內建平台
添加憑證

  如果項目是開源,則可以跳過這一步。反之需要設定憑證,要不然将無法拉取代碼進行建構。

從0搭建屬于自己的jenkins持續內建平台

建構觸發器

  可以根據實際情況選擇,案例采用輪詢的方式進行建構。

從0搭建屬于自己的jenkins持續內建平台

建構

從0搭建屬于自己的jenkins持續內建平台
從0搭建屬于自己的jenkins持續內建平台

建構後操作

  • 将jar包發送到相應的伺服器。
從0搭建屬于自己的jenkins持續內建平台
從0搭建屬于自己的jenkins持續內建平台
  • Source files

    jar包的路徑。支援通配符比對.
  • Remove prefix

    移除字首,一般jar包的路徑都存在于

    **/target

    下,如果不移除,會在目标伺服器上建立相應的目錄結構。
  • Remote directory

    遠端目錄。
注意的點, 在之前配置伺服器時也配置了

Remote directory

,這時候部署的實際目錄是,伺服器設定的遠端目錄+現在配置的遠端目錄。
  • Exec command

    執行腳本,主要用于将

    jar

    發送到目标伺服器後,執行相應的啟動腳本。

配置完成點選儲存即可。

點選開始建構

從0搭建屬于自己的jenkins持續內建平台
從0搭建屬于自己的jenkins持續內建平台

發送釘釘通知

從0搭建屬于自己的jenkins持續內建平台

流水線

  流水線建構,将上述建構步驟代碼化,友善調整。

項目建立

從0搭建屬于自己的jenkins持續內建平台

流水線編寫

  由于配置步驟類似,前面簡單的步驟可以參照,

自由風格的軟體項目

。這裡主要講流水線如何編寫。

從0搭建屬于自己的jenkins持續內建平台
注意右下角的流水線文法,後續會用上。

  我們可以點選右上角的下拉按鈕,生成一個簡單的流水線。比如說hello world。

從0搭建屬于自己的jenkins持續內建平台
pipeline {
    
    // 表示所有機器都能運作   
   agent any

   stages {
      stage('Hello') {
         steps {
            echo 'Hello World'
         }
      }
   }
}           

  通過上面的

pipeline

可以知道,有一個

Hello

的步驟,這個步驟執行的是,輸出

hello world

。依葫蘆畫瓢,一次完整的建構我們可以總結出如下幾個步驟:拉取代碼(checkout) -> 打包(build) -> 部署(deploy)。

pipeline {
   agent any
   
   stages {
      stage('checkout') {
         steps {
            
         }
      }
      
      stage('build') {
         steps {
                     
         }
      }
      
    stage('deploy') {
         steps {

         }
      }
   }
}           

  步驟梳理好了,這個時候就可以完善對應的步驟了,這就需要用到提到的,流水線文法。

從0搭建屬于自己的jenkins持續內建平台
從0搭建屬于自己的jenkins持續內建平台
從0搭建屬于自己的jenkins持續內建平台

将生成好的流水線腳本複制到對應的步驟即可。

注意:如果使用到maven需要将maven引入,

tools

相應的内容就是配置

maven

時配置的别名。
pipeline {
   agent any
   
    // 工具
    tools {
        maven 'maven'
        jdk 'jdk'
    }

   stages {
      stage('checkout') {
         steps {
            checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[url: 'https://gitee.com/huangxunhui/jenkins_demo.git']]])
         }
      }
      
      stage('build') {
         steps {
            sh 'mvn clean package -Dmaven.test.skip=true'            
         }
      }
      
    stage('deploy') {
         steps {
            sshPublisher(publishers: [sshPublisherDesc(configName: 'dev', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''cd /home/project 
nohup java -jar jenkins_demo.jar > nohup.out 2>&1 &''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '/home/project', remoteDirectorySDF: false, removePrefix: '/target', sourceFiles: '**/target/jenkins_demo.jar')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
         }
      }
      
   }
}           

配置完成點選應用即可。

建構測試

從0搭建屬于自己的jenkins持續內建平台

上面示範的是将流水線配置在jenkins内,其實我們還可以從

SCM

中擷取,比如

git

從0搭建屬于自己的jenkins持續內建平台
從0搭建屬于自己的jenkins持續內建平台
我們可以建立一個倉庫專門維護不同項目的建構腳本

Jenkinsfile

,也可以在每個項目下,建立對應的

Jenkinsfile

.

  注意的點:項目中的

Jenkinsfile

需要和配置的一緻。比如說上面的配置,是掃描項目根目錄下名字為

Jenkinsfile

的檔案。

是以我們可以在

jenkins_demo

倉庫内添加

Jenkinsfile

檔案。

從0搭建屬于自己的jenkins持續內建平台

配置點選完成,即可。

從0搭建屬于自己的jenkins持續內建平台

多分支流水線

  在日常開發中,通常是基于

git-flow

進行開發的,前面兩種都是基于單分支建構,如果每個分支都去配置,那将耗費大量時間。是以多分支流水線就是用來解決這個問題的。

建立項目

從0搭建屬于自己的jenkins持續內建平台

配置分支源

從0搭建屬于自己的jenkins持續內建平台

建構配置

從0搭建屬于自己的jenkins持續內建平台

掃描觸發器

從0搭建屬于自己的jenkins持續內建平台

完成上述配置,點選應用即可。

編寫

jenkinsfile

檔案

  核心思想是,根據不同的分支使用不同的打包指令,發送到不同的伺服器進行運作。

pipeline {
    // 指定叢集 any 表示所有
    agent any

    // 工具
    tools {
        maven 'maven'
        jdk 'jdk'
    }

    // 定義常量
    environment {

        // 釘釘機器人編号
        rebootId = 'a3c07482-d031-47a6-8542-05ac56c5f17a'

        // 開始logo
        imageOfStart = 'https://www.easyicon.net/api/resizeApi.php?id=1229977&size=128'

        // 成功logo
        imageOfSuccess = 'https://www.easyicon.net/api/resizeApi.php?id=1194837&size=128'

        // 失敗logo
        imageOfFailure = 'https://www.easyicon.net/api/resizeApi.php?id=1201052&size=128'

        // 不穩定logo
        imageOfUnstable = 'https://www.easyicon.net/api/resizeApi.php?id=1219854&size=128'

        // 終止logo
        imageOfAborted = 'https://www.easyicon.net/api/resizeApi.php?id=1183198&size=128'

        // 認證Id
        credentialsId = '98e9c197-f0ae-44c3-8f67-4ca0339028a8'

        // 倉庫位址
        repositoryUrl = 'https://gitee.com/huangxunhui/jenkins_demo.git'

        // 打包指令 - 項目需要配置maven多環境
        mavenProd = 'mvn clean package -P prod -Dmaven.test.skip=true'
        mavenTest = 'mvn clean package -P test -Dmaven.test.skip=true'
        mavenDev = 'mvn clean package -P dev -Dmaven.test.skip=true'

        // 伺服器名稱 - 案例測試-全部部署到dev環境
        devServer = 'dev'
        testServer = 'dev'
        prodServer = 'dev'

        // sshPublisher 配置
        removePrefix = '/target'
        remoteDirectory = '/home/project/jenkins_demo'
        sourceFiles = '**/target/jenkins_demo.jar'

        execCommandProd = 'cd /home/project && ./manage.sh jenkins_demo/ restart'
        execCommandTest = 'cd /home/project && ./manage.sh jenkins_demo/ restart'
        execCommandDev = 'cd /home/project && ./manage.sh jenkins_demo/ restart'

    }

    stages {

        stage('開始建構通知'){
            steps {
                dingtalk (
                        robot: "${rebootId}",
                        type: 'LINK',
                        title: "${env.JOB_NAME}",
                        text: [
                            "開始建構-編号為#${BUILD_NUMBER}"
                        ],
                        messageUrl: "${env.BUILD_URL}",
                        picUrl: "${imageOfStart}"
                )
            }
        }

        stage('拉取代碼'){
            steps {
                echo "拉取 ${BRANCH_NAME} 分支的代碼。"
                checkout([$class: 'GitSCM', branches: [[name: "*/${BRANCH_NAME}"]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "${credentialsId}", url: "${repositoryUrl}"]]])
            }
        }

        stage('進行打包'){
            steps {
                script {
                    if (env.BRANCH_NAME == 'master') {
                        sh "${mavenProd}"
                    } else if (env.BRANCH_NAME == 'test') {
                        sh "${mavenTest}"
                    } else if (env.BRANCH_NAME == 'dev') {
                        sh "${mavenDev}"
                    } else {
                        sh "${mavenDev}"
                    }
                }
            }
        }

        stage('項目部署'){
            steps {
                script {
                    if (env.BRANCH_NAME == 'master') {
                        // 部署生産環境
                        sshPublisher(publishers: [sshPublisherDesc(configName: "${prodServer}", transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: "${execCommandProd}", execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory:  "${remoteDirectory}", remoteDirectorySDF: false, removePrefix: "${removePrefix}", sourceFiles: "${sourceFiles}")], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
                    } else if (env.BRANCH_NAME == 'test') {
                        // 部署測試環境
                        sshPublisher(publishers: [sshPublisherDesc(configName: "${testServer}", transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: "${execCommandTest}", execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory:  "${remoteDirectory}", remoteDirectorySDF: false, removePrefix: "${removePrefix}", sourceFiles: "${sourceFiles}")], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
                    } else if (env.BRANCH_NAME == 'dev') {
                        // 部署開發環境
                        sshPublisher(publishers: [sshPublisherDesc(configName: "${devServer}", transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: "${execCommandDev}", execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory:  "${remoteDirectory}", remoteDirectorySDF: false, removePrefix: "${removePrefix}", sourceFiles: "${sourceFiles}")], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
                    } else {
                        sshPublisher(publishers: [sshPublisherDesc(configName: "${devServer}", transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: "${execCommandTest}", execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory:  "${remoteDirectory}", remoteDirectorySDF: false, removePrefix: "${removePrefix}", sourceFiles: "${sourceFiles}")], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
                    }
                }
            }
        }
    }

    // 流水線結束通知
    post {

        // 成功通知
        success {
            dingtalk (
                    robot: "${rebootId}",
                    type: 'LINK',
                    title: "${env.JOB_NAME}",
                    text: [
                        "建構成功-編号為#${BUILD_NUMBER}"
                    ],
                    messageUrl: "${env.BUILD_URL}",
                    picUrl: "${imageOfSuccess}"
                )
        }

        // 失敗通知
        failure {
            dingtalk (
                    robot: "${rebootId}",
                    type: 'LINK',
                    title: "${env.JOB_NAME}",
                    text: [
                        "建構失敗-編号為#${BUILD_NUMBER}"
                    ],
                    messageUrl: "${env.BUILD_URL}",
                    picUrl: "${imageOfFailure}"
            )
        }

        // 建構不穩定通知
        unstable {
            dingtalk (
                    robot: "${rebootId}",
                    type: 'LINK',
                    title: "${env.JOB_NAME}",
                    text: [
                        "建構不穩定-編号為#${BUILD_NUMBER}"
                    ],
                    messageUrl: "${env.BUILD_URL}",
                    picUrl: "${imageOfUnstable}"
            )
        }

        // 建構終止通知
        aborted {
            dingtalk (
                    robot: "${rebootId}",
                    type: 'LINK',
                    title: "${env.JOB_NAME}",
                    text: [
                        "建構終止-編号為#${BUILD_NUMBER}"
                    ],
                    messageUrl: "${env.BUILD_URL}",
                    picUrl: "${imageOfAborted}"
            )
        }
    }
}           

使用到的啟動腳本

manage.sh

釘釘機器人插件使用文檔

建構結果

從0搭建屬于自己的jenkins持續內建平台
從0搭建屬于自己的jenkins持續內建平台
從0搭建屬于自己的jenkins持續內建平台

結尾

  如果覺得對你有幫助,可以多多評論,多多點贊哦,也可以到我的首頁看看,說不定有你喜歡的文章,也可以随手點個關注哦,謝謝。

  我是不一樣的科技宅,每天進步一點點,體驗不一樣的生活。我們下期見!