(runc)容器是怎么创建的
来源:互联网 发布:写私密日记的软件 编辑:程序博客网 时间:2024/05/20 07:35
大家都知道容器是通过namespace和cgroup技术来创建的,但是具体代码是如何控制namespace和cgroup的呢?下面就以runc源码为例子简单介绍下。
runc的前身实际上是Docker的libcontainer项目演化而来。runC实际上就是libcontainer配上了一个轻型的客户端。
从本质上来说,容器是提供一个与宿主机系统共享内核但与系统中的其它进程资源相隔离的执行环境。Docker通过调用libcontainer包对namespaces、cgroups、capabilities以及文件系统的管理和分配来“隔离”出一个上述执行环境。同样的,runC也是对libcontainer包进行调用,去除了Docker包含的诸如镜像、Volume等高级特性,以最朴素简洁的方式达到符合OCF标准的容器管理实现。
runc主要的核心代码libcontainer中,调用namespace和cgroup的函数在libcontainer/process_linux.go:func (p *initProcess) start()中
func (p *initProcess) start() error {defer p.parentPipe.Close()err := p.cmd.Start() //开始执行初始化进程命令p.process.ops = pp.childPipe.Close()if err != nil {p.process.ops = nilreturn newSystemErrorWithCause(err, "starting init process command")}if err := p.manager.Apply(p.pid()); err != nil {return newSystemErrorWithCause(err, "applying cgroup configuration for process")}//通过进程号对进程使用cgroupif 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 herep.manager.Destroy()if p.intelRdtManager != nil {p.intelRdtManager.Destroy()}}}()if _, err := io.Copy(p.parentPipe, p.bootstrapData); err != nil {return newSystemErrorWithCause(err, "copying bootstrap data to pipe")} //前面创建bootstrapData(命名空间映射参数)从parentPipe传出去(init进程会从childPipe接收到这些数据, reverse出写入的内容,在C语言实现的nsexec.c 中控制clone函数的参数,最终实现namespace相关的配置)if err := p.execSetns(); err != nil {return newSystemErrorWithCause(err, "running exec setns process for init")}//对进程使用namesapce结束。fds, err := getPipeFds(p.pid())if err != nil {return newSystemErrorWithCausef(err, "getting pipe fds for pid %d", p.pid())}p.setExternalDescriptors(fds)if err := p.createNetworkInterfaces(); err != nil {return newSystemErrorWithCause(err, "creating network interfaces")} //进行网络相关配置,主要遍历网络配置,对各种网络根据相应的策略添加veth对,最终都是调用linux ip link 命令实现if err := p.sendConfig(); err != nil {return newSystemErrorWithCause(err, "sending config to init process")}var (sentRun boolsentResume bool)......}
其中关于croup控制的apply函数调用
libcontainer/cgroups/fs/apply_raw.go:func (m *Manager) Apply(pid int)
func (m *Manager) Apply(pid int) (err error) {if m.Cgroups == nil {return nil}m.mu.Lock()defer m.mu.Unlock()var c = m.Cgroupsd, err := getCgroupData(m.Cgroups, pid)if err != nil {return err}m.Paths = make(map[string]string)if c.Paths != nil {for name, path := range c.Paths {_, err := d.path(name)if err != nil {if cgroups.IsNotFound(err) {continue}return err}m.Paths[name] = path}return cgroups.EnterPid(m.Paths, pid)}for _, sys := range subsystems {// 对每个subsystems 进行applyp, err := d.path(sys.Name())if err != nil {// The non-presence of the devices subsystem is// considered fatal for security reasons.if cgroups.IsNotFound(err) && sys.Name() != "devices" {continue}return err}m.Paths[sys.Name()] = pif err := sys.Apply(d); err != nil {if os.IsPermission(err) && m.Cgroups.Path == "" {delete(m.Paths, sys.Name())continue}return err}}return nil}
以cpu为例
func (s *CpuGroup) Apply(d *cgroupData) error {// We always want to join the cpu group, to allow fair cpu scheduling// on a container basispath, err := d.path("cpu")if err != nil && !cgroups.IsNotFound(err) {return err}return s.ApplyDir(path, d.config, d.pid)}func (s *CpuGroup) ApplyDir(path string, cgroup *configs.Cgroup, pid int) error {if path == "" {return nil}if err := os.MkdirAll(path, 0755); err != nil {return err}if err := s.SetRtSched(path, cgroup); err != nil {return err}if err := cgroups.WriteCgroupProc(path, pid); err != nil {return err}return nil}func WriteCgroupProc(dir string, pid int) error {// Normally dir should not be empty, one case is that cgroup subsystem// is not mounted, we will get empty dir, and we want it fail here.if dir == "" {return fmt.Errorf("no such directory for %s", CgroupProcesses)}// Dont attach any pid to the cgroup if -1 is specified as a pidif pid != -1 {if err := ioutil.WriteFile(filepath.Join(dir, CgroupProcesses), []byte(strconv.Itoa(pid)), 0700); err != nil {return fmt.Errorf("failed to write %v to %v: %v", pid, CgroupProcesses, err)}}return nil}也就是将进程号写入到cgroup中cpu的文件夹下,在容器内新建子进程时,根据cgroup规则,子进程也就是子节点是父节点进程组的子集,并继承父节点属性,也就是说容器内的子进程会也受该cgroup约束
回到namespace
func (p *initProcess) execSetns() error {status, err := p.cmd.Process.Wait()if err != nil {p.cmd.Wait()return err}if !status.Success() {p.cmd.Wait()return &exec.ExitError{ProcessState: status}}var pid *pidif err := json.NewDecoder(p.parentPipe).Decode(&pid); err != nil {p.cmd.Wait()return err}// Clean up the zombie parent processfirstChildProcess, err := os.FindProcess(pid.PidFirstChild)if err != nil {return err}// Ignore the error in case the child has already been reaped for any reason_, _ = firstChildProcess.Wait()process, err := os.FindProcess(pid.Pid)if err != nil {return err}p.cmd.Process = process // 将cmd.Process对应的进程替换为该进程。p.process.ops = preturn nil}
阅读全文
0 0
- (runc)容器是怎么创建的
- Runc容器生命周期
- ATL是怎么创建类厂的
- Activity具体是怎么创建的?又是怎么显示出来的?
- Android 一个窗口是怎么创建出来的?
- Navicat Premium图标编辑器是怎么创建表的
- docker -- 标准化容器执行引擎——runC
- tomcat是怎么工作的学习——一个简单的Servlet容器
- 一个小Demo,帮助你理解ioc容器是怎么进行依赖注入的
- 先码后看 Tomcat是怎么启动容器的——Digester篇 侵立删
- 先码后看 Tomcat是怎么启动容器的——Lifecycle篇 侵立删
- 先码后看 Tomcat是怎么启动容器的——Context篇 侵立删
- 先码后看 Tomcat是怎么启动容器的——web.xml篇 侵立删
- 先码后看 Tomcat是怎么启动容器的——web.xml应用 侵立删
- 怎么是空白的
- 最新版的SDK创建工程默认的是actionbaractivity怎么变为activity
- warden创建容器的过程
- 创建支持ssh的容器
- java集合类
- PTA——List Leaves
- NoSQL Options for Java Developers
- python 字符编码
- JavaScript 对象(三)
- (runc)容器是怎么创建的
- Java多线程编程-(19)-多线程异步调用之Future模式
- 计算机学院大学生程序设计竞赛(2017新生赛) 1004 正品的概率
- 解决spark-md5.js和java计算文件md5值不一致问题
- android屏幕状态检测, 动态广播
- String储存数据库字段限长Utils
- Swift 【DESIGNATED,CONVENIENCE 和 REQUIRED】
- Kafka 安装&常用操作命令
- ICMP TYPE-CODE查阅表