1. 程式人生 > >8 分鐘入門 K8s | 詳解容器基本概念

8 分鐘入門 K8s | 詳解容器基本概念

作者| 阿里巴巴高階開發工程師 傅偉

一、容器與映象

什麼是容器?

在介紹容器的具體概念之前,先簡單回顧一下作業系統是如何管理程序的。

首先,當我們登入到作業系統之後,可以通過 ps 等操作看到各式各樣的程序,這些程序包括系統自帶的服務和使用者的應用程序。那麼,這些程序都有什麼樣的特點?

  • 第一,這些程序可以相互看到、相互通訊;
  • 第二,它們使用的是同一個檔案系統,可以對同一個檔案進行讀寫操作;
  • 第三,這些程序會使用相同的系統資源。

這樣的三個特點會帶來什麼問題呢?

  • 因為這些程序能夠相互看到並且進行通訊,高階許可權的程序可以攻擊其他程序;
  • 因為它們使用的是同一個檔案系統,因此會帶來兩個問題:這些程序可以對於已有的資料進行增刪改查,具有高階許可權的程序可能會將其他程序的資料刪除掉,破壞掉其他程序的正常執行;此外,程序與程序之間的依賴可能會存在衝突,如此一來就會給運維帶來很大的壓力;
  • 因為這些程序使用的是同一個宿主機的資源,應用之間可能會存在資源搶佔的問題,當一個應用需要消耗大量 CPU 和記憶體資源的時候,就可能會破壞其他應用的執行,導致其他應用無法正常地提供服務。

針對上述的三個問題,如何為程序提供一個獨立的執行環境呢?

  • 針對不同程序使用同一個檔案系統所造成的問題而言,Linux 和 Unix 作業系統可以通過 chroot 系統呼叫將子目錄變成根目錄,達到檢視級別的隔離;程序在 chroot 的幫助下可以具有獨立的檔案系統,對於這樣的檔案系統進行增刪改查不會影響到其他程序;
  • 因為程序之間相互可見並且可以相互通訊,使用 Namespace 技術來實現程序在資源的檢視上進行隔離。在 chroot 和 Namespace 的幫助下,程序就能夠執行在一個獨立的環境下了;
  • 但在獨立的環境下,程序所使用的還是同一個作業系統的資源,一些程序可能會侵蝕掉整個系統的資源。為了減少程序彼此之間的影響,可以通過 Cgroup 來限制其資源使用率,設定其能夠使用的 CPU 以及記憶體量。

那麼,應該如何定義這樣的程序集合呢?

其實,**容器就是一個檢視隔離、資源可限制、獨立檔案系統的程序集合。**所謂“檢視隔離”就是能夠看到部分程序以及具有獨立的主機名等;控制資源使用率則是可以對於記憶體大小以及 CPU 使用個數等進行限制。容器就是一個程序集合,它將系統的其他資源隔離開來,具有自己獨立的資源檢視。

容器具有一個獨立的檔案系統,因為使用的是系統的資源,所以在獨立的檔案系統內不需要具備核心相關的程式碼或者工具,我們只需要提供容器所需的二進位制檔案、配置檔案以及依賴即可。只要容器執行時所需的檔案集合都能夠具備,那麼這個容器就能夠執行起來。

什麼是映象?

綜上所述,我們將這些容器執行時所需要的所有的檔案集合稱之為容器映象。

那麼,一般都是通過什麼樣的方式來構建映象的呢?通常情況下,我們會採用 Dockerfile 來構建映象,這是因為 Dockerfile 提供了非常便利的語法糖,能夠幫助我們很好地描述構建的每個步驟。當然,每個構建步驟都會對已有的檔案系統進行操作,這樣就會帶來檔案系統內容的變化,我們將這些變化稱之為 changeset。當我們把構建步驟所產生的變化依次作用到一個空資料夾上,就能夠得到一個完整的映象。   changeset 的分層以及複用特點能夠帶來幾點優勢:

  • 第一,能夠提高分發效率,簡單試想一下,對於大的映象而言,如果將其拆分成各個小塊就能夠提高映象的分發效率,這是因為映象拆分之後就可以並行下載這些資料;
  • 第二,因為這些資料是相互共享的,也就意味著當本地儲存上包含了一些資料的時候,只需要下載本地沒有的資料即可,舉個簡單的例子就是 golang 映象是基於 alpine 映象進行構建的,當本地已經具有了 alpine 映象之後,在下載 golang 映象的時候只需要下載本地 alpine 映象中沒有的部分即可;
  • 第三,因為映象資料是共享的,因此可以節約大量的磁碟空間,簡單設想一下,當本地儲存具有了 alpine 映象和 golang 映象,在沒有複用的能力之前,alpine 映象具有 5M 大小,golang 映象有 300M 大小,因此就會佔用 305M 空間;而當具有了複用能力之後,只需要 300M 空間即可。

如何構建映象?

如下圖所示的 Dockerfile 適用於描述如何構建 golang 應用的。 圖片

如圖所示:

  1. FROM 行表示以下的構建步驟基於什麼映象進行構建,正如前面所提到的,映象是可以複用的;
  2. WORKDIR 行表示會把接下來的構建步驟都在哪一個相應的具體目錄下進行,其起到的作用類似於 Shell 裡面的 cd;
  3. COPY 行表示的是可以將宿主機上的檔案拷貝到容器映象內;
  4. RUN 行表示在具體的檔案系統內執行相應的動作。當我們執行完畢之後就可以得到一個應用了;
  5. CMD 行表示使用映象時的預設程式名字。

當有了 Dockerfile 之後,就可以通過 docker build 命令構建出所需要的應用。構建出的結果儲存在本地,一般情況下,映象構建會在打包機或者其他的隔離環境下完成。

那麼,這些映象如何執行在生產環境或者測試環境上呢?這時候就需要一箇中轉站或者中心儲存,我們稱之為 docker registry,也就是映象倉庫,其負責儲存所有產生的映象資料。我們只需要通過 docker push 就能夠將本地映象推動到映象倉庫中,這樣一來,就能夠在生產環境上或者測試環境上將相應的資料下載下來並運行了。

如何執行容器?

執行一個容器一般情況下分為三步:

  • 第一步:從映象倉庫中將相應的映象下載下來;
  • 第二步:當映象下載完成之後就可以通過 docker images 來檢視本地映象,這裡會給出一個完整的列表,我們可以在列表中選中想要的映象;
  • 第三步:當選中映象之後,就可以通過 docker run 來執行這個映象得到想要的容器,當然可以通過多次執行得到多個容器。一個映象就相當於是一個模板,一個容器就像是一個具體的執行例項,因此映象就具有了一次構建、到處執行的特點。

小結

簡單回顧一下,容器就是和系統其它部分隔離開來的程序集合,這裡的其他部分包括程序、網路資源以及檔案系統等。而映象就是容器所需要的所有檔案集合,其具備一次構建、到處執行的特點。  

二、容器的生命週期

容器執行時的生命週期

容器是一組具有隔離特性的程序集合,在使用 docker run 的時候會選擇一個映象來提供獨立的檔案系統並指定相應的執行程式。這裡指定的執行程式稱之為 initial 程序,這個 initial 程序啟動的時候,容器也會隨之啟動,當 initial 程序退出的時候,容器也會隨之退出。

因此,可以認為容器的生命週期和 initial 程序的生命週期是一致的。當然,因為容器內不只有這樣的一個 initial 程序,initial 程序本身也可以產生其他的子程序或者通過 docker exec 產生出來的運維操作,也屬於 initial 程序管理的範圍內。當 initial 程序退出的時候,所有的子程序也會隨之退出,這樣也是為了防止資源的洩漏。   但是這樣的做法也會存在一些問題,首先應用裡面的程式往往是有狀態的,其可能會產生一些重要的資料,當一個容器退出被刪除之後,資料也就會丟失了,這對於應用方而言是不能接受的,所以需要將容器所產生出來的重要資料持久化下來。容器能夠直接將資料持久化到指定的目錄上,這個目錄就稱之為資料卷。

資料卷有一些特點,其中非常明顯的就是資料卷的生命週期是獨立於容器的生命週期的,也就是說容器的建立、執行、停止、刪除等操作都和資料卷沒有任何關係,因為它是一個特殊的目錄,是用於幫助容器進行持久化的。簡單而言,我們會將資料卷掛載到容器內,這樣一來容器就能夠將資料寫入到相應的目錄裡面了,而且容器的退出並不會導致資料的丟失。

通常情況下,資料卷管理主要有兩種方式:

  • 第一種是通過 bind 的方式,直接將宿主機的目錄直接掛載到容器內;這種方式比較簡單,但是會帶來運維成本,因為其依賴於宿主機的目錄,需要對於所有的宿主機進行統一管理。
  • 第二種是將目錄管理交給執行引擎。

三、容器專案架構

moby 容器引擎架構

moby 是目前最流行的容器管理引擎,moby daemon 會對上提供有關於容器、映象、網路以及 Volume的管理。moby daemon 所依賴的最重要的元件就是 containerd,containerd 是一個容器執行時管理引擎,其獨立於 moby daemon ,可以對上提供容器、映象的相關管理。

containerd 底層有 containerd shim 模組,其類似於一個守護程序,這樣設計的原因有幾點:

  • 首先,containerd 需要管理容器生命週期,而容器可能是由不同的容器執行時所創建出來的,因此需要提供一個靈活的外掛化管理。而 shim 就是針對於不同的容器執行時所開發的,這樣就能夠從 containerd 中脫離出來,通過外掛的形式進行管理。
  • 其次,因為 shim 外掛化的實現,使其能夠被 containerd 動態接管。如果不具備這樣的能力,當 moby daemon 或者 containerd daemon 意外退出的時候,容器就沒人管理了,那麼它也會隨之消失、退出,這樣就會影響到應用的執行。
  • 最後,因為隨時可能會對 moby 或者 containerd 進行升級,如果不提供 shim 機制,那麼就無法做到原地升級,也無法做到不影響業務的升級,因此 containerd shim 非常重要,它實現了動態接管的能力。

本節課程只是針對於 moby 進行一個大致的介紹,在後續的課程也會詳細介紹。  

四、容器 VS VM

容器和 VM 之間的差異

VM 利用 Hypervisor 虛擬化技術來模擬 CPU、記憶體等硬體資源,這樣就可以在宿主機上建立一個 Guest OS,這是常說的安裝一個虛擬機器。

每一個 Guest OS 都有一個獨立的核心,比如 Ubuntu、CentOS 甚至是 Windows 等,在這樣的 Guest OS 之下,每個應用都是相互獨立的,VM 可以提供一個更好的隔離效果。但這樣的隔離效果需要付出一定的代價,因為需要把一部分的計算資源交給虛擬化,這樣就很難充分利用現有的計算資源,並且每個 Guest OS 都需要佔用大量的磁碟空間,比如 Windows 作業系統的安裝需要 10~30G 的磁碟空間,Ubuntu 也需要 5~6G,同時這樣的方式啟動很慢。正是因為虛擬機器技術的缺點,催生出了容器技術。   容器是針對於程序而言的,因此無需 Guest OS,只需要一個獨立的檔案系統提供其所需要檔案集合即可。所有的檔案隔離都是程序級別的,因此啟動時間快於 VM,並且所需的磁碟空間也小於 VM。當然了,程序級別的隔離並沒有想象中的那麼好,隔離效果相比 VM 要差很多。

總體而言,容器和 VM 相比,各有優劣,因此容器技術也在向著強隔離方向發展。  

本文總結

  • 容器是一個程序集合,具有自己獨特的檢視視角;
  • 映象是容器所需要的所有檔案集合,其具備一次構建、到處執行的特點;
  • 容器的生命週期和 initial 程序的生命週期是一樣的;
  • 容器和 VM 相比,各有優劣,容器技術在向著強隔離