基于 Bitbucket Pipeline + Amazon S3 的自动化运维体系
1 前言介紹
隨著自動化運維水平的提高,一個基礎的運維人員維護成百上千臺節點已經不是太難的事情,當然,這需要依靠于穩定、高效的自動化運維體系。本篇文章即是闡述如何利用 bitbucket pipeline 結合 Amazon S3 存儲實現項目的自動構建、自動發布以及異常報警等完整的自動化流程處理。不同于常見的自動化項目一般服務于局域網體系,有很大的并發限制與網絡帶寬限制,本篇介紹并實現的運維體系不存在網絡瓶頸,可以支撐廣域網運維自動化,并且支持高并發、升級速度快、架構穩定可擴展性強等優點。
2.1 應用概述
本架構中涉及到的主要應用有:
Bitbucket:一種基于 Git 做版本控制的代碼托管平臺,其他比較流行的平臺有 Github、GitLab、Coding。Bitbucket 支持 pipeline 功能,這也是我們自動化體系CI(持續集成)/CD(持續交付)的基礎。
Amzaon S3:AWS 官網介紹是:“提供了一個簡單 Web 服務接口,可用于隨時在 Web 上的任何位置存儲和檢索任何數量的數據”。簡而言之,就是 Amazon S3 提供一個網絡存儲平臺,我們可以利用其提供的 Web API 進行內容的存儲與訪問。
Docker Hub:目前 Docker 官方維護的一個公共 Docker 鏡像倉庫,而在我們的自動化體系中,也是基于基礎的 Docker 鏡像所做的二次開發。
Ansible:一個基于 SSH 安全認證的批量操作工具,相比較于 saltstack 其操作、部署以及維護比較簡單,但是連接速度較慢。
Slack:優秀的企業協作通訊平臺,我們可以利用其提供的接口進行自動化執行結果的消息通知與展示。
2.2倉庫結構與倉庫權限概述
我們將一個產品項目(ProjectX)根據邏輯功能不同拆分拆分成若干個獨立的模塊,每個模塊存儲在一個獨立的倉庫中。另外,我們根據倉庫的維護者不同將倉庫分為源碼庫(source repository)、發布庫(release repository)、整合庫(distribution repository)。比如我們的項目 ProjectX 對應的整合倉庫為 ProjectX.dist ,該項目中有個功能模塊為 ModuleX ,針對這個模塊需要有源碼倉庫(ModuleX.src)以及發布倉庫(ModuleX.rel)。 其中源碼庫(后簡稱 SRC 倉庫)由研發人員維護,即提供某個功能模塊的源代碼、模塊功能介紹與使用說明等信息。開發人員對源碼庫可讀寫,運維人員可讀;發布庫(后簡稱 REL 倉庫)由系統運維人員維護,提供源碼(假設源碼為編譯型語言)編譯后的二進制文件安裝程序。運維人員對發布庫可讀寫,開發人員無權限;整合庫(后簡稱 DIST 倉庫)則是將不同的倉庫模塊按照一定的邏輯順序組合起來,形成一個完整的產品項目。運維人員對整合庫可讀寫,開發人員無權限。產品項目的 Git 倉庫邏輯分層結構示意圖如下:
圖1 Git 倉庫邏輯分層結構示意圖 ?一個項目由一個 DIST 倉庫、N 個 SRC 倉庫、N+M 個 REL 倉庫組成。只有一個整合倉庫,這個很好理解,但是 SRC 與 REL 倉庫的關系如何理解呢?一般來說一個 SRC 倉庫會對應一個 REL 倉庫,即研發人員只負責提供源代碼,至于如何安裝到系統、安裝到系統的哪個目錄、以及如何制定安全備份策略、日志回滾策略等問題則不需要關注,這些由系統運維人員將該程序的執行策略以程序或者腳本的形式存在對應的 REL 發布庫中,因此一個 SRC 倉庫會有一個相應的 REL 倉庫與之對應。但是一個 REL 倉庫有可能是不需要 SRC 倉庫的,比如我們在系統中會引入某個開源程序(如 Nginx),為了項目的穩定性,我們會把 Nginx 指定版本的 RPM 安裝包作為一個文件存儲至 REL 倉庫中,這樣這個倉庫其實就不需要源碼庫即 SRC 倉庫,但是如果我們要對 Nginx 做二次研發,然后再編譯成二進制文件,則需要額外創建 SRC 倉庫用于存儲源代碼。
2.3 架構執行流程概述
當開發者將代碼推送至 Bitbucket 上的源碼倉庫之后,會觸發 SRC 倉庫的 pipeline ,調用指定的 Docker Image (一般需要我們做二次修改、發布才能使用)完成源碼自動編譯、打包,我們定義打包文件名同二進制文件名加上指定的壓縮后綴(比如 ModuleX.src 中提供了一個二進制程序為 modulex-client ,那么該模塊打包名稱即為 modulex-client.tar.gz),并且存儲至 Amazon S3 存儲桶中;然后運維人員需要根據程序的配置文檔(由研發人員提供相應的 README 文件,指明程序如何安裝、如何指定配置文件等)開發相應的安裝程序,然后將安裝程序推送至 REL 倉庫中。這同樣會觸發 REL 倉庫的 pipeline ,通過調用相應的 Docker Image 將安裝腳本打包存儲至 Amazon S3 中,同樣,我們也需要定義發布庫庫的打包文件名同發布倉庫名稱(去掉 .rel 后綴)加速指定的壓縮后綴(比如 ModuleX.rel 打包之后的名稱為 ModuleX.tar.gz)。這樣在 S3 中就有了編譯后的二進制程序,以及該程序的安裝腳本,我們在 DIST 倉庫的整合程序中只需要根據兩個包的 S3 存儲位置將其下載下來,執行安裝腳本即完成該模塊的安裝,對于其他模塊也是如此。DIST 倉庫只需要一個整合程序將所有的子模塊(倉庫)按照一定的邏輯順序組合起來,就可以完成了一個項目的發布。至此,我們已經能夠完成一個“半自動”的項目安裝,運維人員只需要將 DIST 倉庫中的整合程序下載到節點并執行,整合程序會去相應的 Amazon S3 存儲上下載各個倉庫的安裝程序與二進制文件(如果有的話),然后執行各個模塊的安裝程序,這樣當所有的模塊安裝配置完成后,一個完整的項目就已經正常運行起來了。
接下來,我們可以借助自動化執行工具(如 Ansible、Puppet、Saltstack 等)與 pipeline 完成節點的自動化批量部署。即系統運維人員將整合腳本提交至 DIST 倉庫后,會觸發倉庫的 pipeline ,將整合腳本與其他相關文件(如項目版本信息、子模塊引用信息,以及相應的存儲信息等)推送至安裝了自動化執行工具的中控節點,中控節點將安裝或者更新任務分發到子節點上,繼而完成全球節點持續集成與持續發布。
當完成節點的發布任務之后,我們需要對發布結果(特別是失敗結果)做有效監控與通知反饋。從中控平臺發布任務到節點執行,至少包含兩個關鍵節點:一是中控平臺能否成功將任務下發到被控節點,二是被控節點能否成功執行下發任務。前一個關鍵節點可以通過中控平臺的分發任務執行結果(或者日志)得知,后一個關鍵節點可以通過被控節點的任務執行狀態(或者日志)得知。架構的執行流程圖如下:
圖2 架構執行流程圖3 架構實現
3.1 Amazon S3 存儲規則
我們需要制定 S3 Bucket 存儲桶結構規則,以便于后期程序可以按照這樣的規則完成自動存儲與獲取。首先,一個項目對應一個存儲桶,即 Amazon S3 Bucket;其次,每個子倉庫(模塊)是該存儲桶下的一個對象(可以簡單的理解為一個目錄),而上文提到的源碼倉庫編譯后打包的二進制文件,以及發布倉庫打包的安裝配置等文件皆存儲在該目錄中。如果我們繼續以上文提到的 ProjectX 項目為例,那么當前存儲桶的結構如下:
ProjectX:??? ???? -?ModuleX??? ???????? -?modulex-client.tar.gz??? ???????? -?ModuleX.tar.gz??這樣已經可以滿足單個模塊功能的信息存儲,但是只能存儲最近一次打包結果。如果我想存儲之前發布的某個版本,則不能實現,設置不能夠進行該版本的任何測試就會被推上生產系統,這顯然不合理。為了解決這個問題,我們給存儲桶又加了一層“分支”與“版本控制”的結構概念。對于分支,一般我們在生產開發中至少都會有兩個 git 分支,即 master 與 developer 分支,其中 developer 分支代碼用于功能測試,當測試通過之后提交到 master 分支,用于正式版本發布。因此我們在 S3 存儲桶中模塊對象下,又新增了一層目錄結構,developer 目錄與 master 目錄,針對測試代碼,我們無需存儲之前的版本,只保留當前最新代碼即可,因此直接將打包后的文件存儲至 developer 目錄下即可替換之前的測試程序。但是對于發布版,則需要保留歷史版本,以便快速回滾,或者由于某些其他模塊引用了當前模塊的某個指定版本,而導致該版本必須保留。因此 master 分支與 developer 不同,不能通過簡單的創建一個 master 目錄來解決問題,基于此,我們對 mater 分支制定了一個規則,即一旦合并了 developer 分支,必須打上相應的 tag 信息(否則無法觸發 PIPELINE 完成自動構建),存儲桶功能模塊下不再以 master 作為主分支打包程序的放置目錄,而是以 tag 名稱作為放置目錄。這樣的話,我們就可以通過 tag 信息,獲知某個目錄下存儲的是哪個 tag 的發布代碼,這樣當前的存儲桶目錄結構如下:
??????? ProjectX:?????? ????-?ModuleX????????? ????????-?developer?????? ????????????-?modulex-client.tar.gz?????? ????????????-?ModuleX.tar.gz?????? ????????-?1.0.0?????? ????????????-?modulex-client.tar.gz????????? ????????????-?ModuleX.tar.gz?????? ????????-?2.0.0????????? ????????????-?modulex-client.tar.gz????????? ????????????-?ModuleX.tar.gz?????? ????-?ModuleY?????? ????????-?developer?????? ????????????-?moduley-client.tar.gz?????? ????????????-?ModuleY.tar.gz?????? ????????-?3.2.0?????? ????????????-?moduley-client.tar.gz?????? ????????????-?ModuleY.tar.gz??3.2 倉庫目錄結構與功能說明
源碼倉庫(source repository):倉庫名稱以 ‘.src’ 作為后綴,包含源代碼、PIPELINE 文件(注意:bitbucket 的 PIPELINE 文件名稱必須是 bitbucket-pipelines.yml)、程序配置文件模板(如果需要的話)、CHANGELOG 與 README 等說明文件,以便運維人員開發相應的安裝配置程序。
?
發布倉庫(release repository):倉庫名稱以 ‘.rel’ 作為后綴,包含源碼編譯后的二進制程序安裝與配置程序(腳本)、升級程序、配置文件、源碼庫版本信息(安裝程序就是根據這個版本(或者說是 tag)信息去 S3 上獲取相應 tag 名稱目錄下的打包文件)。
??
整合倉庫(distribution repository):倉庫名稱以 ‘.dist’ 作為后綴,包含整合程序(安裝與升級)、維護一個項目(project)所需的模塊列表以及各模塊的版本信息,整合程序根據各個模塊列表的版本信息從 Amazon S3 的存儲上下載相應模塊的指定版本,通過執行各模塊的安裝腳本,完成各個模塊的安裝。
?
模塊版本信息文件(repo-info):該文件存在于 REL 倉庫與 DIST 倉庫中,滿足 yaml 文件格式。該文件在 rel 倉庫中主要用于記錄該模塊的源碼 tag 信息(后面如無特殊數碼,同“發布版本信息”同義),發布庫分支或版本信息,以及依賴模塊的發布版本信息,文件格式如下:
通過上面的格式定義, rel 倉庫中的安裝腳本就可以去 S3 存儲上獲取源碼安裝包,以及某個版本的依賴模塊安裝包。簡單說明下配置文件各個參數的含義: rel_branch: 用于指定 REL 倉庫的打包文件存放位置,如果是 developer 則表示存儲在 S3 上模塊名稱目錄下,developer 目錄中,用于生產測試;而如果是 rel_tag_info 則表示存儲在 S3 上模塊名稱目錄下,對應 tag 名稱目錄中,用于生成發布。因為只有在測試通過之后,才會合并到 master 分支,而 master 分支也只有在打上 tag 之后,才會觸發 pipeline 完成自動構建,將對應的打包文件存儲在以當前 tag 為名稱的目錄下。所以,S3 存儲中 tag 目錄下的打包文件,均是生產發布版本。 src_branch: 用于指定 SRC 倉庫的打包文件存放位置,與 rel_branch 設計理念相同,測試分支會存儲在 developer 目錄下,正式發布版本會存儲在以 tag 信息作為名稱的目錄下。 depend_list: 用于指定該模塊的依賴模塊(或者說是庫),如果值為 ‘none’ ,表示該模塊沒有依賴模塊,可以直接安裝。如果不是 ‘none’ ,則需要指定依賴模塊的名稱,以及相應依賴模塊的版本信息。需要注意的是,這里的模塊版本信息我們規定只能使用 tag 信息,也就是只能指定某個模塊的發布版,而不能使用 developer 下的打包文件,即該模塊的測試版,因為依賴模塊作為一個底層模塊,必須保證穩定的前提下才能被其他上層模塊所引用,也才能保證上層模塊的穩定性。 而在 DIST 倉庫中的 repo-info 文件,文件格式同樣滿足 yaml 語法,文件的內容參數略有調整,以下為模板文件:
?rel_branch:?[developer|rel_tag_info]?#?生產環境只能?developer?OR?rel_tag_info?二選一?? sub_repo:??[none]?????? ##如果是?none?,則不會有下面需要安裝的倉庫列表信息 ??repo_name1:?1.2.3?? ??repo_name2:?1.1.1?? ??repo_name3:?1.1.2?? ??repo_name4:?2.1.3?? ??repo_name5:?3.1.3?? upgrade_list:?[none]?#?如果是?none?,則不會有下面需要升級的倉庫列表信息?? ??repo_name3:?1.1.3?? ??repo_name4:?2.1.4?? ??repo_name5:?4.0.0??其中 rel_branch 與在 REL 倉庫中的含義一樣,用于表示 DIST 倉庫存儲在 Amazon S3 上的位置,但是需要注意的是 DIST 倉庫是沒有 src_branch 的,因為這個倉庫就是專門用于各個子模塊的整合,不存任何代碼,只保留各模塊版本(也即是存儲)信息。 sub_repo: 表示這個項目(Project)由哪些模塊組成,各模塊的版本是什么,整合腳本就是根據 sub_repo 的信息進行對應的功能模塊獲取與安裝的,這里需要注意的是,模塊安裝會存在順序關系,這里需要區別對待依賴關系。依賴關系是缺少某個模塊會導致當前模塊無法運行,或者部分功能不能使用;而順序關系,則是邏輯關系,缺少某個或者順序不對不會導致另一個無法安裝,只是整個業務邏輯上會存在一定的問題。舉個例子,我們編譯 nginx 需要 gcc 庫,必須先安裝 gcc 庫才能編譯安裝 nginx,這就是依賴關系。而 nginx 在接收客戶端請求后,可能會將請求轉給 php 處理,php 可能會去操作數據庫,我們一般安裝的時候,就會先安裝數據庫,但是這兩者之間的安裝就不存在依賴關系,而是一種順序關系。如果先安裝 nginx 然后在安裝數據庫,可能只會導致業務不能處理動態請求而已,而不會影響兩個應用的正常安裝。 upgrade_list: 用于指定項目升級(也即 DIST 倉庫升級)涉及到哪些模塊的更新,‘none’ 表示無升級需求,如果為空,則需要根據下面的倉庫名稱,下載相應的子模塊,執行里面的升級腳本(upgrade.sh)。
3.3安裝與升級
在我們定義完 Amazon S3 存儲規則,并且在發布庫與整合庫中提供了模塊信息,各個模塊的安裝或升級程序就可以利用這些信息到 S3 上獲取相應的打包文件,這個文件會包含源碼編譯后的二進制文件(如果有的話)和針對該二進制文件的安裝配置腳本,這樣我們只需到讓整合倉庫根據 repo-info 文件中中的 sub_repo 模塊信息到 S3 上下載各個模塊的打包文件,執行各自的安裝與升級腳本即可。至于某個模塊依賴哪些模塊,則不用在 DIST 庫中處理,只需要在執行單個模塊的安部署裝(deploy.sh)或升級腳本(upgrade.sh)即可,因為這個模塊是最清楚自己依賴哪些模塊的,在這里處理也是最清晰、簡單的。下圖為項目的安裝流程,升級流程與安裝流程類似,只不過安裝流程是下載各個模塊,執行各模塊的安裝腳本,而升級是執行各模塊的升級腳本。
圖3 項目安裝與升級流程圖3.4 PIPELINE 規則制定
Bitbucket 支持 pipeline 功能,在滿足條件時觸發某種操作,這個操作我們可以簡單的理解為調用 docker 鏡像完成某種行為,比如編譯、打包、上傳至 S3 存儲等。這里我們所需要闡述的是,如何制定 pipeline 的觸發規則?要回答這個問題,我們首先要弄明白利用 pipeline 的目的,即我們希望當開發人員 push 代碼時,能夠完成代碼的自動編譯、打包、發布、測試、以及上線等行為。其中代碼編譯、打包、發布等行為是統一的,而測試與上線是最終不同的兩個目的,這樣我們就像需要設定不同的規則來觸發測試與上線的不同行為。根據 bitbucket 的官方文檔 中關于 pipeline 的觸發條件,可以分為三種(實際上是四種,但是 bookmarks 是針對 Mercurial ,我們暫不討論),下面簡單說下這三種:
- default :即一旦用戶 push 代碼即會觸發當前pipeline 。
- tags: 即一旦用戶為代碼打上 tag 標簽時就會觸。
- branches: 即一旦用戶往指定分支上提交代碼時就會觸發。
結合我們設定的自動化執行邏輯,需要使用 tags 與 branch 作為觸發條件,branch 設置為 developer 分支,這樣當開發人員將代碼提交到 developer 分支時會立刻觸發 pipeline ,執行相關的編譯、打包、發布、測試工作。tags 則用于觸發測試之后的發布,即一旦我們在分支上打上 tag 標簽,就會自動觸發 pipeline 完成編譯、打包、發布、上線工作。這里需要注意的是,bitbucket pipeline 無法區分 tag 是來自于 master 分支或者 developer 等任意分支,只要檢測到有 tag 產生,即會觸發 pipeline ,所以打 tag 時一定要慎重,我們規定:必須在 developer 分支完成充分測試之后,才能 pull request 到 master 分支,而所有的 tag 也必須是打在 master 分支上的。當然,意外不可避免,我們做了相關的考慮,如果因為誤操作或者測試不充分導致當前版本部署到實際生產節點不能正常使用,只需要 rerun 上個 tag 的 pipeline 即會重新打包發布老版本。
3.5一源多存問題
在講述該問題前,我們先來聊一個業務場景,公司研發了一個底層的基礎功能模塊,如何被兩個甚至多個項目所引用,最簡單的做法是將這個模塊作為代碼的一部分直接提供給各個項目使用,這就容易造成生產中經常遇到的多源問題,后期隨著由于各項目的發展,可能會對引入的基礎模塊功能做調整,那么調整后的基礎模塊就不再能夠保證被其他項目所通用,那么修改后的代碼提交到哪呢?只能建立新倉庫提交了,這樣我們從維護一個基礎庫就變成了維護多個基礎庫,開發基礎模塊的目的本來就是抽象功能、代碼復用、提高開發效率,這顯示事與愿違。
當然針對這種多源問題,比較常見的解決辦法是使用 subtree 或者 submodule 的方式將倉庫引入到項目中,subtree 會直接將代碼引入,這樣會導致我很難快速的指定當前項目引入的是哪個版本的基礎模塊。submodule 可以理解為引入的是一個指針,指向某個版本的基礎庫,這在一定程度上能夠達到我們的目的,但是一旦被多個項目或者多級(特別是多級引用)引用,如果更新基礎模塊,則引用該模塊的所有上級模塊需要一級一級重新引入,簡直就是噩夢。而我們實現的通過倉庫中指定引用基礎模塊版本的方式就非常簡單了,當基礎模塊更新時,引用模塊如果不需要更新,則不用修改自身的依賴倉庫版本信息,如果需要使用新版本基礎庫,只需要將依賴庫的版本號修改為想要引用的版本號即可, S3 存儲桶中存放了所有的穩定版本,操作非常之簡單。這樣我們只需要從一個源提供代碼,就可以被多個項目引用,而且可以引用不同版本,互相不影響。
我們知道,一個完整的項目需要多個不同的模塊,我們是使用存儲桶的方式將各個模塊打包存放,那么也就是說每個存儲桶中都包含了所有的模塊打包文件,以及不同版本的打包文件,那么當一個基礎模塊在觸發了 pipeline 完成自動編譯打包之后,如果分發到不同的存儲桶中呢?這就是我們接下來要討論的“一源多存問題”。 這里我們需要引入 pipeline 中變量的概念,我們從 bitbucket 關于變量的 官方文檔 中可以得知,pipeline 可以使用 bitbucket 內置的變量,當然我們也可以自己定義變量,如果變量名稱相同,會以自定義變量為準(準確的說,會以最后聲明的變量為準,但是內置變量聲明是在自定義變量聲明之前就完成的)。這樣我們就可以利用變量的方式,給不同的存儲桶設定不同的變量名稱,那么打包存儲之后再根據不同的變量名稱將文件存儲至 S3 上即可。這樣我們就需要規范某些自定義變量,這些變量專門用于 pipeline ,而不能被其他腳本定義,這些變量有:
表1 PIPELINE 自動構建變量定義 ?上述表格,左邊第一列表示需要在 bitbucket 的每個模塊倉庫中定義為 pipeline 使用的變量,第二列表示每個模塊的安裝與升級程序中定義的變量,第三列表示該變量的含義。下面我們從上到下解釋這幾個變量的含義以及用法:
S3 存儲桶名稱:用于告訴 pipeline 在完成打包之后發往哪個存儲桶。對于一源多存的問題,也是在此處解決的。首先由于我們是從一個源倉庫打包引入到不同項目中,那么這份代碼在源倉庫中就是唯一且通用的,因此我們定義了變量“BUCKET_NAME” 作為存儲桶名稱,有需要獲取 S3 存儲上的文件時,用變量代替;另外,我們通過在 bitbucket 的不同項目倉庫中設置不同的存儲桶名稱變量,在 pipeline 中將 ${BUCKET_NAME} 替換成 ${PROJECTA_BUCKET_NAME} ? 或者 ${PROJECTB_BUCKET_NAME},來將源文件中的存儲桶名稱修改為當前項目的存儲桶名稱,并且發送至對應的存儲桶下,示例代碼如下:
\t#?基礎模塊?REL?倉庫中的代碼(deploy.sh?與?upgrade.sh?均包含如下代碼)截取??\tBUCKET_NAME=''??\t??\t#?獲取源碼倉庫打包后的文件??aws?s3?cp?s3://${BUCKET_NAME}/${REPO_NAME}/${BITBUCKET_TAG}/${BIN_NAME}.tar.gz?./??從上面的代碼中,我們可以看到,獲取 S3 上存儲的文件,需要四個變量,即:${BUCKET_NAME}、${REPO_NAME}、${BITBUCKET_TAG}、${BIN_NAME},其中除了 ${BITBUCKET_TAG} 外,其余的三個都是我們在上面自己定義的。稍后會對這幾個參數的使用做說明,我們繼續討論 ${BUCKET_NAME} 的使用。這里我們看下 pipeline 中的部分代碼,就會明白是如何借助與 bitbucket 的變量定義,完成不同源的多存儲問題了。
\t#?bitbucket-pipelines.yml?中部分代碼??\tscript:??\t????#?發送存儲桶?BUCKET_A?中的打包文件??\t????-?export?AWS_ACCESS_KEY_ID=${BUCKETA_S3KEY}?AWS_SECRET_ACCESS_KEY=${BUCKETA_S3SECRET}??\t????-?sed?-i?'s#\\${REPO_NAME}#'${REPO_NAME}'#g?;?s#\\${BUCKET_NAME}#'${PROJECTA_BUCKET_NAME}'#g'?deploy.sh?upgrade.sh??\t????-?tar?czf?${REPO_NAME}.tar.gz?etc?repo-info?deploy.sh?upgrade.sh??\t????-?aws?s3?cp?${REPO_NAME}.tar.gz?s3://${PROJECTA_BUCKET_NAME}/${REPO_NAME}/${BITBUCKET_TAG}/??\t??\t????#?發送存儲桶?BUCKET_B?中的打包文件??\t????-?export?AWS_ACCESS_KEY_ID=${BUCKETB_S3KEY}?AWS_SECRET_ACCESS_KEY=${BUCKETB_S3SECRET}??\t????-?sed?-i?'s#\\${REPO_NAME}#'${REPO_NAME}'#g?;?s#\\${BUCKET_NAME}#'${PROJECTB_BUCKET_NAME}'#g'?deploy.sh?upgrade.sh??\t????-?tar?czf?${REPO_NAME}.tar.gz?etc?repo-info?deploy.sh?upgrade.sh??????-?aws?s3?cp?${REPO_NAME}首先需要說明的一點是,上面的 pipeline 使用的 docker 鏡像是 atlassian/pipelines-awscli ,由 aws 官方提供,其中變量 AWS_ACCESS_KEY_ID 與 AWS_SECRET_ACCESS_KEY 分別表示訪問存儲通的 key 和 secret ,每個存儲桶的 key 和 secret 均不一樣,具體請參考 AWS IAM ,此處不再贅述。
我們通過在 bitbucket 上設置變量 ${PROJECTA_BUCKET_NAME} 和 ${PROJECTB_BUCKET_NAME} 來替換腳本中 ${BUCKET_NAME} ,以將存儲在某個桶下的項目腳本可以正確的適配該項目。
接下來我們說下其余的幾個變量:
倉庫名稱:即 ${REPO_NAME},需要在 bitbucket 的每個倉庫中設定,主要有兩個目的,一是確定 S3 上的存儲位置;二是確定 REL 倉庫打包后的名稱。
二進制文件名稱:即 ${BIN_NAME},同樣需要在 bitbucket 的每個倉庫中設定,目的與 ${REPO_NAME} 一樣,用于確認 SRC 文件打包后的名稱,已經在 S3 上的文件存儲位置。
而變量 ${BITBUCKET_TAG} 則是 bitbucket 內置的變量,是用于獲取最近一次倉庫 tag 名稱的,這個不需要我們手動設定,只需要直接在 pipeline 中引用即可。
綜上所述,我們通過規定變量名稱,以及利用 bitbucket pipeline 內置變量、自定義變量,解決了同一份數據源適配不同項目的問題。這樣不同的項目整合程序,在各自的項目中引入的模塊就是以及適配好的程序,可以直接用于項目的安裝與升級。那么接下來,我們就開始討論項目的自動化部署。
3.6 自動化部署
在我們完成 Amazon S3 存儲規則、倉庫結構、安裝與升級邏輯流程、PIPELINE 等規則內容設定以及一源多存問題解決之后,我們已經可以快速便捷的完成單個模塊、部分模塊以及整個項目的安裝與更新操作,但是這還需要人工參與,接下來我們就討論如何利用批量化部署工具,完成全球節點的自動化部署。 首先我們對批量化部署工具進行選擇,當前比較熱門的有 saltstack 、puppet、ansible 等,為了降低維護成本以及快速部署,我們結合當前生產環境現狀以及生產節點數量,選擇了輕便快捷的 ansible 作為批量控制平臺,它是基于 ssh 協議完成任務分發,所以安全性還是比較高的,另外不需要客戶端,操作簡單、學習成本低等特點,都是我們現下比較需要的。當然,后期隨著自動化平臺功能的不斷完善,以及運維節點數量的增加、對運維時效性的要求提高等因素,可能需要更換中控平臺或者自研該平臺,這雖是后話,但是我在設計當前的自動化運維體系時已經將該問題考慮進去,將整個體系進行模塊化分割,與中控平臺之間的耦合性降到最低,這樣以后無論使用何種中控平臺,都不會對現有體系造成太大波動,以達到降低影響、快速迭代上線的目的。
接下來我們就討論如何利用 Ansible 完成全球節點的自動構建與更新,核心邏輯處理流程如下: 為了不產生歧義,我們以節點自動構建作為說明對象,自動更新與之類似。當我們決定采用 ansible 作為批量部署工具后,就需要根據業務邏輯設計相應的 ansible 執行腳本即 playbook ,腳本需要具備判斷某個節點是否滿足安裝或者更新條件的能力。為了達到這個目的,我們通過在節點上創建 images.yaml 文件,模板如下:
#?定義?image?版本信息?? image:?? ????version:?2.1.0?? status:?building??我們對鏡像文件引入“節點狀態”與“節點版本”的概念,并且將節點狀態分為:節點狀態文件不存在、building(節點構建中)、maintain(節點維護中)、failure(失敗)、active(可用)。對于這五個狀態,其意義如下:
節點狀態文件 images.yaml 不存在:即我們任務該節點為新節點,會對該節點執行安裝操作(這個狀態文件作為自動化執行的重要邏輯判斷依據,是絕對不允許被人為干預的,即使是誤操作,程序也會自動重建該節點,以保證節點后期的可維護性)。
building(節點構建中):這是當對一個新節點進行項目安裝時,會新增 images.yaml 文件,并將狀態值修改為 building,其實我們在 DIST 倉庫中存儲的 imags.yaml 文件初始狀態就是 building ,這也是符合我們的執行邏輯,安裝時直接將該文件放置到指定位置即可。
?maintain(節點維護中):這種狀態是由節點的 active 轉變,即節點符合更新維護條件,則會將 active 修改為 maintain 。
failure(失敗):表示節點安裝或者更新失敗,需要進行人工干預。
active(可用):解釋節點安裝或者更新成功,可以正常提供服務。
那么 ansible 是如何根據上面的五種狀態,來決定該節點是否能夠進行安裝或者更新?如何避免對正在維護的節點再次觸發維護執行維護程序?Ansible 的判斷依據是,節點狀態文件不存在,直接執行安裝操作,安裝操作由 DIST 的安裝程序執行,該程序執行完會判斷本次執行是否成功,成功則將節點狀態由 building 修改為 active ,不成功則修改為 failure 。這樣,我們就能得出 failure 狀態是由于整合倉庫的安裝(或者更新)腳本導致,而跟 ansible 無關,也就不應該再讓 ansible 繼續對 failure 狀態的節點下發安裝或者更新任務。因此,我們得出結論,ansible 只會對新節點執行安裝操作,只應該對 active 狀態的節點執行升級操作(當然這是必要條件,而不是充分條件,否則節點會一直處在 maintain 跟 active 間切換)。因此我們通過引入“節點版本”的概念,來作為另外一個判斷是否可以進行升級操作的必要條件。我在開發 ansible playbook 腳本時,引入了“可更新節點版本”的變量(該變量通過 playbook 的變量文件存儲,因此每次升級項目修改整合倉庫時,也需要修改該變量文件中關于“可更新節點”變量的值),即只有當這個變量與鏡像文件中的節點版本相匹配時,才會滿足升級條件。即,我們判斷節點是否應該升級會對節點狀態與節點版本判斷,只有節點狀態為 active 且節點版本與 ansible 中定義的可升級版本所匹配,才會執行升級操作。
接下來,我們繼續討論新版本發布問題。當我們確定要發布新版本時,會根據本次版本中各個模塊的調整修改 DIST 倉庫的 repo-info 信息、ansible playbook 執行文件、CHANGELOG 、 README 等信息,當然,可能還需要修改 DIST 倉庫中的安裝腳本。為什么說可能呢?根據前面的設計邏輯, DIST 倉庫中的安裝腳本只負責根據 repo-info 文件從 S3 上獲取各個模塊的安裝包,然后執行各安裝包中的部署腳本即可。這樣的話,其實 DIST 倉庫的安裝腳本只需要執行一個循環,依次讀取 repo-info 中模塊信息,然后下載文件、安裝文件即可。無論是新增、刪除、修改功能模塊,只需要對 repo-info 中該模塊的內容進行相應的新增、刪除、修改即可,而不必去修改整合腳本的內部邏輯,這樣極大的降低了維護 DIST 倉庫的人員水平。也就是說,絕大多數情況下都不需要修改 DIST 倉庫的安裝腳本,但是有些情況下可能需要單獨處理某個模塊,例如這個模塊的處理邏輯與其他模塊不同,那么就需要調整整合腳本,以兼容該模塊。舉個最簡單的例子,假設模塊C 在執行部署程序時,需要給該程序傳遞指定參數,那么這就與其他模塊的處理邏輯不同,必須單獨設定。
總而言之,我們需要根據將要發布的版本做整合倉庫的適配,然后將代碼提交至 DIST 倉庫 developer 分支,觸發 pipeline ,將倉庫中指定文件(包括 playbook 以及其變量配置文件、images.yaml、安裝與升級腳本、倉庫 repo-info 文件等)推送至 ansible 控制節點的某個目錄下,ansible 會定期執行該目錄下的 playbook 文件,進行全球節點任務分發,對滿足安裝或者升級的節點進行自動化安裝與更新操作。綜上所述,ansible 執行自動化安裝與升級流程圖如下:
圖4 Ansible 執行自動化安裝與升級流程圖整個流程可以概括為:
(1). 適配 DIST 倉庫內容,觸發 pipeline 推送至 ansible 控制節點。
(2). Ansible 執行 DIST 倉庫中 playbook 文件,全球節點下發任務。
(3). 利用 playbook 邏輯對各節點進行狀態判斷,決定執行安裝、更新程序或者終止任務。
(4). 對于執行安裝或者更新程序的節點,會根據程序的執行結果修改節點狀態為 active 或 failure 。
3.7 監控與報警
當我們利用 bitbucket pipeline + ansible 完成全球節點的持續集成與持續發布之后,接下來需要解決的問題就是監控。需要注意的是,這里我們提到的監控是指 CI/CD 結果的監控,而不是常規的節點流量、內存、CPU 等性能監控。通過上面的分析我們知道從 ansible 獲取最新的 playbook 到將任務推送至全球節點執行,可以分為兩個環節,第一是 ansible 將任務推送至全球各節點上,進行安裝或更新邏輯判斷;第二是 ansible 執行完判斷邏輯后,符合安裝或更新的節點會執行相應的腳本進行安裝或者升級,然后腳本根據自身的執行結果對節點狀態進行修改。這樣的話,我們的監控也應該觸及到這兩個環節,第一、Ansible 是否成功將任務推送至全球各節點;第二、節點在接收到 ansible 推送的任務后,是否成功執行了安裝或升級腳本。對于第一個點,我們可以通過分析 ansible 的執行日志獲取;對于第二點則可以從各節點狀態中獲取。這樣就形成了監控報警的框架,即我們首先對 ansible playbook 日志中 recap 結果進行分析,對其中產生失敗(比如 failed、unreachable)的主機節點進行報警;然后通過 ssh 的方式連接遠程節點,對狀態值為 failure 也進行報警,報警平臺我們使用 slack ,其提供了高效的報警接口(我們對該接口又做了二次開發,使其功能更加豐富),調用起來非常簡單,如下是發送 slack 的報警信息如下:
? 時間:?2018-10-25?08:17:46,282?? 主機:?192.168.10.129?? Ansible?執行狀態:?success?? 節點狀態:?failure解釋一下報警信息各條目的含義:
時間:表示 ansible 推送任務到該節點的時間,以 ansible 服務器時間為準。
主機:表示 ansible 正在將任務推送至哪臺節點。
Ansible 執行狀態:表示從 ansible 對該主機下發任務的狀態,狀態值有unreachable、 failed、success。 其中 unreachable 表示 ansible 與這臺節點連接失敗; failed 表示雖然 ansible 可以成功連接到該節點,并且執行 playbook 中的任務,但是因為某些原因導致任務執行失敗;success 表示 ansible 成功連接到該節點,并且將 playbook 中的任務依次成功執行完。而根據我們之前的設計,節點的升級或者安裝,是由 ansible 調用升級或安裝腳本去執行的,因此 ansible 執行狀態成功,并不能表示節點被成功安裝或者升級了,我們需要繼續對節點狀態進行判斷。
節點狀態: 表示各個節點上 images.yaml 文件中記錄的節點狀態信息,有 active、failure、building、maintain 四個,另外加上節點狀態文件不存在與連接遠程節點失敗,一共有六個狀態值,但是 active、building、maintain 為正常狀態,因此會產生報警行為的節點狀態有 failure、節點狀態文件不存在以及連接遠程節點失敗三種。
綜上所述,下面的情況行為會產生報警行為:
(1). Ansible 連接遠程節點失敗
(2). 節點上執行 playbook 失敗
(3).節點上執行安裝或者升級腳本失敗
?
雖然我們設計的報警邏輯基本上涵蓋了大多數可能出現的錯誤,但并不是很嚴謹,我們也會繼續努力去制定更多維度的監控,以提高監控的有效性。
4 當前架構的優缺點分析
4.1 架構優點分析:
???? 權限分配清晰
我們通過對 Git 倉庫實行三層邏輯分層,將開發人員與運維人員權限做了嚴格的區分,使其可以很好的協作而不會互相干擾。
???? 自動化程度較高,且運維成本低
通過利用 Bitbucket pipeline + Amazon S3 + Ansible 實現了自動化持續集成與持續部署,且因為各個功能模塊打包存放在 S3 中,中控節點僅推送 DIST 倉庫中的幾個文本文件到各節點上,然后在每個節點上執行安裝或升級腳本即可,各個節點會從 S3 上下載相應的模塊文件進行安裝或升級,這樣對中控節點的物理硬件性能要求非常低。縱觀這個運維項目,從 bitbucket 代碼倉庫,到 Amazon S3 存儲,再到 ansible 中控節點服務器,整個運維成本大概在 20 - 30 $/Month 。另外,由于自動化流程經過高度抽象與模塊劃分,對操作人員的技能要求也大大降低,這也減輕了運維成本。
???? 支持廣域網自動化運維,不存在網絡瓶頸,并發能力非常強
傳統的自動化運維平臺一般是面向局域網的,一般的執行流程是把代碼下載至與同一局域網內的中控節點,然后由中控節點將其推送局域網內的所有節點,這就對中控節點的網絡帶寬帶來了極大的考驗,因為一個項目上線,所有功能模塊的安裝包少則幾十兆,多則百兆甚至到 GB 級別,這是根本沒法實現廣域網自動化部署的,因此傳統自動化運維平臺一般都會有比較嚴重的網絡瓶頸,任務并發處理能力較低。而我們通過架構優化,讓中控平臺只推送數量很小的文本文件,可以輕松達到幾十甚至上百個任務并發,依然不會對中控節點造成太大的壓力。各個節點在執行安裝或者升級腳本時,會從 Amazon S3 上下載文件,而 S3 所支持的下行帶寬是不存在任何下載上的瓶頸的。
???? 模塊引入非常靈活簡單
基礎模塊的引入一般是比較頭疼的問題,我們通過借助于物理存儲的形式,以及存儲規則設定,可以輕松實現多個項目對某個模塊不同版本的引用,且數據源僅一個,不會造成后期的多源問題。
???? 版本升級與回滾非常便捷
我們對整個運維流程進行高度抽象化、模塊化,項目或者單個模塊的安裝與升級全部依靠于各自的倉庫信息文件,即 repo-info 文件,升級或者回滾操作,僅需要修改配置文件中 S3 存儲的指向即可,秒級完成版本升級與回滾。
???? 監控報警形成自動化流程閉環,安全性較高
監控是對整個運維框架的邏輯補充,可以幫助我們簡單且高效找到問題點,提升整個自動化流程的安全可靠性。
4.2架構缺點分析:
???? 基于 ansible 處理,連接速度較慢,節點數在千臺以內
由于中控平臺采用 ansible 作為批量化操作工具,所以也會受到其性能的影響。Ansible 是基于 ssh 連接對各節點進行操作,這就導致了連接速度比較慢,且維護量級一般在千臺以內。
???? 監控與分析平臺不夠強大
目前監控平臺是基于兩個核心維度進行的,雖然可以發現絕大多數常見的自動化部署產生的問題,但是維度太小,遇到比較復雜的情況無法實現有效監控,而我們也正努力從其他維度去攻克這一監控難題。
5 下個版本需要做什么
當前的自動化運維體系支持多種云平臺、支持廣域網,能夠滿足中小企業(批量操作節點在 1000 臺以內)的日常運維工作,但是在該體系的構建與應用中,發現了一些問題,另外還有一些新的想法希望能夠補充進來,我們將這些放在下個版本中實,這些問題或想法包括但不限于以下這些:
???? 提高監控展示能力:
監控維度不夠豐富全面,容易造成某些問題的遺漏。下個版本我們會從目標結果的方向出發(比如當前版本號、當前用戶連接數、當前流量等),做正向匹配監控,擬引入 elasticsearch 平臺,對這些目標數據進行采集,同時也將對安裝與升級日志進行收集、以用于分析與結果展示,最終達到深度定位潛在的問題的目的。
???? 提高中控節點連接速度
節點連接速度比較慢,ansible playbook 邏輯判斷太簡單,不太適合復雜模型處理。下個版本應該會調整批量化部署工具,以提高邏輯判斷能力與節點部署速度。
???? 引入 CMDB 系統,實現節點分組
當前節點尚未分組,人工不干預的情況下,無法做灰度上線。這對生產發布始終是一個隱患,下個版本應該會設計并引入合適的資產管理系統,擬通過引入 tag 的方式解決這一問題。
???? 引入堡壘機與審計系統
這一功能雖然與當前版本不存在直接關系,但是確能夠解決運維中經常出現的節點權限分配混亂、事故責任糾纏不清、以及人員離職全球節點修改密碼等瑣碎問題。通過堡壘機機制,可以統一登錄入口,增加接入的安全性;另外可以在堡壘機中對人員權限進行嚴格分組、記錄用戶操作行為等,這都是標準運維流程需要解決的事情。
作者簡介
陳龍飛,北京聯宇益通科技發展有限公司(netpas) 系統部負責人,主要負責全球節點系統與應用的自動化運維工作,保證節點穩定、高效運行。通過建立自動化運維體系,完成 DevOps 精準落地,提升運維與開發之間的工作效率,使產品上線與迭代流程更加簡便、高效。主要關注領域有:Linux架構與優化、運維自動化、云計算、人工智能。
總結
以上是生活随笔為你收集整理的基于 Bitbucket Pipeline + Amazon S3 的自动化运维体系的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [网络流24题-7]圆桌问题
- 下一篇: 纯CSS实现立方体旋转