成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

docker系列--namespace解讀

cikenerd / 1995人閱讀

摘要:目前內(nèi)核總共實現(xiàn)了種隔離和消息隊列。參數(shù)表示我們要加入的的文件描述符。提供了很多種進程間通信的機制,針對的是和消息隊列。所謂傳播事件,是指由一個掛載對象的狀態(tài)變化導(dǎo)致的其它掛載對象的掛載與解除掛載動作的事件。

前言

理解docker,主要從namesapce,cgroups,聯(lián)合文件,運行時(runC),網(wǎng)絡(luò)幾個方面。接下來我們會花一些時間,分別介紹。

docker系列--namespace解讀

docker系列--cgroups解讀

docker系列--unionfs解讀

docker系列--runC解讀

docker系列--網(wǎng)絡(luò)模式解讀

namesapce主要是隔離作用,cgroups主要是資源限制,聯(lián)合文件主要用于鏡像分層存儲和管理,runC是運行時,遵循了oci接口,一般來說基于libcontainer。網(wǎng)絡(luò)主要是docker單機網(wǎng)絡(luò)和多主機通信模式。

namespace簡介 什么是namespace

Namespace是將內(nèi)核的全局資源做封裝,使得每個Namespace都有一份獨立的資源,因此不同的進程在各自的Namespace內(nèi)對同一種資源的使用不會互相干擾。實際上,Linux內(nèi)核實現(xiàn)namespace的主要目的就是為了實現(xiàn)輕量級虛擬化(容器)服務(wù)。在同一個namespace下的進程可以感知彼此的變化,而對外界的進程一無所知。這樣就可以讓容器中的進程產(chǎn)生錯覺,仿佛自己置身于一個獨立的系統(tǒng)環(huán)境中,以此達到獨立和隔離的目的。

這樣的解釋可能不清楚,舉個例子,執(zhí)行sethostname這個系統(tǒng)調(diào)用時,可以改變系統(tǒng)的主機名,這個主機名就是一個內(nèi)核的全局資源。內(nèi)核通過實現(xiàn)UTS Namespace,可以將不同的進程分隔在不同的UTS Namespace中,在某個Namespace修改主機名時,另一個Namespace的主機名還是保持不變。

目前Linux內(nèi)核總共實現(xiàn)了6種Namespace:

IPC:隔離System V IPC和POSIX消息隊列。

Network:隔離網(wǎng)絡(luò)資源。

Mount:隔離文件系統(tǒng)掛載點。每個容器能看到不同的文件系統(tǒng)層次結(jié)構(gòu)。

PID:隔離進程ID。

UTS:隔離主機名和域名。

User:隔離用戶ID和組ID。

namespae接口的使用

namespace的API包括clone()、setns()以及unshare(),還有/proc下的部分文件。為了確定隔離的到底是哪種namespace,在使用這些API時,通常需要指定以下六個常數(shù)的一個或多個,通過|(位或)操作來實現(xiàn)。你可能已經(jīng)在上面的表格中注意到,這六個參數(shù)分別是CLONE_NEWIPC、CLONE_NEWNS、CLONE_NEWNET、CLONE_NEWPID、CLONE_NEWUSER和CLONE_NEWUTS。

1: 通過clone()創(chuàng)建新進程的同時創(chuàng)建namespace
使用clone()來創(chuàng)建一個獨立namespace的進程是最常見做法,它的調(diào)用方式如下。

int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg);

clone()實際上是傳統(tǒng)UNIX系統(tǒng)調(diào)用fork()的一種更通用的實現(xiàn)方式,它可以通過flags來控制使用多少功能。一共有二十多種CLONE_*的flag(標(biāo)志位)參數(shù)用來控制clone進程的方方面面(如是否與父進程共享虛擬內(nèi)存等等),下面外面逐一講解clone函數(shù)傳入的參數(shù)。

參數(shù)child_func傳入子進程運行的程序主函數(shù)。

參數(shù)child_stack傳入子進程使用的??臻g

參數(shù)flags表示使用哪些CLONE_*標(biāo)志位

參數(shù)args則可用于傳入用戶參數(shù)

2: 通過setns()加入一個已經(jīng)存在的namespace
在進程都結(jié)束的情況下,也可以通過掛載的形式把namespace保留下來,保留namespace的目的自然是為以后有進程加入做準(zhǔn)備。通過setns()系統(tǒng)調(diào)用,你的進程從原先的namespace加入我們準(zhǔn)備好的新namespace,使用方法如下。

int setns(int fd, int nstype);

參數(shù)fd表示我們要加入的namespace的文件描述符。上文已經(jīng)提到,它是一個指向/proc/[pid]/ns目錄的文件描述符,可以通過直接打開該目錄下的鏈接或者打開一個掛載了該目錄下鏈接的文件得到。

參數(shù)nstype讓調(diào)用者可以去檢查fd指向的namespace類型是否符合我們實際的要求。如果填0表示不檢查。

3: 通過unshare()在原先進程上進行namespace隔離
后要提的系統(tǒng)調(diào)用是unshare(),它跟clone()很像,不同的是,unshare()運行在原先的進程上,不需要啟動一個新進程,使用方法如下。

int unshare(int flags);

調(diào)用unshare()的主要作用就是不啟動一個新進程就可以起到隔離的效果,相當(dāng)于跳出原先的namespace進行操作。這樣,你就可以在原進程進行一些需要隔離的操作。Linux中自帶的unshare命令,就是通過unshare()系統(tǒng)調(diào)用實現(xiàn)的。

各個namespace介紹

UTS Namespace

UTS Namespace用于對主機名和域名進行隔離,也就是uname系統(tǒng)調(diào)用使用的結(jié)構(gòu)體struct utsname里的nodename和domainname這兩個字段,UTS這個名字也是由此而來的。
那么,為什么要使用UTS Namespace做隔離?這是因為主機名可以用來代替IP地址,因此,也就可以使用主機名在網(wǎng)絡(luò)上訪問某臺機器了,如果不做隔離,這個機制在容器里就會出問題。

IPC Namespace

IPC是Inter-Process Communication的簡寫,也就是進程間通信。Linux提供了很多種進程間通信的機制,IPC Namespace針對的是SystemV IPC和Posix消息隊列。這些IPC機制都會用到標(biāo)識符,例如用標(biāo)識符來區(qū)別不同的消息隊列,然后兩個進程通過標(biāo)識符找到對應(yīng)的消息隊列進行通信等。
IPC Namespace能做到的事情是,使相同的標(biāo)識符在兩個Namespace中代表不同的消息隊列,這樣也就使得兩個Namespace中的進程不能通過IPC進程通信了。

PID Namespace

PID Namespace用于隔離進程PID號,這樣一來,不同的Namespace里的進程PID號就可以是一樣的了。

Network Namespace

這個Namespace會對網(wǎng)絡(luò)相關(guān)的系統(tǒng)資源進行隔離,每個Network Namespace都有自己的網(wǎng)絡(luò)設(shè)備、IP地址、路由表、/proc/net目錄、端口號等。網(wǎng)絡(luò)隔離的必要性是很明顯的,舉一個例子,在沒有隔離的情況下,如果兩個不同的容器都想運行同一個Web應(yīng)用,而這個應(yīng)用又需要使用80端口,那就會有沖突了。

Mount namespace

Mount namespace通過隔離文件系統(tǒng)掛載點對隔離文件系統(tǒng)提供支持,它是歷史上第一個Linux namespace,所以它的標(biāo)識位比較特殊,就是CLONE_NEWNS。隔離后,不同mount namespace中的文件結(jié)構(gòu)發(fā)生變化也互不影響。你可以通過/proc/[pid]/mounts查看到所有掛載在當(dāng)前namespace中的文件系統(tǒng),還可以通過/proc/[pid]/mountstats看到mount namespace中文件設(shè)備的統(tǒng)計信息,包括掛載文件的名字、文件系統(tǒng)類型、掛載位置等等。

進程在創(chuàng)建mount namespace時,會把當(dāng)前的文件結(jié)構(gòu)復(fù)制給新的namespace。新namespace中的所有mount操作都只影響自身的文件系統(tǒng),而對外界不會產(chǎn)生任何影響。這樣做非常嚴(yán)格地實現(xiàn)了隔離,但是某些情況可能并不適用。比如父節(jié)點namespace中的進程掛載了一張CD-ROM,這時子節(jié)點namespace拷貝的目錄結(jié)構(gòu)就無法自動掛載上這張CD-ROM,因為這種操作會影響到父節(jié)點的文件系統(tǒng)。

ps
在mount這塊,需要特別注意,掛載的傳播性。在實際應(yīng)用中,很重要。2006 年引入的掛載傳播(mount propagation)解決了這個問題,掛載傳播定義了掛載對象(mount object)之間的關(guān)系,系統(tǒng)用這些關(guān)系決定任何掛載對象中的掛載事件如何傳播到其他掛載對象。所謂傳播事件,是指由一個掛載對象的狀態(tài)變化導(dǎo)致的其它掛載對象的掛載與解除掛載動作的事件。

User Namespace

User Namespace用來隔離用戶和組ID,也就是說一個進程在Namespace里的用戶和組ID與它在host里的ID可以不一樣,這樣說可能讀者還不理解有什么實際的用處。User Namespace最有用的地方在于,host的普通用戶進程在容器里可以是0號用戶,也就是root用戶。這樣,進程在容器內(nèi)可以做各種特權(quán)操作,但是它的特權(quán)被限定在容器內(nèi),離開了這個容器它就只有普通用戶的權(quán)限了。

代碼解讀

首先runc中有一個nsenter文件夾,主要是go通過cgo,實現(xiàn)了nsexec等方法。

在Go運行時啟動之前,nsenter包注冊了一個特殊init構(gòu)造函數(shù)。這讓我們有可能在現(xiàn)有名稱空間“setns”,并避免了Go運行時在多線程場景下可能出現(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")
    },
}

下面重點講一下在linux container中namespace的實現(xiàn)。

runc/libcontainer/configs/config.go中定義了container對應(yīng)的Namespaces。另外對于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"
)

除了驗證 Namespce Type是否在以上常量中,還要去驗證 /proc/self/ns/$nsName是否存在并且可以read,都通過時,才認為該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
       }
       ...
       // 除了驗證 Namespce Type是都在指定列表中,還要去驗證 /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時這些namespace對應(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://systransis.cn/yun/27449.html

相關(guān)文章

  • docker系列--namespace解讀

    摘要:目前內(nèi)核總共實現(xiàn)了種隔離和消息隊列。參數(shù)表示我們要加入的的文件描述符。提供了很多種進程間通信的機制,針對的是和消息隊列。所謂傳播事件,是指由一個掛載對象的狀態(tài)變化導(dǎo)致的其它掛載對象的掛載與解除掛載動作的事件。 前言 理解docker,主要從namesapce,cgroups,聯(lián)合文件,運行時(runC),網(wǎng)絡(luò)幾個方面。接下來我們會花一些時間,分別介紹。 docker系列--names...

    wupengyu 評論0 收藏0
  • docker系列--namespace解讀

    摘要:目前內(nèi)核總共實現(xiàn)了種隔離和消息隊列。參數(shù)表示我們要加入的的文件描述符。提供了很多種進程間通信的機制,針對的是和消息隊列。所謂傳播事件,是指由一個掛載對象的狀態(tài)變化導(dǎo)致的其它掛載對象的掛載與解除掛載動作的事件。 前言 理解docker,主要從namesapce,cgroups,聯(lián)合文件,運行時(runC),網(wǎng)絡(luò)幾個方面。接下來我們會花一些時間,分別介紹。 docker系列--names...

    Acceml 評論0 收藏0
  • docker系列--網(wǎng)絡(luò)模式解讀

    摘要:網(wǎng)絡(luò)主要是單機網(wǎng)絡(luò)和多主機通信模式。下面分別介紹一下的各個網(wǎng)絡(luò)模式。設(shè)計的網(wǎng)絡(luò)模型。是以對定義的元數(shù)據(jù)。用戶可以通過定義這樣的元數(shù)據(jù)來自定義和驅(qū)動的行為。 前言 理解docker,主要從namesapce,cgroups,聯(lián)合文件,運行時(runC),網(wǎng)絡(luò)幾個方面。接下來我們會花一些時間,分別介紹。 docker系列--namespace解讀 docker系列--cgroups解讀 ...

    haitiancoder 評論0 收藏0
  • docker系列--網(wǎng)絡(luò)模式解讀

    摘要:網(wǎng)絡(luò)主要是單機網(wǎng)絡(luò)和多主機通信模式。下面分別介紹一下的各個網(wǎng)絡(luò)模式。設(shè)計的網(wǎng)絡(luò)模型。是以對定義的元數(shù)據(jù)。用戶可以通過定義這樣的元數(shù)據(jù)來自定義和驅(qū)動的行為。 前言 理解docker,主要從namesapce,cgroups,聯(lián)合文件,運行時(runC),網(wǎng)絡(luò)幾個方面。接下來我們會花一些時間,分別介紹。 docker系列--namespace解讀 docker系列--cgroups解讀 ...

    zollero 評論0 收藏0
  • docker系列--網(wǎng)絡(luò)模式解讀

    摘要:網(wǎng)絡(luò)主要是單機網(wǎng)絡(luò)和多主機通信模式。下面分別介紹一下的各個網(wǎng)絡(luò)模式。設(shè)計的網(wǎng)絡(luò)模型。是以對定義的元數(shù)據(jù)。用戶可以通過定義這樣的元數(shù)據(jù)來自定義和驅(qū)動的行為。 前言 理解docker,主要從namesapce,cgroups,聯(lián)合文件,運行時(runC),網(wǎng)絡(luò)幾個方面。接下來我們會花一些時間,分別介紹。 docker系列--namespace解讀 docker系列--cgroups解讀 ...

    xiaotianyi 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<