天天看點

容器基本概念

本節課程要點

  1. 什麼是容器與鏡像?如何建構容器與鏡像
  2. 容器的生命周期
  3. 容器項目的架構
  4. 容器 VS.VM

容器與鏡像

什麼是容器?

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

首先,當我們登入到作業系統之後,可以通過 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 相比,各有優劣,容器技術在向着強隔離方向發展。

繼續閱讀