Docker這么火,,喜歡技術(shù)的朋友可能也會(huì)想,如果要自己實(shí)現(xiàn)一個(gè)資源隔離的容器,,應(yīng)該從哪些方面下手呢,?也許你第一反應(yīng)可能就是chroot命令,這條命令給用戶最直觀的感覺(jué)就是使用后根目錄/的掛載點(diǎn)切換了,,即文件系統(tǒng)被隔離了,。然后,,為了在分布式的環(huán)境下進(jìn)行通信和定位,容器必然需要一個(gè)獨(dú)立的IP,、端口,、路由等等,自然就想到了網(wǎng)絡(luò)的隔離,。同時(shí),,你的容器還需要一個(gè)獨(dú)立的主機(jī)名以便在網(wǎng)絡(luò)中標(biāo)識(shí)自己。想到網(wǎng)絡(luò),,順其自然就想到通信,,也就想到了進(jìn)程間通信的隔離??赡苣阋蚕氲搅藱?quán)限的問(wèn)題,,對(duì)用戶和用戶組的隔離就實(shí)現(xiàn)了用戶權(quán)限的隔離。最后,,運(yùn)行在容器中的應(yīng)用需要有自己的PID,自然也需要與宿主機(jī)中的PID進(jìn)行隔離。 由此,,我們基本上完成了一個(gè)容器所需要做的六項(xiàng)隔離,,Linux內(nèi)核中就提供了這六種命名空間(namespace)隔離的系統(tǒng)調(diào)用,如下表所示,。
表 namespace六項(xiàng)隔離 實(shí)際上,,Linux內(nèi)核實(shí)現(xiàn)namespace的主要目的就是為了實(shí)現(xiàn)輕量級(jí)虛擬化(容器)服務(wù)。在同一個(gè)namespace下的進(jìn)程可以感知彼此的變化,,而對(duì)外界的進(jìn)程一無(wú)所知,。這樣就可以讓容器中的進(jìn)程產(chǎn)生錯(cuò)覺(jué),仿佛自己置身于一個(gè)獨(dú)立的系統(tǒng)環(huán)境中,,以此達(dá)到獨(dú)立和隔離的目的,。 需要說(shuō)明的是,本文所討論的namespace實(shí)現(xiàn)針對(duì)的均是Linux內(nèi)核3.8及其以后的版本,。接下來(lái),,我們將首先介紹使用namespace的API,然后針對(duì)這六種namespace進(jìn)行逐一講解,,并通過(guò)程序讓你親身感受一下這些隔離效果{![參考自http:///Articles/531114/]},。 1. 調(diào)用namespace的APInamespace的API包括clone()、setns()以及unshare(),,還有/proc下的部分文件,。為了確定隔離的到底是哪種namespace,,在使用這些API時(shí),通常需要指定以下六個(gè)常數(shù)的一個(gè)或多個(gè),,通過(guò)|(位或)操作來(lái)實(shí)現(xiàn),。你可能已經(jīng)在上面的表格中注意到,這六個(gè)參數(shù)分別是CLONE_NEWIPC,、CLONE_NEWNS,、CLONE_NEWNET、CLONE_NEWPID,、CLONE_NEWUSER和CLONE_NEWUTS,。 (1)通過(guò)clone()創(chuàng)建新進(jìn)程的同時(shí)創(chuàng)建namespace使用clone()來(lái)創(chuàng)建一個(gè)獨(dú)立namespace的進(jìn)程是最常見(jiàn)做法,它的調(diào)用方式如下,。
clone()實(shí)際上是傳統(tǒng)UNIX系統(tǒng)調(diào)用fork()的一種更通用的實(shí)現(xiàn)方式,,它可以通過(guò)flags來(lái)控制使用多少功能。一共有二十多種CLONE_*的flag(標(biāo)志位)參數(shù)用來(lái)控制clone進(jìn)程的方方面面(如是否與父進(jìn)程共享虛擬內(nèi)存等等),,下面外面逐一講解clone函數(shù)傳入的參數(shù),。
在后續(xù)的內(nèi)容中將會(huì)有使用clone()的實(shí)際程序可供大家參考,。 (2)查看/proc/[pid]/ns文件從3.8版本的內(nèi)核開(kāi)始,用戶就可以在/proc/[pid]/ns文件下看到指向不同namespace號(hào)的文件,,效果如下所示,,形如[4026531839]者即為namespace號(hào)。
如果兩個(gè)進(jìn)程指向的namespace編號(hào)相同,,就說(shuō)明他們?cè)谕粋€(gè)namespace下,,否則則在不同namespace里面。/proc/[pid]/ns的另外一個(gè)作用是,,一旦文件被打開(kāi),,只要打開(kāi)的文件描述符(fd)存在,那么就算PID所屬的所有進(jìn)程都已經(jīng)結(jié)束,,創(chuàng)建的namespace就會(huì)一直存在,。那如何打開(kāi)文件描述符呢?把/proc/[pid]/ns目錄掛載起來(lái)就可以達(dá)到這個(gè)效果,,命令如下,。
如果你看到的內(nèi)容與本文所描述的不符,那么說(shuō)明你使用的內(nèi)核在3.8版本以前,。該目錄下存在的只有ipc,、net和uts,并且以硬鏈接存在,。 (3)通過(guò)setns()加入一個(gè)已經(jīng)存在的namespace上文剛提到,,在進(jìn)程都結(jié)束的情況下,,也可以通過(guò)掛載的形式把namespace保留下來(lái),保留namespace的目的自然是為以后有進(jìn)程加入做準(zhǔn)備,。通過(guò)setns()系統(tǒng)調(diào)用,,你的進(jìn)程從原先的namespace加入我們準(zhǔn)備好的新namespace,使用方法如下,。
為了把我們創(chuàng)建的namespace利用起來(lái),,我們需要引入execve()系列函數(shù),這個(gè)函數(shù)可以執(zhí)行用戶命令,,最常用的就是調(diào)用/bin/bash并接受參數(shù),,運(yùn)行起一個(gè)shell,用法如下,。
假設(shè)編譯后的程序名稱為setns,。
至此,你就可以在新的命名空間中執(zhí)行shell命令了,,在下文中會(huì)多次使用這種方式來(lái)演示隔離的效果。 (4)通過(guò)unshare()在原先進(jìn)程上進(jìn)行namespace隔離最后要提的系統(tǒng)調(diào)用是unshare(),,它跟clone()很像,,不同的是,unshare()運(yùn)行在原先的進(jìn)程上,,不需要啟動(dòng)一個(gè)新進(jìn)程,,使用方法如下。
調(diào)用unshare()的主要作用就是不啟動(dòng)一個(gè)新進(jìn)程就可以起到隔離的效果,,相當(dāng)于跳出原先的namespace進(jìn)行操作,。這樣,你就可以在原進(jìn)程進(jìn)行一些需要隔離的操作,。Linux中自帶的unshare命令,,就是通過(guò)unshare()系統(tǒng)調(diào)用實(shí)現(xiàn)的,有興趣的讀者可以在網(wǎng)上搜索一下這個(gè)命令的作用,。 (5)延伸閱讀:fork()系統(tǒng)調(diào)用系統(tǒng)調(diào)用函數(shù)fork()并不屬于namespace的API,,所以這部分內(nèi)容屬于延伸閱讀,如果讀者已經(jīng)對(duì)fork()有足夠的了解,,那大可跳過(guò),。 當(dāng)程序調(diào)用fork()函數(shù)時(shí),,系統(tǒng)會(huì)創(chuàng)建新的進(jìn)程,為其分配資源,,例如存儲(chǔ)數(shù)據(jù)和代碼的空間,。然后把原來(lái)的進(jìn)程的所有值都復(fù)制到新的進(jìn)程中,只有少量數(shù)值與原來(lái)的進(jìn)程值不同,,相當(dāng)于克隆了一個(gè)自己,。那么程序的后續(xù)代碼邏輯要如何區(qū)分自己是新進(jìn)程還是父進(jìn)程呢? fork()的神奇之處在于它僅僅被調(diào)用一次,,卻能夠返回兩次(父進(jìn)程與子進(jìn)程各返回一次),,通過(guò)返回值的不同就可以進(jìn)行區(qū)分父進(jìn)程與子進(jìn)程。它可能有三種不同的返回值:
下面給出一段實(shí)例代碼,,命名為fork_example.c,。
編譯并執(zhí)行,結(jié)果如下,。
使用fork()后,,父進(jìn)程有義務(wù)監(jiān)控子進(jìn)程的運(yùn)行狀態(tài),并在子進(jìn)程退出后自己才能正常退出,,否則子進(jìn)程就會(huì)成為“孤兒”進(jìn)程,。 下面我們將分別對(duì)六種namespace進(jìn)行詳細(xì)解析。 |
|