Learn Jenkins the hard way (3) - Jenkins的存儲模型
前言
在上篇文章中我們主要講解了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 version='1.0' encoding='UTF-8'?>
<sites>
<site>
<id>default</id>
<url>https://updates.jenkins-ci.org/update-center.json</url>
</site>
</sites>
文件是以標準的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的全兼容可以讓開發者快速上手完成自己的持續交付流程。
最後更新:2017-10-15 21:34:15