彻底搞懂Docker镜像分层的实现

目录
  • 创建测试镜像
  • 查看镜像
  • 使用dockerinspect
  • 使用dockerhistory
  • 镜像分层图
  • 镜像分层的好处
  • 镜像分层的实现
  • Copy-on-write策略

创建测试镜像

我们创建一个最简单的镜像:

1.构建测试镜像v1.0:docker build -t image_test:1.0 .

FROM alpine:3.15.0 #除了继承基础镜像,啥也不做

2.构建测试镜像v2.0:docker build -t image_test:2.0 .

FROM alpine:3.15.0
RUN dd if=/dev/zero of=file1 bs=10M count=1 #添加一个10M的文件file1

3.构建测试镜像v3.0:docker build -t image_test:3.0 .

FROM alpine:3.15.0
RUN dd if=/dev/zero of=file1 bs=10M count=1 #添加一个10M的文件file1
RUN dd if=/dev/zero of=file2 bs=10M count=1 #添加一个10M的文件file2

这样本地就构建了3个测试镜像:

查看镜像

我们有2种方法查看镜像:

  1. 使用docker inspect:获取镜像的元数据
  2. 使用docker history:查看镜像的构建历史 使用docker inspect

使用docker inspect

查看镜像的元数据。
其中Parent可以看到父镜像, Layers这一项下面可以看到镜像的所有层。

使用docker history

使用docker history可以看到镜像的构建历史。
我们每一行列出了镜像包含的层。

使用docker history我们看到有一行很特别,就是镜像ID为的行,这一行是什么呢?
看了 官方文档 的描述,我们知道这些构建步骤要么是构建在另一个系统,要么是镜像的部分是从DockerHub上拉取下来的,要么是使用的是另一种构建工具BuildKit构建的。
很显然,我们这里就是第二种情况,因为我们的Dockerfile中第一句指令就是FROM alpine:3.15.0.

镜像分层图

根据上面的docker history命令,我们可以轻松的画出三个镜像的分层图:

从上面的图可以看到,我们的镜像是分层的,我们的Dockerfile中新增一条指令,就会新增一层!
如果我们将多个命令合成一个,那么也只会生成一层。修改一下上面的image_test:3.0,把两条RUN合并成一条:

FROM alpine:3.15.0
RUN dd if=/dev/zero of=file1 bs=10M count=1 && \
    dd if=/dev/zero of=file2 bs=10M count=1

使用docker history 查看image_test:4.0,可以看到,只有2层了!

镜像分层的好处

知道了镜像是分层的,那么我们是不是好奇为啥要这么设计呢?
试想一下我们如果不分层会有什么问题?

以拉取镜像为例!
拉取镜像的镜像很大,比如Redis的镜像有100多M
第一次我们拉取6.2版本的Redis,下载了完成的100M到本地,下次我要下载6.2.6版本的,是不是又得下载100M。
尽管可能两个版本之间就改了几行配置文件。

这样是非常低效的。如果能只下载有差异的部分就好了!

这个痛点,也就是镜像分层要解决的问题。实际上,Docker也是这么实现的。
第一次下载redis:6.2时,因为之前没有下载过,所以下载了所有的层,总共113M。网络慢点的话还是需要花一些时间的!

第二次下载redis:7.0-rc,就变得快了很多!因为前面3层是redis:6.2是一样的,这些层已经下载过了!

这种思想和我们常用的版本管理工具git也是一样的!
如果版本2是基于版本1的基础上,那么版本2不需要copy一份全量的数据,只需一份和版本1差异化的增量数据即可!

这样的最终好处是,可以体现在以下方面:

  • 拉取更快:因为分层了,只需拉取本地不存在的层即可!
  • 存储更少:因为共同的层只需存储一份即可!
  • 运行时存储更少:容器运行时可以共享相同的层!

对于第3点,多个基于相同镜像运行的容器,都可以直接使用相同的镜像层,每个容器只需一个自己的可写层即可:

镜像分层的实现

前面说过,Docker镜像分层和Git的版本很像!我们不妨以此类比!只是为了方便我们理解:

Git Docker
版本 分层
在前一个版本的基础上 在前一层的基础上
修改了代码 Dockerfile中加了RUN等指令
commit了修改,新增了一个版本 新建一个镜像层
新版本只包含差异 新镜像只包含了差异修改
已提交的commit是不能修改的 旧的镜像层是不能修改的

总而言之,镜像层是只读的,新的镜像层是基于前一个镜像层的修改,只保留了增量修改的部分!
使用了联合文件系统,对文件系统的修改作为一次提交来一层层的叠加!

容器本质上也是在镜像的基础上加了一层可写层!这个在另外的章节再详细讨论!

Copy-on-write策略

Copy-on-write是一种提高文件共享和复制效率的策略。
如果一个文件和目录在低一层的镜像层中存在,并且其它层想要读取这个文件,就直接使用这个文件。
如果其它层想要修改这个文件(不管是构建镜像时,还是在容器运行的过程中),这个文件都会被先拷贝到新的一层中,然后再修改它。

这样做的好处是可以大大减少每一层的大小!
更具体的实现请参考官方文档中:Copy-on-write策略

到此这篇关于彻底搞懂Docker镜像分层的文章就介绍到这了,更多相关Docker镜像分层内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 深入理解docker镜像的分层(小白必看)

    大家好,今天分享docker镜像的分层理解 我们拉取Redis 镜像 [root@localhost ~]# docker pull redis Using default tag: latest latest: Pulling from library/redis a2abf6c4d29d: Pull complete c7a4e4382001: Pull complete 4044b9ba67c9: Pull complete c8388a79482f: Pull complete 413c

  • Docker 镜像分层及dockerfile 编写技巧

    docker镜像分层 分层介绍 Dockerfile中的每个指令都会创建一个新的镜像层 镜像层将被缓存和复用 当Dockerfile的指令修改了,复制的文件变化了,或者构建镜像时指定的变量不同了,对应的镜像层缓存就会失效 某一层的镜像缓存失效之后,它之后的镜像层缓存都会失效 镜像层是不可变的,如果在某一层中添加一个文件,然后在下一层中删除它,则镜像中依然会包含该文件 Docker镜像原理 Docker镜像是由特殊的文件系统叠加而成 ·最底端是bootfs,并使用宿主机的bootfs ·第二层是r

  • Spring Boot 分层打包 Docker 镜像实践及分析(推荐)

    1. 准备 spring boot 项目 简单,略过 spring boot 版本 > 2.3.x 即可 简单写了一个 hello world 2. 进行相关配置 这里网上很多 blog 都强调了要去 pom.xml 里手动配置启用分层打包: <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot

  • 浅析Docker镜像分层的注意事项

    前言 我们平常在对程序进行Docker镜像打包的时候总会有些困惑,到底是将最终的镜像分层打包最后汇总成程序的镜像(也就是一层一层的 From )合适,还是说直接将程序从Source code就打包出最终的镜像更合适呢?其实这里面没有说那个是对或错的,要看程序包自身的情况做选择. Docker build的注意点 如果接触过Docker,Docker build大家都清楚怎么用了,但是有几个容易忽略的注意点: 1.Dockerfile开头的 From 和 MAINTAINER 其实都是一层镜像 2

  • Docker镜像分层的原理详解

    base镜像 base镜像有两层含义: 不依赖其他镜像,从scratch构建 其他镜像可以之为基础进行扩展 所以,base镜像一般都是各种Linux发行版本的Docker镜像,比如:Ubuntu,Debian或者CentOS等. base镜像提供的都是最小安装的Linux发行版本. 我们大部分镜像都将是基于base镜像构建的.所以,通常使用的是官方发布的base镜像.可以在docker hub里找到.比如centos:https://hub.docker.com/_/centos 我们可以自己构

  • 彻底搞懂Docker镜像分层的实现

    目录 创建测试镜像 查看镜像 使用dockerinspect 使用dockerhistory 镜像分层图 镜像分层的好处 镜像分层的实现 Copy-on-write策略 创建测试镜像 我们创建一个最简单的镜像: 1.构建测试镜像v1.0:docker build -t image_test:1.0 . FROM alpine:3.15.0 #除了继承基础镜像,啥也不做 2.构建测试镜像v2.0:docker build -t image_test:2.0 . FROM alpine:3.15.0

  • 一文带你彻底搞懂Docker中的cgroup的具体使用

    目录 什么是cgroup cgroup的组成 cgroup提供的功能 限制cgroup中的CPU 限制cgroup中的内存 限制cgoup的进程数 前言 进程在系统中使用CPU.内存.磁盘等计算资源或者存储资源还是比较随心所欲的,我们希望对进程资源利用进行限制,对进程资源的使用进行追踪.这就让cgroup的出现成为了可能,它用来统一将进程进行分组,并在分组的基础上对进程进行监控和资源控制管理. 什么是cgroup Linux CGroup(Linux Contral Group),它其实是Lin

  • 一篇文章弄懂Docker镜像的制作、上传、拉取和部署

    目录 一.镜像(images) 1. 什么是镜像? 2. 镜像的组成和用途 (1)Dockerfile (2)scratch (3)一个完整的操作系统需要: 3. 为什么要自己制作镜像? 二.镜像制作的步骤(10步法) 第1步:编辑Dockerfile 第2步:编辑requirements.txt文件 第3步:编辑app.py文件,我们的程序文件 第4步:生成镜像文件 第5步:检查镜像是否成功 第6步:使用镜像,启动容器 第7步:访问容器的web服务 第8步:启动redis容器 第9步:再次启动

  • Docker 进阶之镜像分层方案详解

    目录 导读 入门图解 创建测试镜像 查看镜像 使用docker inspect 使用docker history 镜像分层图 镜像分层的好处 Docker镜像加载原理 rootfs Union mount image layer Docker 镜像下载 镜像存储 镜像在远端仓库存储 本地镜像存储 导读 可以想象,像 ubuntu等基础镜像,体积必然不小.那么,思考以下几个问题: 我们基于同一个镜像(ubuntu 18.4)启动了两个容器,会占用两倍磁盘空间吗? 我们在容器内修改或者新建了某个文件

  • 理解Docker(2):Docker 镜像详细介绍

    本系列文章将介绍Docker的有关知识: (1)Docker 安装及基本用法 (2)Docker 镜像 (3)Docker 容器的隔离性 - 使用 Linux namespace 隔离容器的运行环境 (4)Docker 容器的隔离性 - 使用 cgroups 限制容器使用的资源 (5)Docker 网络 对于每个软件,除了它自身的代码以外,它的运行还需要有一个运行环境和依赖.不管这个软件是象往常一样运行在物理机或者虚机之中,还是运行在现在的容器之中,这些都是不变的.在传统环境中,软件在运行之前也

随机推荐