發布系統有那麼難么?
- 2019 年 10 月 6 日
- 筆記
必要性
如果一個上點規模的公司,技術團隊有什麼值得一做的系統,那麼發布系統算一個。 jenkins
用的好好的,為什麼要自己搞呢?總結下來,有下面幾點原因:
1) 每個公司的流程和技術棧都是不一樣的,生搬硬套,就像便秘一樣不順暢。 2) 發布系統技術開發成本不高,很容易搞。你要是覺得難搞,那一定是卡在複雜的公司人員環境和流程上。 3) jenkins
這樣的工具要想做個增強功能,不比自己開發簡單。比如加個審批環節,做個表單嵌入什麼的。
一句話,自己搞個套餐不比改造現成的難。
概要
開發一個發布系統是很簡單的,雖然需要全棧的知識(前端、後台、腳本)。有多簡單呢?我們提供了兩個人力,只花了10個工作日就全部完成了。看完本文,你要是覺得不簡單,那就是我們太牛掰了(囧),畢竟也是見識過七八個高開去做同樣事情的豪華陣容。
流程上是幫不了你什麼忙了,但在技術上,我將奉上個人覺得很不錯的一套實現。如果你的架構是基於SpringCloud
的,你會發現很貼心。發布系統主要有以下功能:構建模組、部署模組、許可權管理模組、審計模組
構建模組
用戶登錄系統後,會看到所有擁有許可權的項目列表。點點擊前往,則進入構建頁面。

構建模組其實是非常簡單的。不論你是用maven
,還是gradle
,再或者cnpm
,g++
等,都是通過傳入一堆參數到腳本中執行。在這裡推薦使用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 -15
和kill -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親自給你來電了,這並不見得是一件好事。