歡迎光臨
每天分享高質量文章

容器發展簡史

在過去四年中(2015-2019),雲以及分散式計算成為最受歡迎的技術之一,它們從小眾技能逐漸變成更被僱主看重的突出技能。容器化技術是雲經濟和IT生態系統中最新潮的技術之一。這篇文章可能會幫助您理解有關Docker和容器的一些令人困惑的概念。我們還將看到容器化生態系統在2019年的現狀以及演變方向。
“不是我們造就了歷史,而是歷史造就了我們。” 
——馬丁·路德·金
Docker是當今最知名的容器平臺之一,它於2013年釋出。但是在此之前,隔離和容器化已經被使用。讓我們回到1979年,當時我們剛開始使用Chroot Jail,之後便出現了最著名的容器化技術。瞭解這段歷史不但有助於我們理解相關的新概念,也有利於我們理解這項技術。
 
 
時間退回到1979年,Unix版本7在開發過程中引入Chroot Jail以及Chroot系統呼叫。Chroot jail被用於“Change Root”,它被認為是最早的容器化技術之一。它允許您將行程及其子行程與作業系統的其餘部分隔離開來。這種隔離的唯一問題是根行程(root process)可以輕鬆地退出chroot。它從未考慮實現安全機制。FreeBSD Jail於2000年在FreeBSD OS中引入,旨在為簡單的Chroot檔案隔離帶來更多安全性。與Chroot不同,FreeBSD還實現了將行程及其活動隔離到檔案系統的特定檢視中。 
A Chroot Jail. 來源:https://linuxhill.wordpress.com/2014/08/09/014-setting-up-a-chroot-jail-in-crunchbang-11debian-wheezy
當Linux核心具有作業系統級的虛擬化的功能以後,Linux VServer於2001年被推出,它使用了類似chroot的機制與“安全背景關係”(“security context”)以及作業系統虛擬化(容器化)相結合來提供虛擬化解決方案。它比簡單的chroot更先進,允許您在單個Linux發行版(VPS)上執行多個Linux發行版。 
2004年2月,Oracle釋出了Oracle Solaris Containers,這是一個用於X86和SPARC處理器的Linux-Vserver版本。
SPARC是由Sun Microsystems開發的RISC(精簡指令集計算)架構。
Solaris Container是由系統資源控制和“區域”(zone)提供的邊界隔離組合而成。 
Oracle Solaris 11.3 與Solaris Containers類似,OpenVZ的第一個版本於2005年推出。OpenVZ與Linux-VServer一樣,使用作業系統級虛擬化,許多託管公司採用它來隔離和銷售VPS。作業系統級虛擬化有一些限制,因為容器共享相同的體系結構和核心版本,當客戶需要不同於主機的核心版本的情況下這種缺點就會顯現出來。
Linux-VServer和OpenVZ需要為核心打補丁以新增一些用於建立隔離容器的控制機制。 OpenVZ的補丁未整合到核心中。 
2007年,谷歌釋出了CGroups,這是一種機制,這種機制能限制和隔離一系列行程的資源使用(CPU,記憶體,磁碟I / O,網路等)。與OpenVZ 核心相反,CGroups在2007年整合進了Linux核心。
2008年,LXC(Linux containers,Linux容器)的第一個版本釋出。LXC與OpenVZ,Solaris Containers和Linux-VServer類似,但是它使用的是已經在Linux核心中實現的CGroup。然後,Cloud Foundry於2013年建立了Warden,這是一個管理隔離,短暫存在和被資源控制的環境的API。在其第一個版本中,Warden使用了LXC。 
2013年,Docker推出了的第一個版本。它像OpenVZ和Solaris Containers一樣,實現作業系統級虛擬化。
2014年,谷歌推出了LMCTFY(Let me contain that for you),谷歌容器棧的開源版本,提供Linux應用程式容器。谷歌工程師一直在與Docker合作libcontainer,並將核心概念和抽象移植到libcontainer。因此沒有積極開發LMCTFY專案,未來LMCTFY專案的核心可能會被libcontainer取代。
LMCTFY在同一核心上的隔離環境中執行應用程式,並且無需補丁,因為它使用CGroup,名稱空間和其他Linux核心功能。 谷歌是容器化行業的領導者。谷歌的一切都在容器上執行。每週有超過20億個容器在Google基礎架構上執行。 2014年12月,CoreOS釋出並開始支援rkt(最初作為Rocket釋出)作為Docker的替代品。 
Jails,虛擬專用伺服器(VPS),區域(Zones),容器和VM

 

 

使用Jails,Zones,VPS,VM和容器都是為了隔離和資源控制,但每種技術是透過不同的方式實現它,每種方式都有其侷限性和優勢。
到目前為止,我們已經簡要介紹了Jail如何工作,並介紹了Linux-VServer如何允許執行隔離的使用者空間,其中計算機程式直接在主機作業系統的核心上執行,但程式只能訪問其資源的受限子集。
Linux-VServer允許執行“虛擬專用伺服器”(“Virtual Private Servers”),但必須為主機核心打補丁才能使用它。(將VPS視為商業名稱。)
Solaris容器被稱為區域(Zone)。
“虛擬機器”是在“真實硬體機器”之上模擬虛擬機器的通用術語。該術語最初由Popek和Goldberg定義為真實計算機的有效,孤立副本。
虛擬機器可以是“系統虛擬機器”或“過程虛擬機器”。在我們日常使用VM這個詞時,我們通常指的是“系統虛擬機器”,它是模擬的是主機硬體來模擬整個作業系統。但是,“行程虛擬機器”(“Process Virtual Machine”,有時稱為應用虛擬機器)是用於模擬執行單個行程的程式設計環境:Java Virtual Machine(JVM,Java虛擬機器)就是一個例子。
作業系統級虛擬化也稱為容器化。Linux-VServer和OpenVZ等技術可以執行多個作業系統,同時共享相同的體系架構和核心版本。
在guest需要的虛擬機器於主機的核心版本的情況時,共享相同的體系結構和核心會有一些限制和缺點。 
來源:https://fntlnz.wtf/post/why-containers
系統容器(例如LXC)提供的環境非常接近您從虛擬機器獲得的環境,與此同時又省去了執行單獨內核和模擬所有硬體所帶來的開銷。 
VM與容器。來源:Docker部落格
作業系統容器與應用容器
作業系統級虛擬化有助於我們建立容器。LXC和Docker等技術使用這種隔離方式。我們這裡有兩種型別的容器:
  • OS容器會打包整個應用程式棧的作業系統【示例LEMP技術棧,LEMP是指一組一起使用來執行動態網站或者伺服器的開源軟體,軟體名稱的首字母縮寫:L代表Linux,E代表Nginx(Engine X),M代表MariaDB或MySQL,P代表PHP】。

  • 應用容器通常每個容器會執行一個行程

而對於App容器來說,它會建立3個容器來構成LEMP技術棧:
  • PHP伺服器(或者是PHP FPM)

  • Web伺服器(Nginx)

  • MySQL

 

系統(OS)容器與應用(App)容器

Docker:是容器還是平臺?

 

 

簡短回答:都是 
詳細答案:當Docker啟動時,它使用LXC作為容器執行時,其想法是建立一個API來管理容器執行時,隔離執行應用程式的單個行程,並監督容器的生命週期及其使用的資源。
在2013年初,Docker專案是建立一個“標準容器”,我們可以在這個宣言[1]中看到。 
現在標準容器宣言已刪除。 
Docker開始構建一個具有多種功能的單體應用程式,這些功能包括啟動雲伺服器以及構建和執行映象/容器等等。Docker使用“libcontainer”與Linux核心功能(如:控制組和名稱空間)進行互動。 
Docker,Libcontainer和Linux核心功能
讓我們使用名稱空間和Cgroup(控制組)建立一個容器
我在這個例子中使用的是Ubuntu,但對於大多數發行版來說操作應該類似。首先安裝CGroup工具和壓力測試工具程式(因為我們將進行一些壓力測試)。
  1. sudo apt install cgroup-tools
  2. sudo apt install stress
此命令將建立一個新的執行背景關係:
  1. sudo unshare --fork --pid --mount-proc bash
unshare命令解除了行程的部分執行背景關係的關聯。
unshare()允許行程(或執行緒)與當前和其他行程(或執行緒)共享的執行背景關係解除關聯。但是部分執行背景關係(例如mount > namespace),當使用fork(2)或vfork(2)去建立新行程時,執行背景關係,與此同時,其他部分(例如虛擬記憶體),可能在使用clone(2)建立行程或執行緒,透過顯式請求共享虛擬記憶體。
現在,使用cgcreate我們來建立一個控制組並定義兩個控制器,一個在記憶體上,另一個在CPU上。 
下一步是定義記憶體限制並使之生效:
  1. echo 3000000 > /sys/fs/cgroup/memory/mygroup/memory.kmem.limit_in_bytes cgexec -g memory:mygroup bash
現在讓我們壓力測試一下我們建立的獨立名稱空間(包含記憶體限制)。

  1. stress --vm 1 --vm-bytes 1G --timeout 10s
可以看到執行失敗了,於是我們知道記憶體限制正常工作。如果我們在主機上做同樣的事情(不要在16G RAM上模仿),測試永遠不會失敗,除非你真的沒有足夠的可用記憶體: 
遵循這些步驟有利於你理解Linux工具(如CGroup和其他資源管理功能)是如何在Linux系統中建立和管理隔離的環境。libcontainer與這些工具互動以管理和執行Docker容器。
runC:在不使用Docker的情況下使用libcontainer
2015年,Docker釋出runC:一個輕量級,可移植的容器執行時。 
runC實際上是一個直接利用libcontainer的命令列工具,無需透過Docker引擎。runC的標的是使標準容器隨處可用。這個專案被捐贈給了Open Container Initiative,OCI。libcontainer儲存庫現已被存檔。 
實際上,libcontainer並沒有被拋棄,而是被轉移到了runC倉庫。 
讓我們回到實踐部分並使用runC建立一個容器。首先安裝runC執行時:
 
讓我們建立一個目錄(/mycontainer),我們將匯出映象Busybox的內容。
BusyBox[2]的大小介於1到5 Mb之間(取決於變體),在製作高效利用空間發行版的時候,是一個非常好的組成部分。
BusyBox將許多常見UNIX實用程式的微小版本組合到一個小的可執行檔案中。它提供了你通常在GNU fileutils,shellutils等中找到的大多數實用程式的替代工具。BusyBox中的工具程式通常比它們包含所有功能的GNU版本有更少的選項;但包含的選項提供了大部分你所需要的功能,與GNU的對應版本非常相似。BusyBox為任何小型或嵌入式系統提供了相當完善的環境。 
來源:Docker Hub。
使用runC命令,我們可以用提取的映象和spec檔案(config.json)來執行BusyBox容器。runc spec命令一開始會建立以下JSON檔案:
  1. {
  2. "ociVersion": "1.0.1-dev",
  3. "process": {
  4. "terminal": true,
  5. "user": {
  6. "uid": 0,
  7. "gid": 0
  8. },
  9. "args": [
  10. "sh"
  11. ],
  12. "env": [
  13. "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
  14. "TERM=xterm"
  15. ],
  16. "cwd": "/",
  17. "capabilities": {
  18. "bounding": [
  19. "CAP_AUDIT_WRITE",
  20. "CAP_KILL",
  21. "CAP_NET_BIND_SERVICE"
  22. ],
  23. "effective": [
  24. "CAP_AUDIT_WRITE",
  25. "CAP_KILL",
  26. "CAP_NET_BIND_SERVICE"
  27. ],
  28. "inheritable": [
  29. "CAP_AUDIT_WRITE",
  30. "CAP_KILL",
  31. "CAP_NET_BIND_SERVICE"
  32. ],
  33. "permitted": [
  34. "CAP_AUDIT_WRITE",
  35. "CAP_KILL",
  36. "CAP_NET_BIND_SERVICE"
  37. ],
  38. "ambient": [
  39. "CAP_AUDIT_WRITE",
  40. "CAP_KILL",
  41. "CAP_NET_BIND_SERVICE"
  42. ]
  43. },
  44. "rlimits": [
  45. {
  46. "type": "RLIMIT_NOFILE",
  47. "hard": 1024,
  48. "soft": 1024
  49. }
  50. ],
  51. "noNewPrivileges": true
  52. },
  53. "root": {
  54. "path": "rootfs",
  55. "readonly": true
  56. },
  57. "hostname": "runc",
  58. "mounts": [
  59. {
  60. "destination": "/proc",
  61. "type": "proc",
  62. "source": "proc"
  63. },
  64. {
  65. "destination": "/dev",
  66. "type": "tmpfs",
  67. "source": "tmpfs",
  68. "options": [
  69. "nosuid",
  70. "strictatime",
  71. "mode=755",
  72. "size=65536k"
  73. ]
  74. },
  75. {
  76. "destination": "/dev/pts",
  77. "type": "devpts",
  78. "source": "devpts",
  79. "options": [
  80. "nosuid",
  81. "noexec",
  82. "newinstance",
  83. "ptmxmode=0666",
  84. "mode=0620",
  85. "gid=5"
  86. ]
  87. },
  88. {
  89. "destination": "/dev/shm",
  90. "type": "tmpfs",
  91. "source": "shm",
  92. "options": [
  93. "nosuid",
  94. "noexec",
  95. "nodev",
  96. "mode=1777",
  97. "size=65536k"
  98. ]
  99. },
  100. {
  101. "destination": "/dev/mqueue",
  102. "type": "mqueue",
  103. "source": "mqueue",
  104. "options": [
  105. "nosuid",
  106. "noexec",
  107. "nodev"
  108. ]
  109. },
  110. {
  111. "destination": "/sys",
  112. "type": "sysfs",
  113. "source": "sysfs",
  114. "options": [
  115. "nosuid",
  116. "noexec",
  117. "nodev",
  118. "ro"
  119. ]
  120. },
  121. {
  122. "destination": "/sys/fs/cgroup",
  123. "type": "cgroup",
  124. "source": "cgroup",
  125. "options": [
  126. "nosuid",
  127. "noexec",
  128. "nodev",
  129. "relatime",
  130. "ro"
  131. ]
  132. }
  133. ],
  134. "linux": {
  135. "resources": {
  136. "devices": [
  137. {
  138. "allow": false,
  139. "access": "rwm"
  140. }
  141. ]
  142. },
  143. "namespaces": [
  144. {
  145. "type": "pid"
  146. },
  147. {
  148. "type": "network"
  149. },
  150. {
  151. "type": "ipc"
  152. },
  153. {
  154. "type": "uts"
  155. },
  156. {
  157. "type": "mount"
  158. }
  159. ],
  160. "maskedPaths": [
  161. "/proc/kcore",
  162. "/proc/latency_stats",
  163. "/proc/timer_list",
  164. "/proc/timer_stats",
  165. "/proc/sched_debug",
  166. "/sys/firmware",
  167. "/proc/scsi"
  168. ],
  169. "readonlyPaths": [
  170. "/proc/asound",
  171. "/proc/bus",
  172. "/proc/fs",
  173. "/proc/irq",
  174. "/proc/sys",
  175. "/proc/sysrq-trigger"
  176. ]
  177. }
  178. }
另一個替代方案是使用“oci-runtime-tool”的子命令“oci-runtime-tool generate”,它包含很多選項幫助你做更多自定義配置。
更多資訊,請參閱runtime-tools[3]。
使用生成的規範JSON檔案,您可以自定義容器的執行時。例如,我們可以更改要執行的應用程式的引數。 
讓我們來看看原始config.json檔案和新檔案之間的差別: 
現在讓我們再次執行容器並觀察到它在退出前休眠了10秒。
行業標準的容器執行時

 

 

鑒於容器逐漸成為主流,容器生態系統中的不同參與者一直致力於標準化。
標準化是自動化和總結最佳實踐的關鍵。
在將runC專案提供給OCI的同時,Docker在2016年開始使用containerd作為容器執行時,containerd可以與更底層執行時runC進行互動。 
Cotnainerd完全支援啟動OCI下的軟體並管理這些軟體的生命週期。 Containerd(以及其他像cri-o這樣的執行時)都是使用runC來執行容器,但Containerd實現了其他更高階功能,如映象管理和更高階API。 
containerd與Docker和OCI執行時整合

Containerd,Shim和RunC,這一切是如何一起工作的

 

 

runC建立在libcontainer之上,libcontainer是以前為Docker引擎提供動力的容器庫。在1.11版之前,Docker引擎被用於管理捲,網路,容器,影象等。 現在,Docker架構被拆分成四個部分:
  • Docker引擎

  • containerd執行時

  • containerd-shim

  • 和runC執行時。

二進位制檔案分別稱為docker,docker-containerd,docker-containerd-shim和docker-runc。讓我們用Docker的新架構來列舉執行容器的步驟:
  1. Docker引擎建立容器(來自映象)並將其傳遞給containerd

  2. Containerd呼叫containerd-shim

  3. Containerd-shim使用runC來執行容器

  4. Containerd-shim可以讓執行時(此處為runC)在啟動容器後退出

     

使用新架構,我們可以執行“無守護容器”(“daemon-less containers” ),它有兩個優點:
  1. runC可以在啟動容器後退出,我們不必執行整個執行時行程。

  2. 即使Docker和/或容器死亡,containerd-shim也會保證stdin,stdout和stderr這些檔案描述符為開啟狀態。

“既然runC和Containerd都是執行時,為什麼執行單個容器時需要兩個執行時?”
這可能是被問的最多的問題之一。在之前講解為什麼Docker將其架構拆分為runC和Containerd時,您會發現這兩個都是執行時。
如果您是從頭開始閱讀的,那麼您一定註意到有高層級和低層級的執行時。這就是runC和Containerd兩者之間的實際差異。
兩者都可以稱為執行時,但每個執行時都有不同的用途和功能。為了使容器生態系統保持標準化,低層級的容器執行時只允許執行容器。
低階執行時(如runC)應該輕巧,快速,並且不會與其他更高層級的容器管理髮生衝突。
當你建立一個容器時,這個容器實際上同時被containerd和runC兩個執行時同時管理。
您會發現有很多容器執行時,其中一些是遵循OCI標準的而另一些不是,一些是低層級的執行時,而另一些不僅僅是執行時,它們同時實現了工具層來管理容器的生命週期等等:
  • 映象傳輸和儲存

  • 容器執行和監控,

  • 底層儲存

  • 網路附件

  • 等等

來源:https://www.ianlewis.org/en/container-runtimes-part-1-introduction-container-r
我們可以為Docker新增新的執行時,只需執行:
  1. sudo dockerd --add-runtime==
示例:
  1. sudo apt-get install nvidia-container-runtime
  2. sudo dockerd --add-runtime=nvidia=/usr/bin/nvidia-container-runtime
容器執行時介面

 

 

Kubernetes是當下最流行的容器編排系統之一。隨著容器執行時數量的不斷增加,Kubernetes標的是變得更加靈活,並且與更多的容器執行時(不僅僅是Docker)進行互動。
最初,Kubernetes使用Docker執行時來執行容器,並且現在它仍然是預設的執行時。
但是,CoreOS希望將Kubernetes與RKT執行時一起使用,併為Kubernetes提供補丁,以便將來此執行時可以用來替代Docker執行時。在新增新的容器執行時,Kubernetes不希望改變自己的程式碼庫,於是它決定建立容器執行時介面(CRI或Container Runtime Interface),這是一組API和庫,允許在Kubernetes中執行不同的容器執行時。Kubernetes透過CRI API與它支援的執行時進行互動。 
以下是一些CRI外掛:
CRI-O:是為Kubernetes CRI介面建立的第一個容器執行時。 cri-o不是為了取代Docker,而是可以在Kubernetes的特定背景關係中使用它而不是Docker執行時。 
Containerd CRI:使用cri-containerd,使用者可以使用containerd作為底層執行時執行Kubernetes叢集,而無需安裝Docker。 
gVisor CRI:gVisor是由Google開發的專案,它在使用者空間中實現了大約200個Linux系統呼叫,與直接在Linux核心上執行的Docker容器(使用名稱空間隔離)相比,具有更高的安全性。Google Cloud App Engine使用gVisor CRI實現客戶之間的隔離。 
gVisro執行時與Docker和Kubernetes整合,使得執行沙盒容器變得簡單。
CRI-O Kata容器:Kata Containers是一個開源專案,用於構建輕量級虛擬機器,可插入容器生態系統。 CRI-O Kata容器允許在Kubernetes上執行Kata Containers來替代預設的Docker執行時。 
Moby專案

 

建立一個單體的Docker平臺的專案在某種程度上已被拋棄,並催生了Moby專案,在這個專案中,Docker被拆分成多個元件,例如RunC。 
來源:Solomon Hykes Twitter
Moby是一個將Docker開發進行組織和模組化的專案。它是一個開發和生產的生態系統。 Docker的常規使用者很難意識到變化。 
資料來源:Solomon Hykes Twitter
Moby幫助開發和執行Docker CE和EE(Moby是Docker上游)以及為其他執行時和平臺建立開發和生產環境。
開放容器計劃(The Open Containers Initiative)

 

 

正如我們所看到的,Docker將RunC捐贈給Open Container Initiative(OCI),但這項計劃是什麼?
OCI是一個輕量級,開放型治理架構的組織,由Docker,CoreOS和容器行業的其他領導者於2015年發起。
開放容器計劃(OCI)旨在建立軟體容器的通用標準,以避免容器生態系統內部可能出現的分裂和分化。 
它包含兩個規範:
  • runtime-spec:執行時規範

  • image-spec:映象規範

使用不同執行時的容器可以與Docker API一起使用。使用Docker建立的容器可以能在任何其他引擎上執行。
相關連結:
  1. https://github.com/moby/moby/commit/0db56e6c519b19ec16c6fbd12e3cee7dfa6018c5

  2. http://www.busybox.net/

  3. https://github.com/opencontainers/runtime-tools

原文連結:https://medium.com/faun/the-missing-introduction-to-containerization-de1fbb73efc5
贊(1)

分享創造快樂