JB的測試之旅-利用jenkins達到提tag自動打包

jb發表於2018-12-21

最近都好忙,需求太多了,幹不完,加上最近作息時間的調整,都沒時間寫部落格了,趁著這兩天稍微有點時間,想擼一發;

image.png-6.6kB

其實在寫的部落格有好幾篇,但都是陸陸續續的,本來想繼續寫之前的,突然想起半個月前,同學提出,能不能在開發提tag時自動打包

之前釋出過一篇專案流程規範,目前的專案都在遵守這個規範,而研發每次有改動過程式碼的時候,都會提測,而提測就相當於提tag,這個tag的概念不說了,就是基於當前產品分支重新拉的分支,tag的格式如下圖;

image.png-26.2kB

而測試拿到這個tag後,就需要去到jenkins打包,要輸入一堆引數,然後等待打包結果;

image.png-73kB

有時候,研發勤奮點,一天提多次tag,測試就需要多次去打包,測試同學覺得太麻煩了,因此就提出一個想法,能不能在開發提tag時自動打包

能,必須能,沒有做不到的事情;

image.png-99.3kB

分解

做事之前,要先想想要做什麼?

  • 搭建jenkins環境及安裝對應外掛
  • jenkins打通到gitlab
  • 觸發任務

想了下,大致就這3步,那就來一起做吧;

搭建jenkins

為了測試,jb就用了自己的阿里雲伺服器從頭開始搭建一遍,具體搭建過程不說明,之前寫的一篇文章有提及到,直接對照操作就好;

安裝jenkins

步驟 說明
安裝Java sudo yum install java
安裝jenkins yum install jenkins
啟動jenkins service jenkins start
安裝外掛

注意

修改jenkins埠 jenkins預設埠是8080,可能會跟別的軟體衝突,因此建議修改下埠;

進入jenkins配置檔案

vi /etc/sysconfig/jenkins
複製程式碼

開啟後,找到JENKINS_USERJENKINS_PORT這兩項進行修改即可;

image.png-55.7kB

修改成root跟具體埠儲存退出即可;

這時候,直接輸入主機IP+剛設定的埠就好啦;

阿里雲開放埠許可權 上面訪問ip+埠,有可能打不開連結,因為阿里雲對埠做了限制,因此需要開放埠;

登入阿里雲,首頁右上找到控制檯;

image.png-52.4kB

找到雲伺服器ECS

image.png-24.9kB

點選例項,找到該例項的安裝組配置

image.png-103.5kB

點選規則說明,新增即可;

image.png-14kB

如果是按照上面的文件來,安裝完外掛,建立完使用者後,就應該進入到首頁,會顯示如下內容,至此,jenkins環境搭建就完啦~!

image.png-11.1kB

對了,jenkins的目錄在這裡:

/var/lib/jenkins
複製程式碼

jenkins打通到gitlab

公司的程式碼倉庫是用gitlab,因某種原因,就開放公網啦,那jb就在gitlab新建一個jbtest專案;

image.png-51.5kB

如果有小夥伴說,沒有gitlab怎麼辦?沒關係,可以用github代替,至於github相關的配置,可以檢視該文章:git介紹及GitHub配置教程

jenkins安裝gitlab

既然確定是要用jenkins+gitlab,那就先在jenkins上安裝下gitlab外掛吧;

點選jenkins首頁,有個系統管理按鈕;

image.png-48kB

點選後右側會顯示內容,下滑,點選外掛管理;

image.png-144.7kB

進入到外掛管理介面,就看到updates等4個欄目,那點選Available,右側輸入gitlab,然後找到GitLabGitLab Hook,勾選,點選底部的install即可;

image.png-42.4kB
image.png-140.1kB

test專案有了,gitlab的外掛也安裝了,那我們就在jenkins新建一個job吧,輸入了專案名稱,就進入到設定項;

image.png-55.9kB

丟棄舊的構建

這裡的丟棄舊的構建,可選,但是習慣選擇7天,,最大保留300個; 原因是,jenkins每次構建都會生成一個歷史構建記錄及對應的產物,如果公司有100個產品,每天自動打包10次,一天就有1000個產物,伺服器磁碟空間是個問題,因此設定個7天,設定個最大數,定時刪除即可;

  • 持構建的天數:根據你所填寫的天數來儲存構建記錄
  • 保持構建的最大個數:有幾條構建記錄就儲存幾條
  • 釋出包保留天數:釋出的產物儲存的天數
  • 釋出包最大保留#個構建:釋出了多少個產物就儲存多少個

上面的丟棄舊構建不是要點,繼續;

繼續下滑,會發現Source Code Management,中文是原始碼管理,而我們的程式碼是存放到gitlab的,因此就選擇git了,點選後如下圖展開;

image.png-76.5kB

這裡有小夥伴可能有疑問,既然是放gitlab,為啥不是選擇gitlab,而是選擇git?

git、github、gitlab

這裡就花點時間說明下git、github、gitlab;

git是一款免費的、開源的分散式版本管理控制系統(工具);

gitHub是一個面向開源及私有軟體專案的託管平臺,
只支援git作為唯一的版本庫格式進行託管;

gitlab,是一款基於Git的專案管理軟體,擁有GitHub擁有的一切,但它還具備讓團隊對它們的repositories進行控制的功能;  
複製程式碼

希望經過這麼一說,能讓你稍微清楚下;

原始碼管理

點選git,把專案的地址copy到這裡;

image.png-21.6kB
image.png-47.4kB

沒許可權是肯定的,那我們就點選add建立賬戶唄,選擇jenkins;

image.png-37.9kB

輸入gitlab/github的賬號密碼,但是輸入完後發現還是報錯;

image.png-31.1kB

彆著急,那去伺服器上看看,git安裝了嗎?

image.png-13.7kB

沒安裝,那就安裝羅;

yum install git
複製程式碼

等待安裝完,再輸入git --versionwhere is git來看版本及安裝位置;

image.png-28.1kB

安裝完後,就順便試試能不能clone專案吧;

image.png-54.8kB

不出所料,果然不行,因為還要配置金鑰啊;

$ ssh-keygen -t rsa -C "your_email@youremail.com"
//注意,雙引號裡面是你的郵箱。填你註冊github的郵箱就行了。按enter執行。
複製程式碼

一路回車就行,密碼一般不用設定~

image.png-95.7kB

上圖紅框裡的就是地址,cd過去;

id_rsaid_rsa.pub兩個檔案,這兩個就是SSH Key的祕鑰對, id_rsa是私鑰,不能洩露, id_rsa.pub是公鑰,可以公開;

配置ssh 在git bash 執行 cat id_rsa.pub,把輸出的內容copy出來,然後開啟gitlab/github網站,點選右上角自己的圖示,點選Setting->ssh key 頁面,點選add ssh key~

image.png-66.5kB

這裡還有一個特殊的問題: 因開放公網的原因,每次git clone的下載程式碼的時候是連線的https://而不是git@git (ssh)的形式,導致每次pull/push等遠端操作的時候,都需要輸入賬號密碼,非常麻煩;

解決辦法:

在專案根目錄輸入:
git config --global credential.helper store
git pull/git push(第一次要再輸入一次,以後就不用)
複製程式碼

會在使用者目錄下生成檔案.git-credential記錄使用者名稱密碼的資訊;

那回到jenkins,開啟測試job的設定,再看是否正常;

image.png-46.1kB

發現已經好了,沒問題,當然也會有小夥伴依然有問題,那就需要這樣做;

  • 開啟jenkins首頁
  • 點選系統管理
  • 全域性工具設定
  • 修改git地址

image.png-73.4kB

開啟後,滑動到git欄,如果有問題的話,可能會看到錯誤提示:

There's no such executable git in PATH: /sbin, /usr/sbin, /bin, /usr/bin.
複製程式碼

image.png-69kB

在出錯的地方填入:

"whereis git"的地址 + "/bin/git" 
(如上面"whereis git"的地址為"/usr/bin/git",則應該填入 "/usr/bin/git/bin/git") 並儲存
複製程式碼

然後再回到job的原始碼管理中新增git地址,就會好了;

image.png-32kB

既然可以連線到gitlab了,那我們就直接下滑到build,選擇add build step,因為是伺服器,所以直接選擇執行shell,如果是Windows系統,也可以選擇執行Windows批處理命令

image.png-18.1kB

因jb的專案裡面有個test.py檔案,那專案clone下來肯定是想執行這個檔案啦,因此內容就是輸入python test.py,然後點選儲存;

回到job的首頁,點選立即構建;

test.py的作用是向釘釘發一條訊息,因此成功的話,釘釘就會收到一條資訊;

image.png-138.4kB

到這裡,說明jenkins是對接到gitlab是通的了;

觸發任務

看看標題,目的是:提tag自動打包

那先看看,tag是怎麼生成的? 具體看git命令列第11章,這裡有說明;

這裡就不bb了,直接貼:

 git tag -a '3.4.0/1811111212' -m 'jbtest tag'
 git push --tags
複製程式碼
  • 建立一個含附註型別的標籤非常簡單,用-a指定標籤名字即可;
  • -m 選項則指定了對應的標籤說明;
  • git push --tags就是一次推送所有本地新增的標籤;

然後再輸入git tag檢視當前tag資訊;

image.png-15.2kB

既然知道tag是怎麼生成的,那就看看有沒有辦法知道tag被建立了?

第一時間當然是想到鉤子-hook啦;

image.png-7.7kB

來看下官方解釋:

鉤子(hooks)是一些在"$GIT-DIR/hooks"目錄的指令碼, 在被特定的事件(certain points)觸發後被呼叫。
當"git init"命令被呼叫後, 一些非常有用的示例鉤子檔案(hooks)被拷到新倉庫的hooks目錄中; 但是在預設情況下這些鉤子(hooks)是不生效的。 把這些鉤子檔案(hooks)的".sample"檔名字尾去掉就可以使它們生效了。
複製程式碼

簡單地來說有點類似回撥,就是特定事情完成後回撥執行事件

那這鉤子,在jenkinsgitlab上怎麼搞?gitlab支援嗎?

gitlab鉤子

開啟gitlab對應的專案,點選settings-Integrations(整合)

image.png-34.3kB

開啟後,就是hook的內容啦,簡單瞄下,發現有個選項叫Tag push events,看了下描述,好像蠻符合我們的要求,介面顯示,需要輸入URLSecret Token,這兩玩意怎麼來?

image.png-84.1kB

jenkins獲取URL跟Secret Token

開啟jenkins,開啟對應的job,點選設定,查詢Build when a change is pushed to GitLab,gitlab上的URL就是該處的連結,如下圖紅框,複製過去即可;

同時點選右下的Advanced(高階)按鈕;

image.png-107.6kB

然後點選右下的Generate按鈕;

image.png-89kB

然後會生成一串Secret token,複製到gitlab那;

image.png-11.9kB

那我們再試試,直接在伺服器打個tag且push看釘釘會不會收到資訊?

image.png-123.6kB

嘖嘖嘖,沒問題啊,搞定啦,另外看了下hook,支援的內容蠻多的,tag pushpush eventmerge requests等;

image.png-39.5kB

gitlab沒有setting許可權

上面說的條條是道,但實際現實是殘忍的,開啟實際的專案,會發現自己沒有setting許可權:

image.png-34.2kB

遇到這種情況,不是說不能處理,只能看門檻變高了,有2個方法:

嘗試跟對應的負責人溝通,看能否給一個帶master許可權的賬號;

如果有足夠的資料讓老大認可這個功能,就會給一個帶master許可權的賬號,就可以為所欲為啦;

但如果給的理由不讓人信服或者老大覺得沒鳥用,但依然要做這個功能,那隻能換種方式了,但至少要求依然有倉庫的可讀許可權; 換個角度,連倉庫許可權都沒有,就別想做了。。

image.png-30.5kB

大致的思路就是,利用定時功能,不斷檢測該專案的tag,當發現原tag不是最新tag時,就認為有最新tag,此時再通知jenkins處理;

如果是這樣,那jenkins上的選項會有點不同,構建的時候就要選擇指令碼呼叫,指定TOKEN_NAME,然後可以通過連線JENKINS_URL/job/JOBNAME/build?token=TOKEN_NAME來啟動build

image.png-87.1kB

對了,做這個功能,還有兩種形式:

  • 檢測指令碼放到產品專案根目錄下;
  • 檢測指令碼單獨一個專案;

第一種方式的好處時,省事,但是一般不允許,因為產品專案一般存放跟該產品相關的內容,而這個指令碼典型是通用的,後面如果多個產品想複用就麻煩了

第二種方式的就是獨立專案,可複用,把檢測專案跟產品專案放到同一臺jenkins機器上,這樣它們都在jenkins/workspace目錄下,通過傳參的方式切換不同目錄即可;

而jb採用的是第二種方式;

檢測指令碼

這個指令碼的目的是什麼? 就是定時檢測tag是不是最新的,如果不是,則執行相應操作;

那難度就在於,怎麼判斷tag是不是最新的? Google下,有例子:

git describe --tags `git rev-list --tags --max-count=1`
複製程式碼

嘗試下,好像沒問題,那就暫且相信吧;

image.png-21.8kB

好奇問下,這命令到底是啥意思?

其實這是兩條git命令;

git describe --tags
git rev-list --tags --max-count=1
複製程式碼

git describe --tags 顯示當前離當前提交最近的tag,實際發現,帶不帶引數都一樣?

image.png-12.8kB

不加任何引數的情況下,git describe 只會列出帶有註釋的tag

實際,該命令是可以帶3個引數的,tagsalwaysdirty,這一般用的比較少,不展開說明;

git rev-list --tags --max-count=1

git-rev-list :按反向時間順序列出提交物件 意思就是把倒序方式展示最新提交內容;

image.png-13.6kB

max-count=1,也很好理解,只獲取1個,如果寫2,則獲取2個;

image.png-20.4kB

那命令實際就變成了這樣:

git describe 9c7246b2026cec052af6a3b6297995e494c7b398
複製程式碼

image.png-9.4kB

最終輸出的就是tag,有種奇淫怪招的感覺;

image.png-6.9kB

當然,不會用git的話,也有另外一種方式達到獲取最終一個tag的效果:

    #獲取tag資訊,轉list
    content = os.popen("git tag" ).read().split("\n")
    for i in content:
        if ( i != ''):
            tag_list.append(i)

    print(tag_list[-1])
複製程式碼

就是輸入git tag,獲取所有tag資訊,再重新存在一個list,只拿最後一個,也可以再做下判斷,只儲存最後一條,方式很多,類似,但這種效果不好是,假如一個專案經過多輪迭代,有幾千條tag,就需要時間了,因此還是推薦第一種;

為了模擬真實場景,就在跟目錄下新建一個指令碼,跟專案同一根目錄;

image.png-12.3kB

指令碼的內容如下:

# _*_ coding:utf-8 _*_
import re
import os
import sys
import json
import time
import requests
from apscheduler.schedulers.blocking import BlockingScheduler

tag = ""
scheduler = BlockingScheduler()
base_dir = os.getcwd()

def postDingDing(content):
    dingdingurl = ["https://oapi.dingtalk.com/robot/send?access_token=你的釘釘token"]
    headers = {'Content-Type': 'application/json'}
    String_textMsg = {
        "msgtype": "markdown",
        "markdown": {
            "title" : "有新tag啦",
            "text":   "新tag:  "+content
        }
    }
    requests.packages.urllib3.disable_warnings()
    String_textMsg = json.dumps(String_textMsg)
    for i in dingdingurl:
        requests.post(i, data=String_textMsg, headers=headers, verify=False)


def getProjectsName():
    if len(sys.argv) > 1:
        return sys.argv[1]
    else:
        print("沒有傳專案引數,請重新輸入,例子:python check_gittag.py jbtest")

def check_tag(dir):
    os.chdir(base_dir+"/"+dir)
    # 原型:git describe --tags 'git rev-list --tags --max-count=1'
    id = os.popen("git rev-list --tags --max-count=1").read()
    content = os.popen("git describe --tags "+ id).read()
    return content


def start_task():
    global tag
    projects_name = getProjectsName()
    last_tag = check_tag(projects_name)

    if (tag != last_tag):
        print("有新tag啦:  "+last_tag)
        postDingDing(last_tag)
        tag = last_tag
    # else:
    #     print("沒有新tag,繼續輪訓")

def get_time():
    """
    獲取當前時間
    """
    return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())


if __name__ == "__main__":
    print(get_time() + " 魔法儀式已啟動!")
    scheduler.add_job(start_task, "interval", seconds=6, id="check_tag")
    scheduler.start()
複製程式碼

因為是用來測試,因此jb是做了通知釘釘的功能,執行上面的程式碼,會發現有一個問題:push了新tag,沒有通知!!!

後來想了下,哦,是沒有,因為本地的程式碼還是舊的,比如研發本地push到倉庫,但是檢測指令碼是在伺服器A,那肯定是不會知道有程式碼更新啦;

image.png-14kB

解決方案,馬上想到,那push後就通知啊,結果想起,沒鉤子許可權。。

image.png-37.9kB

那沒問題,在上面的指令碼加多一個更新命令就好啦;

#更新遠端程式碼到本地倉庫
git fetch origin
複製程式碼

流程通了,那怎麼對接到jenkins?

再接jenkins

看回jenkins,按照要求試試,輸入以下命令:

 curl -i jenkins_url/job/job_name/build?token=yourtoken
複製程式碼

image.png-61.4kB

結果出現403.。。

image.png-91.1kB

兩種解決方案:

  • 使用其他外掛代替
  • jenkins全域性安全點選下圖紅框內容,jenkins首頁-系統管理-全域性安全設定;

image.png-65.5kB

jb選擇的是第二種方案,勾選後,再次執行上面的命令,沒問題,並且可以收到啦~

image.png-27.4kB
image.png-62.4kB

再看鉤子

其實故事到上面就完啦,但是呢,雖然知道hook怎麼用,但還是想了解更多,因此就有了這篇啦;

什麼是鉤子

Git Hooks就是在Git執行特定事件(如commit、push、receive等)後觸發執行的指令碼。

按照Git Hooks指令碼所在的位置可以分為兩類:

  • 本地Hooks,觸發事件如commit、merge等;
  • 服務端Hooks,觸發事件如receive等;

鉤子能做啥

基本上,用上鉤子都是定製化的指令碼程式,在實際工作中,基本鉤子是萬能的,如:

  • post-receive:每當有新的提交的時候就通知專案成員;
  • pre-commit:檢查每次的commit message是否符合編碼規範;
  • post-merge:當merge成功後,通知專案成員;

鉤子放哪裡

$project_name/.git/hooks
複製程式碼

image.png-81.1kB
.git/hooks/資料夾下。當你init一個倉庫的時候,下邊會有一些鉤子的例子,以.sample結尾。

當然也可以在這個目錄下自由定製hooks的功能,當觸發一些git行為的時候,就會自動觸發執行的hooks功能;

常見鉤子

本地鉤子:

名稱 功能
pre-commit 當執行commit動作時先執行此hook,可以用此hook做一些檢查,比如程式碼風格檢查,或者先跑測試;
prepare-commit-msg 當commit時需要輸入message前會觸發此hook,可以用此hook來定製自己的default message資訊;
commit-msg 當使用者輸入commitmessage後被觸發,可以用此hook校驗message的資訊,比如是否符合規定,有沒有cr等;
post-commit 在整個提交過程完成之後會被呼叫,可以用於傳送new commit通知;
post-checkout git checkout命令呼叫,可以用於為自己的專案設定合適的工作區,比如自動生成文件、移動一些大型二進位制檔案等,也可以用於檢查版本庫的有效性;
pre-rebase git rebase命令呼叫,可以用來拒絕所有的已經push的commits進行rebase操作;
post-merge git merge呼叫,在merge成功後執行,可以用於merge後的訊息通知;
post-receive 在成功推送後被呼叫,適合用於傳送通知;
pre-receive git push呼叫,向倉庫推送程式碼時會被執行;
update pre-receive之後被呼叫,分別被每個推送上來的引用分別呼叫;
pre-push 當push時,remote refs被更新,但是在所有的objects傳輸前被觸發;

服務端鉤子:

名稱 功能
pre-receive 當收到push動作之前會被執行;
update 收到push動作之前被執行,但是有可能被執行多次,每個branch一次;
post-receive 當push動作已經完成的時候會被觸發,可以用此hook來push notification等,比如發郵件,通知持續構建伺服器等;

自定義鉤子

如果想自定義鉤子怎麼辦? 比如現在新增一個jb鉤子,用於傳送common通知的;

#!/usr/bin/env python

import smtplib
from email.mime.text import MIMEText
from subprocess import check_output

# 獲得新提交的git log --stat輸出
log = check_output(['git', 'log', '-1', '--stat', 'HEAD'])

# 建立一個純文字的郵件內容
msg = MIMEText("Look, I'm actually doing some work:\n\n%s" % log)

msg['Subject'] = 'Git post-commit hook notification'
msg['From'] = 'mary@example.com'
msg['To'] = 'boss@example.com'

# 傳送資訊
SMTP_SERVER = 'smtp.example.com'
SMTP_PORT = 587

session = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
session.ehlo()
session.starttls()
session.ehlo()
session.login(msg['From'], 'secretPassword')

session.sendmail(msg['From'], msg['To'], msg.as_string())
session.quit()
複製程式碼

當然,記得給許可權給你鉤子,chmod

小結

好啦,本章就到此接觸啦,基礎內容有點,但是不多,都是環境配置,本章主要講述以下內容:

  • 搭建jenkins
  • git配置
  • jenkins打通git
  • gitlab hook
  • 檢測指令碼
  • jenkins通過url觸發任務
  • git tag命令部分講解

大致是這樣,還有些細節,差不多就的啦;

好啦,如果有什麼疑問,歡迎一起溝通;

最後,謝謝大家~

1-140R3154U8.jpg-9kB

相關文章