
Kubernetes使用Linux容器技術來實現應用的隔離。因此在深入學習k8s之前,我們需要先學習容器的基礎知識以便更好地去理解k8s的原理機制。
揭開容器的神秘面紗
當一個應用只由較少數量的大組件構成時,完全可以給每個組件分配一個專用的虛擬機,以及通過給每個組件分配它自己的操作系統實例來隔離它們的環境。
但是當這些組件變得越來越小且數量變得越來越多時,就不能給每個組件分配專用的VM,除非你不想節約硬件資源、降低硬件成本。但是硬件資源只是一方面,每個VM通常還需要獨立配置和管理,想象一下有大量的虛擬機等著你去配置和管理時你會是什么心情。這不僅是力資源的浪費,還會讓系統管理員的工作變得更加高負荷。
利用Linux容器技術隔離應用組件
越來越多的開發人員開始轉向Linux容器技術,而不是使用虛擬機來隔離每個微服務環境(或者通常說的進程)。容器技術使我們能夠在同一臺宿主機上運行多個服務,不僅為每個服務提供不同的環境,還將它們彼此隔離,就像虛擬機一樣,但是需要的開銷更小。
容器中運行的進程實際上還是在宿主機的操作系統上,跟其他進程沒啥區別,不像虛擬機,進程是運行在不同的操作系統上的。但是需要注意的是,容器中的進程仍然是與其他進程隔離的。對于進程本身而言,看起來就像是機器和操作系統上運行的唯一進程。
虛擬機 VS 容器
與虛擬機相比,容器更加輕量級。同樣的硬件情況下,基于容器,我們可以運行更多的應用組件。這主要是因為虛擬機還需要運行它自己的一組系統進程,這些進程需要額外的計算資源,而且應用本身所在的進程也需要消耗資源。而對于容器來說,其本身實際上就是一個運行在宿主機上被隔離的進程,只消耗應用本身需要消耗的資源,再無任何其他進程的開銷。
比較尷尬的情況是,由于虛擬機比較耗資源,導致沒有足夠的資源為每個應用程序分配一個專用的虛擬機,我們最終可能會將多個應用分組部署到每個虛擬機內。當使用容器時,我們可以而且應該為每個應用分配一個容器。因此,在同一臺裸機上可以運行更多的應用程序。

當在一臺主機上運行三個虛擬機的時候,你就擁有了三個完全隔離的操作系統,它們運行并共享一臺裸機。宿主機操作系統和一個虛擬層(Hypervisor)位于這些虛擬機下面,這個虛擬層會將物理硬件資源分成一些更小塊的虛擬資源,從而被每個虛擬機內的操作系統使用。應用程序在虛擬機中運行,會調用虛擬機操作系統內核程序,然后內核程序會通過Hypervisor在宿主機的物理CPU上執行x86指令。而多個容器則會對運行在宿主機操作系統上的同一個內核執行調用,該內核是唯一一個在宿主機CPU上執行x86指令的內核。通過使用容器技術,CPU不需要做任何虛擬化,而虛擬機是需要對CPU做虛擬化的。可以通過下圖了解應用程序在虛擬機或容器中使用CPU方式的差異:


虛擬機的主要優點是提供了一個完全隔離的環境,因為每個虛擬機都運行在自己的Linux內核之上,而容器都是調用同一個內核,這顯然是有安全隱患的。硬件資源有限的情況下,如果需要隔離的進程不多,使用虛擬機可能是一個選擇。然而如果要在同一臺機器上運行更多相互隔離的進程的話,容器技術才是更好的選擇,因為它開銷更少。需要注意的是,每個虛擬機都會運行它自己的一組系統服務(也即進程)而所有的容器是不會的,因為它們都運行在同一個OS之上。這就意味著運行一個容器是不需要像虛擬機那樣還要先開機。容器上的進程是可以很快就可以被啟動起來的。
容器隔離機制
可能你會好奇,容器是怎么對運行在同一個操作系統上的進程進行隔離的呢。這里就不得不提到兩個機制。第一個就是Linux的命名空間(Namespace),
它可以確保每個只能看到它自己的系統視圖(文件、進程、網絡接口、主機名等等)。第二個是Linux控制組(cgroups),它限制了進程可以使用的資源量,包括CPU、內存、網絡帶寬等等。
命名空間

默認情況下,每個Linux系統初始化的時候只有一個命名空間。所有的系統資源,比如文件系統、進程ID、用戶ID、網絡接口以及其他資源都是屬于這個命名空間。但是我們也可以創建額外的命名空間以及在它們之間分配資源。對于一個進程,我們可以選擇讓其在某一個命名空間下運行,該進程就只能看到這個命名空間下的資源視圖。不過,命名空間是有多種類型的,一個進程并不是屬于某一個命名空間,而是屬于每種類型的一個命名空間,因此一個進程可能屬于多個命名空間,只是這些命名空間是不同類型的。
命名空間的種類有如下幾種:
- MNT: 管理系統文件掛載點 (MNT: Mount)
- PID: 進程隔離 (PID: Process ID)
- NET: 管理網絡接口 (NET: Networking)
- IPC: 管理對IPC資源的訪問 (IPC: Inter-Process Communication)
- UTS: 隔離內核和版本標識符 (UTS: Unix Timesharing System)
- User ID (user)
每一種命名空間被用來隔離一組特定的資源。例如,UTS命名空間就決定了運行在該命名空間下的進程所能看到的主機名和域名。通過給兩個進程指定兩個不同的UTS命名空間,能夠使它們看到不同的本地主機名。換句話說,對于這兩個進程來說,就好像運行在兩個不同的機器上(至少就主機名來說是這樣)。
同樣的,一個進程屬于什么網絡命名空間決定了運行在進程中的應用程序所能看到的網絡接口。每個網絡接口屬于一個命名空間,但是可以從一個命名空間轉移到另一個命名空間。每個容器使用它自己的網絡命名空間,因此每個容器看到的都是屬于它自己的一套網絡接口。現在我們應該明白命名空間是如何被用來隔離運行在容器中的應用。
cgroup
還可以通過cgroups來限制容器能夠消耗的資源來實現進程的隔離。cgroups是Linux的一個內核功能,它可以限制一個進程或者一組進程的資源使用。一個進程的資源(CPU、內存、網絡帶寬等等)使用量不能超過分配給它的量。通過這樣方式,進程就不能過度地使用為其他進程保留的資源,這就和每個進程運行在單獨的機上上類似。
如果想了解關于cgoups的知識,可以參考:
https://tech.meituan.com/2015/03/31/cgroups.html