国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

探索 runC (上)

yanest / 1942人閱讀

摘要:當前業內比較有名的有,等。至少在筆者的主機上是這樣。而第部加載,在上,就是返回一個結構。方法的實現如下第部分第部分上面的可分為兩部分調用方法用創建注意第二個參數是,表示新創建的會作為新創建容器的第一個。

前言

容器運行時(Container Runtime)是指管理容器和容器鏡像的軟件。當前業內比較有名的有docker,rkt等。如果不同的運行時只能支持各自的容器,那么顯然不利于整個容器技術的發展。于是在2015年6月,由Docker以及其他容器領域的領導者共同建立了圍繞容器格式和運行時的開放的工業化標準,即Open Container Initiative(OCI),OCI具體包含兩個標準:運行時標準(runtime-spec)和容器鏡像標準(image-spec)。簡單來說,容器鏡像標準定義了容器鏡像的打包形式(pack format),而運行時標準定義了如何去運行一個容器。

本文包含以下內容:

runC的概念和使用

runC運行容器的原理剖析

本文包含以下內容:

docker engine使用runC

runC概念

runC是一個遵循OCI標準的用來運行容器的命令行工具(CLI Tool),它也是一個Runtime的實現。盡管你可能對這個概念很陌生,但實際上,你的電腦上的docker底層可能正在使用它。至少在筆者的主機上是這樣。

root@node-1:~# docker info
.....
Runtimes: runc
Default Runtime: runc 
.....
安裝runC

runC不僅可以被docker engine使用,它也可以多帶帶使用(它本身就是命令行工具),以下使用步驟完全來自runC"s README,如果

依賴項

Go version 1.6或更高版本

libseccomp庫

 yum install libseccomp-devel for CentOS
 apt-get install libseccomp-dev for Ubuntu

下載編譯
# 在GOPATH/src目錄創建"github.com/opencontainers"目錄
> cd github.com/opencontainers
> git clone https://github.com/opencontainers/runc
> cd runc

> make
> sudo make install

或者使用go get安裝

# 在GOPATH/src目錄創建github.com目錄
> go get github.com/opencontainers/runc
> cd $GOPATH/src/github.com/opencontainers/runc
> make
> sudo make install

以上步驟完成后,runC將安裝在/usr/local/sbin/runc目錄

使用runC 創建一個OCI Bundle

OCI Bundle是指滿足OCI標準的一系列文件,這些文件包含了運行容器所需要的所有數據,它們存放在一個共同的目錄,該目錄包含以下兩項:

config.json:包含容器運行的配置數據

container 的 root filesystem

如果主機上安裝了docker,那么可以使用docker export命令將已有鏡像導出為OCI Bundle的格式

# create the top most bundle directory
> mkdir /mycontainer
> cd /mycontainer

# create the rootfs directory
> mkdir rootfs

# export busybox via Docker into the rootfs directory
> docker export $(docker create busybox) | tar -C rootfs -xvf -
> ls rootfs 
bin  dev  etc  home  proc  root  sys  tmp  usr  var

有了root filesystem,還需要config.json,runc spec可以生成一個基礎模板,之后我們可以在模板基礎上進行修改。

> runc spec
> ls
config.json rootfs

生成的config.json模板比較長,這里我將它process中的argterminal進行修改

{
    "process": {
        "terminal":false,     <--  這里改為 true
        "user": {
            "uid": 0,
            "gid": 0
        },
        "args": [
            "sh"               <-- 這里改為 "sleep","5"
        ],
        "env": [
            "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
            "TERM=xterm"  
        ],
        "cwd": "/",
    },
    "root": {
        "path": "rootfs",
        "readonly": true
    },   
    "linux": {
        "namespaces": [
            {
                "type": "pid"
            },
            {
                "type": "network"
            },
            {
                "type": "ipc"
            },
            {
                "type": "uts"
            },
            {
                "type": "mount"
            }
        ],
    }
} 

config.json 文件的內容都是 OCI Container Runtime 的訂制,其中每一項值都可以在Runtime Spec找到具體含義,OCI Container Runtime 支持多種平臺,因此其 Spec 也分為通用部分(在config.md中描述)以及平臺相關的部分(如linux平臺上就是config-linux)

process:指定容器啟動后運行的進程運行環境,其中最重要的的子項就是args,它指定要運行的可執行程序, 在上面的修改后的模板中,我們將其改成了"sleep 5"

root:指定容器的根文件系統,其中path子項是指向前面導出的中root filesystem的路徑

linux: 這一項是平臺相關的。其中namespaces表示新創建的容器會額外創建或使用的namespace的類型

運行容器

現在我們使用create命令創建容器

# run as root
> cd /mycontainer
> runc create mycontainerid

使用list命令查看容器狀態為created

# view the container is created and in the "created" state
> runc list
ID              PID         STATUS      BUNDLE                           CREATED                          OWNER
mycontainerid   12068       created     /mycontainer   2018-12-25T19:45:37.346925609Z                      root 

使用start命令查看容器狀態

# start the process inside the container
> runc start mycontainerid

在5s內 使用list命令查看容器狀態為running

# within 5 seconds view that the container is running
runc list
ID              PID         STATUS      BUNDLE                           CREATED                          OWNER
mycontainerid   12068       running     /mycontainer   2018-12-25T19:45:37.346925609Z                      root 

在5s后 使用list命令查看容器狀態為stopped

# after 5 seconds view that the container has exited and is now in the stopped state
runc list
ID              PID         STATUS      BUNDLE                           CREATED                          OWNER
mycontainerid   0           stopped     /mycontainer   2018-12-25T19:45:37.346925609Z                       root 

使用delete命令可以刪除容器

# now delete the container
runc delete mycontainerid
runC 實現

runC可以啟動并管理符合OCI標準的容器。簡單地說,runC需要利用OCI bundle創建一個獨立的運行環境,并執行指定的程序。在Linux平臺上,這個環境就是指各種類型的Namespace以及Capability等等配置

代碼結構

runC由Go語言實現,當前(2018.12)最新版本是v1.0.0-rc6,代碼的結構可分為兩大塊,一是根目錄下的go文件,對應各個runC命令,二是負責創建/啟動/管理容器的libcontainer,可以說runC的本質都在libcontainer

runc create 實現原理 (上)

以上面的例子為例,以"runc create"這條命令來看runC是如何完成從無到有創建容器,并運行用戶指定的 "sleep 5" 這個進程的。

創建容器,運行 sleep 5 就是我們的目標,請牢記
本文涉及的調用關系如下,可隨時翻閱
 setupSpec(context)
 startContainer(context,?spec,?CT_ACT_CREATE,?nil) 
   |- createContainer
      |- specconv.CreateLibcontainerConfig
      |- loadFactory(context)
         |- libcontainer.New(......)
      |- factory.Create(id, config)
   |- runner.run(spec.Process)
      |- newProcess(*config, r.init) 
      |- r.container.Start(process)
         |- c.createExecFifo()
         |- c.start(process)
            |- c.newParentProcess(process)
            |- parent.start()

create命令的響應入口在 create.go, 我們直接關注其注冊的Action的實現,當輸入runc create mycontainerid時會執行注冊的Action,并且參數存放在Context

/* run.go  */
Action:?func(context?*cli.Context)?error?{ 
  ......
? spec,?err?:=?setupSpec(context)   /* (sleep 5 在這里) */

??status,?err?:=?startContainer(context,?spec,?CT_ACT_CREATE,?nil) 
  .....
}

setupSpec:從命令行輸入中找到-b 指定的 OCI bundle 目錄,若沒有此參數,則默認是當前目錄。讀取config.json文件,將其中的內容轉換為Go的數據結構specs.Spec,該結構定義在文件 github.com/opencontainers/runtime-spec/specs-go/config.go,里面的內容都是OCI標準描述的。

sleep 5 到了變量 spec

startContainer:嘗試創建啟動容器,注意這里的第三個參數是 CT_ACT_CREATE, 表示僅創建容器。本文使用linux平臺,因此實際調用的是 utils_linux.go 中的startContainer()startContainer()根據用戶將用戶輸入的 id 和剛才的得到的 spec 作為輸入,調用 createContainer() 方法創建容器,再通過一個runner.run()方法啟動它

/× utils_linux.go ×/
func startContainer(context *cli.Context, spec *specs.Spec, action CtAct, criuOpts *libcontainer.CriuOpts) (int, error) {
    id := context.Args().First()

    container, err := createContainer(context, id, spec)

    r := &runner{
        container:       container,
        action:          action,
        init:            true,
        ......
    }
    return r.run(spec.Process)
}

這里需要先了解下runC中的幾個重要數據結構的關系

Container 接口

runC中,Container用來表示一個容器對象,它是一個抽象接口,它內部包含了BaseContainer接口。從其內部的方法的名字就可以看出,都是管理容器的基本操作

/* libcontainer/container.go */
type BaseContainer interface {
    ID() string
    Status() (Status, error)
    State() (*State, error)
    Config() configs.Config
    Processes() ([]int, error)
    Stats() (*Stats, error)
    Set(config configs.Config) error
    Start(process *Process) (err error)
    Run(process *Process) (err error)
    Destroy() error
    Signal(s os.Signal, all bool) error
    Exec() error
}

/* libcontainer/container_linux.go */
type Container interface {
    BaseContainer

    Checkpoint(criuOpts *CriuOpts) error
    Restore(process *Process, criuOpts *CriuOpts) error
    Pause() error
    Resume() error
    NotifyOOM() (<-chan struct{}, error)
    NotifyMemoryPressure(level PressureLevel) (<-chan struct{}, error)
}

有了抽象接口,那么一定有具體的實現,linuxContainer 就是一個實現,或者說,它是當前版本runC在linux平臺上的唯一一種實現。下面是其定義,其中的 initPath 非常關鍵

type linuxContainer struct {
    id                   string
    config               *configs.Config
    initPath             string
    initArgs             []string
    initProcess          parentProcess
    .....
}
Factory 接口

runC中,所有的容器都是由容器工廠(Factory)創建的, Factory 也是一個抽象接口,定義如下,它只包含了4個方法

type Factory interface {
    Create(id string, config *configs.Config) (Container, error)
    Load(id string) (Container, error)
    StartInitialization() error
    Type() string
}

linux平臺上的對 Factory 接口也有一個標準實現---LinuxFactory,其中的 InitPath 也非常關鍵,稍后我們會看到

// LinuxFactory implements the default factory interface for linux based systems.
type LinuxFactory struct {
    // InitPath is the path for calling the init responsibilities for spawning
    // a container.
    InitPath string
    ......

    // InitArgs are arguments for calling the init responsibilities for spawning
    // a container.
    InitArgs []string
}

所以,對于linux平臺,Factory 創建 Container 實際上就是 LinuxFactory 創建 linuxContainer

回到createContainer(),下面是其實現

func createContainer(context *cli.Context, id string, spec *specs.Spec) (libcontainer.Container, error) {
    /* 1. 將配置存放到config */
    rootlessCg, err := shouldUseRootlessCgroupManager(context)
    config, err := specconv.CreateLibcontainerConfig(&specconv.CreateOpts{
        CgroupName:       id,
        UseSystemdCgroup: context.GlobalBool("systemd-cgroup"),
        NoPivotRoot:      context.Bool("no-pivot"),
        NoNewKeyring:     context.Bool("no-new-keyring"),
        Spec:             spec,                              
        RootlessEUID:     os.Geteuid() != 0,
        RootlessCgroups:  rootlessCg,
    })

    /* 2. 加載Factory */
    factory, err := loadFactory(context)
    if err != nil {
        return nil, err
    }

    /* 3. 調用Factory的Create()方法 */
    return factory.Create(id, config)
}

可以看到,上面的代碼大體上分為

將配置存放到 config, 數據類型是 Config.config

加載 Factory,實際返回 LinuxFactory

調用 Factory 的Create()方法

sleep 5 到了變量 config

第1步存放配置沒什么好說的,無非是將已有的 spec 和其他一些用戶命令行選項配置換成一個數據結構存下來。而第2部加載Factory,在linux上,就是返回一個 LinuxFactory 結構。而這是通過在其內部調用 libcontainer.New()方法實現的

/* utils/utils_linux.go */
func loadFactory(context *cli.Context) (libcontainer.Factory, error) {
    .....
    return libcontainer.New(abs, cgroupManager, intelRdtManager,
        libcontainer.CriuPath(context.GlobalString("criu")),
        libcontainer.NewuidmapPath(newuidmap),
        libcontainer.NewgidmapPath(newgidmap))
}

libcontainer.New() 方法在linux平臺的實現如下,可以看到,它的確會返回一個LinuxFactory,并且InitPath設置為"/proc/self/exe",InitArgs設置為"init"

/* libcontainer/factory_linux.go */
func New(root string, options ...func(*LinuxFactory) error) (Factory, error) {
    .....
    l := &LinuxFactory{
        .....
        InitPath:  "/proc/self/exe",
        InitArgs:  []string{os.Args[0], "init"},
    }
    ......
    return l, nil
}

得到了具體的 Factory 實現,下一步就是調用其Create()方法,對 linux 平臺而言,就是下面這個方法,可以看到,它會將 LinuxFactory 上記錄的 InitPathInitArgs 賦給 linuxContainer 并作為結果返回

func (l *LinuxFactory) Create(id string, config *configs.Config) (Container, error) {
    ....
    c := &linuxContainer{
        id:            id,    
        config:        config,
        initPath:      l.InitPath,
        initArgs:      l.InitArgs,
    }
     .....
    return c, nil
}

回到 startContainer() 方法,再得到 linuxContainer 后,將創建一個 runner 結構,并調用其run()方法

/* utils_linux.go */
func startContainer(context *cli.Context, spec *specs.Spec, action CtAct, criuOpts *libcontainer.CriuOpts) (int, error) {
    id := context.Args().First()

    container, err := createContainer(context, id, spec)

    r := &runner{
        container:       container,
        action:          action,
        init:            true,     
        ......
    }
    return r.run(spec.Process)
}

runnerrun() 的入參是 spec.Process 結構,我們并不需要關注它的定義,因為它的內容都來源于 config.json 文件,spec.Process 不過是其中 Process 部分的 Go 語言數據的表示。run() 方法的實現如下:

func (r *runner) run(config *specs.Process) (int, error) { 
    ......
    process, err := newProcess(*config, r.init)                                  /*  第1部分 */
    ......
    switch r.action {
    case CT_ACT_CREATE:
        err = r.container.Start(process)   /* runc start */                      /*  第2部分 */
    case CT_ACT_RESTORE:
        err = r.container.Restore(process, r.criuOpts) /* runc restore */
    case CT_ACT_RUN:
        err = r.container.Run(process)     /* runc run */
    default:
        panic("Unknown action")
    }
    ......
    return status, err
}

上面的 run() 可分為兩部分

調用 newProcess() 方法, 用 spec.Process 創建 libcontainer.Process,注意第二個參數是 true ,表示新創建的 process 會作為新創建容器的第一個 process

根據 r.action 的值決定如何操作得到的 libcontainer.Process

sleep 5 到了變量 process

libcontainer.Process 結構定義在 /libcontainer/process.go, 其中大部分內容都來自 spec.Process

/* parent process */
// Process specifies the configuration and IO for a process inside
// a container.
type Process struct {
    Args []string
    Env []string
    User string
    AdditionalGroups []string
    Cwd string
    Stdin io.Reader
    Stdout io.Writer
    Stderr io.Writer
    ExtraFiles []*os.File

    ConsoleWidth  uint16
    ConsoleHeight uint16
    Capabilities *configs.Capabilities
    AppArmorProfile string
    Label string
    NoNewPrivileges *bool
    Rlimits []configs.Rlimit
    ConsoleSocket *os.File
    Init bool

    ops processOperations
}

接下來就是要使用 Start() 方法了

func (c *linuxContainer) Start(process *Process) error {

    if process.Init {
        if err := c.createExecFifo(); err != nil {  /* 1.創建fifo   */
            return err
        }
    }
    if err := c.start(process); err != nil {        /* 2. 調用start() */
        if process.Init {
            c.deleteExecFifo()
        }
        return err
    }
    return nil
}

Start() 方法主要完成兩件事

創建 fifo: 創建一個名為exec.fifo的管道,這個管道后面會用到

調用 start() 方法,如下

func (c *linuxContainer) start(process *Process) error {
    parent, err := c.newParentProcess(process) /*  1. 創建parentProcess */

    err := parent.start();                     /*  2. 啟動這個parentProcess */
    ......
   

start() 也完成兩件事:

創建一個 ParentProcess

調用這個 ParentProcessstart() 方法

sleep 5 到了變量 parent

那么什么是 parentProcess ? 正如其名,parentProcess 類似于 linux 中可以派生出子進程的父進程,在runC中,parentProcess 是一個抽象接口,如下:

type parentProcess interface {
    // pid returns the pid for the running process.
    pid() int

    // start starts the process execution.
    start() error

    // send a SIGKILL to the process and wait for the exit.
    terminate() error

    // wait waits on the process returning the process state.
    wait() (*os.ProcessState, error)

    // startTime returns the process start time.
    startTime() (uint64, error)

    signal(os.Signal) error

    externalDescriptors() []string

    setExternalDescriptors(fds []string)
}

它有兩個實現,分別為 initProcesssetnsProcess ,前者用于創建容器內的第一個進程,后者用于在已有容器內創建新的進程。在我們的創建容器例子中,p.Init = true ,所以會創建 initProcess

func (c *linuxContainer) newParentProcess(p *Process) (parentProcess, error) {
    parentPipe, childPipe, err := utils.NewSockPair("init")  /* 1.創建 Socket Pair */

    cmd, err := c.commandTemplate(p, childPipe)              /* 2. 創建 *exec.Cmd */

    if !p.Init {
        return c.newSetnsProcess(p, cmd, parentPipe, childPipe) 
    }

    if err := c.includeExecFifo(cmd); err != nil {           /* 3.打開之前創建的fifo */
        return nil, newSystemErrorWithCause(err, "including execfifo in cmd.Exec setup")
    }
    return c.newInitProcess(p, cmd, parentPipe, childPipe)   /* 4.創建 initProcess */
}

newParentProcess() 方法動作有 4 步,前 3 步都是在為第 4 步做準備,即生成 initProcess

創建一對 SocketPair 沒什么好說的,生成的結果會放到 initProcess

創建 *exec.Cmd,代碼如下,這里設置了 cmd 要執行的可執行程序和參數來自 c.initPath,即源自 LinuxFactory 的 "/proc/self/exe",和 "init" ,這表示新執行的程序就是runC本身,只是參數變成了 init,之后又將外面創建的 SocketPair 的一端 childPipe放到了cmd.ExtraFiles ,同時將_LIBCONTAINER_INITPIPE=%d加入cmd.Env,其中 %d為文件描述符的數字

func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.Cmd, error) {
    cmd := exec.Command(c.initPath, c.initArgs[1:]...)
    cmd.Args[0] = c.initArgs[0]
    
    cmd.ExtraFiles = append(cmd.ExtraFiles, p.ExtraFiles...)
    cmd.ExtraFiles = append(cmd.ExtraFiles, childPipe)
    cmd.Env = append(cmd.Env,
        fmt.Sprintf("_LIBCONTAINER_INITPIPE=%d", stdioFdCount+len(cmd.ExtraFiles)-1),
    )
    ......
    return cmd, nil
}

includeExecFifo() 方法打開之前創建的 fifo,也將其 fd 放到 cmd.ExtraFiles 中,同時將_LIBCONTAINER_FIFOFD=%d記錄到 cmd.Env

最后就是創建 InitProcess 了,這里首先將_LIBCONTAINER_INITTYPE="standard"加入cmd.Env,然后從 configs 讀取需要新的容器創建的 Namespace 的類型,并將其打包到變量 data 中備用,最后再創建 InitProcess 自己,可以看到,這里將之前的一些資源和變量都聯系了起來

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,          /*  sleep 5 在這里 */
        bootstrapData:   data,
        sharePidns:      sharePidns,
    }, nil
}
sleep 5 在 initProcess.process 中

回到 linuxContainerstart() 方法,創建好了 parent ,下一步就是調用它的 start() 方法了

func (c *linuxContainer) start(process *Process) error {
    parent, err := c.newParentProcess(process) /*  1. 創建parentProcess (已完成) */

    err := parent.start();                     /*  2. 啟動這個parentProcess */
    ......

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/33103.html

相關文章

  • 探索 runC ()

    摘要:當前業內比較有名的有,等。至少在筆者的主機上是這樣。而第部加載,在上,就是返回一個結構。方法的實現如下第部分第部分上面的可分為兩部分調用方法用創建注意第二個參數是,表示新創建的會作為新創建容器的第一個。 前言 容器運行時(Container Runtime)是指管理容器和容器鏡像的軟件。當前業內比較有名的有docker,rkt等。如果不同的運行時只能支持各自的容器,那么顯然不利于整個容...

    Aomine 評論0 收藏0
  • 探索runC (下)

    摘要:而不幸的是是多線程的。至此,子進程就從父進程處得到了的配置,繼續往下,又創建了兩個從注釋中了解到,這是為了和它自己的子進程和孫進程進行通信。 回顧 本文接 探索runC(上) 前文講到,newParentProcess() 根據源自 config.json 的配置,最終生成變量 initProcess ,這個 initProcess 包含的信息主要有 cmd 記錄了要執行的可執行...

    gekylin 評論0 收藏0
  • 探索runC (下)

    摘要:而不幸的是是多線程的。至此,子進程就從父進程處得到了的配置,繼續往下,又創建了兩個從注釋中了解到,這是為了和它自己的子進程和孫進程進行通信。 回顧 本文接 探索runC(上) 前文講到,newParentProcess() 根據源自 config.json 的配置,最終生成變量 initProcess ,這個 initProcess 包含的信息主要有 cmd 記錄了要執行的可執行...

    jzman 評論0 收藏0
  • runc容器逃逸漏洞最強后續:應對之策匯總與熱點疑問解答

    摘要:年月日,研究人員通過郵件列表披露了容器逃逸漏洞的詳情,根據的規定會在天后也就是年月日公開。在號當天已通過公眾號文章詳細分析了漏洞詳情和用戶的應對之策。 美國時間2019年2月11日晚,runc通過oss-security郵件列表披露了runc容器逃逸漏洞CVE-2019-5736的詳情。runc是Docker、CRI-O、Containerd、Kubernetes等底層的容器運行時,此...

    PingCAP 評論0 收藏0
  • runc 1.0-rc7 發布之際

    摘要:在年月底時,我寫了一篇文章發布之際。為何有存在前面已經基本介紹了相關背景,并且也基本明確了就是在正式發布之前的最后一個版本,那為什么會出現呢我們首先要介紹今年的一個提權漏洞。 在 18 年 11 月底時,我寫了一篇文章 《runc 1.0-rc6 發布之際》 。如果你還不了解 runc 是什么,以及如何使用它,請參考我那篇文章。本文中,不再對其概念和用法等進行說明。 在 runc 1....

    zhunjiee 評論0 收藏0

發表評論

0條評論

yanest

|高級講師

TA的文章

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