CICD05 Jenkins流水線, 程式碼質量檢查sonarqube ubuntu使用

战斗小人發表於2024-11-13

3.2.3.5 Pipeline 簡單案例

#範例: 指令碼式
node {
     stage('Get code') {
        echo '獲取程式碼'
         //git clone
     }
     stage('Build') {
         echo '構建專案程式碼'
     }
     stage('Test') {
         echo '測試專案功能'
     }
     stage('Deploy') {
         echo '部署專案'
     }
}

#範例: 宣告式
pipeline {
     agent any
     stages {
         stage('獲取程式碼') {
             steps {
                 echo '獲取程式碼'
             }
         }
         stage('構建程式碼') {
             steps {
                 echo '構建專案程式碼'
             }
         }
         stage('程式碼測試') {
             steps {
                 echo '測試專案功能'
             }
         }
         stage('專案部署') {
             steps {
                 echo '部署專案'
             }
         }
     }
}

3.2.5 實現一個簡單 Pipeline Job

安裝 Pipeline 外掛 (不裝也能創流水線任務, 執行任務時可能會報錯 )

Pipeline Stage View 外掛 (執行任務,右側直接顯示執行過程,階段檢視)

#jenkins建立流水線任務
#流水線下有流水線語法,點進去,可以自動生成語句,左側可調出環境變數
#流水線輸入程式碼
pipeline {
     agent any    #應用在任意主機上
     stages {
         stage('獲取程式碼') {    #構建任務階段
             steps {
                 echo '獲取程式碼'
             }
         }
         stage('構建程式碼') {
             steps {
                 echo '構建專案程式碼'
             }
         }
         stage('程式碼測試') {
             steps {
                 echo '測試專案功能'
             }
         }
         stage('專案部署') {
             steps {
                 echo '部署專案'
             }
         }
     }
}
#點儲存,構建任務
#可以點選左側 從指定階段重新執行,可以選擇階段開始,不用從頭開始執行,方便除錯
#左側 開啟Blue Ocean,可以看到每個階段狀態,也可選階段執行,如果沒有先點上方整體重新執行
#某個構建任務下,左側回放,相當於臨時修改程式碼除錯,程式碼不儲存

3.2.7 實戰案例: 宣告式 Pipeline

3.2.7.1 案例:基本語法

steps 內部的命令,每一條單獨的命令都在當前任務的工作目錄下執行。

pipeline {
   environment {
       APP = "testapp"
   }
   agent any
   stages {
       stage ('cmd test') {
           steps {
                echo '命令測試'
                sh 'pwd'
                sh 'rm -rf *'
                sh 'mkdir testdir1'
                sh 'cd testdir1 && pwd && mkdir testdir2 && cd testdir2 && pwd'
                sh 'pwd && tree'    #pwd還是在初始目錄下,上面的命令不影響這行
                sh 'echo $WORKSPACE'
                sh 'echo $JOB_NAME'
                sh 'mkdir $WORKSPACE/$JOB_NAME'
                sh 'touch $WORKSPACE/$JOB_NAME/${APP}.log'
                sh 'pwd && tree'
           }
       }
   }
}

3.2.7.2 案例:變數

Jenkins環境變數可分為內建變數和使用者自定義變數兩類

引用全域性環境變數格式有三種:

#注:sh裡不屬於pipeline語法,不支援這些變數定義
${env.<ENV_VAR_NAME>}
$env.<ENV_VAR_NAME>
$<ENV_VAR_NAME>
${ENV_VAR_NAME}
#注意:變數引用有時要加雙引號引起來,如:"${env.<ENV_VAR_NAME>}"

範例:宣告和使用變數

pipeline {
   agent any    
   environment {
       NAME = "wangxiaochun"
   }
   stages {
       stage('declare var') {
           steps {
               script {
                   #把who|wc -l 賦值給LOGIN變數
                   env.LOGIN=sh(returnStdout: true, script: "who|wc -l") 
               }
           }
       }
       stage('get var') {
           steps {
                echo "NAME=${NAME}"
                echo "LOGIN=${LOGIN}"
           }
       }
   }
}

範例:spring-boot-helloworld 完整案例(推薦)

#說明:環境準備
#提前在部署目標伺服器上建立目錄 mkdir -p /data/appdir 
#提前在Manage Jenkins-tools-Maven安裝中建立maven-3.6.3
pipeline {
   agent any
   //修改git地址和jenkins用的憑據,APP_PATH部署的路徑(目標機器建目錄)
   environment {
        REPO="git@gitlab.wang.org:example1/spring-boot-helloworld.git"
        CREDENTIAL="gitlab-root-private-key"
        APP="spring-boot-helloworld"
        APP_PATH="/data/appdir"
   }
   tools {
         //jenkins系統管理/全域性工具配置的Maven(可jenkins安裝,也可配自己安裝的)
       maven 'maven-3.6.3'
   }
    
   stages {
       stage('code clone') {
           steps {
               git branch: 'main', credentialsId: "${CREDENTIAL}" ,url: "${REPO}"
           }
       }
        
       stage('Build') {
           steps {
               //sh 'mvn clean package -Dmaven.test.skip=true'
                sh 'mvn -B -DskipTests clean package'
           }
       }
        
       stage('Test') {
           steps {
                sh 'mvn test'
           }
       }
        
       stage("停止spring boot服務"){
           steps {
                sh 'ssh root@10.0.0.153 "killall -0 java && killall -9 java|| true"'
                sh 'ssh root@10.0.0.154 "killall -0 java && killall -9 java || true"'
           }  
       }
       stage("程式碼複製"){
           steps {
                sh "scp target/${APP}-*-SNAPSHOT.jar root@10.0.0.153:${APP_PATH}"
                sh "scp target/${APP}-*-SNAPSHOT.jar root@10.0.0.154:${APP_PATH}"
           }
       }
       stage("啟動spring boot服務"){
           steps {
            sh 'ssh root@10.0.0.153 "nohup java -jar ${APP_PATH}/${APP}-*.jar --server.port=8888 &>/dev/null & "'
            sh 'ssh root@10.0.0.154 "nohup java -jar ${APP_PATH}/${APP}-*.jar --server.port=8888 &>/dev/null & "'
           }  
       }
   }
}

3.2.7.3 案例:使用憑據 Credential

1.Username with password
#使用者名稱和密碼使用變數名可自行指定,Jenkins都會透過credentialsID從指定的憑證提取出來使用者名稱和密碼並賦值給指定對應的變數
withCredentials([usernamePassword(credentialsID: '<ID>',
usernameVariable: '<variable to hold username>',
passwordVariable: '<variable to hold password>')])

2.SSH金鑰
withCredentials(sshUserPrivateKey(credentialsId: '<credentials-id>’,
keyFileVariable: 'MYKEYFILE'
passphraseVariable: 'PASSPHRASE’,
usernameVariable: USERNAME')])
{ // some block }

範例:構建和推送Docker映象

#確保docker信任harbor,docker info檢視Insecure Registries是否信任harbor網址

pipeline {
   agent any
   tools {
       maven 'maven-3.6.3'
   }
   environment {
        codeRepo="http://gitlab.wang.org/example1/spring-boot-helloworld.git"
        credential="gitlab-root-password"
        harborServer='harbor.wang.org'
        projectName='spring-boot-helloworld'
        imageUrl="${harborServer}/example/${projectName}"
        imageTag="${BUILD_ID}"
        harborUserName="admin"
        harborPassword="123456"
   }
   stages {
       stage('Source') {
           steps {
                git branch: 'main', credentialsId: "${credential}", url: "${codeRepo}"
           }
       }
       stage('Build') {
           steps {
                sh 'mvn -B -DskipTests clean package'
           }
       }
       stage('Test') {
           steps {
                 //注意:不要修改hello()函式,否則會導致下面失敗
                sh 'mvn test'
           }
       }
       stage('Build Docker Image') {
           steps {
                sh 'docker build . -t "${imageUrl}:${imageTag}"'
           }           
       }
       stage('Push Docker Image') {
           steps {
                sh "echo ${harborPassword} | docker login -u ${harborUserName} --password-stdin ${harborServer}"
               //sh "docker login -u ${harborUserName} -p ${harborPassword} ${harborServer}"
                sh "docker push ${imageUrl}:${imageTag}"
           }   
       }
       stage('Run Docker ') {
           steps {
                //sh 'ssh root@10.0.0.153 "docker rm -f ${projectName} ; docker run --name ${projectName} -p 80:8888 -d ${imageUrl}:${imageTag}"'
                //sh 'ssh root@10.0.0.154 "docker rm -f ${projectName} ; docker run --name ${projectName} -p 80:8888 -d ${imageUrl}:${imageTag}"'
               sh "docker -H 10.0.0.153 rm -f ${projectName} ; docker -H 10.0.0.153 run --name ${projectName} -p 80:8888 -d ${imageUrl}:${imageTag}"
               sh "docker -H 10.0.0.154 rm -f ${projectName} ; docker -H 10.0.0.154 run --name ${projectName} -p 80:8888 -d ${imageUrl}:${imageTag}"     
           }   
       } 
   }
}

範例:基於憑據實現構建和推送Docker映象

pipeline {
   agent any
   tools {
       maven 'maven-3.6.3'
   }
   environment {
        codeRepo="http://gitlab.wang.org/example1/spring-boot-helloworld.git"
        credential="gitlab-root-password"
        harborServer='harbor.wang.org'
        projectName='spring-boot-helloworld'
        imageUrl="${harborServer}/example/${projectName}"
        imageTag="${BUILD_ID}"
        //harborUserName="admin"
        //harborPassword="123456"
   }
   stages {
       stage('Source') {
           steps {
                git branch: 'main', credentialsId: "${credential}", url: "${codeRepo}"
           }
       }
       stage('Build') {
           steps {
                sh 'mvn -B -DskipTests clean package'
           }
       }
       stage('Test') {
           steps {
                 //注意:不要修改hello()函式,否則會導致下面失敗
                sh 'mvn test'
           }
       }
       stage('Build Docker Image') {
           steps {
                sh 'docker build . -t "${imageUrl}:${imageTag}"'
           }           
       }
       stage('Push Docker Image') {
           steps {
               withCredentials([usernamePassword(credentialsId: 'harbor-xiaoming-password', \
                       passwordVariable: 'harborPassword', usernameVariable: 'harborUserName')]) {
                    sh "echo ${harborPassword} | docker login -u ${env.harborUserName} --password-stdin ${harborServer}"
                   //sh "docker login -u ${env.harborUserName} -p ${harborPassword} ${harborServer}"
                    sh "docker push ${imageUrl}:${imageTag}"
                    echo "username=${env.harborUserName}"
                    echo "password=${harborPassword}"    //列印不出密碼
               }
           }   
       }
       stage('Run Docker ') {
           steps {
                //sh 'ssh root@10.0.0.153 "docker rm -f ${projectName} ; docker run --name ${projectName} -p 80:8888 -d ${imageUrl}:${imageTag}"'
                //sh 'ssh root@10.0.0.154 "docker rm -f ${projectName} ; docker run --name ${projectName} -p 80:8888 -d ${imageUrl}:${imageTag}"'
               sh "docker -H 10.0.0.153 rm -f ${projectName} ; docker -H 10.0.0.153 run --name ${projectName} -p 80:8888 -d ${imageUrl}:${imageTag}"
               sh "docker -H 10.0.0.154 rm -f ${projectName} ; docker -H 10.0.0.154 run --name ${projectName} -p 80:8888 -d ${imageUrl}:${imageTag}"     
           }   
       } 
   }
}

範例:透過Jenkinsfile 實現

Jenkinsfile放在gitlab專案中, 優點: 可以跟著版本走, 有些公司是開發寫的

#把pipeline程式碼寫入Jenkinsfile檔案中,把該檔案放入gitlab專案中
#jenkins流水線專案,流水線定義選pipeline script from SCM
#SCM選git,輸入倉庫,憑據,分支
#指令碼路徑   Jenkinsfile   (這裡沒寫路徑代表專案根目錄下)

3.2.7.4 案例:引數選項和密碼

相當於自由風格中的引數化構建

注意:第一個執行PipleLine沒有Build with Parameters的提示,會用預設值, 第二次執行才會出現

pipeline {
   agent any
   parameters {
       string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?')
       text(name: 'BIOGRAPHY', defaultValue: '', description: 'Enter some information about the person')
       booleanParam(name: 'TOGGLE', defaultValue: true, description: 'Toggle this value')
       choice(name: 'CHOICE', choices: ['One', 'Two', 'Three'], description: 'Pick something')
       password(name: 'PASSWORD', defaultValue: 'SECRET', description: 'Enter a password')
   }
   stages {
       stage('Example') {
           steps {
                echo "Hello ${params.PERSON}"
                echo "Biography: ${params.BIOGRAPHY}"
                echo "Toggle: ${params.TOGGLE}"
                echo "Choice: ${params.CHOICE}"
                echo "Password: ${params.PASSWORD}"
           }
       }
   }
}

3.2.7.5 案例:互動輸入實現確認和取消

input 指令支援中斷當前任務,以待確認和取消

pipeline {
   agent any
   stages {
       stage('Example') {
           steps {
               script {
                   def userInput = input(submitterParameter: "approver",
                       id: "approve", message: "Provide your approval to proceed",
                       parameters: [string(defaultValue: "approved",
                       description: 'Please provide the message why you are approving',
                       name: 'remarks')])
                    echo "Remarks:${userInput['remarks']}"
                    echo "It was ${userInput.approver} who approved this job"
               }
           }
         }
     }    
}

3.2.7.6 案例:條件判斷 (瞭解)

對於pipeline來說,使用if語句或者try語句,或者when來進行條件的流程控制,這兩種方式效果相似

when是用在stage段中的指令,用於限定當前stage的執行條件

pipeline {
   agent any
   parameters {
       booleanParam(name:'pushImage', defaultValue: 'true', description: 'Push Image to Harbor?')
   }
   tools {
       maven 'maven-3.8.6'
   }
   environment {
        codeRepo="http://gitlab.wang.org/root/spring-boot-helloWorld.git"
        harborServer='harbor.wang.org'
        projectName='spring-boot-helloworld'
        imageUrl="${harborServer}/example/${projectName}"
        imageTag='latest'
   }
   stages {
       stage('Source') {
           steps {
                git branch: 'main', credentialsId: 'gitlab-root-credential', url: "${codeRepo}"
           }
       }
       stage('Build') {
           steps {
                sh 'mvn -B -DskipTests clean package'
           }
       }
       stage('Test') {
           steps {
                sh 'mvn test'
           }
       }
       stage('Build Docker Image') {
           steps {
                sh 'docker image build . -t "${imageUrl}:${imageTag}"'
           }           
       }
       stage('Push Docker Image') {
           agent any 
           when {
               expression { params.pushImage }
               // expression { "${params.pushImage}" == 'true' }
               //beforeAgent: true
           }
           steps {
               // input(message: 'continue?')
               withCredentials([usernamePassword(credentialsId: 'harbor-user-credential', passwordVariable: 'harborPassword', usernameVariable: 'harborUserName')]) {
                    sh "echo ${harborPassword} | docker login -u ${env.harborUserName} --password-stdin ${harborServer}"
                    sh "docker image push ${imageUrl}:${imageTag}"
               }
           }   
       }        
   }
}

3.2.7.7 案例:並行

宣告性Parallel的程式碼塊中的可以巢狀多個stage,從而讓多個stage任務並行執行。

jenkins左側 開啟blue ocean才能看到效果

pipeline {
   agent any
   stages {
       stage( 'Deploy') {
           parallel {
               stage('deploy_proxy'){
                   steps {
                        echo "部署反向代理服務"
                   }
               }
               stage('deploy app1') {
                   steps {
                        echo "部署deploy_app1應用"
                   }
               }
               stage ('deploy app2'){
                   stages {
                       stage ('delete container') {
                           steps {
                                 echo "刪除舊容器"
                           }
                       }
                       stage('start container') {
                           steps {
                                echo "啟動新容器"
                           }
                       }
                   }
               }
           }
       }
   }
}

3.2.7.8 案例:觸發器

#注意:第一次需要手動執行,後續才能生效
pipeline {
   agent any
   tools {
       maven 'maven-3.8.6'
   }
   triggers {
       gitlab(triggerOnPush: true,
           acceptMergeRequestOnSuccess: true, //合併請求
           //triggerOnMergeRequest: true,
           branchFilterType: 'All',    //所有分支
           secretToken: '62dad2cd1d9ae62686ada8dc4cdOae66')
       }
   parameters {
       booleanParam(name: "PUSH", defaultValue: true)
   environment {
        GitRepo="http://gitlab.wang.org/ops/spring-boot-helloWorld.git
        HarborServer='harbor.wang.org'
        ImageUrl="example/spring-boot-helloworld
        ImageTag="latest"
   stages {
       stage('Source'){
           steps { 
                git branch: 'main', url: "${GitRepo}"
           }
       }
   .......
   }
}

範例:透過 Gitlab 外掛實現

#第一次構建後,再次進入會顯示對應的配置
pipeline {
   agent any
   parameters {
       booleanParam(name:'pushImage', defaultValue: 'true', description: 'Push Image to Harbor?')
   }
   tools {
       maven 'maven-3.8.6'
   }
   triggers {
       gitlab(triggerOnPush: true,
           acceptMergeRequestOnSuccess: true,
           triggerOnMergeRequest: true,
           branchFilterType: 'All',
           addVoteOnMergeRequest: true,
           secretToken: '62dad2cd1d9ae62686ada8dc4cdOae66')
   }
   environment {
        codeRepo="http://gitlab.wang.org/devops/spring-boot-helloWorld.git"
        harborServer='harbor.wang.org'
        projectName='spring-boot-helloworld'
        imageUrl="${harborServer}/example/${projectName}"
        imageTag="${BUILD_ID}"
   }
   stages {
       stage('Source') {
           steps {
                git branch: 'main', credentialsId: 'gitlab-root-credential', url: "${codeRepo}"
           }
       }
       stage('Build') {
           steps {
                sh 'mvn -B -DskipTests clean package'
           }
       }
       stage('Test') {
           steps {
                sh 'mvn test'
           }
       }
       stage('Build Docker Image') {
           steps {
                sh 'docker image build . -t "${imageUrl}:${imageTag}"'
           }           
       }
       stage('Push Docker Image') {
           agent any 
           when {
               expression { params.pushImage == 'true' }
               beforeAgent: true
           }
           steps {
               // input(message: 'continue?')
               withCredentials([usernamePassword(credentialsId: 'harbor-user-credential', passwordVariable: 'harborUserPassword', usernameVariable: 'harborUserName')]) {
                    sh "echo ${harborUserPassword} | docker login -u ${env.harborUserName} --password-stdin ${harborServer}"
                    sh "docker image push ${imageUrl}:${imageTag}"
               }
           }  
       }        
   }
}

3.2.7.9 案例:構建後操作

流水線也提供了構建後的動作,常可用於訊息通知

#在stages同級別的位置,新增一個post配置段
post {
 always {
 echo '任執行結束後執行此操作'
 }
}

#例如
...
   post {
       always {    //成功失敗都執行
           mail to: 'root@wangxiaochun.com',
           subject: "Status of pipeline: ${currentBuild.fullDisplayName}",
           body: "${env.BUILD_URL} has result ${currentBuild.result}"
       }
   }  
}


#範例:綜合案例,成功發郵件,失敗發企業微信
pipeline {
   agent any
   parameters {
       booleanParam(name:'pushImage', defaultValue: 'true', description: 'Push Image to Harbor?')
   }    
   tools {
       maven 'maven-3.6.3'
   }
   stages {
       stage('Source') {
           steps {
                echo "source"
           }
       }
       stage('Build') {
           steps {
                echo "Build"
           }
       }
       stage('Build Docker Image') {
           steps {
                echo "Build Docker image"
           }           
       }
       stage('Push Docker Image') {
           agent any
           when {
               expression { "${params.pushImage}" == 'true' }
           }
           steps {
                   echo "Push"
           }   
       }
       stage('Run Docker ') {
           steps {
             echo "run docker"
           }   
       }
   }
   post {
       success {
           mail to: 'root@wangxiaochun.com',
           subject: "Status of pipeline: ${currentBuild.fullDisplayName}",
           body: "${env.BUILD_URL} has result ${currentBuild.result}"
       }
       failure{
           qyWechatNotification failNotify: true, webhookUrl: 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=1a4e1e74-e7d6-4376-99f6-048b01a7b7fc'
       }
   }  
}

4 程式碼質量檢測 SonarQube

4.1.3 架構和整合

4.1.3.1 SonarQube 架構

1.SonarQube Server 包括三個主要部分
    Web Server: UI 介面
    Search Server :為UI提供搜尋功能,基於 ElasticSearch 實現
    Compute Engine Server:處理程式碼分析報告,並將之儲存到 SonarQube Database
2.SonarQube Database: 負責儲存 SonarQube 的配置,以及專案的質量快照等
#下面2個屬於客戶端
3.SonarQube Plugin: 開發語言工具idea上可以安裝該外掛,傳送資料給 SonarQube Server
4.Code analysis Scanners: 程式碼掃描器,是SonarQube Server的客戶端, 將程式碼掃描後得出報告提交給 SonarQube Server

4.1.4 SonarQube 版本說明

SonarQube 分為: 社群版,開發版,企業版和資料中心版

其中只有社群版是開源免費的

#SonarQube 分兩種版本: LTS 和非 LTS 版
#官方LTS版本說明
https://www.sonarqube.org/downloads/lts/

#各種版本下載
https://www.sonarsource.com/products/sonarqube/downloads/historical-downloads/
https://www.sonarqube.org/downloads/

#LTS 版有如下版本
9.9
8.9
7.9
6.7
5.6
4.5
3.7

4.2 安裝環境準備

4.2.1 硬體要求

#硬體需求
1.小型應用至少需要2GB的RAM (最好起步4個G)
2.磁碟空間取決於SonarQube分析的程式碼量
3.必須安裝在讀寫效能較好的磁碟, 儲存資料的目錄中包含ElasticSearch的索引,伺服器啟動並執行時,將會在該索引上進行大是I/O操作
4.不支援32位作業系統

4.2.2 系統核心最佳化

[root@SonarQube-Server ~]#vim /etc/sysctl.conf
vm.max_map_count=262144  #此項必須修改,否則無法啟動
fs.file-max=65536        #此項可不改,預設值滿足要求

#此檔案可不改,可選
[root@SonarQube-Server ~]# vim /etc/security/limits.conf
sonarqube  - nofile  65536
sonarqube  - nproc  4096

#如果以systemd 執行SonarQube,需要在service檔案配置
[servcie]
.....
LimitNOFILE=65536
LimitNPROC=4096
......

4.2.3 資料庫環境依賴說明

注意:SonarQube 7.9 不再支援MySQL,可以選擇安裝 PostgreSQL

4.2.4 Java 環境依賴說明

SonarQube 7.9 以上版本不再支援 java 11, 客戶端還支援java 11

4.2.5 建立SonarQube使用者

#使用普通賬戶啟動sonarqube,因為sonarqube內建了ES,所以不允許能root啟動
#Ubuntu使用useradd建立使用者時預設使用/bin/sh,並且不建立家目錄
[root@SonarQube-Server ~]#useradd -s /bin/bash -m sonarqube 

4.3 安裝 SonarQube 伺服器

4.3.1.1 安裝和配置 PostgreSQL 資料庫

4.3.2 下載 SonarQube 和修改配置檔案

4.3.3 啟動 SonarQube

4.3.4 建立 service 檔案

範例: 建立 service 檔案

#先停止sonarqube
[root@SonarQube-Server ~]#su - sonarqube 
sonarqube@SonarQube-Server:~$ /usr/local/sonarqube/bin/linux-x86-64/sonar.sh stop           
sonarqube@SonarQube-Server:~$ /usr/local/sonarqube/bin/linux-x86-64/sonar.sh status
SonarQube is not running.
sonarqube@SonarQube-Server:~$exit

#建立service檔案
[root@SonarQube-Server ~]#vim /etc/systemd/system/sonarqube.service
[Unit]
Description=SonarQube service
After=syslog.target network.target

[Service]
Type=simple
User=sonarqube
Group=sonarqube
PermissionsStartOnly=true
ExecStart=/usr/bin/nohup /usr/bin/java -Xms32m -Xmx32m -Djava.net.preferIPv4Stack=true -jar /usr/local/sonarqube/lib/sonar-application-8.9.2.46101.jar
#ExecStart=/usr/bin/nohup /usr/bin/java -Xms32m -Xmx32m -Djava.net.preferIPv4Stack=true -jar /usr/local/sonarqube/lib/sonar-application-7.9.6.jar
StandardOutput=syslog
LimitNOFILE=65536
LimitNPROC=4096
TimeoutStartSec=5
Restart=always

[Install]
WantedBy=multi-user.target

[root@SonarQube-Server ~]#systemctl daemon-reload 
[root@SonarQube-Server ~]#systemctl enable --now   sonarqube.service 
[root@SonarQube-Server ~]#systemctl status sonarqube.service

下面為指令碼實現整個安裝過程

#!/bin/bash
#SONARQUBE從9.9版本以後要求安裝JDK17

#支援線上和離線安裝,線上下載可能很慢,建議離線安裝

SONARQUBE_VER="9.9.4.87374"
#SONARQUBE_VER="9.9.3.79811"
#SONARQUBE_VER="9.9.2.77730"
#SONARQUBE_VER="9.9.1.69595"
#SONARQUBE_VER="9.9.0.65466"
#SONARQUBE_VER="8.9.10.61524"
#SONARQUBE_VER="8.9.9.56886"
#SONARQUBE_VER="8.9.2.46101"
#SONARQUBE_VER="7.9.2"

SONARQUBE_URL="https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-${SONARQUBE_VER}.zip"
SONAR_USER=sonarqube
SONAR_USER_PASSWORD=123456

WORK_DIR=`pwd`
HOST=`hostname -I|awk '{print $1}'`

GREEN="echo -e \E[32;1m"
END="\E[0m"

. /etc/os-release

color () {
    RES_COL=60
    MOVE_TO_COL="echo -en \\033[${RES_COL}G"
    SETCOLOR_SUCCESS="echo -en \\033[1;32m"
    SETCOLOR_FAILURE="echo -en \\033[1;31m"
    SETCOLOR_WARNING="echo -en \\033[1;33m"
    SETCOLOR_NORMAL="echo -en \E[0m"
    echo -n "$1" && $MOVE_TO_COL
    echo -n "["
    if [ $2 = "success" -o $2 = "0" ] ;then
        ${SETCOLOR_SUCCESS}
        echo -n $"  OK  "    
    elif [ $2 = "failure" -o $2 = "1"  ] ;then 
        ${SETCOLOR_FAILURE}
        echo -n $"FAILED"
    else
        ${SETCOLOR_WARNING}
        echo -n $"WARNING"
    fi
    ${SETCOLOR_NORMAL}
    echo -n "]"
    echo 
}


install_jdk() {
    java -version &>/dev/null && { color "JDK 已安裝!" 1 ; return;  }
    if command -v yum &>/dev/null ; then
        yum -y install java-1.8.0-openjdk-devel || { color "安裝JDK失敗!" 1; exit 1; }
    elif command -v apt &>/dev/null ; then
        apt update
        apt install openjdk-17-jdk -y || { color "安裝JDK失敗!" 1; exit 1; } 
        #apt install openjdk-11-jdk -y || { color "安裝JDK失敗!" 1; exit 1; } 
        #apt install openjdk-8-jdk -y || { color "安裝JDK失敗!" 1; exit 1; }
    else
        color "不支援當前作業系統!" 1
        exit 1
    fi
    java -version && { color "安裝 JDK 完成!" 0 ; } || { color "安裝JDK失敗!" 1; exit 1; }
}

system_prepare () {
    useradd -s /bin/bash -m sonarqube 
    cat >> /etc/sysctl.conf <<EOF
vm.max_map_count=524288
fs.file-max=131072
EOF
    sysctl -p
    cat >> /etc/security/limits.conf  <<EOF
sonarqube  -  nofile 131072
sonarqube  -  nproc  8192
EOF
}

install_postgresql(){
    if [ $ID = "centos" -o $ID = "rocky" ];then
        if [ $VERSION_ID -eq 7 ];then
            rpm -i http://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm
            yum -y install postgresql12-server postgresql12 postgresql12-libs
            postgresql-12-setup --initdb
        else
            #rpm -i http://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm
            color "不支援此作業系統!" 1
            exit 
        fi
        systemctl enable  postgresql.service
        systemctl start  postgresql.service
    else 
        apt update
        apt -y install postgresql
    fi
    if [ $? -eq 0 ];then
       color "安裝postgresql完成!" 0
    else
       color "安裝postgresql失敗!" 1
       exit
    fi
}


config_postgresql () {
    if [ $ID = "centos" -o $ID = "rocky" ];then
        sed -i.bak "/listen_addresses/a listen_addresses = '*'"  /var/lib/pgsql/data/postgresql.conf
        cat >>  /var/lib/pgsql/data/pg_hba.conf <<EOF
host    all             all             0.0.0.0/0               md5
EOF
    else 
        sed -i.bak "/listen_addresses/c listen_addresses = '*'" /etc/postgresql/1*/main/postgresql.conf 
        cat >>  /etc/postgresql/*/main/pg_hba.conf <<EOF
host    all             all             0.0.0.0/0               md5
EOF
    fi
    systemctl restart postgresql
    
    su - postgres -c "psql -U postgres <<EOF
CREATE USER $SONAR_USER WITH ENCRYPTED PASSWORD '$SONAR_USER_PASSWORD';
CREATE DATABASE sonarqube ;
GRANT ALL PRIVILEGES ON DATABASE sonarqube TO $SONAR_USER;
EOF"
}

install_sonarqube() {
    cd $WORK_DIR
    if [ -f sonarqube-${SONARQUBE_VER}.zip ] ;then
        mv sonarqube-${SONARQUBE_VER}.zip /usr/local/src
    else
        wget -P /usr/local/src ${SONARQUBE_URL}  || { color  "下載失敗!" 1 ;exit ; }
    fi
    cd /usr/local/src
    unzip ${SONARQUBE_URL##*/}
    ln -s /usr/local/src/sonarqube-${SONARQUBE_VER} /usr/local/sonarqube
    chown -R sonarqube.sonarqube /usr/local/sonarqube/
    cat > /lib/systemd/system/sonarqube.service <<EOF
[Unit]
Description=SonarQube service
After=syslog.target network.target

[Service]
Type=simple
User=sonarqube
Group=sonarqube
PermissionsStartOnly=true
ExecStart=/usr/bin/nohup /usr/bin/java -Xms32m -Xmx32m -Djava.net.preferIPv4Stack=true -jar /usr/local/sonarqube/lib/sonar-application-${SONARQUBE_VER}.jar
StandardOutput=syslog
LimitNOFILE=65536
LimitNPROC=8192
TimeoutStartSec=5
Restart=always

[Install]
WantedBy=multi-user.target
EOF
    cat >> /usr/local/sonarqube/conf/sonar.properties <<EOF
sonar.jdbc.username=$SONAR_USER
sonar.jdbc.password=$SONAR_USER_PASSWORD
sonar.jdbc.url=jdbc:postgresql://localhost/sonarqube
EOF
}

start_sonarqube() { 
    systemctl enable --now   sonarqube.service 
    systemctl is-active sonarqube
    if [ $?  -eq 0 ];then  
        echo 
        color "sonarqube 安裝完成!" 0
        echo "-------------------------------------------------------------------"
        echo -e "訪問連結: \c"
        ${GREEN}"http://$HOST:9000/"${END}
        echo -e "使用者和密碼: \c"
        ${GREEN}"admin/admin"${END}
    else
        color "sonarqube 安裝失敗!" 1
        exit
    fi
}

install_jdk
system_prepare
install_postgresql
config_postgresql
install_sonarqube
start_sonarqube

4.3.5 登入到 Web 介面

用瀏覽器訪問地址: http:10.0.0.155:9000

新版預設必須登入,不支援匿名訪問
預設使用者名稱和密碼都是 admin
#第一次要修改密碼  改成   123456

4.4 管理 SonarQube 伺服器

4.4.1 安裝中文支援

#上方Administration下Marketplace可以安裝外掛,需要先同意風險才能安裝
#漢化外掛,搜chinese,安裝Chinese Pack
#安裝完後,點 Restart Server

#外掛在這個路徑下(可以複製給其他機器)
[root@SonarQube-Server ~]#ll /usr/local/sonarqube/extensions/plugins/

4.4.3 許可權管理

#上方許可權下使用者(全域性許可權可以看到對應許可權)
#建立使用者  xiaoming/123456
#建立完,右側點選令牌,輸入名稱,過期時間,生成,獲得令牌(自己儲存下來,視窗一次性的)
squ_8f7075d74b1f707610bc935a4b9cbf9884af769e

4.4.3.1 允許匿名訪問

8.9.X 新版預設取消了匿名使用者訪問,可以在上方配置下許可權 關閉Force user authentication

4.5 部署程式碼掃描器 sonar-scanner

下載地址:
https://docs.sonarqube.org/latest/analyzing-source-code/scanners/sonarscanner/

#在jenkins機器上安裝 sonar-scanner 
[root@jenkins-ubuntu ~]#cd /usr/local/src
#新版
[root@jenkins-ubuntu src]#wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.6.2.2472-linux.zip
[root@jenkins-ubuntu src]#unzip sonar-scanner-cli-4.6.2.2472-linux.zip
[root@jenkins-ubuntu src]#ln -s /usr/local/src/sonar-scanner-4.6.2.2472-linux/ /usr/local/sonar-scanner

#建立軟連結方便執行
[root@jenkins-ubuntu ~]#ln -s /usr/local/sonar-scanner/bin/sonar-scanner /usr/local/bin/

[root@jenkins-ubuntu ~]#ldd /usr/local/sonar-scanner/bin/sonar-scanner
 不是動態可執行檔案
 
#配置sonar-scanner連線sonarqube伺服器
[root@jenkins-ubuntu ~]#vim /usr/local/sonar-scanner/conf/sonar-scanner.properties
#指向sonarqube伺服器的地址和埠
sonar.host.url=http://sonarqube.wang.org:9000 
sonar.sourceEncoding=UTF-8

#如果登入sonarqube server需要驗證,還要加下面兩行sonarqube的預先建立的使用者資訊
#sonar.login=admin
#sonar.password=123456
#sonar.login=xiaoming
#密碼方式未來會淘汰
#sonar.password=123456
#建議使用Token方式
sonar.login=squ_8f7075d74b1f707610bc935a4b9cbf9884af769e

4.5.3 在原始碼目錄執行掃描

#專案目錄下要有 sonar-project.properties檔案
[root@jenkins python-sonar-runner]#ls
README.md  sonar-project.properties  src  validation.txt

#sonar-project.properties下要有這兩項,projectKey是id,projectName是sonar出報告的專案名
[root@jenkins python-sonar-runner]#cat sonar-project.properties
sonar.projectKey=org.sonarqube:python-simple-sonar-scanner
sonar.projectName=Python :: Simple Project : SonarQube Scanner
#專案原始碼編譯生成的二進位制檔案路徑,從sonar-scanner-4.1.2起,僅java專案要求sonar.java.binaries引數
sonar.java.binaries=.    #路徑寫.可以在子目錄下,如寫./target編譯前沒該檔案掃描會報錯
...

#把程式碼提交給SonarQube伺服器,自動尋找sonar-project.properties檔案
[root@jenkins python-sonar-runner]#sonar-scanner

#範例:無需配置檔案sonar-project.properties,掃描專案
[root@jenkins spring-boot-helloWorld]#sonar-scanner -Dsonar.projectName=myapp -Dsonar.projectKey=myapp -Dsonar.java.binaries=./
#無需配置sonar-scanner.properties中的token,掃描專案
[root@jenkins spring-boot-helloWorld]#sonar-scanner -Dsonar.login=<token>

#SonarQube網站專案能看到提交的結果

4.8 案例2: 基於 PipeLine 實現 JAVA專案整合 SonarQube 程式碼檢測通知 Jenkins(推薦)

4.8.1 安裝 GitLab 和準備專案

4.8.2 安裝 Harbor 並配置 Jenkins 連線 Harbor

4.8.3 安裝 Jenkins 並安裝相關外掛

4.8.4 在 Jenkins 建立憑據連線 Harbor

4.8.5 在 Jenkins 安裝 Maven 工具

4.8.6 在 Jenkins 上配置 Maven 環境

4.8.7 在 Jenkins 建立連線 GitLab 的憑據

4.8.8 在 Jenkins 上修改 Docker 的 socket 檔案許可權

4.8.9 安裝 SonarQube 並建立使用者和令牌

4.8.10 在 SonarQube 新增 Jenkins 的回撥介面

jenkins需要知道SonarQube的報告質量如何

#sonarqube上方配置下,左側配置下webhook,點選建立
#名稱隨便起,這裡寫 jenkins
#URL(jenkins固定地址,保證後面域名可以解析)
http://jenkins.wang.org:8080/sonarqube-webhook
#密碼防止攻擊使用,隨意輸入,下面透過命令生成密碼
[root@ubuntu2204 ~]#openssl rand -base64 21
PiTVO6epEqIUlvU1DBoK1bVS3bkh
#點選建立

4.8.11 在 Jenkins 安裝 SonarQube Scanner 外掛

4.8.12 在 Jenkins 建立訪問 Sonarqube的令牌憑據

用前面小節生成的Sonarqube中使用者令牌,在Jenkins 建立憑據

#jenkins左側系統管理,憑據管理,建立一個憑據
#型別(光一個key)  Secret text
#Secret輸入上面生成的使用者令牌   squ_8f7075d74b1f707610bc935a4b9cbf9884af769e
#ID/描述寫   sonarqube-xiaoming-secret

4.8.13 在 Jenkins 上配置系統的 SonarQube 伺服器資訊

#jenkins左側系統管理,系統配置
#SonarQube servers下,點選Add SonarQube
#Name(未來呼叫的就是這個名字)寫  sonarqube
#Server URL  寫   http://10.0.0.155:9000
#Server authentication token  選上面的憑據   sonarqube-xiaoming-secret
#儲存

4.8.14 在 Jenkins 上安裝 SonarQube Scanner

上面已經手動安裝了, 這裡不再進行安裝

如果未安裝,也可以在jenkins系統管理/全域性工具配置中SonarQube Scanner安裝

4.8.15 準備微信機器人

4.8.16 建立任務使用 Pipeline Script

#jenkins新建pipeline任務
pipeline {
   agent any
   tools {
       maven 'maven-3.6.3'
   }
   environment {
        codeRepo="http://gitlab.wang.org/example1/spring-boot-helloworld.git"
        harborServer='harbor.wang.org'
        credential='gitlab-root-password'
        projectName='spring-boot-helloworld'
        imageUrl="${harborServer}/example/${projectName}"
        imageTag="${BUILD_ID}"
   }
   stages {
       stage('Source') {
           steps {
                git branch: 'main', credentialsId: "${credential}", url: "${codeRepo}"
           }
       }
       stage("SonarQube Analysis") {
           steps {
              //這個名字要寫在jenkins上配SonarQube 伺服器名字
               withSonarQubeEnv('sonarqube') {
                       //java這個寫法也支援向sonar伺服器傳送程式碼掃描
                    sh 'mvn sonar:sonar -Dsonar.java.binaries=target/'
               }
           }
       }
       stage("Quality Gate") {
           steps {
               timeout(time: 30, unit: 'MINUTES') {
                   //質量閾透過往下執行,失敗就不往下執行
                   waitForQualityGate abortPipeline: true
               }
           }
       } 
       stage('Build') {
           steps {
                sh 'mvn -B -DskipTests clean package'
           }
       }
       stage('Test') {
           steps {
                sh 'mvn test'
           }
       }
       stage('Build Docker Image') {
           steps {
                sh 'docker image build . -t "${imageUrl}:${imageTag}"'
               // input(message: '映象已經構建完成,是否要推送?')
           }           
       }
       stage('Push Docker Image') {
           steps {
               withCredentials([usernamePassword(credentialsId: 'harbor-xiaoming-password', passwordVariable: 'harborPassword', usernameVariable: 'harborUserName')]) {
                    sh "docker login -u ${env.harborUserName} -p ${env.harborPassword} ${harborServer}"
                    sh "docker image push ${imageUrl}:${imageTag}"
               }
           }   
       }
       stage('Run Docker') {
           steps {
               //sh 'ssh root@10.0.0.202 "docker rm -f ${projectName} && docker run --name ${projectName} -p 80:8888 -d ${imageUrl}:${imageTag}"'
               //sh 'ssh root@10.0.0.203 "docker rm -f ${projectName} && docker run --name ${projectName} -p 80:8888 -d ${imageUrl}:${imageTag}"'
                sh "docker -H 10.0.0.153:2375 rm -f ${projectName} && docker -H 10.0.0.153:2375 run --name ${projectName} -p 80:8888 -d ${imageUrl}:${imageTag}"
                sh "docker -H 10.0.0.154:2375 rm -f ${projectName} && docker -H 10.0.0.154:2375 run --name ${projectName} -p 80:8888 -d ${imageUrl}:${imageTag}"
           }   
       }         
        
   }
 post {
       success {
           mail to: 'root@wangxiaochun.com',
           subject: "Status of pipeline: ${currentBuild.fullDisplayName}",
           body: "${env.BUILD_URL} has result ${currentBuild.result}"
       }
       failure{
           qyWechatNotification failNotify: true, webhookUrl: 
'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=938039da-1123-4862-b47b-f0396020f45b'
       }
   } 
}

4.8.17 修改 Sonarqube Server的質量閾再次執行構建驗證結果

相關文章