500行代码代码手写docker-将rootfs设置为只读镜像

这篇具有很好参考价值的文章主要介绍了500行代码代码手写docker-将rootfs设置为只读镜像。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

(3)500行代码代码手写docker-将rootfs设置为只读镜像

本系列教程主要是为了弄清楚容器化的原理,纸上得来终觉浅,绝知此事要躬行,理论始终不及动手实践来的深刻,所以这个系列会用go语言实现一个类似docker的容器化功能,最终能够容器化的运行一个进程。

本章的源码已经上传到github,地址如下:

https://github.com/HobbyBear/tinydocker/tree/chapter3

前文提到,如果仅仅将ubuntu-base-16.04.6-base-amd64 目录作为容器的根目录, 那么当运行多个容器,就会同时修改到ubuntu-base-16.04.6-base-amd64目录,这样将达不到不同容器使用不同的根文件系统的目的。

所以这节我将会演示如何运行内核提供到联合文件系统的功能,来达到一份镜像,多次运行的目的。

这节代码运行效果:

500行代码代码手写docker-将rootfs设置为只读镜像

可以看到我其实启动了两个容器 hello1 ,hello2 然后在hello1 下创建test目录,但是test目录在hello2容器里是不可见的。

联合文件系统原理

首先,来先简单的看看联合文件系统的概念。

🦧🦧🦧 联合文件系统可以把其他文件系统的文件和目录挂载到同一个挂载点下,形成统一的文件系统,在挂载点下形成统一的文件视图

在linux内核里,自带了一种叫做overlay类型的文件系统类型,它是一种联合文件系统,类似的还有aufs,不过本文还是用overlay 类型进行举例。

如下是一个挂载overlay 文件系统的mount命令

sudo mount -t overlay overlay -o lowerdir=image-layer1:image-layer2,upperdir=container-layer,workdir=work mnt/

其中contailber-layer 后续会作为容器的读写层,image-layer会作为镜像层,mnt作为overlay联合文件系统的挂载目录,而work后续会作为overlay联合文件系统的工作目录,这个目录是overlay自己用的,对用户不可见。挂载目录为mnt。

也就是说后续进程可以统一访问mnt目录就能看到image-layer 和contailber-layer 这两个目录的内容,但是对mnt目录进行修改的话,则只会将修改体现在contailber-layer这个目录下,image-layer这个目录永远不会变。

关于联合文件系统更详细的解释和命令演示可以参考之前我的一篇博文容器镜像原理- 联合文件系统实践

如何用go代码实现

接着,我们来看看如何对前文的代码进行改造。

已经知道了,当挂载一个overlay文件系统时,镜像层的文件是永远不会变的,所以ubuntu-base-16.04.6-base-amd64这个roofs目录毫无疑问将会作为镜像层进行参数传递,而我们还需要为容器创建其自身的可写层和工作层目录。因为可以运行多个容器,如何区分这些容器各自的可写层呢?最简单的方法就是拥有一个容器名,通过容器名创建属于他们自己的目录。

所以,现在运行命令的方式变了,之前我们是这样运行一个容器:

./tinydocker run /bin/sh

现在将变成这样

./tinydocker run 容器名 /bin/sh

先统一浏览下目前main方法中的代码

func main() {

	switch os.Args[1] {
	case "run":
		initCmd, err := os.Readlink("/proc/self/exe")
		if err != nil {
			fmt.Println("get init process error ", err)
			return
		}
		// 获取容器名
		containerName := os.Args[2]
		os.Args[1] = "init"
		cmd := exec.Command(initCmd, os.Args[1:]...)
		cmd.SysProcAttr = &syscall.SysProcAttr{
			Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS |
				syscall.CLONE_NEWNET | syscall.CLONE_NEWIPC,
		}
		cmd.Env = os.Environ()
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		err = cmd.Run()
		if err != nil {
			fmt.Println(err)
		}
		// 容器结束后要清理掉它的挂载点和目录
		workspace.DelMntNamespace(containerName)
		return
	case "init":
		var (
			containerName = os.Args[2]
			cmd           = os.Args[3]
		)
		// 创建挂载点和更换rootfs
		if err := workspace.SetMntNamespace(containerName); err != nil {
			fmt.Println(err)
			return
		}
		syscall.Chdir("/")
		defaultMountFlags := syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
		syscall.Mount("proc", "/proc", "proc", uintptr(defaultMountFlags), "")
		err := syscall.Exec(cmd, os.Args[3:], os.Environ())
		if err != nil {
			fmt.Println("exec proc fail ", err)
			return
		}
		fmt.Println("forever exec it ")
		return
	default:
		fmt.Println("not valid cmd")
	}
}

可以看到,在以新命名空间启动一个子进程后,在workspace.SetMntNamespace 里将会进行相关目录的挂载,然后在执行cmd.Run 的父进程中,等待子进程结束后,调用了workspace.DelMntNamespace清理了子进程的挂载点和相关目录。

而workspace.SetMntNamespace 的源码如下:

func SetMntNamespace(containerName string) error {
	if err := os.MkdirAll(mntLayer(containerName), 0700); err != nil {
		return fmt.Errorf("mkdir mntlayer fail err=%s", err)
	}
	if err := os.MkdirAll(workerLayer(containerName), 0700); err != nil {
		return fmt.Errorf("mkdir work layer fail err=%s", err)
	}
	if err := os.MkdirAll(writeLayer(containerName), 0700); err != nil {
		return fmt.Errorf("mkdir write layer fail err=%s", err)
	}

	if err := syscall.Mount("overlay", mntLayer(containerName), "overlay", 0,
		fmt.Sprintf("upperdir=%s,lowerdir=%s,workdir=%s",
			writeLayer(containerName), imagePath, workerLayer(containerName))); err != nil {
		return fmt.Errorf("mount overlay fail err=%s", err)
	}

	if err := syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, ""); err != nil {
		return fmt.Errorf("reclare rootfs private fail err=%s", err)
	}

	if err := syscall.Mount(mntLayer(containerName), mntLayer(containerName), "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil {
		return fmt.Errorf("mount rootfs in new mnt space fail err=%s", err)
	}
	if err := os.MkdirAll(mntOldLayer(containerName), 0700); err != nil {
		return fmt.Errorf("mkdir mnt old layer fail err=%s", err)
	}
	if err := syscall.PivotRoot(mntLayer(containerName), mntOldLayer(containerName)); err != nil {
		return fmt.Errorf("pivot root  fail err=%s", err)
	}
	return nil
}

workspace.SetMntNamespace 先是根据容器名创建了执行overlay挂载所需要的目录,然后通过mount命令讲一个overlay类型的文件系统挂载到mntLayer(containerName)的路径下,然后mntLayer(containerName)路径下文件将作为容器的根文件系统,后续会对其进行pivot root调用,然后mntLayer(containerName)的目录将会成为新的mnt namespace的根目录了。

这样,不同容器名的容器将会有自己独立的根目录。即避免了镜像层文件的改变,又达到了各容器文件系统隔离的目的。文章来源地址https://www.toymoban.com/news/detail-457173.html

到了这里,关于500行代码代码手写docker-将rootfs设置为只读镜像的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Docker 镜像读写层核心概念:rootfs、Union mount、image以及layser原理详解

    Rootfs: 代表一个 Docker Container 在启动时(而非运行后)其内部进程可见的文件系统视角,或者是 Docker Container 的根目录。当然,该目录下含有 Docker Container 所需要的系统文件、工具、容器文件等。 上面这张图展示了Linux系统中的两个重要的文件系统。 bootfs 是指 Linux 系统启动

    2024年02月06日
    浏览(38)
  • 【Docker】Docker 设置国内镜像源_docker国内镜像库

    点击跳转:Docker安装MySQL、Redis、RabbitMQ、Elasticsearch、Nacos等常见服务全套(质量有保证,内容详情) 在国内,从官方的Docker Hub仓库拉取镜像常常会遇到网络很慢甚至不能下载的情况,体验很不好,此时需要配置国内的镜像来加速下载。很多云服务商都提供了Docker镜像加速服

    2024年02月13日
    浏览(49)
  • 【Docker】Docker 设置国内镜像源

    点击跳转:Docker安装MySQL、Redis、RabbitMQ、Elasticsearch、Nacos等常见服务全套(质量有保证,内容详情) 在国内,从官方的Docker Hub仓库拉取镜像常常会遇到网络很慢甚至不能下载的情况,体验很不好,此时需要配置国内的镜像来加速下载。很多云服务商都提供了Docker镜像加速服

    2024年02月16日
    浏览(51)
  • python设置文件只读与取消文件只读

    日期:2023年3月3日 作者:Commas 签名:(ง •_•)ง 积跬步以致千里,积小流以成江海…… 注释:如果您觉得 有所帮助 ,帮忙 点个赞 ,也可以 关注我 ,我们一起成长;如果有不对的地方,还望各位大佬不吝赐教,谢谢^ - ^ 1.01 365 = 37.7834;0.99 365 = 0.0255 1.02 365 = 1377.4083;0.98

    2024年02月03日
    浏览(38)
  • Docker设置国内镜像源

    docker默认的源为国外官方源,下载速度较慢,可改为国内源 修改或新增 docker 默认 的配置文件 vim /etc/docker/daemon.json 修改或新增 vim /etc/sysconfig/docker 阿里云docker镜像加速,提升pull的速度 ​ ​Docker国内源说明: Docker 官方中国区:https://registry.docker-cn.com 网易:http://hub-mirror.

    2024年02月03日
    浏览(53)
  • Docker 设置国内镜像源

    点击跳转:Docker安装MySQL、Redis、RabbitMQ、Elasticsearch、Nacos等常见服务全套(质量有保证,内容详情) 在国内,从官方的Docker Hub仓库拉取镜像常常会遇到网络很慢甚至不能下载的情况,体验很不好,此时需要配置国内的镜像来加速下载。很多云服务商都提供了Docker镜像加速服

    2024年02月11日
    浏览(43)
  • docker容器设置镜像加速实战

    阅读本文需要已经安装docker,docker的安装参考下文: centos7系统安装最新版本的docker实战 完成docker安装centos7操作系统实战 国内从 Docker Hub 拉取镜像有时会遇到困难,从 Docker 官方仓库拉取镜像的速度都是很慢,而且经常会出现超时的情况。要想下载不超时且速度快,就需要配

    2023年04月23日
    浏览(48)
  • Docker镜像下载加速及设置代理

    在使用docker pull拉镜像默认从docker hub里拉取,从国内访问这个网站速度可能会很慢。可以通过配置加速器解决。 登录阿里云控制台-搜索容器镜像服务-镜像工具-镜像加速器,即可看到加速器地址 通过修改daemon配置文件/etc/docker/daemon.json来使用加速器 登录华为云-搜索容器镜像

    2024年02月11日
    浏览(46)
  • Docker Desktop 设置镜像环境变量

    点击run 展开Optional settings container name :容器名称 Ports:根据你需要的端口进行输入,不输入则默认 后面这个 比如我这个 5432 Volumes:卷,也就是做持久化 需要docker 数据保存的地方 Environment variables:环境变量,也就是启动时需要输入的参数,比如我这个 PGSql需要设置密码,就

    2024年02月07日
    浏览(46)
  • docker pull 拉取失败,设置docker国内镜像

    最近在拉取nginx时,显示如下错误: Error response from daemon: Get “https://registry-1.docker.io/v2/”: net/http: request canceled (Client.Timeout exceeded while awaiting headers) 。 这个的问题是 拉取镜像超时 ,通过检索发现可以通过 配置国内镜像的方式解决 ,镜像地址很多,这里只说阿里云的镜像地

    2024年04月17日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包