1. 程式人生 > >docker,容器,編排,和基於容器的系統設計模式

docker,容器,編排,和基於容器的系統設計模式

[toc] 都2020年了,容器,或者說docker容器這個概念,從事網際網路行業的開發者應該都不會感到陌生。無論大廠還是小廠的應用部署現在都首選docker容器。 但是docker雖好,卻並非萬能。docker本身,其實僅僅是提供了一種沙盒的機制,對不同應用進行隔離。映象是它出彩的一個設計,可以讓開發者們快速部署應用。但這對大型應用管理來說,是遠遠不夠的。開發者們在意識到這個問題後,提出了編排這個概念,從而引發的新的紛爭。。。 本篇文章從容器的歷史開始說起,然後介紹編排領域,swarm和k8s的紛爭,最後討論基於容器的系統設計模式,這個設計模式參考自google的論文,當然是基於k8s的啦~ PS:容器並非只有docker,但本篇暫略去了它們的差異,大部分情況下這兩個詞可以等價。 # 從容器說起 ## 背景 在虛擬機器和雲端計算較為成熟的時候,各家公司想在雲伺服器上部署應用,通常都是像部署物理機那樣使用指令碼或手動部署,但由於本地環境和雲環境不一致,往往會出現各種小問題。 這時候有個叫Paas的專案,就是專注於解決本地環境與雲端環境不一致的問題,並且提供了**應用託管**的功能。簡單得說,就是在雲伺服器上部署Paas對應的服務端,然後本機就能一鍵push,將本地應用部署到雲端機器。然後由於雲伺服器上,一個Paas服務端,會接收多個使用者提交的應用,所以其底層提供了一套隔離機制,為每個提交的應用建立一個**沙盒**,每個沙盒之間彼此隔離,互不干涉。 看看,這個沙盒是不是和docker很類似呢?**實際上,容器技術並不是docker的專屬,docker只是眾多實現容器技術中的一個而已**。那為什麼後來docker會變得如日中天呢?還是得從Paas說起。 Paas的本質就是通過一套打包(本地)-分發(雲)的機制,幫助使用者將應用分發到大規模的叢集中,容器技術只是其中比較底層的一部分而已。聽起來很完美,但問題恰恰就出現在這個打包功能上。打包功能比較繁瑣,要為每個應用,語言,版本都打一個包,重點是打包過程常常出現問題,很可能本地執行得好好的,打包到Paas上就出現問題,而且這種問題無跡可尋,只能通過試錯解決。**換句話說,Paas確實可以讓你體驗到一鍵部署的快感,但在這之前,你要先體驗打包過程的萬千痛苦**。 這個讓使用者痛苦萬分的打包,docker的一個小創新的卻能夠解決,**那就是映象**。映象本身也是一種打包機制,並且這個映象通常包含完整的作業系統,可以儘可能還原本地環境,同時你的應用還包含在這裡面。 通過映象這種東西,你可以方便得在本地開發,然後將映象上傳到雲端伺服器部署,且基本不需要或者只需要少量修改就可以使雲端伺服器擁有和本地一樣的應用環境,然後可以通過這個映象建立彼此隔離的沙盒環境,以部署自己的多個應用。 但是,docker雖然解決了Paas打包難的問題,但Paas原本的大規模叢集部署的能力,卻是docker的弱項,甚至docker本身並沒有這方面的功能。 才有了後來提出的容器**編排**概念,Swarm和K8s就是圍繞這塊而起的紛爭,當然那是另外一個故事了。 ## docker實現原理 說完了docker實現原理,接下來就來看看docker底層是如何實現**沙盒**隔離機制的。 說起docker,很多人都會將它與虛擬機器進行比較,基本都會引用下面這張圖: ![docker vs 虛擬機器](https://img2020.cnblogs.com/blog/1011838/202006/1011838-20200627114056500-988436251.jpg) 其中左邊是虛擬機器的結構,右邊是docker容器的結構,但這張圖其實不是那麼準確。在虛擬機器中,通過Hypervisor對硬體資源進行虛擬化,在這部分硬體資源上安裝作業系統,從而可以讓上層的虛擬機器和底層的宿主機相互隔離。但docker是沒有這種功能的,我們在docker容器中看到的與宿主機相互隔離的**沙盒**環境(檔案系統,資源,程序環境等),**本質上是通過Linux的Namespace機制,CGroups(Control Groups)和Chroot等功能實現的。實際上Docker依舊是執行在宿主機上的一個程序(程序組)**,只是通過一些障眼法讓docker以為自己是一個獨立環境。接下來我們簡單介紹下這部分內容。 如果在一個docker容器裡面,使用ps命令檢視程序,可能只會看到如下的輸出: ``` / # ps PID USER TIME COMMAND 1 root 0:00 /bin/bash 10 root 0:00 ps ``` 在容器中執行ps,只會看到1號程序/bin/bash和10號程序ps。前面有說到,docker容器本身只是Linux中的一個程序(組),也就是說在宿主機上,這個/bin/bash的pid可能是100或1000,那為什麼在docker裡面看到的這個/bin/bash程序的pid是1呢?**答案是linux提供的Namespace機制,將/bin/bash這個程序的程序空間隔離開了**。 具體的做法呢,就是在建立程序的時候新增一個可選的引數,比如下面這樣: ``` int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL); ``` 那樣後,建立的執行緒就會有一個新的名稱空間,在這個名稱空間中,它的pid就是1,當然在宿主機的真實環境中,它的pid還是原來的值。上面的這個例子,其實只是pid Namespace(程序名稱空間),除此之外,還有network Namespace(網路名稱空間),mount Namespace(檔案名稱空間,就是將整個容器的根目錄root掛載到一個新的目錄中,然後在其中放入核心檔案看起來就像一個新的系統了)等,用以將整個容器和實際宿主機隔離開來。而這其實也就是容器基礎的基礎實現了。 但是,上述各種Namespace其實還不夠,還有一個比較大的問題,**那就是系統資源的隔離**,比如要控制一個容器的CPU資源使用率,記憶體佔用等,否則一個容器就吃盡系統資源,其他容器怎麼辦。 **而Linux實現資源隔離的方法就是Cgroups**,具體的使用方法就不多介紹。Cgroups主要是提供檔案介面,即通過修改 /sys/fs/cgroup/下面的檔案資訊,比如給出pid,CPU使用時間限制等就能限制一個容器所使用的資源。 **所以,docker本身只是linux中的一個程序,通過Namespace和cgroup將它隔離成一個個單獨的沙盒**。明白這點,就會明白docker的一些特性,比如說太過依賴核心的程式在docker上可能執行會出問題,比如無法在低版本的宿主機上安裝高本版的docker等,因為本質上還是執行在宿主機的核心上。 對了,還有mac和windows系統,這些是怎麼實現的呢?很簡單,它們的docker都是建立在虛擬化的linux上的,所以其實還是linux。 說完容器,接下來就開始介紹編排了。 # 編排之爭 這裡我們主要會介紹編排這個概念,以及從這個概念起引發的docker swarm和k8s的紛爭。 docker本身只是提供打包-部署的功能,它並沒有提供分散式叢集(大規模叢集)管理的功能,這其實是原本Paas專案的主要領域。**而編排才是容器技術的核心魅力所在**,沒有編排,容器就只是一個沙箱工具。所以從docker成熟以後,你會發現它的主要發力點是在編排,也就是swarm專案上,不過這個docker的親兒子,swarm編排工具,卻敗給了橫空出世的k8s。 為什麼會這樣? 先說說什麼是容器的編排,說簡單些**就是對(docker)容器的配置,執行時候的行為的管理**。 那麼docker swarm是怎麼進行編排的呢?這其實還涉及到另一個專案,docker-compose,這兩個專案與docker Machine合稱為docker三劍客(怎麼聽起來有點low)。 前面說到編排就是對容器的配置和執行行為進行管理,那麼很自然的想法就是將這些配置和行為的定義都寫到一個配置檔案裡面,比如使用者需要執行容器A,容器B,容器C。那麼我們可以將這幾個容器相關的配置和關聯,比如網路,磁碟,啟動副本,出錯行為等配置,還有容器間的協作方式(啟動順序等)都寫到一個配置檔案。**最後通過一條命令,載入並執行這個配置檔案,就能夠實現容器的編排了**。 swarm做的事情很簡單,有時候簡單不一定是好事,因為那意味著難以滿足業界複雜的需求。比如它在處理有狀態服務上的無力,又比如它難以處理多個服務間複雜的關係(處理服務的順序是不夠的)。這時候,脫胎於Borg的kubernetes(k8s)出現在人們的面前。它身上,沉澱著google數十年的經驗,可以說它就是那個站在巨人肩膀上的寵兒。那麼相比於swarm,它的優勢到底在哪裡呢? 答案在他的設計上,這個說起來得詳細介紹k8s才能說明白。**從設計上說,k8s整體是基於API設計,即整體架構中涉及的元件都可插拔**,以容器舉例,在k8s中,容器是可替換的,只要滿足對應的介面設計的標準即可,docker是其中一種方案,而其他容器技術也是可選方案。和容器類似的還有網路外掛,volume外掛等等。有關k8s的詳細內容這裡不多介紹,有興趣的童鞋可以參考以下文件: - [kubernetes設計理念](https://www.kubernetes.org.cn/kubernetes%E8%AE%BE%E8%AE%A1%E7%90%86%E5%BF%B5) - [Kubernetes設計架構](https://www.kubernetes.org.cn/kubernetes%E8%AE%BE%E8%AE%A1%E6%9E%B6%E6%9E%84) 即整個設計是以叢集管理為核心,整體架構都是鬆散的,可插拔的。而swarm則是以docker為核心,兩者設計就存在本質上的區別。 **而後在容器的基礎上,k8s添加了另一層的封裝,即Pod**,所謂Pod,是一組相同或功能類似的容器所組成的組(task group)。為什麼要有Pod呢?還記得容器的本質是什麼嗎,是作業系統中的一組程序,從某種程度上來說,從程序這個層次來進行管理,有些繁雜了。比如Linux都有程序組這個概念管理相同或彼此聯絡的一些程序(比如一個功能由多個程序協作完成,這多個程序構成一個程序組)。 在容器編排中,往往多個容器間也會有類似程序和程序組的關係(這裡就不舉例了,很多分散式元件都有這種情況),所以需要一個更高層次的抽象來幫助我們對容器進行管理。在k8s中,承擔類似程序組的就是Pod,Pod是一種邏輯上的概念,同時Pod也是k8s中最小的排程單位,同組Pod中的容器都是共享volumns。 ![Pods](https://www.kubernetes.org.cn/img/2016/10/20161027205746.jpg) 有了Pod,也就是組這個概念後,就能夠更加方便對不同服務進行管理。但是它還有個更重要的意義,那就是基於容器的系統設計模式。 # 基於容器的分散式系統設計之道 讓我們回到1980年,假設你是一個寫慣了C的程式設計師,你接觸到一個名叫面向物件的程式設計概念,你會怎麼看待這個東西呢?能夠想象這個東西在30年後會佔據程式設計領域的大半壁江山嗎? 而如今,**docker容器(或者說Pod)就是一種類似OOP的東西,核心都是通過模組化封裝,將不同的東西相互隔離,讓它們相互配合,完成某些事情**。 從這個角度,或許就能明白為什麼前面說到的,容器價值不高,真正有價值的是編排。因為我們同樣不會覺得一個java object有多大價值,OOP的程式設計思想,及其衍生的設計模式才是精髓。 那麼從分散式系統的設計模式的角度來說,容器可以有多少種分類呢?和分散式系統的搭建模式類似,有三種。 - 單容器模式(single-container patterns for container management) - 單節點協作模式(single-node patterns of closely cooperating containers) - 和多節點協作模式(multi-node patterns) PS:這部分內容多參考自google的Design patterns for container-based distributed systems,想看原味論文的童鞋請戳最下方的連結。 單容器模式和單節點協作模式看起來相似,但實際是完全不同的東西。 單容器模式,簡單說就是在傳統Docker的基礎上(傳統docker的行為比較簡單,只有run(),pause(),stop()),提供更加豐富的功能和生命週期的管理。說得更簡單點,使用k8s管理單個docker服務。 我們主要介紹單節點協作模式和多節點協作模式。 ## 單節點協作模式 單節點協作模式,簡單說就是在一個分散式的容器服務環境中,通過一個單節點的服務輔助進行管理的這類模式。在這種模式中,需要依賴於k8s中,Pod這個概念的抽象,Pod即task group,一組相同或類似服務的容器的集合。 主要有以下幾種設計模式。 ### Sidecar pattern(邊車模式) 邊車,這個詞可能很多人沒聽過(包括我瞭解這個東西之前)。我們先來貼一下邊車的圖, ![](https://img2020.cnblogs.com/blog/1011838/202008/1011838-20200802093544727-2051756979.jpg) 邊車就是摩托車旁邊的那個小車,在某些環境下(比賽,我猜的),旁邊車上的人可以給車手遞水、食物等操作。 邊車模式也是類似的,即在主服務(的容器,main container)身邊提供一個輔助容器,幫助主服務做一些髒活累活。 比如一個web應用,它會將日誌資訊寫入到磁碟中,這時候我們就可以新增加一個日誌採集的`邊車`,協助web服務完成日誌採集的工作。就像下面這樣: ![](https://img2020.cnblogs.com/blog/1011838/202008/1011838-20200802093518211-1055570370.png) 還是挺好理解的,這樣的好處,相信瞭解過設計模式的童鞋隨隨便便就能列舉幾個,不過這裡還是從容器的角度詳細介紹下: 1. 容器是資源分配的一個單元。將`邊車`服務分離後,可以更加靈活地通過cgroup配置資源,或者一些動態調節資源的操作(比如忙時給web服務更多資源而`邊車`更少資源)。 2. 容器是最小的打包單位。有助於不同服務的責任劃分和測試。 3. 容器可以是複用的單位,比如可以將日誌服務用語其他服務。 4. 提供了錯誤邊界,可以使系統可以正常降級,比如日誌服務出錯,不會導致web服務出錯。 5. 容器是最小的部署單位,可以為每個服務升級,和回滾。但這也可能是缺點,因為服務一多難以管理。 因為分離所以多了這些好處,聽起來還是蠻誘人的~ ### Ambassador pattern(外交官模式) 外交官模式,提供一個容器作為代理與主服務(main container)通訊。 就相當於在通訊口出做多一層代理,比如主服務以為是與一個本地redis通訊,但實際上代理會真正與一個redis叢集互動。 ![](https://img2020.cnblogs.com/blog/1011838/202008/1011838-20200802094218379-175437250.png) 外交官模式的好處是,讓主服務與外部元件之間相互隔離。只通過代理的話,那麼外部元件可以無縫進行替換,而這一切主服務都是無感知的。然後是方便測試和複用,其實就是服務之間解耦的好處啦,和上面邊車模式是有點類似的。 ### Adapter pattern(介面卡模式) 前面說的兩種模式,主要是為了幫助主服務(main coninter)更專注於自己的職責。而介面卡模式則是為了方便其他元件。 舉個例子,假設你有多個服務(web,資料庫,快取服務等),然後需要一個監控監控這幾個元件是否正常。正常情況下,需要讓監控系統獲取不同服務之間的指標資訊,然後才能進行監控。 但這樣的問題是,如果增加或減少服務,那麼對監控系統來說會很麻煩。介面卡模式能夠解決這種困擾。 如果多個服務,web,資料庫,快取等都提供一個統一的對外介面,**那麼我們就能夠使用一個`介面卡容器`,統一獲取這些服務的指標資訊,然後由監控系統通過這個`介面卡容器`統一獲取所有的指標資訊**。如下圖所示。 ![](https://img2020.cnblogs.com/blog/1011838/202008/1011838-20200802094237039-144003246.png) OK,那麼以上就是單節點協作情況下的三種設計模式,下面再看看多節點的協作模式。 ## 多節點協作模式 這部分內容會比較簡單一些,這裡就不花太多篇幅進行講述。 除了單節點上的協作容器,模組化容器還使構建協作的多節點分散式應用程式變得更加容易。不過這部分內容聽起來很高大上,但其實是很好理解的東西。 比如分散式領域的zookeeper,大家應該都不陌生,在論文中,這種多個節點提供領導者選舉的模式,被稱為`領導者選舉模式`。同樣的,kafka這類訊息佇列,被稱之為`工作佇列模式(rk queue pattern)`。而最後一種,則是類似spark的,master worker計算模式,即將一個計算任務分佈到多個其他計算節點的這種方式,稱之為`Scatter/gather pattern`模式。 列舉的幾種模式都是通過多個節點協作,並且通過暴露介面提供對外服務。不過其實基本就是常見的使用容器搭建分散式服務的方式,如果使用過docker來搭建hadoop這一套東西,那麼對所謂的**多節點協作模式**肯定不會陌生。 想想也是,如果真的將分散式系統當做一個工程專案,那麼這些多節點的部署模式確實需要一個名分。這可以算是一個典型的實踐先於理論,理論總結實踐的例子吧。(不過我還是覺得這部分內容有水論文的嫌疑) 那麼關於容器的分散式系統設計的內容就先到這吧,有興趣看原論文的童鞋可以翻到最下。 **小結** OK,本文主要介紹了docker容器的發家歷史,然後介紹容器編排的重要性,並簡單說了為什麼swarm會在編排的戰爭中輸給了k8s。最後則從容器編排這個概念延伸到基於容器技術的設計模式,三種模式中,單節點協作模式算是比較新穎,還是有些啟發價值的。 以上~ 參考文章: [Design patterns for container-based distributed systems](https://static.googleusercontent.com/media/research.google.com/zh-CN//pubs/archive/45406.pdf) [kubernetes設計理念](https://www.kubernetes.org.cn/kubernetes%E8%AE%BE%E8%AE%A1%E7%90%86%E5%BF%B5) [Docker 核心技術與實現原理](http://dockone.io/article/2941) [An Introduction to Docker and Analysis of its Performance ](http://paper.ijcsns.org/07_book/201703/201703