分散式系統設計:單點模式之挎斗模式
介紹
第一個單點模式就是挎斗模式,挎斗模式是由兩個容器組成的單節點模式。第一個是應用程序容器,它包含應用程序的核心邏輯,沒有這個容器,應用程序將不存在。除應用容器外,還有一個挎斗容器。挎斗的作用是增強和改進應用程序容器,通常不會有關於應用程序容器的知識。
在這種模式下我們可以以其最簡單的形式,使用挎斗容器來嚮應用程序容器添加功能,否則該容器可能難以改進(通常情況下)。 Sidecar容器通過原子容器組(例如Kubernetes中的Pod API對象)在同一台計算機上共同調度。此外,應用程序容器和挎斗容器共享大量資源,包括文件系統,主機名和網路的部分以及許多其他名稱空間。圖2顯示了這種挎斗模式的一般架構。
挎斗模式設計案例:向傳統服務添加HTTPS
例如,考慮一個遺留的Web服務。 多年前,當它建成時,內部網路安全並不是公司的優先事項,因此,應用程序僅通過未加密的HTTP服務處理請求,而不是HTTPS。 由於最近的安全事件,該公司已經要求所有公司網站使用HTTPS。 但是不幸的是這個應用程序的源代碼是用公司的構建系統的舊版本構建的,該系統已經不再起作用。這個HTTP應用程序以二進位文件形式在一個容器中運行。但是,將HTTPS添加到此應用程序的任務顯然更具挑戰性。團隊成員要在使用挎斗模式和試圖復活舊版系統並將應用程序的源代碼移植到新版系統之間作決定。
挎斗模式在這種情況下的應用很簡單。遺留的Web服務被配置為僅在本地主機(127.0.0.1)上提供服務,這意味著只有與伺服器共享本地網路的服務才能夠訪問該服務。通常情況下,這不是一個實際的選擇,因為這意味著沒有人可以訪問Web服務。但是,除了遺留容器之外,使用挎斗模式,我們添加一個nginx挎斗容器。這個nginx容器與傳統Web應用程序位於相同的網路名稱空間中,因此它可以訪問在本地主機上運行的服務。與此同時,此nginx服務可以在pod的外部IP地址上終止HTTPS流量,並將該流量代理到舊式Web應用程序(請參見圖3)。
由於此未加密流量只能通過容器組內的本地環回適配器發送,因此網路安全團隊確信數據是安全的。通過使用挎斗模式,團隊已經實現了舊版應用程序的現代化,而無需弄清楚如何重建新應用程序來提供HTTPS服務。
使用挎斗模式做動態配置
簡單地將流量代理到現有的應用程序並不是挎斗模式的唯一用途。另一個常見的例子是配置同步,許多應用程序使用配置文件來參數化應用程序,它可能是一個原始文本文件或XML,JSON或YAML結構的東西。許多預先存在的應用程序會假定文件存在於當前文件系統中並從中讀取其配置。但是,在雲環境中,使用API更新配置通常非常很常見。它允許我們通過API動態地推送配置信息,而不是手動登錄到每台伺服器,並使用命令式命令更新配置文件。對這種API的需求既受易用性的影響,又受諸如回滾等自動化的影響,使得配置(和重新配置)變得更加安全和簡單。
與HTTPS的情況類似,可以編寫新的應用程序,期望配置是應該使用雲API獲得的動態屬性,但適配和更新現有應用程序可能會具有挑戰性。幸運的是,挎斗模式可以用來提供新功能,在不改變現有應用程序的情況下增強傳統應用程序的功能。對於圖4所示的挎斗模式,同樣有兩個容器:作為服務的應用程序的容器和作為配置管理器的容器。這兩個容器組合在一起成為一個容器,它們共享一個目錄——配置文件的位置。
當傳統應用程序啟動時,它會按照預期從文件系統載入它的配置。當配置管理器啟動時,它檢查配置API並查找本地文件系統和存儲在API中的配置之間的差異。如果存在差異,配置管理器會將新配置下載到本地文件系統,並向舊應用程序發送信號,指示它應該使用此新配置重新配置自身。這種通知的實際機制因應用而異。有些應用程序實際上在查看配置文件的變化,而其他應用程序則響應SIGHUP信號。在極端情況下,配置管理器可能會發送一個SIGKILL信號來中止傳統應用程序。一旦中止,容器業務流程系統將重新啟動舊應用程序,此時它將載入其新配置。與向現有應用程序添加HTTPS一樣,此模式說明挎斗模式如何幫助將預先存在的應用程序適用於更多雲本地方案。
模塊化應用容器
到這裡,或許你認為挎斗模式存在的唯一原因是為了適應遺留的應用程序,而不對原始源代碼進行修改。雖然這是該模式的常見用例,但使用sidecars設計系統還有許多其他動機。使用挎斗模式的其他主要優點之一是作為挎斗使用的組件的模塊化和重用。在部署任何實際可靠的應用程序時,你可以調試或使用其他應用程序管理所需的功能,例如讀取集群中不同進程佔用的資源,類似於top命令行工具。
提供這種內省的一種方法是要求每個開發人員開發提供讀取資源使用的HTTP /topz介面。為了使這更容易,你可以實現這個webhook作為一個語言特定的插件,開發人員可以簡單地鏈接到他們的應用程序。但即使以這種方式完成,開發人員也不得不選擇將其鏈接起來,並且組織將被迫為其想要支持的每種語言都實現這樣的介面。除非採用極端嚴謹的做法,否則這種方法必然導致各種語言之間的差異以及在使用新語言時缺乏對功能的支持。相反,這個topz功能可以部署為與應用程序容器共享進程ID(PID)名稱空間的挎斗容器。這個topz容器可以覆蓋所有正在運行的進程並提供一致的用戶界面。此外,您可以使用編排系統自動將此容器添加到通過編排系統部署的所有應用程序,以確保為基礎結構中運行的所有應用程序提供了一組可用的工具。
當然,對於任何技術的選擇,這種基於模塊化容器的模式與將代碼迭代到你的應用程序之間是存在權衡的。基於庫的方法總是會根據您的應用程序的具體情況而定製。這意味著它在性能方面可能效率較低,或者API可能需要適應環境。就像購買現成服裝與定製服裝之間的區別,定製的時尚將永遠適合你,但它需要更長的時間才能做成,花費也更多。與衣服一樣,對於我們大多數人來說,在編碼方面花費達到更通用的解決方案是合理的。例如如果你的應用程序需要極高的性能,你可以隨時選擇手寫解決方案。
用挎斗構建一個簡單的PaaS
挎斗模式可以更多地用於適配和監控。它也可以作為一種手段,以簡化的模塊化方式為你的應用程序實現完整的邏輯。舉個例子,想像一下構建一個圍繞git工作流構建的簡單的平台即服務(PaaS)。一旦部署了PaaS,只需將新代碼推送到Git存儲庫,就可以將代碼部署到正在運行的伺服器上。我們將看到挎斗模式如何使這個PaaS非常簡單直觀。
如前所述,在挎斗模式中有兩個容器:主應用容器和挎斗。在我們簡單的PaaS應用程序中,主容器是實現Web伺服器的Node.js伺服器。 Node.js伺服器進行了檢測,以便在更新新文件時自動重新載入伺服器。這是使用nodemon工具完成的。
sidecar容器與主應用程序容器共享一個文件系統,並運行一個簡單的循環,使文件系統與現有的Git存儲庫同步。 Node.js應用程序和Git同步挎斗共同被安排並部署在一起以實現我們簡單的PaaS(圖5)。 一旦部署完畢,每次將新代碼推送到Git存儲庫時,代碼都會由挎斗自動更新並由伺服器重新載入。
設計用於模塊化和可重用性的挎斗
在本文中我們詳細介紹的所有挎斗實例中,最重要的一個主題是每一個都是模塊化的,可重用的組件。 要獲得成功,挎斗應該可以在各種應用和部署中重複使用。 通過實現模塊化重用,挎斗可以顯著加速應用程序的構建。
但是,這種模塊化和可重用性,就像在高質量軟體開發中實現模塊化一樣,需要關注和謹慎。 特別是,你需要重點關注三個方面:
- 參數化您的容器
- 創建容器的API
- 記錄你的容器的操作
參數化容器
參數化你的容器是你可以做的最重要的事情,使你的容器模塊化和可重用,無論它們是否是sidecars,因為sidecars和其他附加容器的參數化特別重要。
當我們說「參數化」時,意思是什麼?在你的程序中考慮你的容器作為一個函數。它有多少個參數?每個參數代表一個輸入,可以為特定情況定製通用的容器。例如,考慮先前部署的SSL挎斗容器。一般而言,它可能至少需要兩個參數:第一個是用於提供SSL的證書的名稱,另一個是在本地主機上運行的「legacy」應用程序伺服器的埠。沒有這些參數,很難想像這個挎斗容器可用於各種應用。本文介紹的所有其他的挎斗都有類似的參數。
現在我們知道了我們想要開放的參數,我們如何將它們暴露給用戶,以及如何在容器內使用它們。有兩種方式可以將這些參數傳遞給容器:通過環境變數或命令行。儘管兩者都可行,但我通過環境變數傳遞參數的更好些。將這些參數傳遞給挎斗容器的例子是:
docker run -e = PORT = <port> -d <image>
當然,將值傳遞給容器只是工作的一部分。另一部分實際上是在容器內使用這些變數。通常情況下,使用簡單的shell腳本來載入附屬容器提供的環境變數,並調整配置文件或對基礎應用程序進行參數化。
例如,您可能會將證書路徑和埠作為環境變數傳遞:
docker run -e = PROXY_PORT = 8080 -e = CERTIFICATE_PATH = / path / to / cert.crt ...
在你的容器中,你可以使用這些變數來配置一個nginx.conf文件將Web伺服器指向正確的文件和代理位置。
定義每個容器的API
鑒於你正在參數化你的容器,很明顯你的容器定義了一個「函數」,當容器執行時被調用。這個函數顯然是你的容器定義的API的一部分,但是這個API還有其他的一些部分,包括容器對其他服務的調用以及傳統的HTTP或者容器的其他API。
在考慮定義模塊化可重用容器時,重要的是要認識到,容器如何與其進行交互的所有方面都是該可重用容器定義的API的一部分。與微服務領域一樣,這些微容器依靠API來確保主應用容器和挎斗之間存在乾淨的分離。此外,API的存在是為了確保隨著後續版本的發布,挎斗的所有消費者都將繼續正常工作。同樣,為挎斗配備乾淨的API使挎斗的開發人員能夠更快地迭代,因為他們對作為挎斗的一部分提供的服務有清晰的定義(並希望進行單元測試)。
要看到這個API為什麼重要的具體示例,請考慮我們之前討論的配置管理挎斗。這個挎斗的一個有用的配置可能是UPDATE_FREQUENCY,它表示配置應該與文件系統同步的頻率。很明顯,如果稍後將參數名稱更改為UPDATE_PERIOD,則此更改將違反挎斗的API,並且顯然會為某些用戶打破。
雖然這個例子很明顯,但更細微的變化可以打破挎斗API。想像一下,例如,UPDATE_FREQUENCY最初在幾秒鐘內就取得了一個數字。隨著時間的推移和用戶的反饋,挎斗開發人員確定,針對大時間值(例如分鐘)的指定秒數是令人討厭的用戶,並將參數更改為接受字元串(10m,5s等)。因為舊的參數值(例如10)不會在這個新的方案中解析,所以這是一個突然的API改變。假設開發者仍然期待這一點,但是在沒有單位解析的情況下,他們將數值解析為之前解析為秒的毫秒數。即使這種變化,儘管不會導致錯誤,但是對於挎斗而言,API被破壞了,因為它會導致更頻繁的配置檢查,並相應地增加雲配置伺服器上的負載。
我希望這個討論已經告訴你,對於真正的模塊化,你需要非常清楚你的挎斗提供的API,並且對這個API的「突破」改變可能並不總是像改變參數名稱一樣明顯。
文檔化容器
到目前為止,你已經看到你如何參數化你的挎斗容器,使它們模塊化和可重用。您已經了解了維護一個穩定的API的重要性,以確保你不會為用戶打破sidecars。但是,構建模塊化,可重複使用的容器還有最後一步:確保人們可以首先使用它們。
與軟體庫一樣,構建真正有用的東西的關鍵是解釋如何使用它。如果沒有人能夠弄清楚如何使用它,那麼構建靈活可靠的模塊化容器幾乎沒有什麼用。可悲的是,很少有正式的工具可用於文檔化容器圖像,但有一些最佳實踐可以用來實現這一點。
對於每個容器鏡像,查找文檔最明顯的地方是容器構建的Dockerfile。有一些Docker文件已經記錄了容器的工作方式。其中一個例子是EXPOSE指令,它指示圖像偵聽的埠。即使EXPOSE不是必需的,將它包含在Dockerfile中也是一種很好的做法,並且添加一個注釋來解釋在該埠上正在監聽的內容。例如:
... # Main web server runs on port 8080 EXPOSE 8080 ...
此外,如果使用環境變數來對容器進行參數化,則可以使用ENV指令為這些參數設置默認值並記錄其用法:
# The PROXY_PORT parameter indicates the port on localhost to redirect # traffic to.ENV PROXY_PORT 8000
最後,您應該始終使用LABEL指令為鏡像添加元數據;例如,維護者的電子郵件地址,網頁和圖像版本:
LABEL「org.label-schema.vendor」=「name@company.com」 LABEL「org.label.url」=「http://images.company.com/my-cool-image」 LABEL「org.label-schema.version」=「1.0.3」
這些標籤的名稱是從標籤架構項目建立的架構中繪製的。該項目正在努力建立一套共享的知名標籤。通過使用鏡像標籤的通用分類標準,多種不同的工具可以依靠相同的元信息來可視化,監控和正確使用應用程序。通過採用共享條款,您可以使用社區開發的一套工具,而無需修改鏡像。當然,您還可以在鏡像上下文中添加任何其他標籤。
總結
在本文中,我們介紹了將容器組合在一台機器上的挎斗模式。在挎斗模式中,挎斗容器增強和擴展應用程序容器以添加功能。當更改應用程序的成本過高時,Sidecars可用於更新現有的舊應用程序。同樣,它們可以用來創建標準化常用功能實現的模塊化實用程序容器。這些實用程序容器可以在大量應用程序中重用,從而提高一致性並降低開發每個應用程序的成本。隨後的章節將介紹了其他單節點模式,演示模塊化可重用容器的其他用途。
推薦閱讀: