雲原生(Cloud Native)不僅僅是趨勢,更是現在進行時,它是構建現代的,可彈性伸縮的,快速迭代的計算網絡服務的事實標準。其中容器編排系統Kubernetes和容器是基石。所以每個工程師都需要學習和了解他們。學習過程中,很多工程師可能會問:為什麼Pod而不是容器是K8s部署的最小單位?基於K8s設計分布式系統有沒有什麼套路?本文針對這些問題,並參考K8s創始人的很多文檔,給出了解答。本文適合進行研發工作2到3年的同學,對架構設計比較感興趣,有一定架構設計意識,同時對容器(Docker)和容器編排系統(Kubernetes)有一定了解。希望可以通過此文,讓同學們更深入的了解到分布式容器系統中的幾種常見模式,以便以後更好的設計和實現雲原生的分布式系統。首先介紹一篇論文,標題是《Design patterns for container-based distributed systems》,作者是Brendan Burns和David Oppenheimer,論文發表於2016年,是雲原生領域系統設計的代表作。第一作者Brenda Burns,相信熟悉雲原生領域的同學都認識他,他之前是Google的工程師,是Kubernetes的三位創始人之一,他在這項目中負責系統設計,很多關鍵的設計決策都是出自他之手,包括聲明式,Pod等。在論文中,他介紹了基於容器的分布式系統中出現的幾種設計模式:單容器模式,單節點多容器模式,以及多節點模式。像之前的面對對象模式一樣,這些分布式系統中的pattern可以簡化開發,並使使用他們的系統更加可靠。他感言,在google做了很多年的分布式系統設計,從web search做到後來的cloud系統,基本都是從頭做起,很費力,因為沒有什麼成熟可重用的模塊;但是進入雲原生時代,隨着容器(docker)和容器編排(K8s)的出現,極大的改變了分布式系統的設計和開發,從而為大規模的重用提供了巨大的可能。系統設計是隨着計算機體系的發展而發展的,計算機系統從單機發展到Client/Server再到Distributed System,而系統設計也從單一的算法Algorithm(例如Quick sort)發展到OOP(面對對象的編程語言),再發展到現在容器系統。Design Pattern是a repeatable solution to a problem,即對軟件設計中普遍存在(反覆出現)的各種問題所提出的解決方案,類似圍棋中的定式(圍棋愛好者應該很熟悉)。在GANG of Four的名著《Design Patters》中,對用面對對象語言實現的,針對編程語言中Interface級別的各種Design Pattern做了很深入的解釋。其中一些設計原則,例如Single Responsibility對後續的系統設計也是非常適用。為什麼我們需要使用Design Pattern?因為絕大多數我們在系統開發中碰到的問題是相似的,可以借鑑別人的經驗,所以站在巨人的肩上作創新;其次Design Pattern給我們提供了共享的概念和詞彙,很容易溝通,例如我們一說起factory,我們就知道這是一個用來創建其他對象的對象;還有我們可以build可重用的組件。Design Pattern的核心:解耦(Decouple)和重用(Reuse)。這兩個核心原則貫穿系統設計的始終,不管我們是基於一種編程語言,使用接口來實現,還是在分布式系統設計中。分布式系統中之前很少有pattern,mapreduce算是一個,但是在容器和容器編排成為主流後,有了大量的可重用的組件,也總結出了大量的pattern,單容器,單節點多容器,多節點等。限於篇幅,本文不介紹多節點容器設計模式,感興趣的同學可以參考書後的資料。先說第一個pattern,Single-Container Pattern先從最小的組件容器開始說起,容器簡單來說,就是把應用程序和它所需要的依賴庫打成一個包。容器(Docker)的出現,極大改變了程序打包和部署的方式,更是徹底改變分布式系統設計的最基礎組件。Container就好比OOP Java編程語言中的Object(Class),是容器分布式系統的最基礎對象。有人做過一個簡單的類比,在java語言中,最基本的對象載體是Class,class被運行起來就是Object,而在容器系統中,最基本的對象載體是Container Image,當被運行起來後就是Container。Java對象有自己的初始化機制,Container中也有自己的Init Container機制。Java對象有銷毀機制,Container也有preStop函數來優雅的停止。在現實的設計中,需要把一個應用拆為多個容器來實現,這麼做的理由有三個:1. 針對資源建立邊界(不同的容器需要不同的CPU和內存,根據實際需要進行限制,而且不同容器間資源隔離,互不影響。2.建立團隊歸屬邊界,即一個Container有一個敏捷團隊來own,最好6到8人。3.提供興趣隔離(separation of concern)。容器的作用和職責應該滿足Single Responsibility的原則,按照Domain Model Design的原則來進行設計,這樣容易理解,也容易測試、更新和部署。那麼在設計用於生產環境的容器的時候,需要設置內存和CPU的最高和最低限制,同時需要設置Liveness Probe和Readiness Probe。在設計的時候,需要重點考慮:解耦和重用。不斷的問自己幾個問題:我的容器足夠解耦了嗎?是否還可以拿出部分來作為獨立的容器?這些容器是否可以很方便的被重用到其他地方?再說多個容器組合成的Multi-Container Pattern同學們在學習K8s的過程中,肯定會有疑問:為什麼Pod(含有一個或者多個Container)是最小的部署單元,而不能直接是容器。這裡就要涉及到K8s中一個非常精巧的設計了。Pod是一組共享生命周期,並部署在同一個節點的容器的組合,他們可以通過共享的volume/network和IPC來進行通訊。之所以不是一個單一容器,而是多個容器來完成特定功能的原因在於:這些容器要完成的職責不同,根據單一職責(single responsibility)的原則,他們應該屬於不同的組件;其次因為職責不同,維護他們的team也不同,迭代周期也不一樣;最後其中一些容器是可以被復用在其他的環境中的。所以從「解耦」和「復用」的設計原則出發,Kubernetes通過增加一個虛擬層即POD,給系統設計帶來了極大的靈活性,同時也產生了多種設計模式。即在一個POD中除了抗流量完成業務的容器(文中稱之為app container)外,還存在其他的輔助容器,可以分為兩類:1. Init Container 2. Sidecar container。就想Java語言中Object有初始化函數一樣,Pod中的Init Container起的作用也是用來初始化。當app container需要滿足一些前置條件才能啟動,例如它依賴一些外部服務db service ready才能啟動,或者需要初始化的更新文件(例如從github clone最新版本的文件)。我們來看一個簡單的例子,如上圖。其中myapp-container是app container,它是來執行業務邏輯的,它的啟動依賴後台的mydb和myservice兩個服務。所以有兩個init containers,他們分別執行nslookup來檢查依賴服務是否已經啟動,如果沒有啟動,等待2秒之後再檢查,如果已經啟動,則順利啟動自身容器,然後app container再啟動。能看出這個例子中,app container利用init container來force wait,直到依賴的兩個後台服務啟動之後再啟動。這樣app container的啟動邏輯就無需關心這兩個依賴服務是否ready。使用Init Container的注意事項如下:1. Init container的執行順序是在pod啟動過程中,最先執行,而且是順序執行,即一個init container執行結束後,再執行下一個init containers。他沒有readiness probe check,應該是邏輯簡單,而且執行快速的。2. Init Container也是container,它也占CPU/內存系統資源,所以在計算資源消耗的時候,需要把它也加進去,不然調度的時候可能會有問題。所謂sidecar,就是類似這種摩托車。在K8s中,sidecar container是和app container同時啟動,並且有自己的職責,並能在別的地方進行復用的容器。Sidecar container和app container之間共享磁盤/網絡/IPC等,我們來看一個典型的例子。App Container是一個web server,sidecar container定期從github sync代碼下來,兩者通過Pod的volume來共享文件。這樣做的好處是把從github定期sync代碼的邏輯剝離出來,成為一個可以重用的模塊,並且能用到其他的場合。而app container只需要單純的做web服務就好,不需要考慮sync之類的邏輯。什麼時候考慮使用sidecar呢?當這兩個container需要同時部署,但是各有自己的職責,而且可以分別去迭代和演進,而且有重用的可能性。那麼什麼時候不適合sidecar呢?當這兩個container有不同的擴容需求時候,即兩者需要獨立的擴容時候,不要sidecar這種模式;另外,兩者的通信可能會帶來一些網絡的消耗,帶來一定的延遲,如果這點延遲是業務無法接受的話,也不要使用sidecar。Ambassadaor Design Pattern它是一種特殊的sidecar,其實就是app container的一個proxy,它來接流量,然後進行處理,然後把流量轉發給app container,讓其完成真正的商業邏輯。例如:我想給一個web server加上認證邏輯或者SSL,但是我又不想改動原來的web server或者改動比較困難,我可以在其所在pod內再部署一個proxy,讓這個proxy來處理這些認證的邏輯,而原有的app container無需任何改動。如下圖所示,完整的例子在https://medium.com/@lukas.eichler/securing-pods-with-sidecar-proxies-d84f8d34be3e。
此外,ambassador pattern還可用於shard a service或者A/B 測試。另外最近新崛起的技術熱點ServiceMesh和其實現istio,都是利用sidecar的方式來做服務代理,把流量控制的邏輯下沉到基礎架構層,通過ambassador的方式很方便的實現。他們的出現將極大的改變微服務架構,可能讓其更關注業務邏輯的高效實現,而把流量控制(包括服務發現,服務限流,小流量等服務路由功能統一交給service mesh來解決。又是一種特殊的sidecar,如果希望對外輸出的內容符合下游的要求而不對app container進行修改,可以增加一個adapter的sidecar,由它來做類似日誌轉換的事情。例如:App container按照自身的要求生成日誌並保存到文件系統中,另外的adapter container通過共享存儲讀取該日誌,然後進行日誌轉換等工作,以便把內容輸出給下游的metrics系統。設計模式(Design Pattern)的核心是解耦和重用;在容器和容器編排的分布式系統中,有大量可重用的組件和pattern,其中Single-Container和Multiple-Container又是最基本的組件。https://ai.google.com/research/pubs/pub45406
https://www.usenix.org/sites/default/files/conference/protected-
files/hotcloud16_slides_burns.pdf
回升!PHP重返前十2021年JavaScript明星項目盤點時隔三年,Elastic8正式發布
覺得不錯,請點個在看呀