有容雲:實戰總結之 利用Docker、Docker Compose &Rancher構建持續部署

前言:

本文由John Patterson 、 Chris Lunsford寫於2016年4月4日,譯者有容雲張向波,轉載請註明出處。(原文鏈接見文末)

 

作者John Patterson和Chris Lunsford 運營了一家提供運營和基礎架構服務的公司,本文是他們給大家分享的內容:關於如何通過使用Docker、Docker-Compose和Rancher來實現容器部署落地。

 

我們想跟你一起從頭開始體驗整個過程,特別是之間遇到的一些痛點和所做的決策。目前,已經有許多的資源和工具可以與Docker一起配合來搭建持續集成和部署工作流程。搭建一個部署工作流程較簡單,但是搭建一個部署系統相對比較複雜,因爲很多相關的工作流程在已有的環境中已經實現,並且存在很多依賴關係,同時還要讓開發和運維部門啓用新的工作流程。希望我們在搭建部署系統中的經驗能夠幫您跳過項目中的那些坑。

 

在這篇文章中,我們將回顧最初只使用Docker來構建的工作流程。隨後的幾篇文章中,我們將進一步通過Docker-Compose和Rancher來一起構建工作流程。

 

以下事件發生在我們長期服務的一家SaaS服務商,接下來我們稱這家公司爲Acme Business Company或者ABC。這個項目始於ABC着手將大部分Java微服務從自建機房的服務器遷移到AWS的Docker環境中。項目目標主要是降低未來上線週期和擁有更高的應用部署服務可靠性。

 

實現應用部署的計劃如下所示:


 

整個流程從代碼更改、提交、推送到Git倉庫開始,這將通知我們的持續集成(CI)系統運行單元測試,單元測試通過的話,編譯代碼並作爲構件保存。如果這步成功通過的話,將觸發新任務,通過代碼構件來生成Docker鏡像並推送到Docker私有鏡像倉庫。最後,我們將鏡像部署到環境中。


必需組件如下:

  • 源代碼倉庫- ABC將代碼保存在私有的GitHub倉庫中。
  • 持續集成和部署工具- ABC使用本地安裝的Jenkins。
  • 私有鏡像倉庫-基於S3的Docker鏡像倉庫爲容器提供服務。
  • 運行Docker的主機環境 -ABC 有多個目標環境,且每個目標均包含預發佈環境和生產環境。 

乍看起來,整個流程還是挺簡單的,而實際上並沒那麼簡單。和大多數其他公司一樣,ABC以前(現在也是)開發和運維是分開的。當代碼準備好部署時,一個包含應用詳細信息和目標環境的工單(ticket)將被創建。這個工單將指派給運維部門,並安排在當週的部署維護窗口進行上線。這種情況下,持續部署和交付的方式是不清晰的。

 

起初,應用部署的工單可能如下:

 

 

部署過程:

 

  • 打開部署窗口,工程師在Jenkins中對相關項目點擊“立即構建”,傳遞分支名字作爲參數。輸出顯示一個被標記tag的Docker鏡像被自動推送到鏡像倉庫。工程師在環境中選擇一個當前不在負載均衡設備中激活的Docker主機,登陸並且從鏡像倉庫拉取新版本鏡像。

 

 

  • 找到當前運行的容器

 

 

  • 停止運行的容器.

 

 

配置必須的標識並正確啓動新的容器。具體容器配置可以從之前運行的容器,主機shell歷史記錄,或者從配置文檔中獲取。

 

 

  • 啓動服務,通過手工執行測試來確認服務正常。

 

 

  • 在生產環境維護窗口,更新負載均衡配置,指向更新的主機。
  • 驗證通過後,更新將被應用到環境中需要故障恢復的其他主機上。

這種部署過程並不引人注目,卻是持續部署中的第一步,還有很多地方需要改進,先權衡一下收益:

  • 運維工程師可以用相同的步驟來部署所有的應用。Docker run 步驟中參數針對不同的服務或應用需要定製,但步驟始終一致:Docker pull,Docker stop,Docker run。這些流程足夠簡單,很小概率會遺漏步驟。

 

  • 最小2臺主機組成的環境中,我們進行了可管理的藍綠部署(灰度發佈)。通過負載均衡設備配置在生產窗口進行簡單版本切換和回滾。隨着部署變得更靈活、支持快速升級、支持快速回滾,後端的服務發現機制也變得更復雜。由於是手動進行,藍綠部署的成本微乎其微,但帶來了直接升級(in-place upgrade)的收益。

痛點:

  • 重複輸入相同的命令。 更準確的說,在bash框口中重複輸入。這個解決辦法很簡單,自動化! 有很多工具來解決Docker容器的啓動。對運維工程師來說最簡單的方式是將重複的邏輯寫成bash腳本,這樣就有了單一的入口。如果你是一個DevOps工程師,你也可以通過Ansible、Puppet、Chef或者 SaltStack來實現。寫腳本邏輯很容易,但有一些問題亟待解決:部署邏輯腳本存放在哪?多個服務使用不同的參數如何追蹤和管理?

 

  • 降低出錯率及日誌需求-即使運維工程師擁有超人類的能力避免了打字輸入錯誤,在工作一整天后的午夜還能保持頭腦清醒,但他也不會知道某個應用服務的端口需要更改爲其他端口。Docker端口參數需要更改,原因在於應用程序的具體工作原理只是開發人員比較清楚,需要將這些信息傳遞給運維團隊。多數情況下,運維腳本邏輯記錄在一個單獨的資源庫,亦或者根本沒有記錄下來。隨着應用的不斷變化而時時更新應用部署步驟是比較困難的,所以將部署腳本邏輯用Dockerfile提交到代碼倉庫是一個不錯的選擇。即使某些情況下不能這樣做,也有其他的解決方式(詳見後文)。重點是具體的部署步驟被記錄並存儲了。代碼實現要比一個部署工單能更好的完成上述工作,但至少工單記錄下來的部署步驟要比沒有任何文字記錄或者只放在腦子裏要好很多。

 

  • 可見性低  對容器進行故障排查需要登錄主機並執行命令。這意味着需要登錄一羣主機,並重復輸入”docker ps” 和”docker logs -tail=100”。有許多很不錯的集中日誌處理的解決方案,很值得花時間去搭建一套。這個過程中,我們發現比較困難的是,如何定位某個容器運行在哪臺主機上。在灰度發佈的場景中,開發比較關心當前環境中各個版本的上線情況和擴展水平。對於運維團隊來說找到對應的容器來進行升級和故障排查是比較麻煩的。

鑑於此,我們着手對這個問題進行解決。

第一步,將通用的部署過程寫成bash腳本,例如:

 

 

這個例子只對最簡單的容器生效:用戶不需要和容器進行直接交互。我們需要添加特定的應用邏輯來啓用主機端口映射(host port mapping)和卷掛載(volume mount)。如下:

 

 

這個腳本放在所有的Docker主機上來優化部署,運維工程師登錄主機,傳遞參數給腳本,然後腳本負責剩下的工作。部署時間因此縮短,但還是需要手動編寫部署腳本的邏輯。此外,有一個新問題,當對通用腳本進行更新後,如何將更新的腳本應用到所有的Host上。通常來說,利用倉庫來進行代碼和腳本管理有以下優勢:代碼審查、測試、變更記錄、複用。需要人爲干預的越少越好。

 

理論上,部署應用的腳本可以與應用存儲在同一個代碼倉庫中,但實際中很難,最重要的一點就是開發會反對將運維相關的東西放在他們的Java代碼倉庫中。特別是一些bash部署腳本也包括Dockerfile本身。

 

這可以歸結爲一個不同部門的文化問題,如果可能的話,還是值得去嘗試的。雖然也可以把部署代碼或腳本單獨存放在其他倉庫中進行維護,但需要額外搞定應用代碼和部署代碼的同步問題。我們在此將以後面這種方式作爲例子。在ABC,每一個項目會單獨創建一個文件夾在獨立的代碼倉庫中來存放Dockerfile,部署腳本放在自己的目錄下。

 

Dockerfiles倉庫會遷出一份副本到Jenkins主機的公共目錄下(如:”/opt/abc/Dockerfiles”)。在構建Docker鏡像之前,Jenkins會檢查本地“docker”文件夾下是否存在Dockerfile。如果不存在,Jenkins會搜索Dockerfiles路徑,拷貝Dockerfile和腳本,再通過“docker build”構建鏡像。由於Dockerfile放在Master分支,有可能Dockerfile在某些情況下會超前(或落後)於應用程序配置,實際生產中,這在大多數情況下,不會造成問題。下面是從Jenkins 構建邏輯摘錄來的:

 

 

漸漸的,Dockerfile和對應的支持腳本被整合到應用的源代碼倉庫中。由於Jenkins優先在本地倉庫中進行查找,所以在整合過程中不需要進行額外的其他配置更改。在遷移完第一個Java應用後,倉庫拓撲如下:

 

 

我們在將Dockerfile和腳本存儲在獨立的倉庫時,遇到的一個問題是,當應用源代碼或者部署腳本發生變更時,都會觸發Jenkins重新構建鏡像。由於Dockerfiles倉庫包含了多個項目的代碼,所以在對某一個Dockerfile進行更新時,不希望觸發所有項目重新構建。解決方法:一個比較隱蔽的Jenkins配置項 Git plugin - Included Regions。 當啓用這個選項後,只有在倉庫中特定的文件夾發生變化時,纔會觸發鏡像構建。這使得我們可以將所有的Dockerfile保存在一個倉庫,只有當特定的內容在代碼庫中更新後觸發特定的Docker 鏡像構建。(相比之前觸發所有的項目重新構建)。

 

 

另一個問題是運維工程師必須在部署之前進行應用的鏡像構建,這會導致上線週期的延長,特別是在鏡像構建中出錯,需要開發介入。爲了減少這種情況,更好的支持持續部署,我們定義每一個提交的大分支版本都觸發鏡像構建。這要求每一個鏡像擁有唯一的版本標識而不只是依靠應用程序的版本標識字符串。我們最終採用應用版本字符-提交次數-提交SHA值


 

最終顯示的版本字符串如:1.0.1-22-7e56158。

在結束我們這次Dockerfile內容的討論之前,還有一些關鍵決策值得我們注意。在大規模使用容器之前,我們基本對此一無所知,這些關鍵點對容器集羣的穩定運行有着重要的作用。

 

  • 重啓策略  – 重啓策略允許用戶定義容器異常終止後的容器動作。容器重啓策略可以實現應用程序的故障恢復,一旦應用依賴的子服務從故障中恢復,應用容器即可恢復正常,這得益於應用容器對依賴容器服務可用性的持續監聽。長遠看來,用戶應該配置合適的規則使異常的容器能夠在新的主機上啓動,這將會節省一部分工作。目前,ABC默認容器重啓策略是“-restart always”,故障容器將會不斷重啓,直到容器恢復正常。一個簡單的重啓策略將會大大降低計劃內(和計劃外)主機重啓帶來的負面影響。
  • 資源配額  – 用戶可以通過運行時資源來限制配置容器最大可消耗的內存和CPU。資源配額不能防止主機容器的超額分配,但是可以防止容器內存泄漏和失控容器造成的影響。我們對內存比較敏感的容器分配比較大的內存配額(如,內存=8G)。資源配額雖然可能會造成單個應用的內存溢出、無法響應,但保證了主機和其它容器的正常工作。

容器策略和資源配置的協同配合將會帶來更高的容器集羣穩定性,同時降低故障影響,縮短故障恢復的時間。實踐過程中,有了這種保護機制,以往被用來故障恢復的時間,現在用在和開發團隊一起進行故障根本原因排查。

 

小結

 

我們最初從源代碼庫構建包含標識符的容器鏡像,接下來通過Docker 命令行工具寫成的腳本和參數來部署容器。同時梳理了我們環境中的容器部署代碼邏輯,強調了那些對有助於運維部門保持服務穩定運行的一些關鍵決策。

 

當前這個階段,鏡像構建和部署之間依然存在欠缺。部署維護工程師需要手動登錄到服務器執行部署腳本。我們已經比最初的時候前進了一大步,但還有很多內容可以自動化。當前所有的部署邏輯集中在一個腳本中,當開發人員需要安裝調試腳本,理清腳本邏輯變得很複雜。目前我們的腳本中也定義了許多環境參數來引入環境參數,查找某個環境變量對應的服務和添加新的環境變量是相對繁瑣並容易混淆。

 

在 下一篇文章,我們將通過解構通用的封裝腳本,對我們是如何解決這些痛處一探究竟,介紹如何使用Docker Compose讓部署邏輯更貼合應用。

溫馨提示

對Docker容器技術或容器生產實施感興趣的朋友歡迎加羣454565480討論。我們彙集了Docker容器技術落地實施團隊精英及業內技術派高人,在線爲您分享Docker技術乾貨。我們的宗旨是爲了大家擁有更專業的平臺交流Docker實戰技術,我們將定期邀請嘉賓做各類話題分享及回顧,共同實踐研究Docker容器生態圈。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章