Docker之基石—镜像

当我们pull一个Docker Image时,就会看到如下的输出,那么pull的时候,控制台输出的这些信息是什么意思呢?想说明什么呢?

[vagrant@nexus3 ~]$ docker pull mongo
Using default tag: latest
latest: Pulling from library/mongo
23884877105a: Pull complete
bc38caa0f5b9: Pull complete 
2910811b6c42: Already exists
36505266dcc6: Pull complete 
a4d269900d94: Pull complete 
5e2526abb80a: Pull complete 
d3eece1f39ec: Pull complete 
358ed78d3204: Pull complete 
1a878b8604ae: Pull complete 
978c572f0440: Pull complete 
35a600ffcf6a: Downloading  11.89MB/129.1MB
fa9f812cdfe6: Waiting 
7a8109e27110: Download complete 

仔细观察pull的过程会发现,在pull开始的时候列出了很多个hash值,这些值分别处于不同的状态,有些已经存在Already exists,有些刚完成pull complete,有些还在等待waiting,而在所有的值都处于pull complet的时候,image就被成功pull下来了,难道这些东西全部合在一起就是一个Image

镜像的组成

在官方文档上有这么一段话。

A Docker image is built up from a series of layers. Each layer represents an instruction in the image’s Dockerfile. Each layer except the very last one is read-only.

一个image是由一系列的 层(layer) 组成的,每一个 层(layer) 都对应 image 的 Dockerfile 中的一条指令,并且除了最后一个 层(layer) 外,其他每一个 层(layer) 都是只读的。

然后官方文档还提供了一个Dockerfile的示例:

FROM ubuntu:18.04
COPY . /app
RUN make /app
CMD python /app/app.py

这个Dockerfile包含了四个命令,每个命令创建一个 layer 。

  1. FROM语句首先根据ubuntu:18.04 的镜像创建 层(只读)
  2. COPY命令从 Docker 客户端的执行目录复制文件,这条指令在步骤1的基础上添加了一个新的层 (只读)
  3. RUN命令使用make构建应用程序,同时而也在步骤2上又添加一个层(只读)
  4. 最后一层,使用CMD指令,指定在容器中运行什么命令,同时在步骤3上添加又添加一个层

所有这些层,按照顺序堆叠在一起就构成了镜像。

当创建容器时,就是将镜像做为基础层,在基础层之上添加新的可写层。该层通常称为“容器层”。对运行中的容器所做的所有更改(例如写入新文件,修改现有文件和删除文件)都将写入此可写容器层。下图显示了基于Ubuntu 18.04映像的容器。

基于Ubuntu映像的容器层

通过上的分析,可以知道pull的时候输出的那些类hash值代表的就是一个个的 image layer

容器和层

通过上一节的讨论,可以知道,容器和镜像之间的主要区别在与容器有一个可读写的顶层。在容器中添加数据和修改数据的操作都在这个可读写的层。当删除容器或者容器崩溃时,可写层也会被删除,而容器的基础镜像是不会变化的。

因为每个容器都有其自己的可读写容器层,并且所有更改都存储在该容器层中,多个容器可以共享对同一个基础镜像的访问,只是具有自己的数据状态。下图显示了共享Ubuntu 18.04镜像的容器。

容器共享相同的图像

镜像大小

有一个比较奇怪的现象,在hub.docker.com中,mysql的镜像的大小标示的是150.33MB
/home/xingmu/.config/Typora/typora-user-images/image-20200531134402599.png

可是在pull到本地后,通过docker image ls查看,会发现mysql镜像的大小居然是是448MB,这是为什么呢?

在这里插入图片描述

这是因为 Docker Hub 中显示的体积是压缩后的体积。在镜像下载和上传过程中镜像是保持着压缩状态的,因此 Docker Hub 所显示的大小是网络传输中更关心的流量大小。

如果仔细观察pull的过程的话,会发现Download complete的状态后有一个Extracting的过程,这个过程就是在从压缩中提取下载的镜像层。

类似mysql这样的 docker 镜像说大不大,说小不小,但是一旦镜像的总数上来之后,岂不是对本地磁盘造成很大的存储压力?平均每个镜像四五百MB, 100 个镜像岂不是需要准备 四五十G的存储空间?

事实上,Docker 在镜像复用方面设计得非常出色,大大节省镜像占用的磁盘空间。Docker 镜像的复用主要体现在多个不同的 Docker 镜像可以共享相同的镜像层。通过docker image ls显示的镜像大小是各个镜像层加起来的大小,而在实际的物理存储上,相同的镜像层只会存储一份。

上面的截图中是本地的所有镜像列表,实际计算了下,大概是3.6GB。下面通过docker system df查看所有镜像的实际占用的存储空间。

docker system df
#TYPE                TOTAL               ACTIVE              SIZE                
#Images              10                  0                   3.007GB  

而实际上,镜像复用带来的好处不只是存储空间方面的。在本文的开始,就提到pull的时候会有一个Already exists状态,出现这个状态的原因就是,将要pull的这个镜像所依赖的层已经被其他的镜像下载的本地了,已经存在无需再从仓库下载了。

中间层镜像

默认的 docker image ls 列表中只会显示顶层镜像,如果希望显示包括中间层镜像在内的所有镜像的话,需要加 -a 参数。

$ docker image ls -a

这样会看到很多无标签的镜像,这些无标签的镜像很多都是中间层镜像,是其它镜像所依赖的镜像。这些无标签镜像不应该删除,否则会导致上层镜像因为依赖丢失而出错。实际上,这些镜像也没必要删除,因为之前说过,相同的层只会存一遍,而这些镜像是别的镜像的依赖,因此并不会因为它们被列出来而多存了一份,无论如何你也会需要它们。只要删除那些依赖它们的镜像后,这些依赖的中间层镜像也会被连带删除。

镜像的删除

该部分内容来自 https://yeasy.gitbook.io/docker_practice/image/rm

如果要删除本地的镜像,可以使用 docker image rm 命令。

$ docker image rm php
# Untagged: php:latest
# Untagged: php@sha256:7c9c0f661ffa956f181c7fded3f06fae21326f8a4cfcd99aa547d87d80e14f06
# Deleted: sha256:e97d32e924d01f000e5bb7371c2207e0e649005930c9c34922adb46bdda06d4b
# Deleted: sha256:e77fc89254ff05def4f34014c3e875db91659b0a5f02cc4b198da38c13f01e4a
# Deleted: sha256:e29014815b2355dc217820f131001f5a80e91068c16546fd02b980c77de213af
# Deleted: sha256:a875d1ab755d4a1fdcb5ae7addf8e7793156c960d4f6762a1dcab21d2d49c806
# Deleted: sha256:c1f69a2ed619964b1b5212c1ad001da55454cc6846eec0d7c931d7376bc438fd
# Deleted: sha256:c96ce9a3e1719c7ce625713cf173dac3f762b980f584fb54cfe40a5d71d8b2d2
# Deleted: sha256:e034a8beee6faaa0afb038a841ac0f0b230e2d9604ae6d5f2dfdefb956b7801a
# Deleted: sha256:891a2717569c40be7e08bbafe0ef074a0f6dbb90f45429cf016e17eb55523dcf
# Deleted: sha256:8072103e605f90bb9a6b6c5cbd47e569908516c37e434b9b4285185e9e907630

如果观察删除命令的输出信息,你会注意到删除行为分为两类,一类是 Untagged,另一类是 Deleted

当我们使用上面命令删除镜像的时候,实际上是在要求删除某个标签的镜像。这里需要说明的是镜像的唯一标识是其 ID 和摘要,而一个镜像可以有多个标签。

因此首先需要做的是将满足我们要求的所有镜像标签都取消,这就是我们看到的 Untagged 的信息。因为一个镜像可以对应多个标签,因此当我们删除了所指定的标签后,可能还有别的标签指向了这个镜像,如果是这种情况,那么 Delete 行为就不会发生。所以并非所有的 docker image rm 都会产生删除镜像的行为,有可能仅仅是取消了某个标签而已。当该镜像所有的标签都被取消了,该镜像很可能会失去了存在的意义,因此会触发删除行为。

镜像是多层存储结构,因此在删除的时候也是从上层向基础层方向依次进行判断删除。镜像的多层结构让镜像复用变得非常容易,因此很有可能某个其它镜像正依赖于当前镜像的某一层。这种情况,依旧不会触发删除该层的行为。直到没有任何层依赖当前层时,才会真实的删除当前层。这就是为什么,有时候会奇怪,为什么明明没有别的标签指向这个镜像,但是它还是存在的原因,也是为什么有时候会发现所删除的层数和自己 docker pull 看到的层数不一样的原因。

除了镜像依赖以外,还需要注意的是容器对镜像的依赖。如果有用这个镜像启动的容器存在(即使容器没有运行),那么同样不可以删除这个镜像。容器是以镜像为基础,再加一层容器存储层,组成这样的多层存储结构去运行的。因此该镜像如果被这个容器所依赖的,那么删除必然会导致故障。如果这些容器是不需要的,应该先将它们删除,然后再来删除镜像。

总结

  1. 容器运行在镜像定义的系统之上
  2. 镜像是由一个或者多个镜像层组成的
  3. Dockerfile 用来定义一个镜像,Dockerfile 中的每一个命令都添加了一个层

<完结>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值