天天看點

Shopify的Docker實戰經驗(二)如何用容器支援10萬的線上商店

本文講的是<b>Shopify的Docker實戰經驗(二)如何用容器支援10萬的線上商店,</b>【編者的話】Shopify是一個電子商務平台,提供專業的網上店面。目前的客戶超過12萬,包括GE、特斯拉汽車、GitHub等。作為首家市值超過10億美元的加拿大網絡公司,Shopify在歐美市場的影響力也與日俱增。Shopify是一個大型的Ruby on Rails應用,其産品伺服器能通過給1700個處理核心和6TB RAM配置設定任務來完成每秒處理8000多個請求。Shopify在其部落格上分享了系列内容來介紹他們的Docker使用經驗。

Shopify的Docker實戰經驗(二)如何用容器支援10萬的線上商店

這是系列文章的第二篇,講述Shopify如何使用Docker來支撐容器化的資料中心。這篇文章重點介紹當使用者通路Shopify商店門戶的時候我們底層的生産環境是如何建立出容器的。

在深入讨論建構容器的原理之前,先讨論下我們的動機。容器對于資料中心的作用可能類似于控制台對于遊戲的作用。在PC遊戲發展的早期階段,遊戲在玩之前一般都會要求顯示卡和聲霸卡驅動massaging。然而,遊戲控制台提供了不一樣的方式:

可預測:cartridge是自包含的:随時可用,無需下載下傳或更新。

快速:cartridge使用隻讀記憶體,是以可以非常快。

簡易:cartridge健壯并且被大範圍證明 - 僅需插上即可遊戲。

可預測、快速、簡易都是閃亮的優點。Docker容器提供了構模組化塊,可以将應用放到自包含,随時可運作的單元裡,這樣使得運作資料中心更為簡單,也更加靈活,就像cartridge帶給控制台遊戲的改進一樣。

要完成容器化的轉變需要開發和運維雙方面的配合。首先,需要和運維團隊溝通,確定容器能夠完全複制現在的生産環境。

如果你在OSX(或者Windows)上運作,部署到Linux上,需要使用虛拟機,比如Vagrant作為本地的測試環境。首先需要得到作業系統資訊和其上需要安裝的支援包。選擇符合生産環境(我們用的是Ubuntu 14.04)的基礎鏡像,拒絕任何非緊急的系統更新請求,誰都不想同時既進行容器化改進又要更新作業系統/包。

我們使用Chef管理生産節點。雖然可以簡單地在容器内部運作Chef,但是這會帶進一些不想複制到每個容器裡的服務(比如,日志索引和stats收集)。與其忍受重複,倒不如給每個Docker主機共享一個單獨的這些服務的拷貝。

建構“纖薄”容器的路徑要求把Chef請求轉換成Dockerfile(後來我們更換成了自定義的建構流程 - 不過這在另一篇文章裡讨論)。這樣的轉換也給了我們很好的機會去審查生産環境并記錄下真正需要的東西(可能需要些考古學知識)。盡可能的删除不需要的東西,并且在這一階段安排盡可能多的代碼審查。

這個過程其實沒有聽上去那麼痛苦。我們最後得到了一個125行,包含很多注釋的Dockerfile,它定義了Shopify所有容器共享的基礎鏡像。這個基礎鏡像包含25個包,涉及各種程式設計語言的運作環境(Ruby、Python、Node),開發工具(Git、Vim、Build-essential、Go)和一些常用庫。它還包括完成一些任務的實用腳本,比如,用優化的參數啟動Ruby,或者給Datadog發送事件。

應用程式也可以向這個基礎鏡像添加特殊的需求。即使這樣,我們最大的應用程式也隻是另外添加了兩個作業系統包,是以我們的基礎鏡像是非常精簡的。

當選擇将什麼服務放到容器裡時,先假定在一台主機上運作了100個小容器,然後,問自己是否真的需要運作100個這樣的服務,還是最好隻共享一台主機的服務。

以下是一些實際的例子,展示100法則如何影響我們的容器:

日志索引:日志對于診斷生産環境問題至關重要,在容器化的世界裡更為重要,因為檔案系統在容器退出後就消失了。我們盡量不要改變應用自己的日志行為(比如強制應用日志重新定向到syslog中),而是應該允許應用繼續記錄日志到檔案系統裡。運作100個日志傳遞代理看上去并不合理,是以我們建構了一個背景程式來處理一些核心任務:

在主控端器上運作,并訂閱Docker事件

當容器啟動時,配置日志索引器監控容器

當容器銷毀時,移除索引指令

注意有時在容器退出後需要延遲容器的銷毀進而確定所有的日志都建立了索引。

統計:Shopify進行多個層級的運作統計:系統,中間件和應用層面。統計資料通常是由代理轉發或者從應用代碼裡直接發出。

主機端系統監控代理能夠跨越容器界限,因為容器歸根到底就是個程序樹。是以可以共享一個系統監控器。

從以容器為中心的視角看,要考慮Datadog的Docker內建,這樣可以将Docker矩陣加入到主機端的監控代理上。

應用級别上,大部分情況都可以工作,因為它們要麼想要發送事件給StatsD,要麼直接和其他服務通信。定義容器的名字很重要,這樣日志裡才會記錄下有效名字。

100法則的使用需要一定的靈活性。有些情況下,隻需要給元件間編寫些“黏合器”。而另外一些情況下,通過配置就可以實作,也有些時候,需要重新設計。最終,你應該獲得一個容器,内含你的應用程式運作所需的所有東西,以及一個提供了Docker托管和共享服務的主機環境。

環境搭好後,接下來需要關注容器化應用本身。

在Shopify裡,我們傾向于隻做一件事情的“纖薄”容器,比如unicorn master和響應web請求的worker,或者響應某個特定隊列的Resque worker。“纖薄”容器允許細粒度按需擴充。比如,可以增加Resque worker的數量來檢查蠕蟲進而應對攻擊。

我們總結了一些将代碼放到容器裡的标準原則:

根目錄始終在容器/app目錄下的應用

服務公開在單一端口的應用

我們也發現一些git倉庫适合被容器化:

/container/files 包含的檔案樹是在容器建構時直接拷貝過來的。比如,請求某個應用日志的Splunk索引/container/files/etc/splunk.d/inputs.conf 檔案指向git倉庫。注意:這是一個微妙且重要的職責轉換 - 開發人員現在控制日志索引,而以前這是屬于運維領域的事情。

/container/complie是編譯應用的shell腳本,它輸出随時可運作的容器。建構這個檔案并且适應自己的應用是很複雜的。

我們用一個也能在本地運作的簡單的Makefile建構build。Dockerfile看上去像:

記住編譯階段的目的是輸出一個即刻可用的容器。Docker的重要優點之一就是極快的啟動時間,盡量不要在容器啟動時做額外的工作進而延長啟動時間。為了達到這個目标,我們需要了解整個部署流程。一些例子說明:

使用Capistrano将代碼部署到機器上,元件編譯已經在部署過程中完成了。将元件編譯挪到容器建構過程中進行,使得部署新的代碼僅僅需要幾分鐘,簡單快速。

Unicorn master啟動時需要詢問資料庫得到表類型。這不僅僅很慢,而且很小的容器空間意味着需要更多的資料庫連接配接。是以,可以考慮在容器建構的同時來做這個進而降低啟動時間。

我們的編譯環節包括以下邏輯步驟:

bundle安裝

元件編譯

資料庫啟動

在大多數情況下,應用運作在容器裡時的行為和它運作在非容器化環境下沒有什麼不同。是以,大多數我們标準的調試技術(strace、gdb、/proc檔案系統)在Docker主控端上也都适用。

還有些領域沒能像預期那樣工作,包括:

盡管我們運作的是瘦容器,也始終會有一個init程序(pid=1),它允許和監控工具,秘密管控,服務發現緊密內建,允許我們進行細粒度的健康檢查。

除了init程序,我們增加了ppidshim程序,在每個容器裡這個程序pid=2,它隻是簡單去啟動應用程序(pid=3)。ppidshim是為了讓應用程序不直接繼承于init(也就是說,ppid!=1),因為那樣會讓它們認為自己是背景程式而出錯。

最後程序層次結構是:

如果你要容器化,很可能需要改變現有的腳本,或者重寫一個新腳本,裡面包含調用docker run。預設地,docer run會發出signal到你的應用,也就意味着你需要了解應用是如何處理signal的。

标準UNIX規範是發送SIGTERM請求來正常關閉程序。確定你的應用是遵守這個規範的,因為我們發現不止一個應用,比如Resque,使用SIGQUIT來正常關閉,而使用SIGTERM來緊急關閉。幸運的是,Resque(&gt;1.22)能夠配置使用SIGTERM來觸發的關閉。

使用容器名來描述容器的工作内容(比如,unicorn-1,resque-2),并且将這個名字和機器的主機名結合使用,友善問題的跟蹤。最後的結果類似:runicorn-1.server2.shopify.com。

使用Docker的--hostname标志将主機名傳進容器,這使得大部分應用能使用正确的主機名。一些程式(Ruby)會使用短名(unicorn-1)而不是FQDN。

因為Docker管理/etc/resolv.conf,而現在的版本不允許随意改動,我們使用LD_PRELOAD來注入庫,這個庫攔截并且重載gethostname()和uname()函數。最終結果是監控工具釋出我們需要的主機名而不需要更改應用代碼。

我們發現建構能夠複制“純實體機”行為的容器的流程其實就是一個調試的過程。一旦你建構了穩健的容器之後就自然想要自動化整個建構過程。

一旦建構出穩定的build,就需要将這個容器放到一個中央系統資料庫裡。我們選擇搭建自己的系統資料庫以便提高部署速度,同時減少對外部的依賴。我們使用nginx反向代理來緩存GET請求,在Nginx之後,使用多個相同的标準python系統資料庫。拓撲看上去如圖:

Shopify的Docker實戰經驗(二)如何用容器支援10萬的線上商店

我們發現龐大的網絡接口(10 Gbps)和反向代理可以有效對抗當多個Docker主機申請同一個鏡像時引起的“驚群效應”。這種采用代理的解決方法允許我們同時運作多個系統資料庫,并且在某個系統資料庫崩潰的時候提供自動故障轉移。

如果按照我們的方法,那麼你會得到自動建構的容器,它們安全地存儲在中央系統資料庫裡,随時可以被用來部署。

我們的下一篇文章會講述如何管理應用,并深入探讨如何自定義建構流程來建立盡可能小的容器。

===========================

譯者介紹

崔婧雯,現就職于VMware,進階軟體工程師,負責桌面虛拟化産品的品質保證工作。曾在IBM WebSphere業務流程管理軟體擔任多年系統測試工作。對虛拟化,中間件技術有濃厚的興趣。

原文釋出時間為:2015-02-01

本文作者:崔婧雯

本文來自雲栖社群合作夥伴DockerOne,了解相關資訊可以關注DockerOne。

原文标題:Shopify的Docker實戰經驗(二)如何用容器支援10萬的線上商店