有“容”乃大:Docker容器,十萬網店輕鬆託管

摘要:Shopify是一家爲數十萬家網店提供解決方案的公司,網站主要的框架是Ruby on Rails,爲了更易擴展和管理業務,開始使用Docker和CoreOS技術。Shopify軟件工程師Graeme Johnson介紹瞭如何在生產環境中使用容器技術。

【編者按】 Shopify是一家提供電商網店解決方案的公司,目前服務的網店數有10萬家以上(Tesla 也是它的用戶)。網站主要的框架是Ruby on Rails,1700個內核和6TB RAM,每秒可以響應8000個用戶請求。爲了更易擴展和管理業務,Shopify開始使用Docker和CoreOS技術,Shopify軟件工程師Graeme Johnson將撰寫一系列文章分享其經驗,本文是系列中的第二篇,重點介紹了Shopify在生產環境中是如何使用容器技術。


這是第一篇召集羣裏(230365411)的同學們一起翻譯的文章,謝謝大家的義務幫助,本次參與翻譯的同學包括王大隆、孫宏亮、吳京潤、吳方洲、周敬濱、趙文舉。我們也歡迎你的加入!

以下爲翻譯原文:

爲什麼使用容器技術?

在我們深入到構建容器的機制之前,首先討論一下這麼做的動機。容器在數據中心擁有的潛能就像遊戲機之於遊戲。在PC遊戲的初期,通常在你開始玩一款遊戲之前,都需要安裝視頻或音頻驅動。然而,遊戲機提供了不同的體驗,與Docker很類似:

  • 可預見性:遊戲帶它自帶遊戲,隨時可運行,不需要下載或更新;
  • 迅速:遊戲帶採用只讀存儲器,從而獲得瞭如同閃電的速度;
  • 簡單:遊戲帶是健壯的,並且很大程度上擁有兒童防護措施,它們是真正的即插即用;
  • 可預見性、迅速、簡單,在擴展規模方面都是好東西。 Docker容器提供了構建模塊,使我們的數據中心更容易運行、更適合使應用成爲獨立模塊,隨時就緒的單元就像是遊戲機上的遊戲帶。

引導程序

爲了努力實現容器化,你需要同時具備開發和運維技能。首先,與你的運維團隊交流,你需要確信你的容器能夠完全複製於你的生產環境。如果你運行在OSX(或windows)桌面操作系統,但部署在Linux上,使用一個虛擬機(比如Vagrant)作爲本地測試環境。第一步,更新你的操作系統和安裝支持包。挑選一個基礎鏡像匹配你的生產環境(我們使用Ubuntu14.01),不能出差錯——你不會想處理容器化和操作系統/包在同一時間升級所帶來的麻煩。

選擇容器類型

在容器類型方面Docker爲你提供了足夠的選擇空間,從一個單進程的“瘦”容器到一個讓你覺得類似於傳統虛擬機的“胖”容器(例如,Phusion)。

我們選擇去遵循“廋”容器方式,從容器內部去除一些無關的組件。雖然從兩種方式作出選擇是困難的,但是我們更青睞於小的那種,因爲容器簡單化會消耗更少的CPU和內存。這種方式被詳細的說明在 Docker blog中。

環境配置

在生產環境中,我們使用Chef這一部署工具來管理系統的各個節點。這樣的話,我們可以輕鬆做到在一個容器之中運行Chef,然而我們並不希望某些服務在每一個容器中都運行,比如:日誌的索引服務,運行狀態採集服務等。而Chef的使用無疑使得很多服務都會重複安裝在不必要的容器中,由於無法忍受以上徒勞的重複工作,我們選擇在每一臺運行Docker的宿主機上共享同一份這些服務的副本。

如何將容器做得輕量級,關鍵是:將Chef部署工具的運行腳本轉換爲一個Dockerfile(這部分內容,我們後來將其替換爲一個自定義的Docker build流程,之後的文章會涉及)。Docker的誕生,可以說是天賜良機,使運維人員評估內部的生產環境,並回顧以及整理整個系統生命週期中到底需要什麼。在這一環節中,對於系統的的割捨請儘量無情,同時也要保證在code review過程中儘量做到謹慎。

其實,整個過程,並沒有聽起來那麼艱難。最終,我們團隊以一個125行Dockerfile的形式告終,而該Dockerfile則是定義了在Shopify上所有容器需要共享的基礎鏡像。該基礎鏡像包含了25 個包,這些包中包括跨度較大的編程語言運行時(Ruby、Python和Node),還有多種開發工具(Git、Vim、build-essential和Go),也有一些需要共享使用的庫文件。同時,基礎鏡像中還包含了一系列工具腳本用以任務的運行,比如通過調整參數來啓動Ruby,或者向Datadog發送監控數據等。

在以上環境下,我們的應用可以很隨意在這個基礎鏡像上添加自身的特定需求。儘管如此,我們最大的應用也僅僅是添加了一些操作系統的依賴包,所以總體來講,我們的基礎鏡像還是相對簡潔精幹的。

容器的100定律

在選擇將何種服務容器化時,可以首先假設你有100個小容器運行在同一個host上,然後想一下是否真的有必要運行100個服務的副本,還是大家共享一個單獨的host更好。

以下是一些實例說明我們如何根據100定律來決定如何使用容器的:

日誌索引:日誌對於診斷錯誤至關重要,尤其在容器退出,文件系統消失後顯得更爲重要。我們特意避免了修改應用程序的日誌行爲(比如強制它們重新定向到系統日誌),並且允許它們繼續寫日誌到文件系統。運行100個日誌代理似乎是錯誤的做法,所以我們創建一個後臺程序去處理以下重要任務:

  • 運行在host端,並訂閱Docker event;
  • 在容器啓動時,配置日誌索引來監視容器;
  • 容器銷燬時,刪除索引指令。
  • 爲了確保容器退出時,所有的日誌都被編入索引,你可能需要稍微延遲容器的銷燬。

統計:Shopify在幾個級別(系統,中間件和應用程序)上都生成了運行時統計,得到的結果通過代理或應用程序代碼轉述。

  • 許多我們的統計結果可以通過StasD傳輸,幸運的是我們可以配置Datadog的host端去接收容器傳來的流量,通過適當的配置,就可以將StasD接收的地址傳給容器;
  • 由於容器從本質上來說就是進程樹,所以一個host端的系統監控代理可以看到容器邊界共享一個單一的系統監控也是自由的;
  • 從一個更加以容器爲中心的角度出發,來考慮Docker和Datadog的集成,會添加Docker metrics到host端的監控代理;
  • 應用級別的metrics大多數運行得還可以,它們或者通過StasD發送事件,或者直接和其它服務對話。爲一個容器指定名字很重要,這樣一來metrics也更容易讀。

Kafka:我們使用Kafka來實時處理從Shopify堆棧到合作伙伴的事件。我們使用Ruby on Rail代碼來發布Kafka事件,生成信息,然後放到SysV消息隊列。一個Go語言寫的後臺程序會在隊列中取出消息發送給Kafka。這減少了Ruby進程的時間,我們也能更好地應對Kafka服務器的事故。有一點不好的是,SysV消息隊列是IPC namespace的一部分,所以我們不能用來連接container:host。我們的解決方案是在host上添加一個socket端,用來將消息放到SysV隊列。

100定律的使用需要一定的靈活性。一些情況下,僅僅需要寫一下組件的“黏合器”,也可以通過配置來達到目的。最終,你應該獲得一個容器,內含你的應用程序運行所需的東西,以及一個提供了Docker託管和共享服務的主機環境。

將你的應用容器化

隨着環境的就緒,我們可以把注意力轉到程序的容器化上來。

我們更傾向於thin container能準確地做一件事。例如一個unicorn(Unicorn是一個Unix和局域網/本地主機優化的HTTP服務器)master和工作線程服務web請求,或者一個Resque(Resque使用Redis創建後臺任務,存儲進隊列,並隨後執行。它是Rails下最常用的後臺任務管理工具之一)工作線程服務一個特定的隊列。thin container允許細粒度的縮放比例(一般是指遠程方法調用的接口的顆粒大小),以滿足需求。例如,我們可以增加檢查垃圾郵件攻擊響應的 Resque工作線程的數目。

我們發現採取一些標準約定對於在容器中的代碼佈局會有用處:

  • 應用程序總是root在容器內部的/app下;
  • 應用程序通常通過單個端口發佈服務;

我們還確立了一些容器化git repo(repo封裝了對git的操作)的約定: 

  • /container/files 擁有一個在其建立時就被直接複製進容器的文件樹,舉個例子,請求 Splunk 索引的應用程序日誌,它添加/container/files/etc/splunk.d/inputs.conf 文件進你的git repo就足夠了(控制日誌索引的責任轉移到開發者,這是一個微妙而重大的轉變,過去這都是運維管理員的工作);
  • /container/compile是一個 shell 腳本,編譯您的應用程序,並生成一個隨時可運行的容器。建立此文件並適應您的應用程序,是最複雜的地方;

  • /container/roles.json保存命令行以機器可讀的形式用來運行該工作負載。很多我們的應用程序以多個角色,運行相同代碼庫,一些處理 web 流量同時處理後臺任務。這部分的靈感來自 Heroku 的 procfile。以下是一個示例的roles.json文件:

我們用一個簡單的Makefile驅動建立,也可以本地運行。我們的 Dockerfile 看起來像這樣:取消編譯階段的目標是產生一個是即刻準備運行的容器。Docker關鍵的優勢之一就是在容器啓動時超級快速啓動而不被損壞因爲額外的工作。爲了實現這一目標您需要了解您的整個部署過程。 舉幾個例子:

  • 我們正在使用Capistrano(Capistrano是一種在多臺服務器上運行腳本的開源工具,它主要用於部署web應用)將代碼部署到機器,並且asset編譯以前發生的事情作爲部署的一部分。通過移動資產編譯到容器裏生成,部署新代碼由更簡單、快幾分鐘。
  • 我們的unicorn 主機啓動來從table 模型中獲取數據(Table 對象代表一個 HTML 表格。)不只這是緩慢的,而且我們更小的容器大小意味着將需要更多的數據庫連接。另一方面,它是可能做到這一點(獲取數據)的在容器建立的時候,以加速啓動。

在本例中,編譯階段包括以下的邏輯步驟:

  • bundle install
  • asset compile
  • database setup

爲了保持這一公告(郵件)的大小合理,我們簡化了一些細節。密鑰管理是一個主要的細節,我們還沒有在這裏討論。不要將其登記入源碼管理。我們已經獲取了用來加密密鑰的代碼,致力於該主題的博客帖子很快就會來了。

調試和細節

運行在容器中的應用程序和容器之外的表現絕大多數情況是相同的。此外,你的大多數調試工具(例如:strace,gdb,/proc/filesystem)是運行在Docker所在的host上。還有大家熟悉的工具nsenter或nsinit,可以用他們掛載到一個運行中的容器裏進行調試。

docker exec,作爲docker 1.3.0版本中提供的新工具,能夠被用來向運行的容器中注入進程。但是,不幸的是,如果你注入進程需要root權限,你還是需要通過nsenter,有些地方的情況可能不會像人們預料的那樣。

進程分層

儘管我們運行輕量級的容器,仍然需要初始化進程(pid=1)從而與監控工具,後臺管理,服務發現等做到緊密集成,也能給我們細粒度的健康監測。

除了初始化進程,我們在每個容器中添加了一個ppidshim進程(pid=2),應用程序進程的pid=3。由於ppdishim進程的存在,應用程序沒有直接繼承自初始化進程,避免它們以爲自己是後臺守護進程,造成錯誤的後果。

最終的進程層次是這樣的:

Signals

如果你在使用容器技術,你很可能會修改現有的運行腳本或寫一套新的腳本,其中包含了docker run的調用。默認的情況下,docker run會代理signal到你的應用程序,所以你必須理解應用程序是如何解讀signal的。

標準的UNIX做法是發送SIGTERM去請求有序地關閉一個進程。爲了確保應用程序遵守這個慣例,Resque使用SIGQUIT來有序地關閉進程,SIGTERM來緊急關閉進程。幸運的是,Resque可以被配置使用SIGTERM來優雅地關閉進程。

Hostnames

我們選擇容器名稱來描述工作負載(例如 unicorn-1, resque-2),並結合這些主機名來進行簡單追溯。最終的結果會是這樣:unicorn-1.server2.shopify.com.我們使用Docker的主機名標籤將結果傳入容器中,這使大多數應用程序報告出正確的值。一些程序(Ruby)詢問主機名來得到縮略名(unicorn-1),而不用詢問預期的FQDN。由於Docker管理着/etc/resolv.conf 文件,我們當前版本不允許任意變更,所以我們通過LD_PRELOAD,重寫了gethostname()和uname()的方法,並注入到library中。最終結果會是,監控工具發佈我們想要得到的主機名卻不用去更改應用程序代碼。

註冊和部署

我們發現構建容器的過程中複製‘bare metal’的行爲相當於一個不斷調試的過程。如果你是理智的的,你肯定想自動化的去構建容器。

爲了每個人都能push,我們使用github commit hook去觸發一個容器的構建,構建過程中我們會提交狀態日誌來判定是否構建成功。我們使用git提交SHA到容器的“docker tag”,所以你能準確的看到什麼樣的代碼版本被包含在容器中。爲了更容易被腳本調試和使用,我們同樣也把SHA放到容器裏的一個文件中(/app/REVISION)。

一旦你的構建是正常的,你會想把容器push到一箇中央的註冊表倉庫。爲了提高部署速度和減少外部依賴我們選擇運行數據中心中自己的庫。 我們在Nginx反向代理(其中緩存了GET請求的內容)背後運行多個標準Python註冊表的副本,如下圖所示:


當多個Docker主機請求相同的鏡像時,我們發現大型網絡接口(10 Gbps)和反向代理,在解決”thundering herd“這樣的代碼部署相關的問題是很有效的。代理方法還允許我們運行多種類型的庫,並在發生故障的情況下,提供自動故障轉移。

如果遵循這個藍圖,你可以自動化去構建容器,並且安全地把容器存儲在一箇中央倉庫註冊表中,這些都可以融入你的部署過程。

在本系列的下一篇文章,作者將描述如何管理應用程序的祕密。

原文鏈接:Docker at Shopify: How we built containers that power over 100,000 online shops(編譯/ CSDN-Docker字幕菌羣(王大隆、孫宏亮、吳京潤、吳方洲、周敬濱、趙文舉) 審校/周小璐)


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