博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
浅入浅出容器文件系统
阅读量:6473 次
发布时间:2019-06-23

本文共 3050 字,大约阅读时间需要 10 分钟。

「层」这个概念在软件领域非常常见。通过「分层」,可以把不同领域的问题隔离开,单独解决。架构上有MVC,即模型、视图、控制器;运行环境上,可以分为CPU/操作系统/运行时;编写代码时,可以分为方法/类/包等等。 容器技术本质上也是创造操作系统之上的「层」,它隔离开其内部的程序和外部的宿主,保证双方最大限度的解耦。

内核空间和用户空间

操作系统中,以Linux为例,宏观上存在两层——内核层和用户层。内核层主要负责一些系统级操作,内存管理、IO操作、进程管理、文件系统等等。用户层区别于内核层,只能通过叫做「系统调用」的方式与内核层交互。

在Linux的无数发行版本中(Ubuntu/CentOS/Fedora/…),内核层都来自于一套内核代码。但是在用户层,每个发行版都打包了各自一套软件库。尽管每个发行版可以自定义,编译出不同的内核,但和内核通讯的「系统调用」在不同发行版中却相差无几。这保证了Linux上的程序可以兼容不同的发行版。(同一容器也因此兼容不同的宿主)。

一个程序运行在一个Linux操作系统中时,一部分会依赖于系统调用,完成一些内核级别的操作;另一部分会依赖用户层的软件库,但这些依赖库,最终又追溯到不同的系统调用。所以在一个程序运行时,「程序/依赖库/内核」三层又可以归为「程序/内核」两层。

从文件系统的维度来看,程序本身就是文件。要么是二进制文件,要么是可解释代码文件。在创建容器时,内核是共享的,所以只需要把程序文件拷贝到容器里,然后在容器里运行程序。

Docker用镜像(image)来管理文件,一个镜像中包含了应用程序和其依赖的程序库。容器启动后,会自动「拷贝」镜像中的文件到容器「本地」,然后运行。

UnionFS和Docker镜像

Linux中的文件系统有些功能设计的很巧妙,比如挂载(mount),允许把一个外部的文件系统(如CD,USB)以本地路径的形式去访问。例如放置一张CD后,我们可以通过访问/mnt/cdrom来查看CD中的内容。

Union File System(UnionFS)则设计的更加巧妙。在「挂载」功能的基础上,UnionFS允许在本地路径挂载多个目标目录。例如,我有两个文件夹a, b,结构如下:

/tmp--1.txt/var--2.txt/opt--2.txt复制代码

UnionFS允许把它们一起挂载到同一个本地路径/mnt/unions,其最终结构变为:

/aufs--1.txt--2.txt--3.txt复制代码

在挂载时,我可以选择新文件和新增的修改写入到哪一个目标目录。如果把修改写入不同于源的目标路径,UnionFS支持增量存储,高效利用存储空间。

UnionFS技术在Docker容器技术中的运用,首先体现在「镜像(image)」和「容器(container)」上。每一个Docker镜像都是一个只读的文件夹,当在容器中运行镜像时,Docker会自动挂载镜像中的、只读的文件目录,以及宿主机上一个临时的、可写的文件目录。容器中所有文件修改,都会写入这个临时目录里去。容器终结后,这个临时目录也会被相应删除。

Docker中的「层」

UnionFS在Docker中的另一个应用,还体现在镜像本身上。

容器运行时,在挂载的临时目录中如果写入数据,还可以选择把这部分数据从临时目录中保存下来,这样就生成了一个新的镜像。Docker在保存新镜像时,会把它们两部分——原镜像和增量——都保存在新镜像中。其中新的增量部分,就被称为「层(layer)」。

事实上,我们的原始镜像,也可能是由另一个镜像和另一个「层」组成。如此追溯下去,会发现最开始的镜像,是「空镜像」。所以,Docker镜像的结构,本质上就是一层层数据增量的堆叠。

Dockerfile

同一镜像,有时代码或配置改变,可能需要重新构建。为了提高效率,我们要设法让这些可重复执行的代码尽量自动化。在虚拟机时代,因为依赖较多,我们不得不借助于诸如Ansible、Chef之类的自动化部署工具。这样的方式在容器中虽然也可行,不过Docker提供了一种更加巧妙的解决方案——Dockerfile。

编写Dockerfile时,有一套简单的语法,包括一些基本的命令。例如FROM选择基础镜像,COPY拷贝文件,RUN <cmd>执行命令等。每一行语句,可以表示镜像中的一层。执行一行语句时,Docker会基于上一行生成的镜像,运行一个容器,然后在容器中执行这行语句代表的命令,随后把执行这行语句所产生的「层」保存下来,生成新的镜像。

这是一个简单Node.Js程序的Dockerfile文件:

FROM alpineRUN apk update && apk upgrade \  && apk add --update --no-cache nodejs npmRUN rm -rf /var/cache/apk/*WORKDIR /appCOPY src/package.json /appRUN npm iCOPY src /appEXPOSE 80ENTRYPOINT ["/bin/sh", "-c", "node index.js"]复制代码

在执行镜像构建时,你会看到类似这样的输出:

...Step 3/9 : RUN rm -rf /var/cache/apk/* ---> Running in 8b4c21e41f5dRemoving intermediate container 8b4c21e41f5d ---> df5172370dc1Step 4/9 : WORKDIR /app ---> Running in 064c9126723fRemoving intermediate container 064c9126723f ---> bad0bb1f5137Step 5/9 : COPY app/package.json /app ...复制代码

可以看出,根据文件行数(10),Docker在构建时分为了9步(第一行是base image)。每一步都会运行一个容器、生成一个镜像。

在通过Dockerfile构建镜像的过程中,产生的这些临时镜像并不会被扔掉。Docker会在下次执行构建时,会自动根据一些规则去判断这行语句是否会和上次构建结果不同,如果相同则直接使用上次的缓存,而不是再次构建。基于这项技术,Docker镜像在构建时可以非常高效。

例如,上述同样的构建我重复执行时,输出会变成这样:

...Step 3/9 : RUN rm -rf /var/cache/apk/* ---> Using cache ---> df5172370dc1Step 4/9 : WORKDIR /app ---> Using cache ---> bad0bb1f5137Step 5/9 : COPY app/package.json /app ---> Using cache ---> 46aa3717f438...复制代码

总结

本文通过介绍介绍「内核空间」和「用户空间」,从文件系统的角度再次理解容器本质。接着从UnionFS技术,引出Docker在容器实践中的实现原理。最后,简单介绍了Dockerfile和它的缓存构建机制。

转载于:https://juejin.im/post/5b87c0cbe51d451a447a831a

你可能感兴趣的文章
centos查找未挂载磁盘格式化并挂载
查看>>
IT人员的职业生涯规划
查看>>
sorry,you must have a tty to run sudo
查看>>
ios开发中使用正则表达式识别处理字符串中的URL
查看>>
项目中的积累,及常见小问题
查看>>
Python类型转换、数值操作(收藏)
查看>>
注释书写格式
查看>>
SQL Server 中 EXEC 与 SP_EXECUTESQL 的区别
查看>>
2013=7=30 自增量的浅谈
查看>>
oracle11g dataguard 安装手册(转)
查看>>
java并发包分析之———Deque和LinkedBlockingDeque
查看>>
1. Two Sum - Easy - Leetcode解题报告
查看>>
SQLiteHelper
查看>>
多线程---同步函数的锁是this(转载)
查看>>
鱼C记事本V1.0(下)- 零基础入门学习Delphi28
查看>>
百练 2742 统计字符数 解题报告
查看>>
Ubuntu搜狗输入法候选词乱码
查看>>
js中回调函数写法
查看>>
React native android 最常见的10个问题
查看>>
数据结构和算法
查看>>