Jenkins部署Python項目實戰

1、背景

咱們工做中經常使用Jenkins部署Java代碼,因其靈活的插件特性,例如jdk,maven,ant等使得java項目編譯後上線部署一鼓作氣,一樣對於腳本語言類型如Python上線部署,利用Jenkins強大的插件功能,輕鬆實現CI/CD,但若是部署多項目到同一臺服務器涉及環境一致性問題,對此能夠利用容器技術Docker解決,也能夠利用Python虛擬環境例如virutalenv或conda等優秀等工具解決,在此因爲後期根據requirements來安裝依賴包比較慢,且後期須要將Python整個環境打包,利用conda工具來對項目環境進行管理,方便快速移植。css

本文較系統的記錄下部署一個具體的Django項目,包括利用conda工具來實現Python多環境管理,Pylint工具來實現代碼檢查,使用nose框架作單元測試和覆蓋率。html

2、必備知識

  • Jenkins基礎安裝部署
  • Conda軟件包管理系統

因爲conda包較大,一般狀況下可使用Miniconda
官網下載地址:https://conda.io/en/latest/miniconda.html
基礎使用命令:node

1、工具包管理命令
1.更新工具包
conda update conda
conda upgrade --all

2.安裝包(進入虛擬環境,也可用pip安裝)
conda install package_name
能夠指定版本
conda install package_name=1.10

3.移除包
conda remove package_name

4.查看包
conda list

5.查詢包
conda search package_name

6.源配置
由於anaconda的服務器在國外,所以有時候速度會比較慢,能夠換到國內源,好比清華的TUNA。

conda config --show-sources #查看當前使用源
conda config --remove channels 源名稱或連接 #刪除指定源
conda config --add channels 源名稱或連接 #添加指定源

# 添加Anaconda的TUNA鏡像
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
# TUNA的help中鏡像地址加有引號,須要去掉
 
# 設置搜索時顯示通道地址
conda config --set show_channel_urls yes


2、Python環境管理命令
1.顯示建立的全部環境
conda env list

2.建立python環境:
conda create -n env_name list of packages
eg:conda create -n py2 python=2.7 request

3.進入python環境:(windows 環境不須要加source)
source activate env_name
eg:source activate py2

4.退出環境:
source deactivate

5.查看某環境下已安裝的包
conda list -n python2

6.複製環境:
conda create --name <new_env_name> --clone <copied_env_name>

7.刪除環境:
conda remove -n go2cloud-api-env --all

8.環境生成爲YAML文件
當分享代碼的時候,同時也須要將運行環境分享給你們,首先進入到環境中,執行以下命令能夠將當前環境下的 package 信息存入名爲 environment 的 YAML 文件中。
  • conda env export > environment.yaml

一樣,當執行他人的代碼時,也須要配置相應的環境。這時你能夠用對方分享的 YAML 文件來建立一摸同樣的運行環境python

conda env create -f environment.yamlgit

Shell基礎docker

  • GIT基礎

3、優化

  • 生成主題

主題工具生成連接:http://afonsof.com/jenkins-material-theme/
在主題工具生成喜歡的顏色已經上傳logo下載生成的主題到Jenkins服務器的jenkins 家目錄,通常爲安裝啓動jenkins系統用戶的家目錄下.jenkins/userContent/material/,若是沒有此目錄須要新建目錄,css文件移動到目錄下,例如/root/.jenkins/userContent/material/blue.css。api

  • Jenkins上配置主題

a.Install Jenkins Simple Theme Plugin
b.點擊Manage Jenkins
c.點擊Configure System
d.找到 Theme 配置
e.填寫本地主題cssurl,例如:http://localhost:8080/jenkins/userContent/material/blue.css
f.Click Save
g.保存可查看效果。

  • 注意

本地css的url能夠瀏覽器打開測試訪問,若是訪問不到會默認加載Jenkins默認主題
後期遷移url最好寫成localhost,若是寫公網IP,css文件不存在404,Jenkins頁面會很卡。

4、部署實戰

4.1 服務器列表

名稱 IP 軟件 備註
Jenkins-server 10.57.61.138 miniconda Jenkins服務器
Des-server 172.21.0.10 miniconda 項目部署服務器

4.2 架構圖

圖片描述

4.3 前期準備

  1. 安裝依賴包

對依賴包須要在Jenkins-server服務器進行安裝,首先根據項目裏面conda建立對應項目對虛擬環境conda create -n <project_name> python=3.6,建立完成利用conda env list查看環境,爲避免環境污染,在項目環境內利用pip工具安裝軟件pip install pylint mock nose coverage

  1. 安裝Jenkins插件
  • JUnit: 用來展現nose框架生成的單元測試報表(Allows JUnit-format test results to be published.)
  • Cobertura Plugin:用來展現Python代碼測試覆蓋率報表(This plugin integrates Cobertura coverage reports to Jenkins.)
  • Violations plugin:用來展現Python靜態代碼審查報表(This plugin does reports on checkstyle, csslint, pmd, cpd, fxcop, pylint, jcReport, findbugs, and perlcritic violations.),參考:https://wiki.jenkins.io/display/JENKINS/Violations
  • Git Plugin:用來從Gitbucket源代碼庫拉取代碼(This plugin allows GitLab to trigger Jenkins builds and display their results in the GitLab UI.)
  • Git Parameter:用於參數化構建選擇git的branch(Adds ability to choose branches, tags or revisions from git repositories configured in project.)

4.4 建立任務

  • 建立自由風格軟件項目

New任務->構建一個自由風格的軟件項目,填寫描述,在此因爲後期會利用pylint進行代碼檢查,給出了代碼檢查的消息類型,能夠根據消息類型進行相應修復處理。
圖片描述

  • 配置參數化構建

配置選擇git具體branch進行構建,和能夠自定義端口,在此須要注意參數化構建的變了Name,在後續須要用到。
圖片描述

  • 源碼管理

在此須要選擇源碼倉庫,選擇gitlab已經認證方式,須要注意因爲參數化構建選擇了branch,在Branches to build須要引用上面的變量$branch。
圖片描述

  • 構建配置
    • 執行shell

執行shell此shell爲在Jenkins服務器上執行,因此須要預先在其上配置Python虛擬環境,在其上進行代碼審查,單元測試生成nosetests.xml文件,已經代碼覆蓋率測試生成coverage.xml文件,pylint測試生成pylint.xml文件。
圖片描述

如下爲此項目示例shell腳本,此腳本須要根據本身的實際狀況來修改,須要注意Python項目結構與須要代碼檢查的標識符。

base_dir=/root/.jenkins/workspace/
project=go2cloud-api-deploy-prod/
project_env=go2cloud-api-env
# 切換python環境
source activate ${project_env}
$(which python) -m pip install mock nose coverage
# 更新python環境
echo "+++更新Python環境+++"

if [ -f ${base_dir}${project}requirements.txt ];then
    $(which python) -m pip install -r ${base_dir}${project}requirements.txt && echo 0 || echo 0
fi

# 代碼檢查/單元測試/代碼測試覆蓋率
echo "+++代碼檢查+++"
cd ${base_dir}
# 生成pylint.xml
$(which pylint) -f parseable --disable=C0103,E0401,C0302 $(find ${project}/* -name *.py) >${base_dir}pylint.xml || echo 0

echo "+++單元測試+++"
# 生成nosetests.xml
#$(which nosetests) --with-xunit --all-modules --traverse-namespace --with-coverage --cover-package=go2cloud-api-deploy-prod --cover-inclusive || echo 0
$(which nosetests) --with-xunit --all-modules --traverse-namespace --with-coverage --py3where=go2cloud-api-deploy-prod --cover-package=go2cloud-api-deploy-prod --cover-inclusive || echo 0
echo "+++代碼覆蓋率+++"
# 生成coverage.xml

python -m coverage xml --include=go2cloud-api-deploy-prod* || echo 0

  • 發送文件及命令到目標服務器

圖片描述
在此須要制定發佈到目標到的服務器遠端目錄,已經執行的命令,在此執行重啓腳本。
因爲在此項目爲Django項目,須要制定虛擬環境/入口啓動文件/啓動端口。
將以前參數化構建的端口看成變量傳遞給腳本,啓動相應的端口

#!/usr/bin/env bash

# 當前目錄
BASEPATH=$(cd `dirname $0`;pwd)

# python解釋器具體路徑
PYTHON_BIN=$1

# mananger文件路徑
MAIN_APP=$2

# python
SERVER_PORT=$3

[ $# -lt 3 ] && echo "缺乏參數" && exit 1

LOG_DIR=${BASEPATH}/logs/
[ ! -d ${LOG_DIR} ] && mkdir ${LOG_DIR}

OLD_PID=`netstat -lntup | awk -v SERVER_PORT=${SERVER_PORT} '{if($4=="0.0.0.0:"SERVER_PORT) print $NF}'|cut -d/ -f1`
[ -n "${OLD_PID}" ] && kill -9 ${OLD_PID}

echo "---------$0 $(date) excute----------" >> ${LOG_DIR}server-$(date +%F).log

# 啓動服務

nohup ${PYTHON_BIN} -u ${MAIN_APP} runserver 0.0.0.0:${SERVER_PORT} &>> ${LOG_DIR}server-$(date +%F).log 2>&1 &
  • 構建後動做
    • JUnit插件實現單元測試報告,須要指定nosetests.xml

圖片描述

  • Cobertura Plugin插件實現覆蓋率測試

圖片描述

  • Violations插件進行代碼審計,須要制定Jenkins-server上的生成的pylint.xml文件。

須要注意文件路徑爲jenkins服務器pylint.xml,以及對應生成文件的編碼。
圖片描述
圖片描述

  • 郵件通知配置

選擇郵件內容爲Content Type爲HTML,這樣能夠編寫郵件HTML模版,生成較爲好看的郵件通知模版。
注意選擇觸發告警能夠選擇類型,失敗幾回或不管構建成功失敗都發送,可根據具體需求配置。
圖片描述
圖片描述
郵件HTML模版

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次構建日誌</title>
</head>

<body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4"
    offset="0">
    <table width="95%" cellpadding="0" cellspacing="0"
        style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif">
        <tr>
            <td>(本郵件是程序自動下發的,請勿回覆!)</td>
        </tr>
        <tr>
            <td><h2>
                    <font color="#0000FF">構建結果 - ${BUILD_STATUS}</font>
                </h2></td>
        </tr>
        <tr>
            <td><br />
            <b><font color="#0B610B">構建信息</font></b>
            <hr size="2" width="100%" align="center" /></td>
        </tr>
        <tr>
            <td>
                <ul>
                    <li>項目名稱&nbsp;:&nbsp;${PROJECT_NAME}</li>
                    <li>構建編號&nbsp;:&nbsp;第${BUILD_NUMBER}次構建</li>
                    <li>SVN&nbsp;版本:&nbsp;${SVN_REVISION}</li>
                    <li>觸發緣由:&nbsp;${CAUSE}</li>
                    <li>構建日誌:&nbsp;<a href="${BUILD_URL}console">${BUILD_URL}console</a></li>
                    <li>構建&nbsp;&nbsp;Url&nbsp;:&nbsp;<a href="${BUILD_URL}">${BUILD_URL}</a></li>
                    <li>工做目錄&nbsp;:&nbsp;<a href="${PROJECT_URL}ws">${PROJECT_URL}ws</a></li>
                    <li>項目&nbsp;&nbsp;Url&nbsp;:&nbsp;<a href="${PROJECT_URL}">${PROJECT_URL}</a></li>
                </ul>
            </td>
        </tr>
        <tr>
      <td><a href="http://www.yxccc.com" target="_blank">電動叉車</a></td> <td><b><font color="#0B610B">Changes Since Last Successful Build:</font></b> <hr size="2" width="100%" align="center" /></td> </tr> <tr> <td> <ul> <li>歷史變動記錄 : <a href="${PROJECT_URL}changes">${PROJECT_URL}changes</a></li> </ul> ${CHANGES_SINCE_LAST_SUCCESS,reverse=true, format="Changes for Build #%n:<br />%c<br />",showPaths=true,changesFormat="<pre>[%a]<br />%m</pre>",pathFormat="&nbsp;&nbsp;&nbsp;&nbsp;%p"} </td> </tr> <tr> <td><b>Failed Test Results</b> <hr size="2" width="100%" align="center" /></td> </tr> <tr> <td><pre style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif">$FAILED_TESTS</pre> <br /></td> </tr> <tr> <td><b><font color="#0B610B">構建日誌 (最後 100行):</font></b> <hr size="2" width="100%" align="center" /></td> </tr> <!-- <tr> <td>Test Logs (if test has ran): <a href="${PROJECT_URL}ws/TestResult/archive_logs/Log-Build-${BUILD_NUMBER}.zip">${PROJECT_URL}/ws/TestResult/archive_logs/Log-Build-${BUILD_NUMBER}.zip</a> <br /> <br /> </td> </tr> --> <tr> <td><textarea cols="80" rows="30" readonly="readonly" style="font-family: Courier New">${BUILD_LOG, maxLines=100}</textarea> </td> </tr> </table> </body> </html>

4.5 進行構建測試

  • 觸發構建

選擇對應的branch與端口
圖片

  • 查看consle log
    • pylint檢查

圖片

  • nosetests單元測試及代碼覆蓋率

圖片
圖片

  • 目標服務器查看具體項目

圖片

4.6 查看構結果

  • 總覽查看

圖片

  • 查看代碼覆蓋率

圖片
查看具體不合規代碼
在源代碼分析結束後面,會有一系列的報告,每一個報告關注於項目的某些方面,如每種類別的 message 的數目,模塊的依賴關係等等。具體來講,報告中會包含以下的方面:
MESSAGE_TYPE 有以下幾種:

(C) convention 慣例。違反了編碼風格標準
(R) refactor 重構。寫得很是糟糕的代碼。
(W) warning 警告。某些 Python 特定的問題。
(E) error 錯誤。極可能是代碼中的錯誤。
(F) fatal 致命錯誤。阻止 Pylint 進一步運行的錯誤。

圖片

  • 查看郵件

圖片

5、流水線部署

5.1 pipeline基礎概念

  • pipeline是什麼

pipeline爲運行於Jenkins上的工做流框架,project中的配置信息以steps的方式放在一個腳本里將本來獨立運行於單個或者多個節點的任務鏈接起來,實現單個任務難以完成的複雜流程編排與可視化。

  • 基礎概念
    • Stage: 階段:告訴Jenkins作什麼,一個Pipeline能夠劃分爲若干個Stage,每一個Stage表明一組操做。注意,Stage是一個邏輯分組的概念,能夠跨多個Node。
    • Node: 節點:告訴Jenkins在job在哪運行一個Node就是一個Jenkins節點,或者是Master,或者是slave,是執行Step的具體運行期環境。
    • Step: 步驟:具體細化到每一步構建操做,Step是最基本的操做單元,小到建立一個目錄,大到構建一個Docker鏡像,由各種Jenkins Plugin提供。
  • 語發工具

Pipeline提供了一組可擴展的工具,經過Pipeline Domain Specific Language(DSL)syntax能夠達到Pipeline as Code(Jenkinsfile存儲在項目的源代碼庫)的目的。

5.2 pipeline的特性

基於 Jenkins Pipeline,用戶能夠在一個 JenkinsFile 中快速實現一個項目的從構建、測試以到發佈的完整流程,而且能夠保存這個流水線的定義。

  • 代碼:Pipeline以代碼的形式實現,一般被檢入源代碼控制,使團隊可以編輯、審查和迭代其CD流程。
  • 可持續性:Jenklins重啓或者中斷後都不會影響Pipeline Job。
  • 停頓:Pipeline能夠選擇中止並等待任工輸入或批准,而後再繼續Pipeline運行。
  • 多功能:Pipeline支持現實世界的複雜CD要求,包括fork/join子進程,循環和並行執行工做的能力
  • 可擴展:Pipeline插件支持其DSL的自定義擴展以及與其餘插件集成的多個選項。

5.3 pipeline語法

  • 聲明式
pipeline {
/* insert Declarative Pipeline here */
}

在聲明式流水線中有效的基本語句和表達式遵循與 Groovy的語法一樣的規則, 有如下例外:

  • 流水線頂層必須是一個 block, 特別地: pipeline { }
  • 沒有分號做爲語句分隔符,,每條語句都必須在本身的行上。
  • 塊只能由 節段指令步驟, 或賦值語句組成。 *屬性引用語句被視爲無參方法調用。 例如, input被視爲 input()

示例:

Jenkinsfile (Declarative Pipeline)
pipeline {
    agent none 
    stages {
        stage('Example Build') {
            agent { docker 'maven:3-alpine' } 
            steps {
                echo 'Hello, Maven'
                sh 'mvn --version'
            }
        }
        stage('Example Test') {
            agent { docker 'openjdk:8-jre' } 
            steps {
                echo 'Hello, JDK'
                sh 'java -version'
            }
        }
    }
}
  • 腳本式

腳本化流水線, 與[declarative-pipeline]同樣的是, 是創建在底層流水線的子系統上的與聲明式不一樣的是, 腳本化流水線其實是由 Groovy構建的通用 DSL [2]。 Groovy 語言提供的大部分功能均可以用於腳本化流水線的用戶。這意味着它是一個很是有表現力和靈活的工具,能夠經過它編寫持續交付流水線。

示例:

node('master') {     //master節點運行,如下stage也可指定節點
    stage 'Prepare'  //清空發佈目錄
        bat '''if exist D:\\publish\\LoginServiceCore (rd/s/q D:\\publish\\LoginServiceCore)
               if exist C:\\Users\\Administrator\\.nuget (rd/s/q C:\\Users\\Administrator\\.nuget)
               exit'''

    //拉取git代碼倉庫
    stage 'Checkout'
        checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], 
       submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'c6d98bbd-5cfb-4e26-aa56-f70b054b350d', 
            url: 'http://xxx/xxx/xxx']]])
   
    //構建
    stage 'Build'
        bat '''cd "D:\\Program Files (x86)\\Jenkins\\workspace\\LoginServiceCore\\LoginApi.Hosting.Web"
            dotnet restore
            dotnet build
            dotnet publish --configuration Release --output D:\\publish\\LoginServiceCore'''
    
    //部署
    stage 'Deploy'
        bat '''
        cd D:\\PipelineScript\\LoginServiceCore
        python LoginServiceCore.py
        '''

    //自動化測試(python代碼實現)    
    stage 'Test'
        bat'''
        cd D:\\PipelineScript\\LoginServiceCore
        python LoginServiceCoreApitest.py
        '''   
}

5.4 示例

在此將上面的項目利用pipeline進行發佈

  • 建立自由風格軟件項目

New任務->流水線,填寫任務描述。
圖片

圖片

  • Pipeline

若是對Pipeline語法不熟悉,能夠利用工具生成
圖片
在此的pipeline

pipeline {
  agent any
  parameters {
    gitParameter branchFilter: 'origin/(.*)', defaultValue: 'master', name: 'BRANCH', type: 'PT_BRANCH'
  }
  stages {
    stage('checkout src code') {
        steps {
            echo "checkout src code"
            git branch: "${params.BRANCH}",'http://123.206.xxx.xxx/xuel/go2cloud_platform.git'
        }
    }
    stage('exec shell'){
        steps{
            echo "pylint,Unit test"
            sh '''# jenkins 服務器項目workspace目錄
                base_dir=/root/.jenkins/workspace/
                # 項目名稱
                project=go2cloud_platform
                # 項目環境python環境
                project_env=go2cloud_platform_pipeline
                # 切換python環境
                source /data/miniconda3/bin/activate ${project_env}
                $(which python) -m pip install mock nose coverage pylint
                # 更新python環境
                echo "++++++更新Python環境+++"
                
                if [ -f ${base_dir}${project}requirements/requirements.txt ];then
                    $(which python) -m pip install -r ${base_dir}${project}/requirements/requirements.txt && echo 0 || echo 0
                fi
                
                # 代碼檢查/單元測試/代碼測試覆蓋率
                echo "++代碼檢查++"
                cd ${base_dir}
                # 生成pylint.xml
                $(which pylint) -f parseable --disable=C0103,E0401,C0302 $(find ${project}/* -name *.py) >${base_dir}${project}_pylint.xml || echo 0
                
                echo "++單元測試++"
                # 生成nosetests.xml
                #$(which nosetests) --with-xunit --all-modules --traverse-namespace --with-coverage --cover-package=go2cloud-api-deploy-prod --cover-inclusive || echo 0
                $(which nosetests) --with-xunit --all-modules --traverse-namespace --with-coverage --py3where=${project} --cover-package=${project} --cover-inclusive || echo 0
                echo "++代碼覆蓋率+++"
                # 生成coverage.xml
                python -m coverage xml --include=${project_env}* || echo 0'''

        }
    }
    stage("deploy") {
        steps {
            echo "send file"
            sshPublisher(publishers: [sshPublisherDesc(configName: 'go2cloud_platform_host', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''base_dir=/devops_pipeline
                project_src=go2cloud_platform
                project_env=/data/miniconda3/envs/go2cloud_platform_pipeline/bin/python
                echo "+++++++更新部署服務器python環境++++++++++"
                if [ -f ${base_dir}/requirements/requirements.txt ];then
                    ${project_env} -m pip install -r ${base_dir}/requirements/requirements.txt && echo 0 || echo 0
                fi
                echo -e "\\033[32m 啓動服務腳本 \\033[0m"
                $(which bash) ${base_dir}/run_server.sh ${project_env} ${base_dir}/apps/manage.py ${port}
                ''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '/devops_pipeline', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '**/**')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
            }
        }
    }
}
  • 運行構建

圖片

  • 查看結果
    圖片描述
    圖片描述

6、注意要點

  • 在部署前必定梳理好流程,須要理解哪一步在目標服務器還上在Jenkins服務器上執行
  • 須要對應好目錄及Python虛擬環境,避免環境污染

7、反思

  • 在此利用來Conda虛擬環境管理,來在同一個服務器上解決環境不一致行,也能夠利用Docker來解決
  • 利用Pipeline項目發佈可視化,明確階段,方便stage查看與排錯,Jenkinsfile可放在git倉庫進行
相關文章
相關標籤/搜索