Open findxc opened 3 years ago
docker为什么节省资源
容器和分层技术,我们看到的只有几M的那个空间占用,是基于底层镜像之上做的新增的操作,每一个操作都是一层,叠加起来的隔离只是分层上的隔离,而不是把镜像拿来隔离,这是和虚拟机最大的不同。
首先要明白,Linux操作系统分别由两部分组成 1.内核空间(kernel) 2.用户空间(rootfs)
内核空间是kernel,Linux刚启动时会加载bootfs文件系统,之后bootf会被卸载掉,用户空间的文件系统是rootfs,包含常见的目录,如/dev、/proc、/bin、/etc等等
不同的Linux发行版本(红帽,centos,ubuntu等)主要的区别是rootfs, 多个Linux发行版本的kernel差别不大。因此通过docker pull centos命令下载镜像,实质上下载centos操作系统的rootfs,共用系统的kernel,所以docker下载的centos镜像大小只有200M。像是alpinelinux的docker镜像甚至只有几兆。
共用kernel 区别rootfs 这两个特点有点类似lxc虚拟化,实际上早期的docker也是基于lxc。
后来docker 重写了使用lxc的那部分。
@jiqing112 谢谢补充,学习了,又去看了一下 比较 Docker 容器和虚拟机 ,我更理解了 ✌️
由于容器所需的资源要少得多(例如,它们不需要一个完整的 OS),所以它们易于部署且可快速启动。 这使你能够具有更高的密度,也就是说,这允许你在同一硬件单元上运行更多服务,从而降低了成本。
本文是根据 About storage drivers | Docker Documentation 整理的。
layer 指层, image 指镜像, container 指容器。
镜像是由层组成的
在拉取镜像时,可以看到会按层去拉取:
如果想查看某个镜像有哪些层,可以使用
docker inspect xxx
命令。docker inspect node:14.16.1-alpine3.13
的部分内容如下。镜像有哪些层:
镜像的一些配置参数,注意这里环境变量、 CMD 等也算是镜像的配置参数:
Dockerfile 中哪些命令会增加一层
除了只是修改镜像的配置参数的命令,其它命令都会增加一层。比如 ENV 、 CMD 、 LABEL 就不会增加一层,而 RUN 、 ADD 就会增加一层。
在构建镜像时其实也能看出来哪些命令会增加一层,比如使用下面这个 Dockerfile 来构建镜像。
镜像的层和层之间是什么关系
先得说一下 copy-on-write (CoW) 这个策略。维基上介绍如下:
这个策略在构建镜像和创建容器时都有用到。这里我们先说镜像中的层和层。
如果某一层需要读取低层的文件,那就直接读就行了,不用把低层的文件拷贝到当前层,而如果是需要修改一个文件,这个文件在当前层不存在,是存在于低层的,那就先拷贝到当前层,然后修改当前层这个文件。
通过
docker history hello:v1
来查看层的大小,能发现确实就是这样的。RUN echo aaabbbcccddd>>hello.txt
这一层由于需要修改COPY ./hello.txt ./
这一层的文件,就只有将文件拷贝过去再改,大小就是比上一层稍微大一点点。如果我们把最后一层改为
RUN cat hello.txt
,这样就只是读取文件,然后构建镜像后查看层大小,可以看到这一层大小就是 0 了,如下:所以简单点理解,镜像的层包含的内容就是这一层文件相对于之前层的 diff 。
镜像和容器文件是由 docker 的 storage driver 来管理的,最常用的就是 overlay2 了,想去看更多细节见 Use the OverlayFS storage driver | Docker Documentation 。
以层为单位来构建镜像的好处
以层为单位可以更高效利用磁盘空间。比如我们一开始构建了镜像 m ,依次有 a , b , c 三层,然后我们再去构建镜像 n ,依次有 a , b , d 三层,那么 a 和 b 其实会使用同一份文件。
以层为单位可以提高缓存命中率。在构建/拉取/推送镜像时,如果某些层已经存在,就不用再构建/拉取/推送这一层了。比如刚才我们测试
RUN cat hello.txt
的时候:那容器和镜像又是咋回事呢
一图胜千言,图片来自 About storage drivers | Docker Documentation 。
基于 copy-on-write 策略,当镜像构建完之后,其实每一层都是只读了,这和镜像是无状态的不可变的这个观点是吻合的。
在创建容器时,其实就是在镜像基础上增加了 container layer ,这一层是可读可写的,容器运行时的临时性数据就放在这一层。
需要注意,删除容器时这一层也会跟着删除,所以需要持久化存储的数据,比如数据库文件,需要使用 volume 来挂载到容器中,这样文件就脱离容器生命周期了。(比如
docker run -v absolute_local_path:absolute_container_path
)同样,由于 copy-on-write 策略,如果容器想修改镜像中某个文件,会先把文件拷贝到 container layer 然后再修改。
如果是基于一个容器启动多个镜像,其实也只是多增加了一些 container layer 。
这里我们来说说镜像和容器占用的磁盘空间。
由于镜像和镜像之间可能存在相同的层,所以多个镜像占用的磁盘空间可能会小于镜像大小求和。
而对于容器,对于基于一个镜像启动多个容器的场景,其实是镜像大小加上多个容器各自 container layer 大小即可。(这里没考虑容器日志、 volume 设置,以及内存交互等也会增加容器大小)
通过
docker images
可以查看镜像大小。通过
docker ps -s
可以查看容器大小。下面的 0B 是 container layer 大小,后面的 virtual 是镜像大小加上 container layer 大小。下面是一个测试容器中的 copy-on-write 策略。如果我们只是启动容器,大小是 0B ,如果修改了一个位于镜像中的文件,由于会把这个文件拷贝到容器中,大小就变为 571B 了。