摘要:目前內(nèi)核總共實(shí)現(xiàn)了種隔離和消息隊(duì)列。參數(shù)表示我們要加入的的文件描述符。提供了很多種進(jìn)程間通信的機(jī)制,針對(duì)的是和消息隊(duì)列。所謂傳播事件,是指由一個(gè)掛載對(duì)象的狀態(tài)變化導(dǎo)致的其它掛載對(duì)象的掛載與解除掛載動(dòng)作的事件。
前言
理解docker,主要從namesapce,cgroups,聯(lián)合文件,運(yùn)行時(shí)(runC),網(wǎng)絡(luò)幾個(gè)方面。接下來我們會(huì)花一些時(shí)間,分別介紹。
docker系列--namespace解讀
docker系列--cgroups解讀
docker系列--unionfs解讀
docker系列--runC解讀
docker系列--網(wǎng)絡(luò)模式解讀
namesapce主要是隔離作用,cgroups主要是資源限制,聯(lián)合文件主要用于鏡像分層存儲(chǔ)和管理,runC是運(yùn)行時(shí),遵循了oci接口,一般來說基于libcontainer。網(wǎng)絡(luò)主要是docker單機(jī)網(wǎng)絡(luò)和多主機(jī)通信模式。
namespace簡介 什么是namespaceNamespace是將內(nèi)核的全局資源做封裝,使得每個(gè)Namespace都有一份獨(dú)立的資源,因此不同的進(jìn)程在各自的Namespace內(nèi)對(duì)同一種資源的使用不會(huì)互相干擾。實(shí)際上,Linux內(nèi)核實(shí)現(xiàn)namespace的主要目的就是為了實(shí)現(xiàn)輕量級(jí)虛擬化(容器)服務(wù)。在同一個(gè)namespace下的進(jìn)程可以感知彼此的變化,而對(duì)外界的進(jìn)程一無所知。這樣就可以讓容器中的進(jìn)程產(chǎn)生錯(cuò)覺,仿佛自己置身于一個(gè)獨(dú)立的系統(tǒng)環(huán)境中,以此達(dá)到獨(dú)立和隔離的目的。
這樣的解釋可能不清楚,舉個(gè)例子,執(zhí)行sethostname這個(gè)系統(tǒng)調(diào)用時(shí),可以改變系統(tǒng)的主機(jī)名,這個(gè)主機(jī)名就是一個(gè)內(nèi)核的全局資源。內(nèi)核通過實(shí)現(xiàn)UTS Namespace,可以將不同的進(jìn)程分隔在不同的UTS Namespace中,在某個(gè)Namespace修改主機(jī)名時(shí),另一個(gè)Namespace的主機(jī)名還是保持不變。
目前Linux內(nèi)核總共實(shí)現(xiàn)了6種Namespace:
IPC:隔離System V IPC和POSIX消息隊(duì)列。
Network:隔離網(wǎng)絡(luò)資源。
Mount:隔離文件系統(tǒng)掛載點(diǎn)。每個(gè)容器能看到不同的文件系統(tǒng)層次結(jié)構(gòu)。
PID:隔離進(jìn)程ID。
UTS:隔離主機(jī)名和域名。
User:隔離用戶ID和組ID。
namespae接口的使用namespace的API包括clone()、setns()以及unshare(),還有/proc下的部分文件。為了確定隔離的到底是哪種namespace,在使用這些API時(shí),通常需要指定以下六個(gè)常數(shù)的一個(gè)或多個(gè),通過|(位或)操作來實(shí)現(xiàn)。你可能已經(jīng)在上面的表格中注意到,這六個(gè)參數(shù)分別是CLONE_NEWIPC、CLONE_NEWNS、CLONE_NEWNET、CLONE_NEWPID、CLONE_NEWUSER和CLONE_NEWUTS。
1: 通過clone()創(chuàng)建新進(jìn)程的同時(shí)創(chuàng)建namespace
使用clone()來創(chuàng)建一個(gè)獨(dú)立namespace的進(jìn)程是最常見做法,它的調(diào)用方式如下。
int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg);
clone()實(shí)際上是傳統(tǒng)UNIX系統(tǒng)調(diào)用fork()的一種更通用的實(shí)現(xiàn)方式,它可以通過flags來控制使用多少功能。一共有二十多種CLONE_*的flag(標(biāo)志位)參數(shù)用來控制clone進(jìn)程的方方面面(如是否與父進(jìn)程共享虛擬內(nèi)存等等),下面外面逐一講解clone函數(shù)傳入的參數(shù)。
參數(shù)child_func傳入子進(jìn)程運(yùn)行的程序主函數(shù)。
參數(shù)child_stack傳入子進(jìn)程使用的棧空間
參數(shù)flags表示使用哪些CLONE_*標(biāo)志位
參數(shù)args則可用于傳入用戶參數(shù)
2: 通過setns()加入一個(gè)已經(jīng)存在的namespace
在進(jìn)程都結(jié)束的情況下,也可以通過掛載的形式把namespace保留下來,保留namespace的目的自然是為以后有進(jìn)程加入做準(zhǔn)備。通過setns()系統(tǒng)調(diào)用,你的進(jìn)程從原先的namespace加入我們準(zhǔn)備好的新namespace,使用方法如下。
int setns(int fd, int nstype);
參數(shù)fd表示我們要加入的namespace的文件描述符。上文已經(jīng)提到,它是一個(gè)指向/proc/[pid]/ns目錄的文件描述符,可以通過直接打開該目錄下的鏈接或者打開一個(gè)掛載了該目錄下鏈接的文件得到。
參數(shù)nstype讓調(diào)用者可以去檢查fd指向的namespace類型是否符合我們實(shí)際的要求。如果填0表示不檢查。
3: 通過unshare()在原先進(jìn)程上進(jìn)行namespace隔離
后要提的系統(tǒng)調(diào)用是unshare(),它跟clone()很像,不同的是,unshare()運(yùn)行在原先的進(jìn)程上,不需要啟動(dòng)一個(gè)新進(jìn)程,使用方法如下。
int unshare(int flags);
調(diào)用unshare()的主要作用就是不啟動(dòng)一個(gè)新進(jìn)程就可以起到隔離的效果,相當(dāng)于跳出原先的namespace進(jìn)行操作。這樣,你就可以在原進(jìn)程進(jìn)行一些需要隔離的操作。Linux中自帶的unshare命令,就是通過unshare()系統(tǒng)調(diào)用實(shí)現(xiàn)的。
各個(gè)namespace介紹UTS Namespace
UTS Namespace用于對(duì)主機(jī)名和域名進(jìn)行隔離,也就是uname系統(tǒng)調(diào)用使用的結(jié)構(gòu)體struct utsname里的nodename和domainname這兩個(gè)字段,UTS這個(gè)名字也是由此而來的。
那么,為什么要使用UTS Namespace做隔離?這是因?yàn)橹鳈C(jī)名可以用來代替IP地址,因此,也就可以使用主機(jī)名在網(wǎng)絡(luò)上訪問某臺(tái)機(jī)器了,如果不做隔離,這個(gè)機(jī)制在容器里就會(huì)出問題。
IPC Namespace
IPC是Inter-Process Communication的簡寫,也就是進(jìn)程間通信。Linux提供了很多種進(jìn)程間通信的機(jī)制,IPC Namespace針對(duì)的是SystemV IPC和Posix消息隊(duì)列。這些IPC機(jī)制都會(huì)用到標(biāo)識(shí)符,例如用標(biāo)識(shí)符來區(qū)別不同的消息隊(duì)列,然后兩個(gè)進(jìn)程通過標(biāo)識(shí)符找到對(duì)應(yīng)的消息隊(duì)列進(jìn)行通信等。
IPC Namespace能做到的事情是,使相同的標(biāo)識(shí)符在兩個(gè)Namespace中代表不同的消息隊(duì)列,這樣也就使得兩個(gè)Namespace中的進(jìn)程不能通過IPC進(jìn)程通信了。
PID Namespace
PID Namespace用于隔離進(jìn)程PID號(hào),這樣一來,不同的Namespace里的進(jìn)程PID號(hào)就可以是一樣的了。
Network Namespace
這個(gè)Namespace會(huì)對(duì)網(wǎng)絡(luò)相關(guān)的系統(tǒng)資源進(jìn)行隔離,每個(gè)Network Namespace都有自己的網(wǎng)絡(luò)設(shè)備、IP地址、路由表、/proc/net目錄、端口號(hào)等。網(wǎng)絡(luò)隔離的必要性是很明顯的,舉一個(gè)例子,在沒有隔離的情況下,如果兩個(gè)不同的容器都想運(yùn)行同一個(gè)Web應(yīng)用,而這個(gè)應(yīng)用又需要使用80端口,那就會(huì)有沖突了。
Mount namespace
Mount namespace通過隔離文件系統(tǒng)掛載點(diǎn)對(duì)隔離文件系統(tǒng)提供支持,它是歷史上第一個(gè)Linux namespace,所以它的標(biāo)識(shí)位比較特殊,就是CLONE_NEWNS。隔離后,不同mount namespace中的文件結(jié)構(gòu)發(fā)生變化也互不影響。你可以通過/proc/[pid]/mounts查看到所有掛載在當(dāng)前namespace中的文件系統(tǒng),還可以通過/proc/[pid]/mountstats看到mount namespace中文件設(shè)備的統(tǒng)計(jì)信息,包括掛載文件的名字、文件系統(tǒng)類型、掛載位置等等。
進(jìn)程在創(chuàng)建mount namespace時(shí),會(huì)把當(dāng)前的文件結(jié)構(gòu)復(fù)制給新的namespace。新namespace中的所有mount操作都只影響自身的文件系統(tǒng),而對(duì)外界不會(huì)產(chǎn)生任何影響。這樣做非常嚴(yán)格地實(shí)現(xiàn)了隔離,但是某些情況可能并不適用。比如父節(jié)點(diǎn)namespace中的進(jìn)程掛載了一張CD-ROM,這時(shí)子節(jié)點(diǎn)namespace拷貝的目錄結(jié)構(gòu)就無法自動(dòng)掛載上這張CD-ROM,因?yàn)檫@種操作會(huì)影響到父節(jié)點(diǎn)的文件系統(tǒng)。
ps:
在mount這塊,需要特別注意,掛載的傳播性。在實(shí)際應(yīng)用中,很重要。2006 年引入的掛載傳播(mount propagation)解決了這個(gè)問題,掛載傳播定義了掛載對(duì)象(mount object)之間的關(guān)系,系統(tǒng)用這些關(guān)系決定任何掛載對(duì)象中的掛載事件如何傳播到其他掛載對(duì)象。所謂傳播事件,是指由一個(gè)掛載對(duì)象的狀態(tài)變化導(dǎo)致的其它掛載對(duì)象的掛載與解除掛載動(dòng)作的事件。
User Namespace
User Namespace用來隔離用戶和組ID,也就是說一個(gè)進(jìn)程在Namespace里的用戶和組ID與它在host里的ID可以不一樣,這樣說可能讀者還不理解有什么實(shí)際的用處。User Namespace最有用的地方在于,host的普通用戶進(jìn)程在容器里可以是0號(hào)用戶,也就是root用戶。這樣,進(jìn)程在容器內(nèi)可以做各種特權(quán)操作,但是它的特權(quán)被限定在容器內(nèi),離開了這個(gè)容器它就只有普通用戶的權(quán)限了。
代碼解讀首先runc中有一個(gè)nsenter文件夾,主要是go通過cgo,實(shí)現(xiàn)了nsexec等方法。
在Go運(yùn)行時(shí)啟動(dòng)之前,nsenter包注冊了一個(gè)特殊init構(gòu)造函數(shù)。這讓我們有可能在現(xiàn)有名稱空間“setns”,并避免了Go運(yùn)行時(shí)在多線程場景下可能出現(xiàn)的問題。
具體是在runc的main.go中引入:
package main import ( "os" "runtime" "github.com/opencontainers/runc/libcontainer" _ "github.com/opencontainers/runc/libcontainer/nsenter" "github.com/urfave/cli" ) func init() { if len(os.Args) > 1 && os.Args[1] == "init" { runtime.GOMAXPROCS(1) runtime.LockOSThread() } } var initCommand = cli.Command{ Name: "init", Usage: `initialize the namespaces and launch the process (do not call it outside of runc)`, Action: func(context *cli.Context) error { factory, _ := libcontainer.New("") if err := factory.StartInitialization(); err != nil { // as the error is sent back to the parent there is no need to log // or write it to stderr because the parent process will handle this os.Exit(1) } panic("libcontainer: container init failed to exec") }, }
下面重點(diǎn)講一下在linux container中namespace的實(shí)現(xiàn)。
runc/libcontainer/configs/config.go中定義了container對(duì)應(yīng)的Namespaces。另外對(duì)于User Namespaces,還定義了UidMappings和GidMappings for user map。
// Config defines configuration options for executing a process inside a contained environment. type Config struct { ... // Namespaces specifies the container"s namespaces that it should setup when cloning the init process // If a namespace is not provided that namespace is shared from the container"s parent process Namespaces Namespaces `json:"namespaces"` // UidMappings is an array of User ID mappings for User Namespaces UidMappings []IDMap `json:"uid_mappings"` // GidMappings is an array of Group ID mappings for User Namespaces GidMappings []IDMap `json:"gid_mappings"` ... }
而Namespaces定義如下:
package configs import ( "fmt" "os" "sync" ) const ( NEWNET NamespaceType = "NEWNET" NEWPID NamespaceType = "NEWPID" NEWNS NamespaceType = "NEWNS" NEWUTS NamespaceType = "NEWUTS" NEWIPC NamespaceType = "NEWIPC" NEWUSER NamespaceType = "NEWUSER" ) var ( nsLock sync.Mutex supportedNamespaces = make(map[NamespaceType]bool) ) // NsName converts the namespace type to its filename func NsName(ns NamespaceType) string { switch ns { case NEWNET: return "net" case NEWNS: return "mnt" case NEWPID: return "pid" case NEWIPC: return "ipc" case NEWUSER: return "user" case NEWUTS: return "uts" } return "" } // IsNamespaceSupported returns whether a namespace is available or // not func IsNamespaceSupported(ns NamespaceType) bool { nsLock.Lock() defer nsLock.Unlock() supported, ok := supportedNamespaces[ns] if ok { return supported } nsFile := NsName(ns) // if the namespace type is unknown, just return false if nsFile == "" { return false } _, err := os.Stat(fmt.Sprintf("/proc/self/ns/%s", nsFile)) // a namespace is supported if it exists and we have permissions to read it supported = err == nil supportedNamespaces[ns] = supported return supported } func NamespaceTypes() []NamespaceType { return []NamespaceType{ NEWUSER, // Keep user NS always first, don"t move it. NEWIPC, NEWUTS, NEWNET, NEWPID, NEWNS, } } // Namespace defines configuration for each namespace. It specifies an // alternate path that is able to be joined via setns. type Namespace struct { Type NamespaceType `json:"type"` Path string `json:"path"` } func (n *Namespace) GetPath(pid int) string { return fmt.Sprintf("/proc/%d/ns/%s", pid, NsName(n.Type)) } func (n *Namespaces) Remove(t NamespaceType) bool { i := n.index(t) if i == -1 { return false } *n = append((*n)[:i], (*n)[i+1:]...) return true } func (n *Namespaces) Add(t NamespaceType, path string) { i := n.index(t) if i == -1 { *n = append(*n, Namespace{Type: t, Path: path}) return } (*n)[i].Path = path } func (n *Namespaces) index(t NamespaceType) int { for i, ns := range *n { if ns.Type == t { return i } } return -1 } func (n *Namespaces) Contains(t NamespaceType) bool { return n.index(t) != -1 } func (n *Namespaces) PathOf(t NamespaceType) string { i := n.index(t) if i == -1 { return "" } return (*n)[i].Path }
runC支持的namespce type包括($nsName) "net"、"mnt"、"pid"、"ipc"、"user"、"uts":
const ( NEWNET NamespaceType = "NEWNET" NEWPID NamespaceType = "NEWPID" NEWNS NamespaceType = "NEWNS" NEWUTS NamespaceType = "NEWUTS" NEWIPC NamespaceType = "NEWIPC" NEWUSER NamespaceType = "NEWUSER" )
除了驗(yàn)證 Namespce Type是否在以上常量中,還要去驗(yàn)證 /proc/self/ns/$nsName是否存在并且可以read,都通過時(shí),才認(rèn)為該Namespace是在當(dāng)前系統(tǒng)中是被支持的。
// IsNamespaceSupported returns whether a namespace is available or // not func IsNamespaceSupported(ns NamespaceType) bool { ... supported, ok := supportedNamespaces[ns] if ok { return supported } ... // 除了驗(yàn)證 Namespce Type是都在指定列表中,還要去驗(yàn)證 /proc/self/ns/$nsName是否存在并且可以read _, err := os.Stat(fmt.Sprintf("/proc/self/ns/%s", nsFile)) supported = err == nil ... return supported }
在runc/libcontainer/configs/namespaces_syscall.go中,定義了linux clone時(shí)這些namespace對(duì)應(yīng)的clone flags。
var namespaceInfo = map[NamespaceType]int{ NEWNET: syscall.CLONE_NEWNET, NEWNS: syscall.CLONE_NEWNS, NEWUSER: syscall.CLONE_NEWUSER, NEWIPC: syscall.CLONE_NEWIPC, NEWUTS: syscall.CLONE_NEWUTS, NEWPID: syscall.CLONE_NEWPID, } // CloneFlags parses the container"s Namespaces options to set the correct // flags on clone, unshare. This function returns flags only for new namespaces. func (n *Namespaces) CloneFlags() uintptr { var flag int for _, v := range *n { if v.Path != "" { continue } flag |= namespaceInfo[v.Type] } return uintptr(flag) }
在容器創(chuàng)建初始化的過程中,主要執(zhí)行以下方法:
func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) (*initProcess, error) { cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initStandard)) nsMaps := make(map[configs.NamespaceType]string) for _, ns := range c.config.Namespaces { if ns.Path != "" { nsMaps[ns.Type] = ns.Path } } _, sharePidns := nsMaps[configs.NEWPID] data, err := c.bootstrapData(c.config.Namespaces.CloneFlags(), nsMaps) if err != nil { return nil, err } return &initProcess{ cmd: cmd, childPipe: childPipe, parentPipe: parentPipe, manager: c.cgroupManager, intelRdtManager: c.intelRdtManager, config: c.newInitConfig(p), container: c, process: p, bootstrapData: data, sharePidns: sharePidns, }, nil }
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/27449.html
摘要:目前內(nèi)核總共實(shí)現(xiàn)了種隔離和消息隊(duì)列。參數(shù)表示我們要加入的的文件描述符。提供了很多種進(jìn)程間通信的機(jī)制,針對(duì)的是和消息隊(duì)列。所謂傳播事件,是指由一個(gè)掛載對(duì)象的狀態(tài)變化導(dǎo)致的其它掛載對(duì)象的掛載與解除掛載動(dòng)作的事件。 前言 理解docker,主要從namesapce,cgroups,聯(lián)合文件,運(yùn)行時(shí)(runC),網(wǎng)絡(luò)幾個(gè)方面。接下來我們會(huì)花一些時(shí)間,分別介紹。 docker系列--names...
摘要:目前內(nèi)核總共實(shí)現(xiàn)了種隔離和消息隊(duì)列。參數(shù)表示我們要加入的的文件描述符。提供了很多種進(jìn)程間通信的機(jī)制,針對(duì)的是和消息隊(duì)列。所謂傳播事件,是指由一個(gè)掛載對(duì)象的狀態(tài)變化導(dǎo)致的其它掛載對(duì)象的掛載與解除掛載動(dòng)作的事件。 前言 理解docker,主要從namesapce,cgroups,聯(lián)合文件,運(yùn)行時(shí)(runC),網(wǎng)絡(luò)幾個(gè)方面。接下來我們會(huì)花一些時(shí)間,分別介紹。 docker系列--names...
摘要:網(wǎng)絡(luò)主要是單機(jī)網(wǎng)絡(luò)和多主機(jī)通信模式。下面分別介紹一下的各個(gè)網(wǎng)絡(luò)模式。設(shè)計(jì)的網(wǎng)絡(luò)模型。是以對(duì)定義的元數(shù)據(jù)。用戶可以通過定義這樣的元數(shù)據(jù)來自定義和驅(qū)動(dòng)的行為。 前言 理解docker,主要從namesapce,cgroups,聯(lián)合文件,運(yùn)行時(shí)(runC),網(wǎng)絡(luò)幾個(gè)方面。接下來我們會(huì)花一些時(shí)間,分別介紹。 docker系列--namespace解讀 docker系列--cgroups解讀 ...
摘要:網(wǎng)絡(luò)主要是單機(jī)網(wǎng)絡(luò)和多主機(jī)通信模式。下面分別介紹一下的各個(gè)網(wǎng)絡(luò)模式。設(shè)計(jì)的網(wǎng)絡(luò)模型。是以對(duì)定義的元數(shù)據(jù)。用戶可以通過定義這樣的元數(shù)據(jù)來自定義和驅(qū)動(dòng)的行為。 前言 理解docker,主要從namesapce,cgroups,聯(lián)合文件,運(yùn)行時(shí)(runC),網(wǎng)絡(luò)幾個(gè)方面。接下來我們會(huì)花一些時(shí)間,分別介紹。 docker系列--namespace解讀 docker系列--cgroups解讀 ...
摘要:網(wǎng)絡(luò)主要是單機(jī)網(wǎng)絡(luò)和多主機(jī)通信模式。下面分別介紹一下的各個(gè)網(wǎng)絡(luò)模式。設(shè)計(jì)的網(wǎng)絡(luò)模型。是以對(duì)定義的元數(shù)據(jù)。用戶可以通過定義這樣的元數(shù)據(jù)來自定義和驅(qū)動(dòng)的行為。 前言 理解docker,主要從namesapce,cgroups,聯(lián)合文件,運(yùn)行時(shí)(runC),網(wǎng)絡(luò)幾個(gè)方面。接下來我們會(huì)花一些時(shí)間,分別介紹。 docker系列--namespace解讀 docker系列--cgroups解讀 ...
閱讀 2458·2021-11-19 09:40
閱讀 3586·2021-11-17 17:08
閱讀 3784·2021-09-10 10:50
閱讀 2213·2019-08-27 10:56
閱讀 1942·2019-08-27 10:55
閱讀 2637·2019-08-26 12:14
閱讀 994·2019-08-26 11:58
閱讀 1493·2019-08-26 10:43