大廠都在玩的容器技術(shù)到底是什么?
引言
著名雜志《經(jīng)濟學(xué)人》曾經(jīng)評價“沒有集裝箱,就沒有全球化”,可以說集裝箱的出現(xiàn)重塑了現(xiàn)代貨運體系,實現(xiàn)了交通運輸行業(yè)的標準化,有效降低物流運輸成本,極大提升了貨物轉(zhuǎn)運效率。而在云原生領(lǐng)域,容器就相當(dāng)于集裝箱,它使得軟件發(fā)布以及軟件運行隔離實現(xiàn)標準化,引領(lǐng)了云原生基礎(chǔ)設(shè)施的跨越式發(fā)展。從某種意義上來說,容器技術(shù)重塑了整個軟件供應(yīng)鏈。今天就和大家聊聊各個大廠都在玩的容器技術(shù)到底是什么。
為什么需要容器技術(shù)
在正式介紹容器技術(shù)之前,我們先來看下軟件領(lǐng)域為什么需要容器技術(shù)。一項新技術(shù)的出現(xiàn)必定是為了解決當(dāng)下遇到的的某項具體問題或者說更加提高現(xiàn)有軟件運行效率。那我們就來分析下在容器技術(shù)出現(xiàn)之前,軟件領(lǐng)域到底面臨什么樣的問題。
在很早很早之前,我們部署服務(wù)的時候都是直接部署在硬件服務(wù)器上。如果想對服務(wù)進行擴容就必須要購買服務(wù)器,然后再進行應(yīng)用部署以及各種繁瑣的環(huán)境配置以及服務(wù)配置,由于都是人工操作所以還特別容易出錯,不僅浪費時間還很費程序猿,因此服務(wù)部署以及遷移效率都極其低下。在互聯(lián)網(wǎng)早期的時候,用戶數(shù)以及業(yè)務(wù)體量還不是很大,人工操作還能夠應(yīng)付得過來。但是隨著業(yè)務(wù)規(guī)模不斷發(fā)展以及用戶數(shù)的爆炸式增長,這樣的軟件服務(wù)生產(chǎn)方式已經(jīng)無法滿足業(yè)務(wù)高速發(fā)展的需求。將應(yīng)用服務(wù)直接部署在服務(wù)器上主要有以下三方面的問題。
服務(wù)互干擾
一臺服務(wù)器一般不會只部署一個服務(wù)應(yīng)用,都是部署多個服務(wù)應(yīng)用。但是由于這些服務(wù)都是公用服務(wù)器中的CPU、內(nèi)存、硬盤以及網(wǎng)絡(luò)IO等服務(wù)器資源,那么必定就會存在資源互相爭用、服務(wù)互相影響的情況。
資源利用低
業(yè)務(wù)是存在高峰期和低谷期的,對于電商平臺來說,一般深夜的屬于業(yè)務(wù)低谷期,這個時候的服務(wù)器的資源利用率相比業(yè)務(wù)高峰期的時候要低很多。因此在業(yè)務(wù)低谷期,實際服務(wù)器的資源利用率比較低,不能物盡其用。
遷移擴展難
原有的服務(wù)器數(shù)量不足以應(yīng)對高速發(fā)展的業(yè)務(wù)時,就需要不斷的進行服務(wù)器實例擴充,但是由于服務(wù)直接部署在服務(wù)器中,在進行服務(wù)遷移擴展的時候,需要各種依賴庫、環(huán)境配置以及網(wǎng)絡(luò)配置等,步驟復(fù)雜,擴展困難。
正是軟件領(lǐng)域面臨這么多問題,因此大神們才會發(fā)揮他們的聰明才智不斷推進技術(shù)發(fā)展。因此大神們就設(shè)想如果有一種部署方式可以實現(xiàn)差別的服務(wù)構(gòu)建,那就可以解決服務(wù)部署的各種配置問題。如果有一種技術(shù)可實現(xiàn)真正的資源隔離,進程之間互相不影響,這樣就可以解決互相影響的問,那將是多么美好的一件事情。這些美好的技術(shù)設(shè)想實際就是容器技術(shù)發(fā)展的原動力。當(dāng)然技術(shù)的發(fā)展并不是一蹴而就的,總是隨著時間的推移不斷進行完善。
容器技術(shù)的思想最早可以追溯到1979年,這一年Unix版本V7發(fā)布,在這個版本中作者發(fā)明了chroot系統(tǒng)調(diào)用,通過它可以實現(xiàn)改變一個進程及其子進程的根目錄到另外一個目錄下,為進程指定一個單獨的、新的文件系統(tǒng)上下文環(huán)境,可見在很早的時候Unix的大神們已經(jīng)有了進行進程隔離的意識和思想了。
那么到底什么叫進程隔離呢?舉個栗子大家一看就明白,相信很多同學(xué)都使用過tomcat這個web容器,我們可以在tomcat中部署war服務(wù)。假設(shè)我們有3 個服務(wù)都部署在了1個tomcat實例中,假如我們需要重啟其中的某個服務(wù),我們就需要重啟整個tomcat,那么tomact中的3個服務(wù)都會被重啟。重啟一個服務(wù)影響其他2個服務(wù),服務(wù)操作存在高度的耦合。但是如果我們把三個服務(wù)部署到三個不同的tocmat容器實例中,那么重啟任何一個服務(wù)都不會影響到其他兩個服務(wù),實現(xiàn)了服務(wù)的獨立管理。
通過部署多個實例,我們實現(xiàn)了服務(wù)之間的進程隔離,而進程擁有獨立的地址空間以及執(zhí)行上下文。但是這種形式的獨立管理并不是真正意義上的獨立管理,為什么這么說呢?因為實際上他們還是共用服務(wù)器的CPU、內(nèi)存以及IO等服務(wù)器資源。假如Tomcat1占用的服務(wù)器內(nèi)存高了,那么剩余給Tomcat2以及Tomcat2的內(nèi)存分配就相對來說會變少。因此實際上這三個Tomcat雖然是獨立的進程但還是會相互影響。有沒有辦法實現(xiàn)真正的獨立,不互相影響呢?
實際上實現(xiàn)資源隔離的方式大概有硬件虛擬化、OS虛擬化以及硬件分區(qū)等幾種常見的實現(xiàn)方式。但是綜合各方面的表現(xiàn),OS虛擬化成為后期容器技術(shù)發(fā)展的主流技術(shù)路線。
容器技術(shù)的解決的核心問題就是實現(xiàn)軟件運行時的環(huán)境隔離,通過容器構(gòu)建一個標準的、無差別的服務(wù)運行環(huán)境,這樣就不會因為環(huán)境、配置以及依賴等原因造成的在這臺服務(wù)器上好好的,在另外的一臺服務(wù)器上又不行的尷尬問題。2008年的時候,通過將Cgroups的資源管理能力以及Namespace的視圖隔離能力糅合在一起,Linux Container被合入linux主線,Linux Container是Linux系統(tǒng)提供的容器技術(shù),能提供輕量級的虛擬化能力,能夠進行隔離進程和資源。通過這種OS層面的虛擬化技術(shù),實際上也就是解決了容器的核心問題即為如何實現(xiàn)服務(wù)運行時的隔離。因此可以說Linux Container是后期實現(xiàn)Docker技術(shù)的基礎(chǔ)。
在2013年,Docker正式發(fā)布。Docker是基于Linux Container技術(shù)發(fā)展而來的,它的口號是:“Build,Ship and Run Any App,Anywhere”。Docker創(chuàng)新構(gòu)建了一種全新的軟件打包、軟件分發(fā)以及軟件運行的機制,它通過容器鏡像,將應(yīng)用服務(wù)本身以及運行服務(wù)所需要的環(huán)境、配置、資源文件以及依賴庫等都打包成一個唯一版本的軟件鏡像包。往后在任何地方運行的服務(wù)都是基于這個軟件鏡像包來進行構(gòu)建和運行的,真正解決了如何高效發(fā)布軟件以及如何高效運行軟件的兩大核心問題。關(guān)于Docker,后面會有專門的文章進行介紹。
類似下圖這種虛擬機與容器的對比圖相信大家都看過,左邊的部分就是虛擬機的大致原理,實際上是通過Hypervisor實現(xiàn)服務(wù)器硬件資源的虛擬化,從而在服務(wù)器中模擬出來具備CPU、內(nèi)存、硬盤等完整計算機硬件基礎(chǔ)設(shè)施同時還有Guest OS,簡單理解就是在服務(wù)器中派生出了新的虛擬服務(wù)器。用戶的應(yīng)用服務(wù)進程都是運行在這些虛擬出來的計算機資源當(dāng)中的。同一臺服務(wù)器可以同時運行多個操作系統(tǒng),各個操作系統(tǒng)之間是相互隔離的,雖然安全性隔離性都很完備,但是大家應(yīng)該能看得出來,硬件虛擬化需要額外的性能開銷,因此它是一種非常重的資源隔離技術(shù)。
容器技術(shù)原理
前面和大家簡要介紹了容器技術(shù)的發(fā)展,我們都知道了容器最核心的是實現(xiàn)了應(yīng)用服務(wù)資源隔離。那么到底容器是如何實現(xiàn)資源隔離的呢?實際上它依賴了Namespace(命名空間)、Cgroups(控制組)底層Linux內(nèi)核技術(shù)。
Namespace?
我們先來看下wiki中關(guān)于Linux Namespace的描述:
Namespaces are a feature of the Linux kernel that partitions kernel resources such that one set of processes sees one set of resources while another set of processes sees a different set of resources. The feature works by having the same namespace for a set of resources and processes, but those namespaces refer to distinct resources。
這描述看上去就很繞,總結(jié)一下就是Linux Namespace是Linux kernel提供的一種進行資源隔離的底層能力。通過Namespace實現(xiàn)對服務(wù)器全局資源的封裝隔離,使得不同Namespace中的進程互相獨立,彼此透明。
如下圖所示,在一臺宿主服務(wù)器當(dāng)中,Linux的Namespace實際就是Linux內(nèi)核中資源隔離的實現(xiàn)方式。玩過Docker的同學(xué)都知道,到我們run了一個docker鏡像之后,在服務(wù)器中就會產(chǎn)生一個docker容器,當(dāng)我們進入到容器里面去之后,使用ps命令查看,我們會驚奇的發(fā)現(xiàn)容器中運行的服務(wù)pid=1。相當(dāng)于這個服務(wù)是容器內(nèi)的第一號進程。而如果我們在服務(wù)器中運行這個服務(wù),操作系統(tǒng)會給這個服務(wù)進程分配一個全局唯一的進程號,假設(shè)是34134。同樣是這個程序在服務(wù)器中運行pid是34134,但是在Docker容器中的pid卻是1。這是怎么回事呢?
這種隔離技術(shù)就是Namspace機制,通過Namespace構(gòu)建了一個全新的運行環(huán)境,與其他運行環(huán)境互相透明,讓服務(wù)在這個小空間里面自封為王。實際上是Linux kernel內(nèi)核提供的系統(tǒng)調(diào)用函數(shù)clone()。通過clone函數(shù)創(chuàng)建的新進程會在一個全新的進程空間當(dāng)中。因此實際上容器的本質(zhì)還是進程,只不過是一種特殊的進程,在創(chuàng)建它的時候指定了一些參數(shù),使得容器只能訪問到當(dāng)前Namespace內(nèi)的文件、IO等資源。
int pid = clone(main_function, stack_size, SIGCHLD, NULL);
Namespace除了PID還實現(xiàn)了其他不同資源級別的隔離。
名稱 宏定義 隔離內(nèi)容
IPC CLONE_NEWIPC System V IPC, POSIX message queues (since Linux 2.6.19)
Network CLONE_NEWNET Network devices, stacks, ports, etc. (since Linux 2.6.24)
Mount CLONE_NEWNS Mount points (since Linux 2.4.19)
PID CLONE_NEWPID Process IDs (since Linux 2.6.24)
User CLONE_NEWUSER User and group IDs (started in Linux 2.6.23 and completed in Linux 3.8)
UTS CLONE_NEWUTS Hostname and NIS domain name (since Linux 2.6.19)
Cgroups
Namespace機制幫助我們解決了資源隔離的問題,那么僅僅只是隔離對于容器運行來說就夠了嗎?雖然在容器內(nèi)部應(yīng)用服務(wù)可能是個王者,資源都是他獨享的,但是映射到服務(wù)器內(nèi)核當(dāng)中的真實的進程后它就是個各個普普通通的青銅,需要和其他青銅共享計算機各類資源。顯然這不是我們想要的容器的效果。而Linux Cgroups技術(shù)就是幫助我們設(shè)置資源限制的功能。Linux Cgroups即Linux Control Group就是限制一個進程組能夠使用的資源上限,包括 CPU、內(nèi)存、磁盤、網(wǎng)絡(luò)帶寬等等。
在Linux操作系統(tǒng)中,Cgroups的能力通過內(nèi)核的文件系統(tǒng)操作接口暴露出來的,它是以文件和目錄的方式組織在操作系統(tǒng)的/sys/fs/cgroup路徑下。在Linux服務(wù)器中輸入mount -t cgroup,可以看到如下的目錄文件結(jié)構(gòu)。
從上圖中我們可以看到,在/sys/fs/cgroup目錄下有很多關(guān)于資源的子目錄或者說子系統(tǒng),如cpucet、cpu以及memory等。這些目錄都是當(dāng)前服務(wù)器可以被Cgroups進行限制的資源類別。而在子系統(tǒng)對應(yīng)的資源種類下,你就可以看到該類資源具體可以被限制的方法。我們可以查看memory下面的配置文件,ls /sys/fs/cgroup/memory。
我們再看下Docker的源碼:
// New creates and initializes a new containerd server
func New(ctx context.Context, config *Config) (*Server, error) {
//...
if err := apply(ctx, config); err != nil {
return nil, err
}
//...
}
// apply sets config settings on the server process
func apply(ctx context.Context, config *Config) error {
if config.OOMScore != 0 {
log.G(ctx).Debugf("changing OOM score to %d", config.OOMScore)
if err := sys.SetOOMScore(os.Getpid(), config.OOMScore); err != nil {
log.G(ctx).WithError(err).Errorf("failed to change OOM score to %d", config.OOMScore)
}
}
if config.Cgroup.Path != "" {
cg, err := cgroups.Load(cgroups.V1, cgroups.StaticPath(config.Cgroup.Path))
if err != nil {
if err != cgroups.ErrCgroupDeleted {
return err
}
if cg, err = cgroups.New(cgroups.V1, cgroups.StaticPath(config.Cgroup.Path), &specs.LinuxResources{}); err != nil {
return err
}
}
if err := cg.Add(cgroups.Process{
Pid: os.Getpid(),
}); err != nil {
return err
}
}
return nil
}
通過代碼我么可以看得出來在創(chuàng)建一個Docker容器的時候,調(diào)用了apply函數(shù),在這個apply函數(shù)中會進行cgourp的加載,而后通過cg.Add將創(chuàng)建的容器id添加到控制組的task文件中。
總結(jié)
本文主要對容器技術(shù)的發(fā)展進行了簡單回顧,從很早之前服務(wù)部署以及應(yīng)用方面存在的不足出發(fā),闡述了容器技術(shù)的出現(xiàn)到底解決了什么問題,同時和大家分享了容器技術(shù)的本質(zhì)以及原理,相信通過本文大家可以對容器技術(shù)有一個基本的感受,后續(xù)再和大家繼續(xù)分享云原生技術(shù)體系。