在過去四年中(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視為商業名稱。)
“虛擬機器”是在“真實硬體機器”之上模擬虛擬機器的通用術語。該術語最初由Popek和Goldberg定義為真實計算機的有效,孤立副本。
虛擬機器可以是“系統虛擬機器”或“過程虛擬機器”。在我們日常使用VM這個詞時,我們通常指的是“系統虛擬機器”,它是模擬的是主機硬體來模擬整個作業系統。但是,“行程虛擬機器”(“Process Virtual Machine”,有時稱為應用虛擬機器)是用於模擬執行單個行程的程式設計環境:Java Virtual Machine(JVM,Java虛擬機器)就是一個例子。
作業系統級虛擬化也稱為容器化。Linux-VServer和OpenVZ等技術可以執行多個作業系統,同時共享相同的體系架構和核心版本。
在guest需要的虛擬機器於主機的核心版本的情況時,共享相同的體系結構和核心會有一些限制和缺點。
來源:https://fntlnz.wtf/post/why-containers
系統容器(例如LXC)提供的環境非常接近您從虛擬機器獲得的環境,與此同時又省去了執行單獨內核和模擬所有硬體所帶來的開銷。
作業系統級虛擬化有助於我們建立容器。LXC和Docker等技術使用這種隔離方式。我們這裡有兩種型別的容器:
而對於App容器來說,它會建立3個容器來構成LEMP技術棧:
-
PHP伺服器(或者是PHP FPM)
-
Web伺服器(Nginx)
-
MySQL
詳細答案:當Docker啟動時,它使用LXC作為容器執行時,其想法是建立一個API來管理容器執行時,隔離執行應用程式的單個行程,並監督容器的生命週期及其使用的資源。
在2013年初,Docker專案是建立一個“標準容器”,我們可以在這個宣言[1]中看到。
Docker開始構建一個具有多種功能的單體應用程式,這些功能包括啟動雲伺服器以及構建和執行映象/容器等等。Docker使用“libcontainer”與Linux核心功能(如:控制組和名稱空間)進行互動。
Docker,Libcontainer和Linux核心功能
讓我們使用名稱空間和Cgroup(控制組)建立一個容器
我在這個例子中使用的是Ubuntu,但對於大多數發行版來說操作應該類似。首先安裝CGroup工具和壓力測試工具程式(因為我們將進行一些壓力測試)。
sudo apt install cgroup-tools
sudo apt install stress
sudo unshare --fork --pid --mount-proc bash
unshare命令解除了行程的部分執行背景關係的關聯。
unshare()允許行程(或執行緒)與當前和其他行程(或執行緒)共享的執行背景關係解除關聯。但是部分執行背景關係(例如mount > namespace),當使用fork(2)或vfork(2)去建立新行程時,執行背景關係,與此同時,其他部分(例如虛擬記憶體),可能在使用clone(2)建立行程或執行緒,透過顯式請求共享虛擬記憶體。
現在,使用cgcreate我們來建立一個控制組並定義兩個控制器,一個在記憶體上,另一個在CPU上。
echo 3000000 > /sys/fs/cgroup/memory/mygroup/memory.kmem.limit_in_bytes cgexec -g memory:mygroup bash
現在讓我們壓力測試一下我們建立的獨立名稱空間(包含記憶體限制)。
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為任何小型或嵌入式系統提供了相當完善的環境。
使用runC命令,我們可以用提取的映象和spec檔案(config.json)來執行BusyBox容器。runc spec命令一開始會建立以下JSON檔案:
{
"ociVersion": "1.0.1-dev",
"process": {
"terminal": true,
"user": {
"uid": 0,
"gid": 0
},
"args": [
"sh"
],
"env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=xterm"
],
"cwd": "/",
"capabilities": {
"bounding": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"effective": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"inheritable": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"permitted": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"ambient": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
]
},
"rlimits": [
{
"type": "RLIMIT_NOFILE",
"hard": 1024,
"soft": 1024
}
],
"noNewPrivileges": true
},
"root": {
"path": "rootfs",
"readonly": true
},
"hostname": "runc",
"mounts": [
{
"destination": "/proc",
"type": "proc",
"source": "proc"
},
{
"destination": "/dev",
"type": "tmpfs",
"source": "tmpfs",
"options": [
"nosuid",
"strictatime",
"mode=755",
"size=65536k"
]
},
{
"destination": "/dev/pts",
"type": "devpts",
"source": "devpts",
"options": [
"nosuid",
"noexec",
"newinstance",
"ptmxmode=0666",
"mode=0620",
"gid=5"
]
},
{
"destination": "/dev/shm",
"type": "tmpfs",
"source": "shm",
"options": [
"nosuid",
"noexec",
"nodev",
"mode=1777",
"size=65536k"
]
},
{
"destination": "/dev/mqueue",
"type": "mqueue",
"source": "mqueue",
"options": [
"nosuid",
"noexec",
"nodev"
]
},
{
"destination": "/sys",
"type": "sysfs",
"source": "sysfs",
"options": [
"nosuid",
"noexec",
"nodev",
"ro"
]
},
{
"destination": "/sys/fs/cgroup",
"type": "cgroup",
"source": "cgroup",
"options": [
"nosuid",
"noexec",
"nodev",
"relatime",
"ro"
]
}
],
"linux": {
"resources": {
"devices": [
{
"allow": false,
"access": "rwm"
}
]
},
"namespaces": [
{
"type": "pid"
},
{
"type": "network"
},
{
"type": "ipc"
},
{
"type": "uts"
},
{
"type": "mount"
}
],
"maskedPaths": [
"/proc/kcore",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/sys/firmware",
"/proc/scsi"
],
"readonlyPaths": [
"/proc/asound",
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger"
]
}
}
另一個替代方案是使用“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的新架構來列舉執行容器的步驟:
-
Docker引擎建立容器(來自映象)並將其傳遞給containerd
-
Containerd呼叫containerd-shim
-
Containerd-shim使用runC來執行容器
-
Containerd-shim可以讓執行時(此處為runC)在啟動容器後退出
使用新架構,我們可以執行“無守護容器”(“daemon-less containers” ),它有兩個優點:
-
runC可以在啟動容器後退出,我們不必執行整個執行時行程。
-
即使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
sudo dockerd --add-runtime==
sudo apt-get install nvidia-container-runtime
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-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執行時。
建立一個單體的Docker平臺的專案在某種程度上已被拋棄,並催生了Moby專案,在這個專案中,Docker被拆分成多個元件,例如RunC。
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建立的容器可以能在任何其他引擎上執行。
-
https://github.com/moby/moby/commit/0db56e6c519b19ec16c6fbd12e3cee7dfa6018c5
-
http://www.busybox.net/
-
https://github.com/opencontainers/runtime-tools
原文連結:https://medium.com/faun/the-missing-introduction-to-containerization-de1fbb73efc5
朋友會在“發現-看一看”看到你“在看”的內容