原生引入了不少新的概念和思維方式,也影響了應(yīng)用所采用的實(shí)現(xiàn)技術(shù)。容器是云原生應(yīng)用的基礎(chǔ)性技術(shù),其顛覆了應(yīng)用的開發(fā)、交付和運(yùn)行模式,在云計(jì)算、互聯(lián)網(wǎng)等領(lǐng)域得到了廣泛應(yīng)用。
本文選自《Harbor權(quán)威指南》一書,下面我們一同了解下容器的發(fā)展進(jìn)程及基本原理。
容器技術(shù)的發(fā)展背景
在電子計(jì)算機(jī)剛出現(xiàn)時(shí),由于硬件成本高昂,人們?cè)噲D尋找能夠多用戶共享計(jì)算資源的方式,以提高資源利用率和降低成本。在20世紀(jì)60年代,基于硬件技術(shù)的主機(jī)虛擬化技術(shù)出現(xiàn)了。一臺(tái)物理主機(jī)可以被劃分為若干個(gè)小的機(jī)器,每個(gè)機(jī)器的硬件互不共享,并可以安裝各自的操作系統(tǒng)來使用。20世紀(jì)90年代后期,X86架構(gòu)的硬件虛擬化技術(shù)逐漸興起,可在同一臺(tái)物理機(jī)上隔離多個(gè)操作系統(tǒng)實(shí)例,帶來了很多的優(yōu)點(diǎn),目前絕大多數(shù)的數(shù)據(jù)中心都采用了硬件虛擬化技術(shù)。
雖然硬件虛擬化提供了分隔資源的能力,但是采用虛擬機(jī)方式隔離應(yīng)用程序時(shí),效率往往較低,畢竟還要在每個(gè)虛擬機(jī)中安裝或復(fù)制一個(gè)操作系統(tǒng)實(shí)例,然后把應(yīng)用部署到其中。因此人們探索出一種更輕量的方案——操作系統(tǒng)虛擬化,使面向應(yīng)用的管理更便捷。所謂操作系統(tǒng)虛擬化,就是由操作系統(tǒng)創(chuàng)建虛擬的系統(tǒng)環(huán)境,使應(yīng)用感知不到其他應(yīng)用的存在,仿佛在獨(dú)自占有全部的系統(tǒng)資源,從而實(shí)現(xiàn)應(yīng)用隔離的目的。在這種方式中不需要虛擬機(jī),也能夠?qū)崿F(xiàn)應(yīng)用彼此隔離,由于應(yīng)用是共享同一個(gè)操作系統(tǒng)實(shí)例的,因此比虛擬機(jī)更節(jié)省資源,性能更好。操作系統(tǒng)虛擬化在不少系統(tǒng)里面也被稱為容器(Container),下面也會(huì)以容器來指代操作系統(tǒng)虛擬化。
操作系統(tǒng)虛擬化最早出現(xiàn)在2000年,F(xiàn)reeBSD 4.0推出了Jail。Jail加強(qiáng)和改進(jìn)了用于文件系統(tǒng)隔離的chroot環(huán)境。到了2004年,Sun公司發(fā)布了Solaris 10的Containers,包括Zones和Resource management兩部分。Zones實(shí)現(xiàn)了命名空間隔離和安全訪問控制,Resource management實(shí)現(xiàn)了資源分配控制。2007年,Control Groups(簡(jiǎn)稱cgroups)進(jìn)入Linux內(nèi)核,可以限定和隔離一組進(jìn)程所使用的資源(包括CPU、內(nèi)存、I/O和網(wǎng)絡(luò)等)。
2013年,Docker公司發(fā)布Docker開源項(xiàng)目,提供了一系列簡(jiǎn)便的工具鏈來使用容器。毫不夸張地說,Docker公司率先點(diǎn)燃了容器技術(shù)的火焰,拉開了云原生應(yīng)用變革的帷幕,促進(jìn)容器生態(tài)圈一日千里地發(fā)展。截至2020年,Docker Hub中的鏡像累計(jì)下載了1300億次,用戶創(chuàng)建了約600萬個(gè)容器鏡像庫(kù)。從這些數(shù)據(jù)可以看到,用戶正在以驚人的速度從傳統(tǒng)模式切換到基于容器的應(yīng)用發(fā)布和運(yùn)維模式。
2015年,OCI(Open Container Initiative)作為L(zhǎng)inux基金會(huì)項(xiàng)目成立,旨在推動(dòng)開源技術(shù)社區(qū)制定容器鏡像和運(yùn)行時(shí)規(guī)范,使不同廠家的容器解決方案具備互操作能力。同年還成立了CNCF,目的是促進(jìn)容器技術(shù)在云原生領(lǐng)域的應(yīng)用,降低用戶開發(fā)云原生應(yīng)用的門檻。創(chuàng)始會(huì)員包括谷歌、紅帽、Docker、VMware等多家公司和組織。
CNCF成立之初只有一個(gè)開源項(xiàng)目,就是后來大名鼎鼎的Kubernetes。Kubernetes是一個(gè)容器應(yīng)用的編排工具,最早由谷歌的團(tuán)隊(duì)研發(fā),后來開源并捐贈(zèng)給了CNCF成為種子項(xiàng)目。由于Kubernetes是廠家中立的開源項(xiàng)目,開源后得到了社區(qū)用戶和開發(fā)者的廣泛參與和支持。到了2018年,Kubernetes已成為容器編排領(lǐng)域事實(shí)上的標(biāo)準(zhǔn),并成為首個(gè)CNCF的畢業(yè)(graduated)項(xiàng)目。2020年8月,CNCF旗下的開源項(xiàng)目增加到了63個(gè),包括原創(chuàng)于中國(guó)的Harbor等項(xiàng)目。
從容器的發(fā)展歷程可以看到,容器在出現(xiàn)的早期并沒有得到人們的廣泛關(guān)注,主要原因是當(dāng)時(shí)開放的云計(jì)算環(huán)境還沒出現(xiàn)或者未成為主流。2010年之后,隨著IaaS、PaaS和SaaS等云平臺(tái)逐漸成熟,用戶對(duì)云端應(yīng)用開發(fā)、部署和運(yùn)維的效率不斷重視,重新發(fā)掘了容器的價(jià)值,最終促成了容器技術(shù)的盛行。
容器的基本原理
下面我們以Linux容器為例,講解容器的實(shí)現(xiàn)原理,主要包括命名空間(Namespace)和控制組(cgroups)。
▊ 命名空間
命名空間是Linux操作系統(tǒng)內(nèi)核的一種資源隔離方式,使不同的進(jìn)程具有不同的系統(tǒng)視圖。系統(tǒng)視圖就是進(jìn)程能夠感知到的系統(tǒng)環(huán)境,如主機(jī)名、文件系統(tǒng)、網(wǎng)絡(luò)協(xié)議棧、其他用戶和進(jìn)程等。使用命名空間后,每個(gè)進(jìn)程都具備獨(dú)立的系統(tǒng)環(huán)境,進(jìn)程間彼此感覺不到對(duì)方的存在,進(jìn)程之間相互隔離。目前,Linux中的命名空間共有6種,可以嵌套使用。
◎ Mount
隔離了文件系統(tǒng)的掛載點(diǎn)(mount points),處于不同“mount”命名空間中的進(jìn)程可以看到不同的文件系統(tǒng)。
◎ Network
隔離進(jìn)程網(wǎng)絡(luò)方面的系統(tǒng)資源,包括網(wǎng)絡(luò)設(shè)備、IPv4和IPv6的協(xié)議棧、路由表、防火墻等。
◎ IPC
進(jìn)程間相互通信的命名空間,不同命名空間中的進(jìn)程不能通信。
◎ PID
進(jìn)程號(hào)在不同的命名空間中是獨(dú)立編號(hào)的,不同的命名空間中的進(jìn)程可以有相同的編號(hào)。當(dāng)然,這些進(jìn)程在操作系統(tǒng)中的全局(命名空間外)編號(hào)是唯一的。
◎ UTS
系統(tǒng)標(biāo)識(shí)符命名空間,在每個(gè)命名空間中都可以有不同的主機(jī)名和NIS域名。
◎ User
命名空間中的用戶可以有不同于全局的用戶ID和組ID,從而具有不同的特權(quán)。
命名空間實(shí)現(xiàn)了在同一操作系統(tǒng)中隔離進(jìn)程的方法,幾乎沒有額外的系統(tǒng)開銷,所以是非常輕量的隔離方式,進(jìn)程啟動(dòng)和運(yùn)行的過程在命名空間中和外面幾乎沒有差別。
▊ 控制組
命名空間實(shí)現(xiàn)了進(jìn)程隔離功能,但由于各個(gè)命名空間中的進(jìn)程仍然共享同樣的系統(tǒng)資源,如CPU、磁盤I/O、內(nèi)存等,所以如果某個(gè)進(jìn)程長(zhǎng)時(shí)間占用某些資源,其他命名空間里的進(jìn)程就會(huì)受到影響,這就是“吵鬧的鄰居(noisy neighbors)”現(xiàn)象。因此,命名空間并沒有完全達(dá)到進(jìn)程隔離的目的。為此,Linux內(nèi)核提供了控制組(Control Groups,cgroups)功能來處理這個(gè)問題。
Linux把進(jìn)程分成控制組,給每組里的進(jìn)程都設(shè)定資源使用規(guī)則和限制。在發(fā)生資源競(jìng)爭(zhēng)時(shí),系統(tǒng)會(huì)根據(jù)每個(gè)組的定義,按照比例在控制組之間分配資源。控制組可設(shè)定規(guī)則的資源包括CPU、內(nèi)存、磁盤I/O和網(wǎng)絡(luò)等。通過這種方式,就不會(huì)出現(xiàn)某些進(jìn)程無限度搶占其他進(jìn)程資源的情況。
Linux系統(tǒng)通過命名空間設(shè)置進(jìn)程的可見且可用資源,通過控制組規(guī)定進(jìn)程對(duì)資源的使用量,這樣隔離進(jìn)程的虛擬環(huán)境(即容器)就建立起來了。
容器運(yùn)行時(shí)
Linux 提供了命名空間和控制組兩大系統(tǒng)功能,它們是容器的基礎(chǔ)。但是,要把進(jìn)程運(yùn)行在容器中,還需要有便捷的SDK或命令來調(diào)用Linux的系統(tǒng)功能,從而創(chuàng)建出容器。容器的運(yùn)行時(shí)(runtime)就是容器進(jìn)程運(yùn)行和管理的工具。
容器運(yùn)行時(shí)分為低層運(yùn)行時(shí)和高層運(yùn)行時(shí),功能各有側(cè)重。低層運(yùn)行時(shí)主要負(fù)責(zé)運(yùn)行容器,可在給定的容器文件系統(tǒng)上運(yùn)行容器的進(jìn)程;高層運(yùn)行時(shí)則主要為容器準(zhǔn)備必要的運(yùn)行環(huán)境,如容器鏡像下載和解壓并轉(zhuǎn)化為容器所需的文件系統(tǒng)、創(chuàng)建容器的網(wǎng)絡(luò)等,然后調(diào)用低層運(yùn)行時(shí)啟動(dòng)容器。主要的容器運(yùn)行時(shí)的關(guān)系如下。
▊ OCI運(yùn)行時(shí)規(guī)范
成立于2015年的OCI是Linux基金會(huì)旗下的合作項(xiàng)目,以開放治理的方式制定操作系統(tǒng)虛擬化(特別是Linux容器)的開放工業(yè)標(biāo)準(zhǔn),主要包括容器鏡像格式和容器運(yùn)行時(shí)(runtime)。初始成員包括Docker、亞馬遜、CoreOS、谷歌、微軟和VMware等公司。OCI成立之初,Docker公司為其捐贈(zèng)了容器鏡像格式和運(yùn)行時(shí)的草案及相應(yīng)的實(shí)現(xiàn)代碼。原來屬于Docker的libcontainer項(xiàng)目被捐贈(zèng)給OCI,成為獨(dú)立的容器運(yùn)行時(shí)項(xiàng)目runC。
OCI運(yùn)行時(shí)規(guī)范定義了容器配置、運(yùn)行時(shí)和生命周期的標(biāo)準(zhǔn),主流的容器運(yùn)行時(shí)都遵循OCI運(yùn)行時(shí)的規(guī)范,從而提高系統(tǒng)的可移植性和互操作性,用戶可根據(jù)需要進(jìn)行選擇。
首先,容器啟動(dòng)前需要在文件系統(tǒng)中按一定格式存放所需的文件。OCI運(yùn)行時(shí)規(guī)范定義了容器文件系統(tǒng)包(filesystem bundle)的標(biāo)準(zhǔn),在OCI運(yùn)行時(shí)的實(shí)現(xiàn)中通常由高層運(yùn)行時(shí)下載OCI鏡像,并將OCI鏡像解壓成OCI運(yùn)行時(shí)文件系統(tǒng)包,然后OCI運(yùn)行時(shí)讀取配置信息和啟動(dòng)容器里的進(jìn)程。OCI運(yùn)行時(shí)文件系統(tǒng)包主要包括以下兩部分。
◎ config.json
這是必需的配置文件,存放于文件系統(tǒng)包的根目錄下。OCI運(yùn)行時(shí)規(guī)范對(duì)Linux、Windows、Solaris和虛擬機(jī)4種平臺(tái)的運(yùn)行時(shí)做了相應(yīng)的配置規(guī)范。
◎ 容器的根文件系統(tǒng)
容器啟動(dòng)后進(jìn)程所使用的根文件系統(tǒng),由 config.json 中的root.path屬性確定該文件系統(tǒng)的路徑,通常是“rootfs/”。
然后,在定義文件系統(tǒng)包的基礎(chǔ)上,OCI運(yùn)行時(shí)規(guī)范制定了運(yùn)行時(shí)和生命周期管理規(guī)范。生命周期定義了容器從創(chuàng)建到刪除的全過程,可用以下三條命令說明。
◎“create”命令
在調(diào)用該命令時(shí)需要用到文件系統(tǒng)包的目錄位置和容器的唯一標(biāo)識(shí)。在創(chuàng)建運(yùn)行環(huán)境時(shí)需要使用config.json里面的配置。在創(chuàng)建的過程中,用戶可加入某些事件鉤子(hook)來觸發(fā)一些定制化處理,這些事件鉤子包括prestart、createRuntime和createContainer。
◎“start”命令
在調(diào)用該命令時(shí)需要運(yùn)行容器的唯一標(biāo)識(shí)。用戶可在 config.json 的process 屬性中指明運(yùn)行程序的詳細(xì)信息。“start”命令包括兩個(gè)事件鉤子:startContainer和poststart。
◎“delete”命令
在調(diào)用該命令時(shí)需要運(yùn)行容器的唯一標(biāo)識(shí)。在用戶的程序終止后(包括正常和異常退出),容器運(yùn)行時(shí)執(zhí)行“delete”命令以清除容器的運(yùn)行環(huán)境。“delete”命令有一個(gè)事件鉤子:poststop。
-----
除了上述生命周期命令,OCI運(yùn)行時(shí)還必須支持另外兩條命令。
(1)“state”命令
在調(diào)用該命令時(shí)需要運(yùn)行容器的唯一標(biāo)識(shí)。該命令查詢某個(gè)容器的狀態(tài),必須包括的狀態(tài)屬性有ociVersion、id、status、pid和bundle,可選屬性有annotation。不同的運(yùn)行時(shí)實(shí)現(xiàn)可能會(huì)有一些差異。下面是一個(gè)容器狀態(tài)的例子:
1{
2 "ociVersion": "1.0.1",
3 "id": "oci-container001",
4 "status": "running",
5 "pid": 8080,
6 "bundle": "/containers/nginx",
7 "annotations": {
8 "key1": "value1"
9 }
10}
(2)“kill”命令
在調(diào)用該命令時(shí)需要運(yùn)行容器的唯一標(biāo)識(shí)和信號(hào)(signal)編號(hào)。該命令給容器進(jìn)程發(fā)送信號(hào),如Linux操作系統(tǒng)的信號(hào)9表示立即終止進(jìn)程。
▊ runC
runC是OCI運(yùn)行時(shí)規(guī)范的參考實(shí)現(xiàn),也是最常用的容器運(yùn)行時(shí),被其他多個(gè)項(xiàng)目使用,如containerd和CRI-O等。runC也是低層容器運(yùn)行時(shí),開發(fā)人員可通過runC實(shí)現(xiàn)容器的生命周期管理,避免繁瑣的操作系統(tǒng)調(diào)用。根據(jù)OCI運(yùn)行時(shí)規(guī)范,runC不包括容器鏡像的管理功能,它假定容器的文件包已經(jīng)從鏡像里解壓出來并存放于文件系統(tǒng)中。runC創(chuàng)建的容器需要手動(dòng)配置網(wǎng)絡(luò)才能與其他容器或者網(wǎng)絡(luò)節(jié)點(diǎn)連通,為此可在容器啟動(dòng)之前通過OCI定義的事件鉤子來設(shè)置網(wǎng)絡(luò)。
由于runC提供的功能比較單一,復(fù)雜的環(huán)境需要更高層的容器運(yùn)行時(shí)來生成,所以runC常常成為其他高層容器運(yùn)行時(shí)的底層實(shí)現(xiàn)基礎(chǔ)。
▊ containerd
在OCI成立時(shí),Docker公司把其Docker項(xiàng)目拆分為runC的低層運(yùn)行時(shí)及高層運(yùn)行時(shí)功能。2017年,Docker公司把這部分高層容器運(yùn)行時(shí)的功能集中到containerd項(xiàng)目里,捐贈(zèng)給云原生計(jì)算基金會(huì)。
containerd 已經(jīng)成為多個(gè)項(xiàng)目共同使用的高層容器運(yùn)行時(shí),提供了容器鏡像的下載和解壓等鏡像管理功能,在運(yùn)行容器時(shí),containerd先把鏡像解壓成OCI的文件系統(tǒng)包,然后調(diào)用runC運(yùn)行容器。containerd提供了API,其他應(yīng)用程序可以通過API與containerd交互。“ctr”是containerd的命令行工具,和“docker”命令很相像。但作為容器運(yùn)行時(shí),containerd只注重在容器運(yùn)行等方面,因而不包含開發(fā)者使用的鏡像構(gòu)建和鏡像上傳鏡像倉(cāng)庫(kù)等功能。
▊ Docker
Docker引擎是最早流行也是最廣泛使用的容器運(yùn)行時(shí)之一,是一個(gè)容器管理工具,架構(gòu)如下圖。
Docker的客戶端(命令行CLI工具)通過API調(diào)用容器引擎Docker Daemon(dockerd)的功能,完成各種容器管理任務(wù)。
Docker引擎在發(fā)布時(shí)是一個(gè)單體應(yīng)用,所有功能都集中在一個(gè)可執(zhí)行文件里,后來按功能分拆成runC和containerd兩個(gè)不同層次的運(yùn)行時(shí),分別捐獻(xiàn)給了OCI和CNCF。上面兩節(jié)已經(jīng)分別介紹了runC和containerd的主要特點(diǎn),剩下的dockerd就是Docker公司維護(hù)的容器運(yùn)行時(shí)。
dockerd同時(shí)提供了面向開發(fā)者和面向運(yùn)維人員的功能。其中,面向開發(fā)者的命令主要提供鏡像管理功能。容器鏡像一般可由Dockerfile構(gòu)建(build)而來。Dockerfile是一個(gè)文本文件,通過一組命令關(guān)鍵字定義了容器鏡像所包含的基礎(chǔ)鏡像(base image)、所需的軟件包及有關(guān)應(yīng)用程序。在Dockerfile編寫完成以后,就可以用“docker build”命令構(gòu)建鏡像了。下面是一個(gè)Dockerfile的簡(jiǎn)單例子:
1FROM ubuntu:18.04
2EXPOSE 8080
3CMD ["nginx", "-g", "daemon off;"]
容器的鏡像在構(gòu)建之后被存放在本地鏡像庫(kù)里,當(dāng)需要與其他節(jié)點(diǎn)共享鏡像時(shí),可上傳鏡像到鏡像倉(cāng)庫(kù)(Registry)以供其他節(jié)點(diǎn)下載。
Docker還提供了容器存儲(chǔ)和網(wǎng)絡(luò)映射到宿主機(jī)的功能,大部分由containerd實(shí)現(xiàn)。應(yīng)用的數(shù)據(jù)可以被保存在容器的私有文件系統(tǒng)里面,這部分?jǐn)?shù)據(jù)會(huì)隨著容器一起被刪除。對(duì)需要數(shù)據(jù)持久化的有狀態(tài)應(yīng)用來說,可用數(shù)據(jù)卷Volume的方式導(dǎo)入宿主機(jī)上的文件目錄到容器中,對(duì)該目錄的所有寫操作都將被保存到宿主機(jī)的文件系統(tǒng)中。Docker可以把容器內(nèi)的網(wǎng)絡(luò)映射到宿主機(jī)的網(wǎng)絡(luò)上,并且可以連接外部網(wǎng)絡(luò)。
▊ CRI和CRI-O
Kubernetes是當(dāng)今主流的容器編排平臺(tái),為了適應(yīng)不同場(chǎng)景的需求,Kubernetes需要有使用不同容器運(yùn)行時(shí)的能力。為此,Kubernetes從1.5版本開始,在kubelet中增加了一個(gè)容器運(yùn)行時(shí)接口CRI(Container Runtime Interface),需要接入Kubernetes的容器運(yùn)行時(shí)必須實(shí)現(xiàn)CRI接口。由于kubelet的任務(wù)是管理本節(jié)點(diǎn)的工作負(fù)載,需要有鏡像管理和運(yùn)行容器的能力,因此只有高層容器運(yùn)行時(shí)才適合接入CRI。CRI和容器運(yùn)行時(shí)的關(guān)系如下圖。
CRI和容器運(yùn)行時(shí)之間需要有個(gè)接口層,通常稱之為shim(墊片),用以匹配相應(yīng)的容器運(yùn)行時(shí)。CRI接口由shim實(shí)現(xiàn),定義如下,分為RuntimeService和ImageServiceManager(代碼參見GitHub上kubernetes/cri-api的項(xiàng)目文件“pkg/apis/services.go”):
1// RuntimeService接口必須由容器運(yùn)行時(shí)實(shí)現(xiàn)
2// 以下方法必須是線程安全的
3type RuntimeService interface {
4RuntimeVersioner
5ContainerManager
6PodSandboxManager
7ContainerStatsManager
8
9// UpdateRuntimeConfig更新運(yùn)行時(shí)配置
10UpdateRuntimeConfig(runtimeConfig *runtimeapi.RuntimeConfig) error
11
12// Status返回運(yùn)行時(shí)的狀態(tài)
13Status() (*runtimeapi.RuntimeStatus, error)
14}
15
16// ImageManagerService接口必須由容器管理器實(shí)現(xiàn)
17// 以下方法必須是線程安全的
18type ImageManagerService interface {
19// ListImages列出現(xiàn)有鏡像
20ListImages(filter *runtimeapi.ImageFilter) ([]*runtimeapi.Image, error)
21
22// ImageStatus返回鏡像狀態(tài)
23ImageStatus(image *runtimeapi.ImageSpec) (*runtimeapi.Image, error)
24
25// PullImage用認(rèn)證配置拉取鏡像
26PullImage(image *runtimeapi.ImageSpec, auth *runtimeapi.AuthConfig, podSandboxConfig *runtimeapi.PodSandboxConfig) (string, error)
27
28// RemoveImage刪除鏡像
29RemoveImage(image *runtimeapi.ImageSpec) error
30
31// ImageFsInfo返回存儲(chǔ)鏡像的文件系統(tǒng)信息
32ImageFsInfo() ([]*runtimeapi.FilesystemUsage, error)
33}
Docker運(yùn)行時(shí)被普遍使用,它的CRI shim被稱為dockershim,內(nèi)置在Kubernetes的kubelet中,由Kubernetes項(xiàng)目組開發(fā)和維護(hù)。其他運(yùn)行時(shí)則需要提供外置的shim。containerd從1.1版本開始內(nèi)置了CRI plugin,不再需要外置shim來轉(zhuǎn)發(fā)請(qǐng)求,因此效率更高。在安裝Docker的最新版本時(shí),會(huì)自動(dòng)安裝containerd,所以在一些系統(tǒng)中,Docker和Kubernetes可以同時(shí)使用containerd來運(yùn)行容器,但是二者的鏡像用了命名空間隔離,彼此是獨(dú)立的,即鏡像不可以共用。因?yàn)镈ocker和containerd常常同時(shí)存在,因此在不需要使用Docker的系統(tǒng)中只安裝containerd即可。
containerd最早是為Docker設(shè)計(jì)的代碼,包含一些用戶相關(guān)的功能。相比之下,CRI-O是替代Docker或者containerd的高效且輕量級(jí)的容器運(yùn)行時(shí)方案,是CRI的一個(gè)實(shí)現(xiàn),能夠運(yùn)行符合OCI規(guī)范的容器,所以被稱為CRI-O。CRI-O是原生為生產(chǎn)系統(tǒng)運(yùn)行容器設(shè)計(jì)的,有個(gè)簡(jiǎn)單的命令行工具供測(cè)試用,但并不能進(jìn)行容器管理。CRI-O支持OCI的容器鏡像格式,可以從容器鏡像倉(cāng)庫(kù)中下載鏡像。CRI-O支持runC和Kata Containers這兩種低層容器運(yùn)行時(shí)。
新書上市
▊《Harbor權(quán)威指南:容器鏡像、Helm Chart等云原生制品的管理與實(shí)踐》
張海寧 等 著
在云原生生態(tài)中,容器鏡像和其他云原生制品的管理與分發(fā)是至關(guān)重要的一環(huán)。本書對(duì)開源云原生制品倉(cāng)庫(kù)Harbor展開全面講解,由Harbor開源項(xiàng)目維護(hù)者和貢獻(xiàn)者傾力撰寫,內(nèi)容涵蓋Harbor的架構(gòu)、原理、功能、部署與配置、運(yùn)維、定制化開發(fā)、API、項(xiàng)目治理和成功案例等,還有很多未公開發(fā)表的內(nèi)容在本書中都有詳盡講解!
Docker 是一個(gè)開源的應(yīng)用容器引擎 誕生于 2013 年初,基于 Go 語言實(shí)現(xiàn), dotCloud 公司出品(后改名為Docker Inc) Docker 可以讓開發(fā)者打包他們的應(yīng)用以及依賴包到一個(gè)輕量級(jí)、可移植的容器中,然后發(fā)布到任何流行的 Linux 機(jī)器上。 容器是完全使用沙箱機(jī)制,相互隔離 容器性能開銷極低。 Docker 從 17.03 版本之后分為 CE(Community Edition: 社區(qū)版) 和 EE(Enterprise Edition: 企業(yè)版)
鏡像(Image):Docker 鏡像(Image),就相當(dāng)于是一個(gè) root 文件系統(tǒng)。比如官方鏡像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系統(tǒng)的 root 文件系統(tǒng)。
容器(Container):鏡像(Image)和容器(Container)的關(guān)系,就像是面向?qū)ο蟪绦蛟O(shè)計(jì)中的類和對(duì)象一樣,鏡像是靜態(tài)的定義,容器是鏡像運(yùn)行時(shí)的實(shí)體。容器可以被創(chuàng)建、啟動(dòng)、停止、刪除、暫停等。
倉(cāng)庫(kù)(Repository):倉(cāng)庫(kù)可看成一個(gè)代碼控制中心,用來保存鏡像。
操作系統(tǒng)組成部分:
進(jìn)程調(diào)度子系統(tǒng) 進(jìn)程通信子系統(tǒng) 內(nèi)存管理子系統(tǒng) 設(shè)備管理子系統(tǒng) 文件管理子系統(tǒng) 網(wǎng)絡(luò)通信子系統(tǒng) 作業(yè)控制子系統(tǒng)
Linux文件系統(tǒng)由bootfs和rootfs兩部分組成 bootfs:包含bootloader(引導(dǎo)加載程序)和 kernel(內(nèi)核) rootfs: root文件系統(tǒng),包含的就是典型 Linux 系統(tǒng)中的/dev,/proc,/bin,/etc等標(biāo)準(zhǔn)目錄和文件 不同的linux發(fā)行版,bootfs基本一樣,而rootfs不同,如ubuntu,centos等
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳
Docker容器組成
Docker鏡像是由特殊的文件系統(tǒng)疊加而成 最底端是 bootfs,并使用宿主機(jī)的bootfs 第二層是 root文件系統(tǒng)rootfs,稱為base image 然后再往上可以疊加其他的鏡像文件 統(tǒng)一文件系統(tǒng)(Union File System)技術(shù)能夠?qū)⒉煌膶诱铣梢粋€(gè)文件系統(tǒng),為這些層提供了一個(gè)統(tǒng)一的視角,這樣就隱藏了多層的存在,在用戶的角度看來,只存在一個(gè)文件系統(tǒng)。 一個(gè)鏡像可以放在另一個(gè)鏡像的上面。位于下面的鏡像稱為父鏡像,最底部的鏡像成為基礎(chǔ)鏡像。 當(dāng)從一個(gè)鏡像啟動(dòng)容器時(shí),Docker會(huì)在最頂層加載一個(gè)讀寫文件系統(tǒng)作為容器
Docker可以運(yùn)行在MAC、Windows、CentOS、UBUNTU等操作系統(tǒng)上
這里以CentOS7為例:
1.安裝yum工具
yum install -y yum-utils \
device-mapper-persistent-data \
lvm2 --skip-broken
2.更新本地鏡像源
# 設(shè)置docker鏡像源
yum-config-manager \
--add-repo \
https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
sed -i 's/download.docker.com/mirrors.aliyun.com\/docker-ce/g' /etc/yum.repos.d/docker-ce.repo
yum makecache fast
3.執(zhí)行安裝
yum install -y docker-ce
等待命令執(zhí)行完成,docker安裝完成
查看Docker版本
docker -v
配置鏡像加速
docker官方鏡像倉(cāng)庫(kù)網(wǎng)速較差,我們這里使用阿里云鏡像加速
打開阿里云官網(wǎng),并登陸
在產(chǎn)品控制臺(tái)搜索 容器與鏡像服務(wù) 并打開
在鏡像工具選擇鏡像加速器
復(fù)制示例代碼到linux執(zhí)行:
如上的加速地址:https://twcgr1t0.mirror.aliyuncs.com 不同的阿里云用戶賬戶不同,可以安裝如上步驟進(jìn)行操作獲取加速地址
Docker 進(jìn)程相關(guān)命令
啟動(dòng)docker服務(wù):
systemctl start docker
停止docker服務(wù):
systemctl stop docker
重啟docker服務(wù):
systemctl restart docker
查看docker服務(wù)狀態(tài):
systemctl status docker
設(shè)置開機(jī)啟動(dòng)docker服務(wù):
systemctl enable docker
Docker鏡像操作命令
查看鏡像:
docker images
docker images –q # 查看所用鏡像的id
搜索鏡像:
docker search 鏡像名稱
拉取鏡像:
docker pull 鏡像名稱
從Docker倉(cāng)庫(kù)下載鏡像到本地,鏡像名稱格式為 名稱:版本號(hào),如果版本號(hào)不指定則是最新的版本。 如果不知道鏡像版本,可以去docker hub 搜索對(duì)應(yīng)鏡像查看。
刪除鏡像:
docker rmi 鏡像id # 刪除指定本地鏡像
docker rmi `docker images -q` # 刪除所有本地鏡像
Docker容器操作命令
查看容器
docker ps # 查看正在運(yùn)行的容器
docker ps –a # 查看所有容器
創(chuàng)建并啟動(dòng)容器
docker run 參數(shù)
參數(shù)說明:
進(jìn)入容器
docker exec 參數(shù) # 退出容器,容器不會(huì)關(guān)閉
停止容器
docker stop 容器名稱/id
啟動(dòng)容器
docker start 容器名稱
刪除容器:如果容器是運(yùn)行狀態(tài)則刪除失敗,需要停止容器才能刪除
docker rm 容器名稱
查看容器信息
docker inspect 容器名稱
復(fù)制宿主機(jī)文件到容器
docker cp 宿主機(jī)目錄 容器:容器目錄
數(shù)據(jù)卷 數(shù)據(jù)卷是宿主機(jī)中的一個(gè)目錄或文件 當(dāng)容器目錄和數(shù)據(jù)卷目錄綁定后,對(duì)方的修改會(huì)立即同步 一個(gè)數(shù)據(jù)卷可以被多個(gè)容器同時(shí)掛載 一個(gè)容器也可以被掛載多個(gè)數(shù)據(jù)卷
數(shù)據(jù)卷作用
配置數(shù)據(jù)卷
創(chuàng)建啟動(dòng)容器時(shí),使用 –v 參數(shù) 設(shè)置數(shù)據(jù)卷
docker run ... –v 宿主機(jī)目錄(文件):容器內(nèi)目錄(文件) ...
注意事項(xiàng):
1. 目錄必須是絕對(duì)路徑
2. 如果目錄不存在,會(huì)自動(dòng)創(chuàng)建
3. 可以掛載多個(gè)數(shù)據(jù)卷
4. 掛載的容器目錄如果存在子目錄或文件也會(huì)同步到宿主機(jī)的目錄
多容器進(jìn)行數(shù)據(jù)交換
數(shù)據(jù)卷容器
1.創(chuàng)建數(shù)據(jù)卷容器
docker run –it --name=c3 –v /root/mydata:/mydata centos:7 /bin/bash
2.使用 –-volumes-from 參數(shù) 設(shè)置數(shù)據(jù)卷
docker run –it --name=c1 --volumes-from c3 centos:7 /bin/bash
docker run –it --name=c2 --volumes-from c3 centos:7 /bin/bash
這里以部署MySQL為例:
1.搜索mysql鏡像
docker search mysql
2.拉取mysql鏡像
docker pull mysql:5.7
3.創(chuàng)建容器,設(shè)置端口映射、目錄映射
docker run -id \
-p 3307:3306 \
--name=mysql \
-v /root/docker/mysql/conf:/etc/mysql \
-v /root/docker/mysql/logs:/var/log/mysql \
-v /root/docker/mysql/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
mysql:5.7
參數(shù)說明:
-p 3307:3306:將容器的 3306 端口映射到宿主機(jī)的 3307 端口。
-v /mydata/mysql/log:/var/log/mysql:將宿主機(jī)目錄下的 /mydata/mysql/log目錄掛載到容器的 var/log/mysql; 日志目錄
-v /mydata/mysql/data:/var/lib/mysql:將宿主機(jī)目錄下的/mydata/mysql/data目錄掛載到容器的 /var/lib/mysql ; 數(shù)據(jù)目錄
/root/docker/mysql/conf:/etc/mysql: 將**/root/docker/mysql/conf目錄掛載到容器/etc/mysql ;配置目錄
4.進(jìn)入容器
docker exec –it mysql bash
5.登錄mysql
mysql -uroot -p123456
部署redis:
docker search redis
docker pull redis:5.0
docker run -id --name=redis -p 6379:6379 redis:5.0
./redis-cli.exe -h 虛擬機(jī)或服務(wù)器ip -p 6379
1.容器轉(zhuǎn)鏡像
目的:啟動(dòng)容器后,對(duì)容器進(jìn)行定制化修改,在導(dǎo)出成為鏡像
相關(guān)命令:
##提交鏡像到本地倉(cāng)庫(kù) 可通過docker images 查看
docker commit 容器id 鏡像名稱:版本號(hào)
## 將鏡像導(dǎo)出為壓縮包文件
docker save -o 壓縮文件名稱 鏡像名稱:版本號(hào)
## 加載鏡像到本地倉(cāng)庫(kù)
docker load –i 壓縮文件名稱
2.dockerfile
概述
Dockerfile 相關(guān)指令:
關(guān)鍵字 | 作用 | 備注 |
FROM | 指定父鏡像 | 指定dockerfile基于那個(gè)image構(gòu)建 |
MAINTAINER | 作者信息 | 用來標(biāo)明這個(gè)dockerfile誰寫的 |
LABEL | 標(biāo)簽 | 用來標(biāo)明dockerfile的標(biāo)簽 可以使用Label代替Maintainer 最終都是在docker image基本信息中可以查看 |
RUN | 執(zhí)行命令 | 執(zhí)行一段命令 默認(rèn)是/bin/sh 格式: RUN command 或者 RUN ["command" , "param1","param2"] |
CMD | 容器啟動(dòng)命令 | 提供啟動(dòng)容器時(shí)候的默認(rèn)命令 和ENTRYPOINT配合使用.格式 CMD command param1 param2 或者 CMD ["command" , "param1","param2"] |
ENTRYPOINT | 入口 | 一般在制作一些執(zhí)行就關(guān)閉的容器中會(huì)使用 |
COPY | 復(fù)制文件 | build的時(shí)候復(fù)制文件到image中 |
ADD | 添加文件 | build的時(shí)候添加文件到image中 不僅僅局限于當(dāng)前build上下文 可以來源于遠(yuǎn)程服務(wù) |
ENV | 環(huán)境變量 | 指定build時(shí)候的環(huán)境變量 可以在啟動(dòng)的容器的時(shí)候 通過-e覆蓋 格式ENV name=value |
ARG | 構(gòu)建參數(shù) | 構(gòu)建參數(shù) 只在構(gòu)建的時(shí)候使用的參數(shù) 如果有ENV 那么ENV的相同名字的值始終覆蓋arg的參數(shù) |
VOLUME | 定義外部可以掛載的數(shù)據(jù)卷 | 指定build的image那些目錄可以啟動(dòng)的時(shí)候掛載到文件系統(tǒng)中 啟動(dòng)容器的時(shí)候使用 -v 綁定 格式 VOLUME ["目錄"] |
EXPOSE | 暴露端口 | 定義容器運(yùn)行的時(shí)候監(jiān)聽的端口 啟動(dòng)容器的使用-p來綁定暴露端口 格式: EXPOSE 8080 或者 EXPOSE 8080/udp |
WORKDIR | 工作目錄 | 指定容器內(nèi)部的工作目錄 如果沒有創(chuàng)建則自動(dòng)創(chuàng)建 如果指定/ 使用的是絕對(duì)地址 如果不是/開頭那么是在上一條workdir的路徑的相對(duì)路徑 |
USER | 指定執(zhí)行用戶 | 指定build或者啟動(dòng)的時(shí)候 用戶 在RUN CMD ENTRYPONT執(zhí)行的時(shí)候的用戶 |
HEALTHCHECK | 健康檢查 | 指定監(jiān)測(cè)當(dāng)前容器的健康監(jiān)測(cè)的命令 基本上沒用 因?yàn)楹芏鄷r(shí)候 應(yīng)用本身有健康監(jiān)測(cè)機(jī)制 |
ONBUILD | 觸發(fā)器 | 當(dāng)存在ONBUILD關(guān)鍵字的鏡像作為基礎(chǔ)鏡像的時(shí)候 當(dāng)執(zhí)行FROM完成之后 會(huì)執(zhí)行 ONBUILD的命令 但是不影響當(dāng)前鏡像 用處也不怎么大 |
STOPSIGNAL | 發(fā)送信號(hào)量到宿主機(jī) | 該STOPSIGNAL指令設(shè)置將發(fā)送到容器的系統(tǒng)調(diào)用信號(hào)以退出。 |
SHELL | 指定執(zhí)行腳本的shell | 指定RUN CMD ENTRYPOINT 執(zhí)行命令的時(shí)候 使用的shell |
案例
將springboot項(xiàng)目打包成為docker鏡像
實(shí)現(xiàn)步驟:
創(chuàng)建文件Dockerfile 內(nèi)容如下:
FROM java:8
MAINTAINER muzidong
ADD springboot.jar app.jar
CMD java –jar app.jar
執(zhí)行腳本:
docker bulid -t myapp:1.0 . ## . 代表當(dāng)前目錄的Dockerfile
鏡像創(chuàng)建完成
基于鏡像啟動(dòng)容器 將容器8080端口映射到宿主機(jī)8080
docker run -it -p 8081:8080 --name=myapp myapp:1.0
訪問測(cè)試
疑惑:
1.Docker 鏡像本質(zhì)是什么? 是一個(gè)分層文件系統(tǒng) 2.Docker 中一個(gè)centos鏡像為什么只有200MB,而一個(gè)centos操作系統(tǒng)的iso文件要幾個(gè)個(gè)G? Centos的iso鏡像文件包含bootfs和rootfs,而docker的centos鏡像復(fù)用操作系統(tǒng)的bootfs,只有rootfs和其他鏡像層 3.Docker 中一個(gè)tomcat鏡像為什么有500MB,而一個(gè)tomcat安裝包只有70多MB? 由于docker中鏡像是分層的,tomcat雖然只有70多MB,但他需要依賴于父鏡像和基礎(chǔ)鏡像,所有整個(gè)對(duì)外暴露的tomcat鏡像大小500多MB
概念
微服務(wù)架構(gòu)的應(yīng)用系統(tǒng)中一般包含若干個(gè)微服務(wù),每個(gè)微服務(wù)一般都會(huì)部署多個(gè)實(shí)例,如果每個(gè)微服務(wù)都要手動(dòng)啟停,維護(hù)的工作量會(huì)很大。
簡(jiǎn)單理解: 按照一定的業(yè)務(wù)規(guī)則批量管理容器
使用步驟
Docker Compose是一個(gè)編排多容器分布式部署的工具,提供命令集管理容器化應(yīng)用的完整開發(fā)周期,包括服務(wù)構(gòu)建,啟動(dòng)和停止。
使用步驟: 利用 Dockerfile 定義運(yùn)行環(huán)境鏡像 使用 docker-compose.yml 定義組成應(yīng)用的各服務(wù) 運(yùn)行 docker-compose up 啟動(dòng)應(yīng)用
Docker Compose安裝
# Compose目前已經(jīng)完全支持Linux、Mac OS和Windows,在我們安裝Compose之前,需要先安裝Docker。下面我 們以編譯好的二進(jìn)制包方式安裝在Linux系統(tǒng)中。
curl -L https://github.com/docker/compose/releases/download/1.22.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
# 設(shè)置文件可執(zhí)行權(quán)限
chmod +x /usr/local/bin/docker-compose
# 查看版本信息
docker-compose -version
卸載
# 二進(jìn)制包方式安裝的,刪除二進(jìn)制文件即可
rm /usr/local/bin/docker-compose
真實(shí)案例
使用docker compose編排springboot+redis+mysql+nginx項(xiàng)目
以真實(shí)電商服務(wù)后臺(tái)為例:
1.將java jar包打包為docker鏡像
# 該鏡像需要依賴的基礎(chǔ)鏡像
FROM java:8
# 將當(dāng)前目錄下的jar包復(fù)制到docker容器的/目錄下
ADD api.jar /api.jar
# 運(yùn)行過程中創(chuàng)建一個(gè).jar文件
RUN bash -c 'touch /api.jar'
# 聲明服務(wù)運(yùn)行在8080端口
EXPOSE 8998
# 指定docker容器啟動(dòng)時(shí)運(yùn)行jar包
CMD java -jar /api.jar
# 指定維護(hù)者的名字
MAINTAINER muzidong
2.編寫docker-compose.yml
version: '3'
services:
##mysql容器
mysql:
image: mysql:5.7
container_name: mysql
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
restart: always
environment:
MYSQL_ROOT_PASSWORD: root #設(shè)置root帳號(hào)密碼
ports:
- 3307:3306
volumes:
- /root/docker/mysql/conf:/etc/mysql
- /root/docker/mysql/logs:/var/log/mysql
- /root/docker/mysql/data:/var/lib/mysql
##redis容器
redis:
image: redis:5
container_name: redis
command: redis-server --appendonly yes
volumes:
- /mydata/redis/data:/data #數(shù)據(jù)文件掛載
ports:
- 6399:6379
# 指定服務(wù)名稱
adminapi:
# 指定服務(wù)使用的鏡像
image: admin-api
# 指定容器名稱
container_name: adminapi
# 指定服務(wù)運(yùn)行的端口
ports:
- 8998:8998
# 指定容器中需要掛載的文件
volumes:
- /root/docker/shop-api/logs:/logs
- /root/docker/nginx/data/:/root/docker/nginx/data/
links:
- mysql:mysql
- redis:redis
nginx:
image: nginx:1.10
container_name: nginx
volumes:
- /root/docker/nginx/nginx.conf:/etc/nginx/nginx.conf #配置文件掛載
- /root/docker/nginx/html:/usr/share/nginx/html #靜態(tài)資源根目錄掛載
- /root/docker/nginx/data:/usr/share/nginx/data #靜態(tài)資源根目錄掛載
- /root/docker/nginx/log:/var/log/nginx #日志文件掛載
ports:
- 81:80
links:
- adminapi:adminapi
3.啟動(dòng)容器:
docker-compose -f docker-compose.yml up -d
docker-compose.yml常用指令:
image:指定運(yùn)行的鏡像名稱
container_name:配置容器名稱
ports:指定宿主機(jī)和容器的端口映射(HOST:CONTAINER)
volumes:將宿主機(jī)的文件或目錄掛載到容器中(HOST:CONTAINER)
environment:配置環(huán)境變量
links:連接其他容器的服務(wù)(SERVICE:ALIAS)
links 訪問注意:
這里mysql:mysql 代表訪問mysql域名映射到對(duì)應(yīng)的mysql容器
在java代碼里面 jdbc:mysql://mysql:3306/admin-api 我們的mysql容器端口3306是映射到主機(jī)的3307端口
我們?cè)谠L問的時(shí)候要注意端口不要寫成 mysql:3307了 3307 是宿主機(jī)的端口,我們?nèi)萜鏖g訪問應(yīng)該使用容器內(nèi)的端口
docker默認(rèn)容器間通信模式
具體可參考文章:
https://www.cnblogs.com/kevingrace/p/6590319.html
Docker官方的Docker hub(https://hub.docker.com)是一個(gè)用于管理公共鏡像的倉(cāng)庫(kù),我們可以從上面拉取鏡像 到本地,也可以把我們自己的鏡像推送上去。但是,有時(shí)候我們的服務(wù)器無法訪問互聯(lián)網(wǎng),或者你不希望將自己的鏡 像放到公網(wǎng)當(dāng)中,那么我們就需要搭建自己的私有倉(cāng)庫(kù)來存儲(chǔ)和管理自己的鏡像。
私有倉(cāng)庫(kù)搭建
# 1、拉取私有倉(cāng)庫(kù)鏡像
docker pull registry
# 2、啟動(dòng)私有倉(cāng)庫(kù)容器
docker run -id --name=registry -p 5000:5000 registry
# 3、打開瀏覽器 輸入地址http://私有倉(cāng)庫(kù)服務(wù)器ip:5000/v2/_catalog,看到{"repositories":[]} 表示私有倉(cāng)庫(kù) 搭建成功
# 4、修改daemon.json
vim /etc/docker/daemon.json
# 在上述文件中添加一個(gè)key,保存退出。此步用于讓 docker 信任私有倉(cāng)庫(kù)地址;注意將私有倉(cāng)庫(kù)服務(wù)器ip修改為自己私有倉(cāng)庫(kù)服務(wù)器真實(shí)ip
{"insecure-registries":["私有倉(cāng)庫(kù)服務(wù)器ip:5000"]}
# 5、重啟docker 服務(wù)
systemctl restart docker
docker start registry
將鏡像上傳至私有倉(cāng)庫(kù)
# 1、標(biāo)記鏡像為私有倉(cāng)庫(kù)的鏡像
docker tag 鏡像名稱:版本號(hào) 私有倉(cāng)庫(kù)服務(wù)器IP:5000/鏡像名稱:版本號(hào)
# 2、上傳標(biāo)記的鏡像
docker push 私有倉(cāng)庫(kù)服務(wù)器IP:5000/鏡像名稱:版本號(hào)
從私有倉(cāng)庫(kù)拉取鏡像
#拉取鏡像
docker pull 私有倉(cāng)庫(kù)服務(wù)器ip:5000/centos:7
相同: 容器和虛擬機(jī)具有相似的資源隔離和分配優(yōu)勢(shì)
不同: 容器虛擬化的是操作系統(tǒng),虛擬機(jī)虛擬化的是硬件。 傳統(tǒng)虛擬機(jī)可以運(yùn)行不同的操作系統(tǒng),容器只能運(yùn)行同一類型操作系統(tǒng)
架構(gòu)圖:
以上就是本期的全部?jī)?nèi)容了