前言
在上篇文章中我們主要講解了Jenkins的頁面與路由,在本章中我們要講解下Jenkins的資料持久化機制。在Jenkins中資料的持久化是通過檔案進行儲存的,大家平時使用Hibernate進行持久化的時候,我們只需要關心哪些地方是需要儲存的,哪些位置是不需要儲存的,並且在不需要儲存的位置新增transient
關鍵字即可,持久化的框架會自動幫我做好Java Object與資料庫儲存之間的序列化與反序列化的過程,而在Jenkins中由於資料的儲存都是通過檔案的方式進行儲存的,有必要讓大家瞭解下一個Jenkins是如何將資料進行組織與儲存的。
從JENKINS_HOME講起
JENKINS_HOME
是Jenkins啟動的時候會識別的一個環境變數,這個環境變數的作用是設定Jenkins的持久化根目錄,所有的Jenkins持久化檔案會以此目錄為根進行建立與儲存,而Jenkins中任務、構建、賬戶等資訊都會以檔案的方式儲存,因此這個檔案目錄會在Jenkins的使用過程中容量快速膨脹,對於需要維護Jenkins的同學建議單獨掛一塊盤。
在JENKINS_HOME
這個目錄下的結構大致如下
可以發現JENKINS_HOME
下有兩種命名的規則,一種是標準的命名,例如config.xml或者nodes的資料夾;還有一種類似類名的檔名,例如hudson.model.UpdateCenter.xml這種的檔案。這兩種檔案的命名可以推測出一種是Jenkins內部實現的一些儲存,而另一種是Jenkins提供出來的通用檔案儲存機制。那麼我們開啟一個檔案來看下儲存的內容是什麼樣子的,比如hudson.model.UpdateCenter.xml,裡面的內容如下
檔案是以標準的XML的格式進行持久化的,熟悉JAVA序列化與反序列化的同學已經可以猜測到,這個檔案應該是對應著Jenkins中的一個物件,Jenkins通過將一個Java物件序列化和反序列化來進行儲存。對於上面兩種不同命名方式的檔案,可以發現檔案內容都是XML格式的Java物件序列化格式,唯一的區別在於儲存路徑上會有所不同,這是為什麼呢。
標準命名的一些路徑和檔案大部分是Jenkins Master的一些實現,比如job、nodes等等,是Jenkins Master直接使用的一些內部實現的儲存,因為這些儲存的資訊通常會有特殊的意義或者行為,比如任務或者節點,如果儲存在單檔案中會造成難以查詢、隔離等等的問題,因此Jenkins對於一些物件的儲存進行了優化。
通用格式的儲存例如com.aliyun.www.cos.DeployBuilder.xml
則大部分是外掛的儲存檔案,開發過Jenkins外掛的開發者可能知道,在Jenkins的外掛中並沒有讀取配置檔案的動作,只有一個save方法,開發者需要進行配置儲存的時候,只需要呼叫父類中繼承下來的save方法即可。而這個save的方法的實現會自動以上面的這種類名為檔名的方式儲存在JENKINS_HOME目錄下,因此,這也就是為什麼不建議大家直接儲存配置的祕鑰到自己的外掛中,而是需要呼叫credentials外掛來實現,因為Jenkins在預設儲存的時候不會直接對你的祕鑰資訊進行加密,會有很高的安全風險。那麼Jenkins在何時會反序列化資料的呢?答案是Jenkins啟動時。Jenkins在啟動的時候,首先會啟動主程式server,然後載入所有的外掛,再載入資料。在載入外掛的時候,會通過類名查詢相應的配置檔案,再將配置中的資訊反序列化Java的物件,因此如果在Jenkins的外掛中儲存了大量的資料,會造成Jenkins重啟載入異常緩慢的問題,如果在外掛中有大量的儲存需求,建議大家將儲存代理給Jenkins的Job,雖然Job在載入的時候也是在Jenkins啟動時通過檔案的方式載入的,但是每個Job的配置是隔離的,不會造成單次大檔案的讀取效率瓶頸。
從儲存模型看Jenkins高可用
很多開發者一直在思考如何將Jenkins做成高可用的,畢竟單節點的Jenkins從某種意義上來講總像一個定時炸彈。但是很遺憾的告訴大家,標準的Jenkins很難做到完整的高可用方案。那麼問題的根源在什麼?這要從我們剛才談到的儲存模型談起,上面我們提到了Jenkins的啟動順序為:主程式啟動 ——> 外掛載入 ——> 資料載入。而當資料載入到記憶體後,Jenkins的資料讀取和儲存模型就會變成記憶體讀非同步寫,也就是說一旦Jenkins啟動,就再也不會嘗試從磁碟重新載入這幾個任務了。假設我們從磁碟中讀取了10個任務,如果有從Jenkins UI頁面或者API提交修改任務的請求,那麼Jenkins會去去讀記憶體中的任務配置資訊,然後修改記憶體資料,再非同步重新整理到磁碟上。換言之,Jenkins是有狀態的,而且是單機有狀態的,如果想通過簡單的共享儲存與負載均衡或者反向代理的方式實現Jenkins的高可用基本是不行的。
那麼有的開發者在想是否可以通過通知然後非同步更新記憶體資料的方式進行資料的共享呢。比如一個Jenkins Master更新了任務資訊,然後通過開發一個外掛通知另一個Jenkins Master在記憶體中更新任務的配置呢。這種方式看樣子是可行的,但是實際操作中我們會發現如下問題。首先如果是單純單個檔案的變更或者變化理論上是可行的,但是需要這個外掛在所有涉及儲存的擴充套件點上fire出事件,並進行處理,並且部分資訊的變更還涉及相關外掛的重新載入,而這些資訊是無法從單純的事件資訊中得出的。其次如果涉及資料夾或者多個檔案的變更,那麼此時需要進行全目錄的掃描或者載入,會觸發全量的資料載入,一旦這個操作涉及任務或者構建,那麼延時將是秒級以上的,如果在這個階段有任何的更新操作,極有可能造成資料的腦裂。最後,由於Jenkins沒有公共的cache,會造成登入態共享等等問題。
因此,目前標準的Jenkins還沒有很好的高可用方案。但是,大家對於Jenkins的可靠性不用過於擔心,一個4C8G的VM,進行JVM調優後可以輕鬆應對上百的構建併發,對於大多數中小型公司而言是足夠了,對於更大型的公司會更傾向於使用多個獨立的Jenkins Master來分擔風險。
Jenkins,廉頗老矣?
Jenkins沒有辦法做到高可用,是否意味著Jenkins的架構已經過於陳舊或者老邁了,我們是否還繼續選擇Jenkins。誠然Jenkins的架構很古老,這種儲存模型也有他避免不了的問題。但是就目前來看,Jenkins是唯一的選擇,無論是GitLab CI、Spinnaker還是Travis CI等等,相比Jenkins都在功能或者生態上有所欠缺,而且如果不是超大型的企業對於目前Jenkins的效能問題基本上也是無感知的,可以說Jenkins目前是剛剛好的狀態。對於CI/CD來講,我們需要的並不是多麼超前的技術或者多麼炫的頁面,更多的是如何通過DevOps來保證質量和整合交付流程,對於不同場景和不同業務的開發者而言,上千個Jenkins外掛和多餘牛毛的介紹文章可以企業的DevOps快速進行實施。
在今年7月,阿里雲推出了CodePipeline服務,CodePipeline是基於Jenkins進行二次開發的,在CodePipeline中,我們通過對儲存模型的改造實現了一個高可用的方案,對於Jenkins高可用有需求的開發者或者公司可以在公有云或者私有云中嘗試使用CodePipeline來替代Jenkins,對Jenkins的全相容可以讓開發者快速上手完成自己的持續交付流程。