initProcess + runc init

 

 

 

 

 

docker: Error response from daemon: OCI runtime create failed: rpc error: code = Internal desc = Could not run process: container_linux.go:349: starting container process caused "process_linux.go:289: mount bind /run/sockets failed caused \"no such file or directory\"": unknown.

 

 

docker: Error response from daemon: OCI runtime create failed: rpc error: code = Internal desc = Could not run process: container_linux.go:349: starting container process caused "process_linux.go:299:
mount bind /run/sockets failed /run/kata-containers/shared/containers/2181a65982bcdfa06619d4fc76ba5b0d2b30df326e3a472e7638df9502ab8ada/rootfs , /run/sockets/xxx not exist caused \"\"
": unknown.

 

 

 

 

 

docker: Error response from daemon: OCI runtime create failed: rpc error: code = Internal desc = Could not run process: container_linux.go:351: mount bind /run/sockets failed 
/run/kata-containers/shared/containers/ad6f3c13cf9f0fb5028678e8200177baebdf4797f0d624aa0b117a607bc4e477/rootfs , /run/sockets/xxx not exist caused "": unknown.

 

RunC 执

行流程与 cgroup 的应用

Container 的创建过程由 factory 调用 create 方法实现,在创建 factory 对象时指定了NewCgroupsManage func,在 factory 创建 container 时调用 func 为容器配置了fs.Manager对象。调用过程 runc create 命令创建容器开始: startContainer() => createContainer() => loadFactory() => libcontainer.New()

libcontainer/factory_linux.go:131

func New(root string, options ...func(*LinuxFactory) error) (Factory, error) {
  //...
    l := &LinuxFactory{
        Root:      root,
        InitPath:  "/proc/self/exe",
        InitArgs:  []string{os.Args[0], "init"},
        Validator: validate.New(),
        CriuPath:  "criu",
    }
    Cgroupfs(l)                   //为LinuxFactory配置NewCgroupsManage实现func
    //...
    return l, nil
}

初始化配置LinuxFactory对象的NewCgroupsManage的func赋值,func将根据参数配置返回一个fs.Manager对象

libcontainer/factory_linux.go:65

// Cgroupfs is an options func to configure a LinuxFactory to return containers
// that use the native cgroups filesystem implementation to create and manage
// cgroups.
func Cgroupfs(l *LinuxFactory) error {
    l.NewCgroupsManager = func(config *configs.Cgroup, paths map[string]string) cgroups.Manager {
        return &fs.Manager{
            Cgroups: config,
            Paths:   paths,
        }
    }
    return nil
}

创建 Container 容器对象,返回 linuxContainer 结构。LinuxFactory.NewCgroupsManager() 调用根据全局 config 赋值并返回 Cgroup Manager 对象 fs.Manger

libcontainer/factory_linux.go:188

func (l *LinuxFactory) Create(id string, config *configs.Config) (Container, error) {
  //...
    c := &linuxContainer{
        id:            id,
        root:          containerRoot,
        config:        config,
        initPath:      l.InitPath,
        initArgs:      l.InitArgs,
        criuPath:      l.CriuPath,
        newuidmapPath: l.NewuidmapPath,
        newgidmapPath: l.NewgidmapPath,
        cgroupManager: l.NewCgroupsManager(config.Cgroups, nil),  //为容器指定fs.Manager
    }
  //...
    return c, nil
}

从容器的执行流程来看,此时已完成 container 对象的创建,接下来startContainer()中已创建的 runner 对象 run() 方法执行,容器进入运行阶段。执行流程runc run命令:runner.run() => newProcess() => runner.container.Run(process) => linuxContainer.strat() => linuxContainer.newParentProcess(process) => =>linuxContainer.commandTemplate() => linuxContaine.newInitProcess() =>parent.start() => initProcess.start()

Parent.start() 执行其实则是 runc init 命令的执行,其基本的执行流程(详细请参考《 RunC 源码通读指南之 Run 》):

  1. parentproces 创建runc init子进程,中间会被 /runc/libcontainer/nsenter 劫持(c代码部分preamble),使 runc init 子进程位于容器配置指定的各个 namespace 内
  2. parentProcess 用init管道将容器配置信息传输给runc init进程,runc init再据此进行容器的初始化操作。初始化完成之后,再向另一个管道exec.fifo进行写操作,进入阻塞状态

 

 

 ]

 

 

 

func (p *initProcess) start() error {
        defer p.messageSockPair.parent.Close()
 
        err := p.cmd.Start()
        p.process.ops = p
        // close the write-side of the pipes (controlled by child)
        p.messageSockPair.child.Close()
        p.logFilePair.child.Close()
        if err != nil {
                p.process.ops = nil
                return newSystemErrorWithCause(err, "starting init process command")
        }
        // Do this before syncing with child so that no children can escape the
        // cgroup. We don't need to worry about not doing this and not being root
        // because we'd be using the rootless cgroup manager in that case.
        if err := p.manager.Apply(p.pid()); err != nil {
                return newSystemErrorWithCause(err, "applying cgroup configuration for process")
        }
        if p.intelRdtManager != nil {
                if err := p.intelRdtManager.Apply(p.pid()); err != nil {
                        return newSystemErrorWithCause(err, "applying Intel RDT configuration for process")
                }
        }
        defer func() {
                if err != nil {
                        // TODO: should not be the responsibility to call here
                        p.manager.Destroy()
                        if p.intelRdtManager != nil {
                                p.intelRdtManager.Destroy()
                        }
                }
        }()

        if _, err := io.Copy(p.messageSockPair.parent, p.bootstrapData); err != nil {
                return newSystemErrorWithCause(err, "copying bootstrap data to pipe")
        }
                         

 

 

Parent.start() 执行其实则是 runc init 命令的执行,其基本的执行流程(详细请参考《 RunC 源码通读指南之 Run 》):

  1. parentproces 创建runc init子进程,中间会被 /runc/libcontainer/nsenter 劫持(c代码部分preamble),使 runc init 子进程位于容器配置指定的各个 namespace 内
  2. parentProcess 用init管道将容器配置信息传输给runc init进程,runc init再据此进行容器的初始化操作。初始化完成之后,再向另一个管道exec.fifo进行写操作,进入阻塞状态

InitProcess.start()执行过程中对cgroup 资源组的配置与应用工作

libcontainer/process_linux.go:282

func (p *initProcess) start() error {
    defer p.messageSockPair.parent.Close()
  //  当前执行空间进程称为bootstrap进程
  //  启动了 cmd,即启动了 runc init 命令,创建 runc init 子进程 
  //  同时也激活了C代码nsenter模块的执行(为了 namespace 的设置 clone 了三个进程parent、child、init)
  //  C 代码执行后返回 go 代码部分,最后的 init 子进程为了好区分此处命名为" nsInit "(即配置了Namespace的init)
  //  runc init go代码为容器初始化其它部分(网络、rootfs、路由、主机名、console、安全等)
    err := p.cmd.Start()  // runc init

  //...

  // 为进程 runc init 应用 Cgroup (p.cmd.Process.Pid())
    if err := p.manager.Apply(p.pid()); err != nil {
        return newSystemErrorWithCause(err, "applying cgroup configuration for process")
    }

   //...
   // messageSockPair 管道写入 bootstrapData 
    if _, err := io.Copy(p.messageSockPair.parent, p.bootstrapData); err != nil {
        return newSystemErrorWithCause(err, "copying bootstrap data to pipe")
    }

  // 获取 nsInit pid
    childPid, err := p.getChildPid()
    if err != nil {
        return newSystemErrorWithCause(err, "getting the final child's pid from pipe")
    }

  //...

  // 为 nsInit 进程应用 Cgroup 
    if err := p.manager.Apply(childPid); err != nil {
        return newSystemErrorWithCause(err, "applying cgroup configuration for process")
    }
  // 为 child 进程应用 intel RDT 
    if p.intelRdtManager != nil {
        if err := p.intelRdtManager.Apply(childPid); err != nil {
            return newSystemErrorWithCause(err, "applying Intel RDT configuration for process")
        }
    }

  //...
  // 解析runc init子进程的所有同步消息,当io.EOF返回
    ierr := parseSync(p.messageSockPair.parent, func(sync *syncT) error {
        switch sync.Type {
        case procReady:   
            // prestart hooks 启动前执行勾子
            if !p.config.Config.Namespaces.Contains(configs.NEWNS) {
        // 根据全局配置设置Cgroup 
                if err := p.manager.Set(p.config.Config); err != nil {
                    return newSystemErrorWithCause(err, "setting cgroup config for ready process")
                }
           //...
               // 运行执行前勾子
                    for i, hook := range p.config.Config.Hooks.Prestart {
                        if err := hook.Run(s); err != nil {
                            return newSystemErrorWithCausef(err, "running prestart hook %d", i)
                        }
                    }
                }
            }
            // 与子进程 runC init 同步
            if err := writeSync(p.messageSockPair.parent, procRun); err != nil {
                return newSystemErrorWithCause(err, "writing syncT 'run'")
            }
            sentRun = true
        case procHooks:   
      //  配置 cgroup
             if err := p.manager.Set(p.config.Config); err != nil {
                return newSystemErrorWithCause(err, "setting cgroup config for procHooks process")
            }
      //...
            if p.config.Config.Hooks != nil {
            //...
        // 执行勾子定义任务
              // 与子进程 runc-init 同步
            }
            sentResume = true
        default:
            return newSystemError(fmt.Errorf("invalid JSON payload from child"))
        }
        return nil
    })
   //...
    return nil
}

从整个执行过程来看,容器 init go 代码运行初始化配置后向exec.fifo管道写数据,阻塞,直到用户调用runc start,读取管道中的数据将最后执行用户定义的entrypoint程序。

上面已为Cgroup在容器创建过程中的配置与应用的管理过程,而接下来我们将看看底层是如何实现cgroup的。

3. Cgroup manager 实现

Cgroup manger 为 Runc 实现对系统的 cgroup 操作的管理器抽象。manger对象实现对 cgroup 的配置项值设置、pid应用、销毁 、暂停/恢复、获取配置等操作。这里Apply() 和 Set() 注意一下两者的差别,一个是设置子系统的相关资源约束项的值,一个是将进程pid操作应用至相关的cgroup子系统。

我们先来查看几个关键接口、结构体定义:

  • cgroup manager接口定义

libcontainer/cgroups/cgroups.go:11

type Manager interface {
  // 为指定的 pid 应用 cgroup 配置
    Apply(pid int) error
    // 返回 cgroup 集内所有 pid
    GetPids() ([]int, error)
  // 返回 cgroup 集和 subcgroups 的所有 pid
    GetAllPids() ([]int, error)
  // 返回 cgroup 集统计信息
    GetStats() (*Stats, error)
    // 任务暂停与恢复操作
    Freeze(state configs.FreezerState) error
  // 销毁 cgroup 集
    Destroy() error
  // 获取保存 cgroup 状态文件路径
    GetPaths() map[string]string
    // 设置 Cgroup 配置值
    Set(container *configs.Config) error           // +configs.Config 容器进程配置结构
}
  • Configs.Config 容器进程配置的结构体内 cgroups 相关定义

libcontainer/configs/config.go:81

// 定义容器内执行进程的配置项,此处仅关注 Cgroup 相关
type Config struct {
   //...
   // 容器的 Cgroups 资源限制配置
    Cgroups *Cgroup `json:"cgroups"`              //+Cgroup 结构
   // ....
   // 当 RootlessCgroups 设置为 true,cgroups 错误将被忽略
    RootlessCgroups bool `json:"rootless_cgroups,omitempty"`
}
  • Configs.cgroups 的结构定义

libcontainer/configs/cgroup_linux.go:11

type Cgroup struct {
    // Deprecated, use Path instead
    Name string `json:"name,omitempty"`

    // name of parent of cgroup or slice
    // Deprecated, use Path instead
    Parent string `json:"parent,omitempty"`

  // Path指定由容器创建(和/或)连接的cgroup的路径。假定路径相对于主机系统cgroup挂载点。
    Path string `json:"path"`

    // ScopePrefix describes prefix for the scope name
    ScopePrefix string `json:"scope_prefix"`

    // Paths represent the absolute cgroups paths to join.
    // This takes precedence over Path.
    Paths map[string]string
    // 资源包含各种Cgroup的应用设置
    *Resources                                    // +参考下面定义
}       

// 每项详细说明参考本文附录一
type Resources struct {
    AllowAllDevices *bool `json:"allow_all_devices,omitempty"`
    AllowedDevices []*Device `json:"allowed_devices,omitempty"`
    DeniedDevices []*Device `json:"denied_devices,omitempty"`
    Devices []*Device `json:"devices"`
    Memory int64 `json:"memory"`
    MemoryReservation int64 `json:"memory_reservation"`
    MemorySwap int64 `json:"memory_swap"`
    KernelMemory int64 `json:"kernel_memory"`
    KernelMemoryTCP int64 `json:"kernel_memory_tcp"`
    CpuShares uint64 `json:"cpu_shares"`
    CpuQuota int64 `json:"cpu_quota"`
    CpuPeriod uint64 `json:"cpu_period"`
    CpuRtRuntime int64 `json:"cpu_rt_quota"`
    CpuRtPeriod uint64 `json:"cpu_rt_period"`
    CpusetCpus string `json:"cpuset_cpus"`
    CpusetMems string `json:"cpuset_mems"`
    PidsLimit int64 `json:"pids_limit"`
    BlkioWeight uint16 `json:"blkio_weight"`
    BlkioLeafWeight uint16 `json:"blkio_leaf_weight"`
    BlkioWeightDevice []*WeightDevice `json:"blkio_weight_device"`
    BlkioThrottleReadBpsDevice []*ThrottleDevice `json:"blkio_throttle_read_bps_device"`
    BlkioThrottleWriteBpsDevice []*ThrottleDevice `json:"blkio_throttle_write_bps_device"`
    BlkioThrottleReadIOPSDevice []*ThrottleDevice `json:"blkio_throttle_read_iops_device"`
    BlkioThrottleWriteIOPSDevice []*ThrottleDevice `json:"blkio_throttle_write_iops_device"`
    Freezer FreezerState `json:"freezer"`
    HugetlbLimit []*HugepageLimit `json:"hugetlb_limit"`
    OomKillDisable bool `json:"oom_kill_disable"`
    MemorySwappiness *uint64 `json:"memory_swappiness"`
    NetPrioIfpriomap []*IfPrioMap `json:"net_prio_ifpriomap"`
    NetClsClassid uint32 `json:"net_cls_classid_u"`
}

Runc 代码在 Manager 接口的实现类有两个版本driver,一个实现类 fs.Manager ,一个是 systemd.Manager ,本文主要分析 cgroupfs 驱动即 fs.Manager 的实现代码。

我们先看一下 fs.Manager 的定义

libcontainer/cgroups/fs/apply_raw.go:65

type Manager struct {
    mu       sync.Mutex
    Cgroups  *configs.Cgroup           // 全局配置的 cgroup 项定义(configs.Cgroup前面有结构体说明)
    Rootless bool                      
    Paths    map[string]string         // 存放子系统名与路径
}

cgroupData cgroup 配置数据定义

libcontainer/cgroups/fs/apply_raw.go:98

type cgroupData struct {
    root      string                    // cgroup 根路径
    innerPath string                    // 指定由容器创建(和/或)连接的cgroup的路径
    config    *configs.Cgroup           // Cgroup 全局配置项
    pid       int                       // 进程id
}

manager.Apply() 将指定的 pid 应用资源的限制

libcontainer/cgroups/fs/apply_raw.go:132

func (m *Manager) Apply(pid int) (err error) {
    if m.Cgroups == nil {                       // 全局 cgroup 配置是否存在检测
        return nil
    }
  //...
    var c = m.Cgroups
    d, err := getCgroupData(m.Cgroups, pid)     // +获取与构建 cgroupData 对象
  //...
    m.Paths = make(map[string]string)
  // 如果全局配置存在 cgroup paths 配置,
    if c.Paths != nil {                        
        for name, path := range c.Paths {
            _, err := d.path(name)                 // 查找子系统的 cgroup path 是否存在
            if err != nil {
                if cgroups.IsNotFound(err) {
                    continue
                }
                return err
            }
            m.Paths[name] = path
        }
        return cgroups.EnterPid(m.Paths, pid)    // 将 pid 写入子系统的 cgroup.procs 文件
    }

  // 遍历所有 cgroup 子系统,将配置应用 cgroup 资源限制
    for _, sys := range subsystems {
        p, err := d.path(sys.Name())             // 查找子系统的 cgroup path
        if err != nil {
          //...
            return err
        }
        m.Paths[sys.Name()] = p                 
    if err := sys.Apply(d); err != nil {     // 各子系统 apply() 方法调用
    //...
    }
    return nil
}

获取与构建 cgroupData 对象

libcontainer/cgroups/fs/apply_raw.go:291

func getCgroupData(c *configs.Cgroup, pid int) (*cgroupData, error) {
    root, err := getCgroupRoot()          // +获取cgroup root根目录
    if err != nil {
        return nil, err
    }

  //...
    return &cgroupData{                  
        root:      root,
        innerPath: innerPath,                   
        config:    c,
        pid:       pid,
    }, nil
}

cgroupRoot 全局变量为空则通过查找" /proc/self/mountinfo "满足条件为" filesystem 列为 cgroup "的挂载点目录,则为 cgroup 的根目录

libcontainer/cgroups/fs/apply_raw.go:77

func getCgroupRoot() (string, error) {
    cgroupRootLock.Lock()
    defer cgroupRootLock.Unlock()

    if cgroupRoot != "" {
        return cgroupRoot, nil
    }

    root, err := cgroups.FindCgroupMountpointDir()  // 查找"/proc/self/mountinfo"挂载点目录
    if err != nil {
        return "", err
    }

    if _, err := os.Stat(root); err != nil {        //判断是否存在
        return "", err  
    }

    cgroupRoot = root
    return cgroupRoot, nil
}

manager.Set() 根据容器的全局配置 Config 的 Cgroups 资源限制项,将 configs 写入至 cgroup 子系统文件

libcontainer/cgroups/fs/apply_raw.go:282

func (m *Manager) Set(container *configs.Config) error {
  //...
    paths := m.GetPaths()
  // 遍历所有子系统,设置容器的全局配置 Config 的 Cgroups 资源限制项
    for _, sys := range subsystems {
        path := paths[sys.Name()]
        if err := sys.Set(path, container.Cgroups); err != nil {
        //...
    }
    return nil
}

manager.Freeze() 根据容器的全局 configs 配置应用 cgroup 暂停与恢复操作状态值

libcontainer/cgroups/fs/apply_raw.go:264

func (m *Manager) Freeze(state configs.FreezerState) error {
    paths := m.GetPaths()
    dir := paths["freezer"]                     // 获取子系统的 path
    prevState := m.Cgroups.Resources.Freezer
    m.Cgroups.Resources.Freezer = state
    freezer, err := subsystems.Get("freezer")   
    if err != nil {
        return err
    }
    err = freezer.Set(dir, m.Cgroups)          // 设置 state 状态值
  //...
}

其它 manager 方法:manager.GetPids() /manager.GetAllPids() / manager.GetPaths() / manager.Destroy() / manager.GetStats() 略

posted on 2020-12-03 20:10  tycoon3  阅读(269)  评论(0编辑  收藏  举报

导航