為什麼建議一個容器中只運行一個進程

在雲原生與容器化時代浪潮下,大多數新手的普遍認識是「容器=虛擬機」,既然容器等同於虛擬機,那麼在容器中想運行多少個進程就運行多少個進程。

作為從新手村走過來的人,筆者想為這個想法糾偏,避免大家和我走一樣的彎路。有兩個概念我們要理清:第一,容器不等同於虛擬機;第二,容器中不建議運行多個進程。

本文以Docker容器為主要討論展開。

為什麼說容器不等同於虛擬機呢?

我們來看一個較為學術的定義:A docker container is not a full virtual machine to run a complete stack of application instances and services. Docker container is the application; or more accurately a service that helps to make up the application. 即,Docker容器不是一個運行完整的應用程序實例和服務堆棧的完整虛擬機。Docker容器是一種應用程序,或更準確地說是一種有助於構建應用程序的服務。

雖然容器不等同於虛擬機,但它們也有相似之處,容器與虛擬機擁有着類似的使命:對應用程序及其關聯性進行隔離,從而構建起一套能夠隨處運行的自容納單元。此外,容器與虛擬機還擺脫了對物理硬件的直接控制,允許我們更為高效地使用計算資源,從而提升能源效率與成本效益。[1]

那麼,容器和虛擬機的不同點究竟在哪呢?筆者在這裡做了一個通俗形象的類比。首先,對於物理機、虛擬機和容器在基礎架構上的不同,可以類比為獨棟別墅、小區樓盤和膠囊式公寓的區別。

物理機

物理機就像獨棟別墅,擁有獨立的基礎設施,水管、電線、地基等都由自己獨享,也就是說,網卡、內存、CPU等硬件都由自身操作系統獨佔。

物理機上的硬件可以看作是基礎設施(Infrastructure),沒有基礎設施一切都無從談起,硬件之上則是主操作系統(Host Operating System)。除此之外,還有各類基礎軟件,如各類硬件配套的驅動軟件。

這裡還要再重點介紹的是Hypervisor,它(也稱為虛擬機監視器或VMM)是創建和運行虛擬機(VM)的軟件。虛擬機管理程序通過虛擬共享其資源(例如內存和CPU),使一台主機可以支持多個Guest OS

虛擬機

虛擬機就像小區樓盤,所有房子共享地基,但房子內部有自己獨立了電線、水管,雖然最終也是走的統一水電管網出小區。也就是說,多個虛擬機之間,網卡、內存、CPU等硬件雖然最終是共用一套,但在虛擬機內部是由獨立的一套虛擬硬件進行運轉的。

前面我們介紹了hypervisorhypervisor是一種虛擬化服務器的軟件,這是在物理機及宿主機操作系統上運行虛擬機VM的基礎,它幫助我們對硬件進行虛擬化。

虛擬機里的操作系統我們稱為Guest OS,這是一個完整的操作系統,只要安裝應用程序運行所必需的二進制文件和庫,在其之上便可以運行應用程序,且能確保各個虛擬機之間、虛擬機與宿主機之間的環境完全獨立、資源相互隔離。

由於虛擬機里運行的是一個完整的操作系統,且需要虛擬出Guest OS運行時所需的所有硬件的虛擬副本,這意味着虛擬機會佔用大量系統資源,特別是CPU和內存的資源佔用率會比較高,且虛擬機的空間佔用可以高達數GB。不過在合理範圍內,虛擬機可以利用固有的硬件資源虛擬出多台完整VM,其存在仍然有價值,與單獨運行的物理機相比仍然是經濟的。

虛擬機是偉大的,它通過抽象來增加並行,服務於多操作系統的使用,並提供業界最好的安全性。但對於隔離,它們相當昂貴。[2]

圖1. 虛擬機層級圖

容器

容器就像膠囊式公寓,所有隔間不僅共享地基,連電線、水管也都是共享的,且每個隔間的空間有限,彼此之間也相互隔離。當然,他們是共用大房間里的基礎設施,如公共空間。

也就是說,容器是共用物理網卡、內存、CPU的。只有當他們之間需要通信時,才會採用容器層面的網橋docker0,而對於其他硬件,可以簡單認為每個容器佔有了限定範圍內的資源(如RAM、CPU等)。

容器只是運行在宿主機上的一種特殊進程,多個容器之間使用的還是同一個宿主機的操作系統內核。可以認為,容器是一個不依賴於操作系統,運行應用程序的環境。

容器通過Linux的NamespaceCgroups技術對應用程序進程進行隔離和資源限制。

Namespace的作用是環境隔離,它讓應用程序只看到該Namespace內的世界。而Cgroups 的作用是限制分配給進程的宿主機資源。不過,對於宿主機來說,這些被「隔離」了的進程跟其他進程並沒有太大區別。

對於Namespace技術,這裡做一個稍微深入一點的解讀。通過Mount Namespace,容器可以修改進程對自己的文件系統「掛載點」的認知。在容器進程啟動之前重新掛載它的整個根目錄”/”,這個掛載在容器根目錄上、用來為容器進程提供隔離後執行環境的文件系統,就是所謂的「容器鏡像」。它還有一個更為專業的名字,叫作:rootfs(根文件系統)。rootfs只是一個操作系統所包含的文件、配置和目錄,並不包括操作系統內核。同一台機器上的所有容器,都共享宿主機操作系統的內核。[3]

Linux CGroup全稱Linux Control Group,是Linux內核的一個功能,用來限制,控制與分離一個進程組群的資源(如CPU、內存、磁盤輸入輸出等)。Cgroup可讓您為系統中所運行任務(進程)的用戶定義組群分配資源—比如CPU時間、系統內存、網絡帶寬或者這些資源的組合。您可以監控您配置的Cgroup,拒絕Cgroup訪問某些資源,甚至在運行的系統中動態配置您的Cgroup[4]

回過頭來談談操作系統內核,由於同一台宿主機上的所有容器都共享宿主機的操作系統內核,那麼如果有一個容器里的應用程序需要配置內核參數,跟內核進行直接交互,則這些參數對所有容器來說就像一個「全局變量」,牽一髮而動全身。

這也是容器劣勢的主要原因,正是因為容器共享宿主機操作系統內核,因此不能像虛擬機一樣模擬出完整的硬件機器充當沙盒,從而實現完全隔離。也就是說,容器是進程級的隔離,它可以通過影響宿主機操作系統內核來影響其他容器。

但容器還是有很多優勢。首先,容器的空間佔用比虛擬機小很多,甚至可以小到10MB,和虛擬機動則數GB相比十分小巧;其次,容器能輕鬆限制內存和CPU使用率,相比虛擬機採用hypervisor來實現虛擬化更加輕量;同時,正是由於容器體積小、採用的技術(Containerzation Engine)更輕量,使得它啟動十分迅速,這十分有利於快速擴展。

值得一提的是,在DevOps理念日益流行的時代,容器對於持續集成和持續部署(CI/CD)實施也是極好的選擇,它使得開發人員更容易構建、分發以及快速部署他們的應用程序。

圖2. 容器層級圖

為什麼說容器中不建議運行多個進程呢?

聊過虛擬機與容器的區別之後,讓我們暫時忘記架構和軟件工程哲學。在前面的討論中我們已經知道,容器其實是應用程序抽象出來的可相互隔離的線程。儘管單個容器中確實可以運行多個應用程序,但出於實際原因,您可能需要考慮遵循「每個容器一個應用程序」的經驗法則。

1、每個容器中只運行一個應用程序,則水平伸縮將變得十分容易。例如,當你需要一個Tomcate容器,可以從現有的容器再擴展出一個,但如果你的這個容器中不僅有Tomcate,還有MySQL等其他應用程序,事情就會變得複雜起來。

2、每個容器中只運行一個應用程序,可以輕鬆地將其重新用於其他項目或目的,極大增加復用度。

3、每個容器中只運行一個應用程序,出現故障時開發人員能方便地對該故障容器進行問題排查,而不必對整個系統的各個部分進行排查,這也使得其更具有可移植性和可預測性。

4、每個容器中只運行一個應用程序,升級程序時能夠將影響範圍控制再更小的粒度,極大增加應用程序生命周期管理的靈活性,避免在升級某個服務時中斷相同容器中的其他進程。

5、每個容器中只運行一個應用程序,從安全性和隔離性角度來看,能夠提供更安全的服務和應用程序間的隔離,以保持強大的安全狀態或遵守PCI之類的規定。[5]

話說回來,容器本身的設計,就是希望容器和服務/應用能夠具備相同的生命周期。即:一個容器對應一個進程。這樣,才能夠最好地應用容器編排來管理好容器和服務。

綜上,建議單個容器中只運行一個獨立的進程。

參考

[1] //blog.csdn.net/qq_24624539/article/details/103445229

[2] //dockone.io/article/723

[3] //zhuanlan.zhihu.com/p/157749762

[4] //coolshell.cn/articles/17049.html

[5] //devops.stackexchange.com/questions/447/why-it-is-recommended-to-run-only-one-process-in-a-container

(全文完)

更多關於大數據、分佈式、存儲、區塊鏈、Linux相關文章請關注微信公眾號:asympTech漸進線實驗室

Github、知乎、博客園、CSDN、簡書全網唯一id:JasonCeng