自己动手写docker 2构建容器

构建容器

linux proc 文件系统

linux下的/proc文件系统是由内核提供的,它其实不是一个真正的文件系统,只是包含了系统运行时的信息(比如系统内寻、mount设备信息、一些硬件配置等),它只存在于内存中,而不占用外存空间。它以文件系统的方式,为访问内核数据的操作提供借口。实际上,很多系统工具都是简单地去读取这个文件系统的某个文件内容, 比如lsmod = cat /proc./modules
当遍历这个目录的时候,会发现很多数字,这些都是为每个进程创建的空间,数字就是它们的 PID 。

下面介绍几个比较重要的的部分

PATH作用
/proc/NPID为N的进程
/proc/N/cmdline进程启动命令
/proc/N/cwd链接带进程当前工作目录
/proc/N/environ进程环境变量列表
/proc/N/exe链接到进程的执行命令文件
/proc/N/fd包含进程相关的所有文件描述符
/proc/N/maps与进程相关的内存映射信息
/proc/N/mem指代进程持有的内存,不可读
/proc/N/root链接到进程的根目录
/proc/N/stat进程的状态
/proc/N/statm进程使用的内存状态
/proc/N/status进程状态信息, 比stat/statm 更具可读性
/proc/self链接到当前正在运行的进程
const usage = `docker-ece is a simple container runtiome implementation.
The purpose of this project is to learn how docker works and how to
write a docker by ourselves. Enjoy it just for fun`

func main() {
	app := &cli.App{
		Name:  "docker-ece",
		Usage: usage,
	}

	app.Commands = []*cli.Command{
		&initCommand,
		&runCommand,
	}

	app.Before = func(context *cli.Context) error {
		log.SetFormatter(&log.JSONFormatter{})
		log.SetOutput(os.Stdout)
		return nil
	}

	if err := app.Run(os.Args); err != nil {
		log.Fatal(err)
	}
}

var runCommand = cli.Command{
	Name: "run",
	Usage: `Create  a container with namespace and cgroups limit
          mydocker run -ti [command ]`,
	Flags: []cli.Flag{
		&cli.BoolFlag{
			Name:  "ti",
			Usage: "enable tty",
		},
		&cli.StringFlag{
			Name:  "mem",
			Usage: "memory limit",
		},
		&cli.StringFlag{
			Name:  "cpumax",
			Usage: "cpu limit",
		},
		//&cli.StringFlag{
		//	Name:  "cpuset",
		//	Usage: "cpuset limit",
		//},
	},
	Action: func(ctx *cli.Context) error {
		if ctx.NArg() < 1 {
			return errors.New("Miss container command")
		}

		var cmdArr []string

		for _, arg := range ctx.Args().Slice() {
			cmdArr = append(cmdArr, arg)
		}

		tty := ctx.Bool("ti")
		resConf := &subsystems.ResourceConfig{
			MemoryMax: ctx.String("mem"),
			CpuMax:    ctx.String("cpumax"),
			//CpuSet:    ctx.String("cpuset"),
		}
		// Run 准备启动容器
		Run(tty, cmdArr, resConf)
		return nil
	},
}

var initCommand = cli.Command{
	Name: "init",
	Usage: `Init container process run user's process in container.
          Do not call it outside!`,
	Action: func(ctx *cli.Context) error {
		log.Infof("init comm on ")
		err := container.RunContainerInitProcess()
		return err
	},
}

func Run(tty bool, comArray []string, res *subsystems.ResourceConfig) {
	parent, writePipe := container.NewParentProcess(tty)
	if parent == nil {
		log.Errorf("New parent process error")
		return
	}

	if err := parent.Start(); err != nil {
		log.Error(err)
	}

	// use ece-cgroup as cgroup name
	cgroupManager := cgroups.NewCgroupManager("ece-cgroup")
	defer cgroupManager.Destory()
	cgroupManager.Apply(parent.Process.Pid)
	cgroupManager.Set(res)

	sendInitCommand(comArray, writePipe)
	parent.Wait()
	os.Exit(0)
}

func sendInitCommand(comArray []string, writePipe *os.File) {
	command := strings.Join(comArray, " ")
	log.Infof("command all is %s", command)
	writePipe.WriteString(command)
	writePipe.Close()
}

func NewParentProcess(tty bool) (*exec.Cmd, *os.File) {
	readPipe, writePipe, err := NewPipe()
	if err != nil {
		log.Errorf("New pipe err %s", err)
	}

	cmd := exec.Command("/proc/self/exe", "init")
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWUTS |
			syscall.CLONE_NEWPID |
			syscall.CLONE_NEWNS |
			syscall.CLONE_NEWNET |
			syscall.CLONE_NEWIPC}

	if tty {
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
	}

	cmd.ExtraFiles = []*os.File{readPipe}
	return cmd, writePipe
}

func NewPipe() (*os.File, *os.File, error) {
	read, write, err := os.Pipe()
	if err != nil {
		return nil, nil, err
	}
	return read, write, nil
}

func RunContainerInitProcess() error {
	comArray := readUserCommand()
	if comArray == nil || len(comArray) == 0 {
		return errors.New("Run container get user command error, cmdArray is nil")
	}

	defaultMountFlags := syscall.MS_NOEXEC | // 在本文件系统中不允许运行其他程序
		syscall.MS_NOSUID | // 在本系统中运行程序中,不允许set-user-ID set-grou-ID
		syscall.MS_NODEV
	syscall.Mount("proc", "/proc", "proc", uintptr(defaultMountFlags), "")
	path, err := exec.LookPath(comArray[0])
	if err != nil {
		log.Errorf("exec loop path error %v", err)
		return err
	}

	log.Infof("Find path %s", path)
	if err := syscall.Exec(path, comArray[0:], os.Environ()); err != nil {
		log.Errorf(err.Error())
	}
	return nil

}

func readUserCommand() []string {
	pipe := os.NewFile(uintptr(3), "pipe")
	msg, err := ioutil.ReadAll(pipe)
	if err != nil {
		log.Errorf("init read pipe error %v", err)
		return nil
	}
	msgStr := string(msg)
	return strings.Split(msgStr, " ")

}

type CgroupManager struct {
	Path     string
	Resource *subsystems.ResourceConfig
}

func NewCgroupManager(path string) *CgroupManager {
	return &CgroupManager{Path: path}
}

// 将新的进程加入当前的cgroup中
func (c *CgroupManager) Apply(pid int) error {
	for _, subSysIns := range subsystems.SubsystemsIns {
		subSysIns.Apply(c.Path, pid)
	}
	return nil

}

// 设置cgroup 资源限制
func (c *CgroupManager) Set(res *subsystems.ResourceConfig) error {
	for _, subSysIns := range subsystems.SubsystemsIns {
		subSysIns.Set(c.Path, res)
	}
	return nil

}

// 释放cgroup
func (c *CgroupManager) Destory() error {
	for _, subSysIns := range subsystems.SubsystemsIns {
		if err := subSysIns.Remove(c.Path); err != nil {
			log.Warn("remove cgroup fail %v", err)
		}
	}
	return nil

}

type ResourceConfig struct {
	MemoryMax string
	CpuMax    string
	//CpuSet    string
}

type Subsystem interface {
	Name() string
	Set(path string, res *ResourceConfig) error
	Apply(path string, pid int) error
	Remove(path string) error
}

var (
	SubsystemsIns = []Subsystem{
		&MemorySubSystem{},
		&CpuSubSystem{},
		//&CpuSetSubSystem{},
	}
)

type CpuSubSystem struct {
}

func (s *CpuSubSystem) Name() string {
	return "cpu"
}

func (s *CpuSubSystem) Set(cgroupPath string, res *ResourceConfig) error {
	if subsysCgroupPath, err := GetCgroupPath(s.Name(), cgroupPath, true); err == nil {
		if res.CpuMax != "" {
			cpuMax, _ := strconv.Atoi(res.CpuMax)
			res.CpuMax = fmt.Sprintf("%d %d", cpuMax, 10000)
			if err := ioutil.WriteFile(
				path.Join(subsysCgroupPath, "cpu.max"),
				[]byte(res.CpuMax), 0644); err != nil {

				return fmt.Errorf("set cgroup memory fail %v", err)
			}
		}
		return nil
	} else {
		return err
	}
}

func (s *CpuSubSystem) Apply(cgroupPath string, pid int) error {
	if subsysCgroupPath, err := GetCgroupPath(s.Name(), cgroupPath, false); err == nil {
		if err := ioutil.WriteFile(
			path.Join(subsysCgroupPath, "cgroup.procs"),
			[]byte(strconv.Itoa(pid)), 0644); err != nil {

			return fmt.Errorf("set cgroup proc fail %v", err)
		}
		return nil
	} else {
		return fmt.Errorf("get cgroup %s error: %v", cgroupPath, err)
	}
}

func (s *CpuSubSystem) Remove(cgroupPath string) error {
	if subsysCgroupPath, err := GetCgroupPath(s.Name(), cgroupPath, false); err == nil {
		return os.RemoveAll(subsysCgroupPath)
	} else {
		return err
	}
}

type MemorySubSystem struct {
}

func (s *MemorySubSystem) Name() string {
	return "memory"
}

func (s *MemorySubSystem) Set(cgroupPath string, res *ResourceConfig) error {
	if subsysCgroupPath, err := GetCgroupPath(s.Name(), cgroupPath, true); err == nil {
		if res.MemoryMax != "" {
			if err := ioutil.WriteFile(
				path.Join(subsysCgroupPath, "memory.max"),
				[]byte(res.MemoryMax), 0644); err != nil {

				return fmt.Errorf("set cgroup memory fail %v", err)
			}
		}
		return nil
	} else {
		return err
	}
}

func (s *MemorySubSystem) Apply(cgroupPath string, pid int) error {
	if subsysCgroupPath, err := GetCgroupPath(s.Name(), cgroupPath, false); err == nil {
		log.Println(cgroupPath, subsysCgroupPath, pid)
		if err := ioutil.WriteFile(
			path.Join(subsysCgroupPath, "cgroup.procs"),
			[]byte(strconv.Itoa(pid)), 0644); err != nil {

			return fmt.Errorf("set cgroup proc fail %v", err)
		}
		return nil
	} else {
		return fmt.Errorf("get cgroup %s error: %v", cgroupPath, err)
	}
}

func (s *MemorySubSystem) Remove(cgroupPath string) error {
	if subsysCgroupPath, err := GetCgroupPath(s.Name(), cgroupPath, false); err == nil {
		return os.RemoveAll(subsysCgroupPath)
	} else {
		return err
	}
}

本章构造了一个简单的容器,具有基本的 amespace 隔离,并且确定了基本的开发架构,基本创建流程如图
在这里插入图片描述

对于 Cgroups 通过这一章在容器上增加可配置的选项,可以实现对于容器可用资源的控制,最后,使用管道机制将用户输入的命令传递给容器初始化进程,实现了数据的传递

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《自己动手Docker》是一本由架构师嵩天和Docker社区联合编的开源技术书籍。书中介绍了Docker的基本概念、背景知识和原理,并且通过实践的方式帮助读者了解Docker的具体应用。以下是对这本书的回答: 《自己动手Docker》这本书是作者嵩天和Docker社区共同编的一本开源技术书籍。通过这本书,读者可以了解到Docker的基本概念、背景知识和工作原理。 首先,书中介绍了Docker的概念和由来。Docker是一个开源的容器化技术,通过容器化的方式,能够将应用程序和其依赖的资源打包在一起,以便于在任何地方都能够运行。Docker的诞生解决了传统虚拟化技术的运行效率问题,并且能够提供更好的资源利用率和应用程序的可移植性。 其次,书中详细介绍了Docker的工作原理和组成部分。Docker的核心是Docker引擎,它负责创建、运行和管理容器Docker镜像是Docker的核心概念之一,它是一个只读的模板,包含了运行应用程序所需的全部依赖项。通过Docker镜像,我们可以快速创建和部署应用程序。此外,书中还介绍了容器和镜像的关系、Docker的网络和存储管理方式等重要内容。 最后,书中通过实际操作引导读者理解和使用Docker。通过一些具体的例子和实践,读者可以学习如何构建自己的Docker镜像、创建和运行容器、使用Docker网络和存储等技术。通过这些实践,读者不仅能够清晰地了解Docker的工作机制,还能够掌握一些实际应用场景下的技巧和经验。 总而言之,《自己动手Docker》这本书通过理论和实践相结合的方式,以简洁明了的语言和实例详细解释了Docker的基本概念、工作原理和实际应用。是一本适合开发者、运维人员和对Docker感兴趣的读者了解和学习Docker的好书籍。 ### 回答2: 《自己动手docker》是一本以Docker为重点的技术书籍,通过学习本书可以了解Docker的原理和使用方法,掌握构建、部署和管理容器化应用的技巧。 这本书首先介绍了Docker的背景和发展,解释了为什么要使用Docker以及它的优势。接着,书中详细讲解了Docker的核心概念,包括镜像、容器、仓库等,帮助读者理解Docker的基本原理和组成部分。 随后,本书介绍了如何在本地环境搭建Docker,并通过实例演示了如何构建自定义镜像、运行容器以及管理容器的生命周期。这些实例涵盖了常见的应用场景,如部署Web应用、数据库容器化等。 此外,本书还介绍了Docker Swarm和Kubernetes等容器编排工具,让读者能够了解如何使用这些工具高效地管理和扩展容器化应用。 总体来说,《自己动手docker》是一本很实用的技术书籍,适合那些想要系统学习和掌握Docker技术的开发者和运维人员。无论是初学者还是有一定经验的人,都可以从这本书中获得价值。通过亲自动手实践各种实例,读者能够深入理解Docker的原理和使用方法,提升自己的技术水平。 ### 回答3: 《自己动手docker》是一本讲述如何编Docker的书籍。Docker是一种流行的容器化技术,可以将应用程序及其依赖项打包到一个可移植的容器中,从而实现应用程序在不同环境中的快速部署和扩展。 这本书主要讲解了Docker的核心原理和技术实现。首先,它介绍了Linux容器的基本概念和原理,包括命名空间、控制组、镜像和容器等。然后,它详细解释了Docker的架构和基本组件,例如Docker引擎、镜像仓库和网络管理等。 接下来,这本书逐步引导读者编自己的Docker。它从编一个简单的容器隔离工具开始,逐渐引入更多功能,例如容器的生命周期管理、网络隔离和卷管理等。这样,读者能够逐步了解容器化技术的实现细节。 此外,这本书还介绍了一些与Docker相关的技术和工具,例如容器编排工具Docker Compose和容器编排平台Kubernetes。这些技术可以帮助读者更好地管理和扩展自己的Docker应用程序。 总而言之,《自己动手docker》是一本编Docker容器的实践指南。通过学习这本书,读者可以深入了解Docker的原理和实现,并掌握使用Docker构建和管理容器的技能。无论是对于开发人员还是系统管理员来说,这本书都是一本不可多得的学习资源。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值