發布系統有那麼難么?

  • 2019 年 10 月 6 日
  • 筆記

必要性

如果一個上點規模的公司,技術團隊有什麼值得一做的系統,那麼發布系統算一個。 jenkins用的好好的,為什麼要自己搞呢?總結下來,有下面幾點原因:

1) 每個公司的流程和技術棧都是不一樣的,生搬硬套,就像便秘一樣不順暢。 2) 發布系統技術開發成本不高,很容易搞。你要是覺得難搞,那一定是卡在複雜的公司人員環境和流程上。 3) jenkins這樣的工具要想做個增強功能,不比自己開發簡單。比如加個審批環節,做個表單嵌入什麼的。

一句話,自己搞個套餐不比改造現成的難。

概要

開發一個發布系統是很簡單的,雖然需要全棧的知識(前端、後台、腳本)。有多簡單呢?我們提供了兩個人力,只花了10個工作日就全部完成了。看完本文,你要是覺得不簡單,那就是我們太牛掰了(囧),畢竟也是見識過七八個高開去做同樣事情的豪華陣容。

流程上是幫不了你什麼忙了,但在技術上,我將奉上個人覺得很不錯的一套實現。如果你的架構是基於SpringCloud的,你會發現很貼心。發布系統主要有以下功能:構建模組、部署模組、許可權管理模組、審計模組

構建模組

用戶登錄系統後,會看到所有擁有許可權的項目列表。點點擊前往,則進入構建頁面。

構建模組其實是非常簡單的。不論你是用maven,還是gradle,再或者cnpmg++等,都是通過傳入一堆參數到腳本中執行。在這裡推薦使用python腳本進行更多控制。

構建者可以選擇任何一次提交進行構建。構建成功後,會自動給提交打tag(也可以自定義tag)。同一個項目不允許同時打包。構建失敗,可以重新進行構建,構建者會看到實時滾動的日誌。這裡有兩個技術點:

1) 如何獲取git的提交記錄並進行切換? 2) 如何顯示滾動日誌?

獲取gitlab的提交記錄

拿gitlab來說(因為用的最多)。加入gitlab的maven即可使用。

<dependency>     <groupId>org.gitlab</groupId>     <artifactId>java-gitlab-api</artifactId>     <version>4.1.0</version></dependency>

獲取最近提交記錄。

api.getAllCommits(projectId,page, branch)

打tag

api.addTag(projectId, tagName, hash, tagTitle, tagContent);

滾動日誌實現

見本公眾號文章《滾動日誌的實現》

部署模組

很多發布系統讓人很不爽的一點,就是不支援單台或者多台發布,不方便而且風險大。

部署頁面只顯示已經打包成功的記錄,按照提交時間倒序顯示。支援重新部署,不管是上次部署成功還是失敗。部署介面如下:

點擊部署按鈕,即可顯示部署機器列表,可以選擇一個或者多個進行部署。

部署記錄都會按照打包記錄進行分組,顯示在日誌列中。同一次部署,如果有一台部署失敗,則默認部署整體部署失敗,可以從部署日誌判斷當前的部署狀態。

可以看到系統其實是沒有回滾的概念的,只有部署哪個版本的概念。一個服務可能有上百台機器,如何更優雅的顯示多版本共存的關係,有條件的團隊是不會放過這個改進的。

SpringCloud的部署過程

許可權管理

github上有很多開源的實現,隨便搬弄一套集成即可。我們主要談一下許可權理念。

許可權設計有兩個要點。第一不能阻礙研發的開發效率,第二要嚴控線上的安全。所以線上和非線上環境是分開設計的。

1) 一個系統的用戶,要麼是超級管理員(就是神馬都能幹的那總);要麼是帶有線上許可權標識的用戶;再就是普通用戶了。 2) 一個用戶,要麼是某個項目的成員,要麼不是

許可權圖如下:

操作都會被記錄進操作歷史,並且發送郵件(或者其他hook):

1) 修改項目,發送給項目成員所有人 2) 構建項目,發送給構建者構建結果 3) 部署項目,發送給項目成員所有人

所有的操作記錄,在項目中都可查。

腳本

發布系統的web端,不過是套層皮囊。真正去執行的,還是我們的腳本。

使用python腳本進行構建和發布,是非常方便的。有些java開發人員對腳本不是很熟,我這裡挑比較重要的點說明一下。

執行過程顯示

建議使用logging模組控制。效果見下圖。

import loggingdef setup_logging():     root = logging.getLogger()     root.setLevel(logging.DEBUG)     ch = logging.StreamHandler(sys.stdout)     ch.setLevel(logging.DEBUG)     formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')     ch.setFormatter(formatter)     root.addHandler(ch)  

腳本異常記錄

默認python執行異常是不輸出到stdout的,我們需要強行轉換一下。

import sysimport tracebacktry:     setup_logging()    #...except Exception as e:     exc_type, exc_value, exc_traceback_obj = sys.exc_info()     traceback.print_exception(exc_type, exc_value, exc_traceback_obj, limit=2, file=sys.stdout)

遠程執行

發布系統的機器應該都做了免密登錄。通過 ssh -t "su - sth" 可以切換到任何一個用戶在遠端執行程式。 但實踐證明,通過python的subprocess模組執行存在諸多問題。所以我們才用了paramiko庫進行了遠程調用。

s = paramiko.SSHClient()  s.load_system_host_keys()  s.connect(srv, 22 )  (stdin, stdout, stderr) = s.exec_command(cmd)  last_line = ""for line in stdout.readlines():     last_line = line     print( line)     s.close()

安全的殺死進程

大家都應該知道kill -15kill -9的區別。以下腳本讓程式等待10秒,然後使用kill -9殺掉她。

注意:以下腳本有巨坑~,一定要傳參

n=0while [[ $n  -lt  10 ]]do     let "n++"     ex=`ps aux|grep /${flag}|grep -v grep | grep -v sc. | awk '{print $2}' `    echo $ex     if [[ $ex == "" ]]    then         echo "program not exist"         break     else         echo "send kill -15 to below:"         echo $ex         ps aux|grep /${flag}|grep -v grep | grep -v sc. | awk '{print $2}' |  xargs kill -15         sleep 1     fi     if [[ $n  -eq 10 ]]    then         # after 10s , try to send kill -9         ps aux|grep /${flag}|grep -v grep | grep -v sc. | awk '{print $2}' |  xargs kill -9     fidone##start jarecho "start jar"nohup  -jar $jar  >/dev/null 2>&1 &echo "restart ${flag} !!!"sleep 2ex=`ps aux|grep /${flag}|grep -v grep | grep -v sc. | awk '{print $2}' `if [[ $ex == "" ]]then     echo "Fail"else     echo "new PID is "     echo $ex     echo "OK"fiexit

前端

vue干這個大材小用了。我們選擇了後台都熟悉的bootstrap,配上一個比較古老好看的AdminLTE框架。

但這麼多頁面寫起來也是非常浪費時間的,所以我們也集成了ejs模版引擎。

對技術團隊的建議

1) 要信得過自己團隊,信得過自己。如果公司環境複雜。少開會去討論,少扯皮。先閉關鎖國再改革開放。先閉門造車,做出個東東來,再讓各位大爺們品頭論足,進行修改。畢竟也花不了多長時間,原型驅動再好不過了,你又不是一次性做個百分項目。 2) 設計時一定要考慮項目類型的多樣性和分散式。別等著需求來了,量來了,把你連根拔起。 3) 自動化雖然好,也要準備好應急的手動化方式。如果CTO親自給你來電了,這並不見得是一件好事。