zoukankan      html  css  js  c++  java
  • 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() 略

  • 相关阅读:
    font-svg
    转 让NET C# 程序独立运行(脱离 .NET Framework运行,绿色运行) 未验证
    自定义纸张 未验证
    CSS 各种形状
    ZIP压缩与解压
    FindWindow SendMessage
    js 简单的滑动4
    js 简单的滑动3
    js 简单的滑动2
    Linux 下的php,nginx,mysql的安装
  • 原文地址:https://www.cnblogs.com/dream397/p/14082488.html
Copyright © 2011-2022 走看看