docker运行容器需要本地存在对应的镜像,如果本地不存在该镜像,docker会从镜像仓库下载该镜像
docker镜像是怎么实现增量的修改和维护的?
每个镜像都由很多层次构成,docker使用Union FS将这些不同的层结合到一个镜像中去
通常UnionFS有两个用户,一方面可以实现不借助LVM、RAID将多个disk挂到同一个目录下,另一个更常用的就是将一个只读的分支和一个可写的分支联合在一起,Live CD正是基于此方法可以允许在镜像不变的基础上允许用户在其上进行一些写操作
docker在OverlayFS上构建的容器也是利用了类似的原理
从docker镜像仓库获取镜像的命令是docker pull,其格式为:
docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]
$ docker pull ubuntu:18.04 18.04: Pulling from library/ubuntu 92dc2a97ff99: Pull complete be13a9d27eb8: Pull complete c8299583700a: Pull complete Digest: sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9c4361e321b26 Status: Downloaded newer image for ubuntu:18.04 docker.io/library/ubuntu:18.04
从下载的过程中可以看到我们之前提及的分层存储的概念,镜像是由多层存储所构成
下载也是一层层的去下载,并非单一文件
下载过程中给出了每一层ID的前12位置
并且下载结束后,给出该镜像完整的sha256的摘要,以确保下载一致性
在使用上面命令的时候,你所看到的层id以及sha256的摘要和这里的不一样
这是因为官方镜像是一直在维护的,有任何新的bug,或者版本更新,都会进行修复再以原来的标签发布,这样可以确保任何使用这个标签的用户可以获得更安全、更稳定的镜像
$ docker run -it --rm ubuntu:18.04 bash root@e7009c6ce357:/# cat /etc/os-release NAME="Ubuntu" VERSION="18.04.1 LTS (Bionic Beaver)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 18.04.1 LTS" VERSION_ID="18.04" HOME_URL="https://www.ubuntu.com/" SUPPORT_URL="https://help.ubuntu.com/" BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" VERSION_CODENAME=bionic UBUNTU_CODENAME=bionic
docker run 就是运行容器的命令,具体格式我们会在 容器 一节进行详细讲解,我们这里简要的说明一下上面用到的参数。
进入容器后,我们可以在 Shell 下操作,执行任何所需的命令。这里,我们执行了 cat /etc/os-release,这是 Linux 常用的查看当前系统版本的命令,从返回的结果可以看到容器内是 Ubuntu 18.04.1 LTS 系统。
要想列出已经下载下来的镜像,可以使用docker image ls 命令
$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE redis latest 5f515359c7f8 5 days ago 183 MB nginx latest 05a60462f8ba 5 days ago 181 MB mongo 3.2 fe9198c04d62 5 days ago 342 MB <none> <none> 00285df0df87 5 days ago 342 MB ubuntu 18.04 329ed837d508 3 days ago 63.3MB ubuntu bionic 329ed837d508 3 days ago 63.3MB
列表包含了仓库名、标签、镜像id、创建时间以及所占用的空间
如果仔细观察,会注意到,这里的标识所占用空间和在DockerHub上看到的镜像大小不同。
比如:ubuntu:18.04镜像大小,在这里是63.3MB,但是在dockerHub显示的却是25.47MB
这是因为DockerHub中显示的体积是压缩后的体积。在镜像下载和上传过程中镜像是保持着压缩的状态的,因此docker Hub所显示的大小是网络传输中更关心的流量大小。
而docker image ls显示的是镜像下载到本地后,展开的大小,准确说,是展开后的各层所占空间的综合,因为镜像到本地后,查看空间的时候,更关心的是本地磁盘空间占用的大小
注意
docker image ls 列表中的镜像体积总和并非是所有镜像实际硬盘消耗
由于docker镜像是多层存储结构,并且可以继承、复用,因此不同镜像可能会因为使用相同的基础镜像,从而拥有共同的层
由于docker使用UnionFS,相同的层只需要存一份即可,因此实际镜像硬盘占用空间很可能要比这个列表镜像大小的总和要小的多
你可以通过docker system df 命令来便捷的查看镜像、容器、数据卷所占用的空间
root@ccc:/home/rabbitmq# docker system df TYPE TOTAL ACTIVE SIZE RECLAIMABLE Images 13 1 5.695GB 5.432GB (95%) Containers 1 1 0B 0B Local Volumes 1 0 63.11kB 63.11kB (100%) Build Cache 0 0 0B 0B
有这样一种特殊的镜像,这个镜像既没有仓库名,也没有标签,均为 'none'
<none> <none> 00285df0df87 5 days ago 342 MB
这个镜像原本是有镜像名和标签的,原来为mongo:3.2,随着官方镜像维护,发布了新版本后,重新docker pull mongo:3.2时,mongo:3.2这个镜像名被转移到了新下载的镜像身上,而旧的镜像上的这个名称则被取消,从而成为了'none'。
除了docker pull可能导致这种情况,docker build也同样可以导致这种现象
由于新旧镜像同名,旧镜像名称被取消,从而出现仓库名、标签均为'none'的镜像
这类无标签镜像也被称为《虚悬镜像》,可以用下面的命令专门显示这类镜像:
$ docker image ls -f dangling=true REPOSITORY TAG IMAGE ID CREATED SIZE <none> <none> 00285df0df87 5 days ago 342 MB
一般来说,虚悬镜像已经失去了存在的价值,是可以随意删除的,可以用下面的命令删除
docker image prune
为了加速镜像构建、重复利用资源,docker会利用中间层镜像
所以在使用一段时间后,可能会看到一些依赖的中间层镜像
默认的docker image ls列表中只会显示顶层镜像,如果希望显示包括中间层镜像在内的所有镜像的话,需要加-a参数
docker image ls -a
这样会看到很多无标签的镜像,与之前的虚悬镜像不同,这些无标签的镜像很多都是中间层镜像,是其他镜像所依赖的镜像
这些无标签镜像不应该删除,否则会导致上层镜像因为依赖丢失而出错
实际上,这些镜像也没必要删除,因为之前说过,相同的层只会存一遍,而这些镜像是别的镜像的依赖,因此并不会因为他们被列出来而多存了一份
无论如何你需要他们。只要删除哪些依赖他们的镜像后,这些依赖的中间镜像也会被连带删除
不加任何参数的情况下,docker image ls 会列出所有顶层镜像,但是有时候我们只希望列出部分镜像。
docker images ls 有好几个参数可以帮助做到这个事情:
除此以外,docker image ls还支持强大的过滤器参数 --filter,或者简写-f
之前我们已经看到了使用过滤器来列出虚悬镜像的用法,它还有更多的用法
比如,我们希望看到mongo:3.2之后建立的镜像,可以用下面的命令:
$ docker image ls -f since=mongo:3.2 REPOSITORY TAG IMAGE ID CREATED SIZE redis latest 5f515359c7f8 5 days ago 183 MB nginx latest 05a60462f8ba 5 days ago 181 MB
想查看某个位置之前的镜像也可以,只需要把since换成before即可
此外,如果镜像构建时,定义了LABEL,还可以通过LABEL来过滤
$ docker image ls -f label=com.example.version=0.1
默认情况下,docker image ls会输出一个完整的表格,但是我们并非所有时候都会需要这些内容
比如,刚才删除虚悬镜像的时候,我们需要利用docker image ls把所有的虚悬镜像的ID列出来,然后才可以给docker image rm命令作为参数来删除制定的这些镜像,这个时候就用到了-q参数
$ docker image ls -q 5f515359c7f8 05a60462f8ba fe9198c04d62 00285df0df87 329ed837d508 329ed837d508
--filter 配合 -q 产出指定范围的ID列表,然后送给另一个docker命令作为参数,从而针对这组实体成批的进行某种操作的做法在docker命令行使用过程中非常常见
docker rmi $(docker images –q )
不仅仅是镜像,将来我们会在各个命令中看到这类搭配以完成很强大的功能。
格式化显示命令结果
$ docker image ls --format "{{.ID}}: {{.Repository}}" 5f515359c7f8: redis 05a60462f8ba: nginx fe9198c04d62: mongo 00285df0df87: <none> 329ed837d508: ubuntu 329ed837d508: ubuntu
或者打算以表格等距显示,并且有标题行,和默认一样,不过自己定义列:
$ docker image ls --format "table {{.ID}}\t{{.Repository}}\t{{.Tag}}" IMAGE ID REPOSITORY TAG 5f515359c7f8 redis latest 05a60462f8ba nginx latest fe9198c04d62 mongo 3.2 00285df0df87 <none> <none> 329ed837d508 ubuntu 18.04 329ed837d508 ubuntu bionic
我们可以用镜像的完整ID,也称为长ID,来删除镜像
使用脚本的时候可能会用长ID,但是人工输入太累了,所以更多的时候用短ID来删除镜像。
docker image rm id名字
$ docker image rm centos Untagged: centos:latest Untagged: centos@sha256:b2f9d1c0ff5f87a4743104d099a3d561002ac500db1b9bfa02a783a46e0d366c Deleted: sha256:0584b3d2cf6d235ee310cf14b54667d889887b838d3f3d3033acd70fc3c48b8a Deleted: sha256:97ca462ad9eeae25941546209454496e1d66749d53dfa2ee32bf1faabd239d38
$ docker image ls --digests REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE node slim sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228 6e0c4c8e3913 3 weeks ago 214 MB $ docker image rm node@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228 Untagged: node@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228
如果观察上面的这几个命令运行输出信息的话,你会注意到删除行为分为两类: 一类是:untagged(先删除标签,后删除镜像) 另一类是:deleted
因此,当我们使用上面命令删除镜像的饿时候,实际上是在要求删除某个标签的镜像
所以首先需要做的是将满足我们要求的所有镜像标签都取消,这就是我们看到的Untagged的信息
因为一个镜像可以对应多个标签,因此当我们删除了所指定的标签后,可能还有别的标签指向了这个镜像,如果是这种情况,那么Delete行为就不会发生
所以并非所有的docker image rm都会产生删除镜像的行为,有可能仅仅是取消了某个标签而已
当该镜像所有的标签都取消了,该镜像很可能会失去了存在的意义,因此会触发删除行为
镜像是多层存储结构,因此在删除的时候也是从上层向基础层方向依次进行判断删除
镜像的多层结构让镜像复用变得非常容易,因此很有可能某个其他镜像正依赖于当前镜像的某一层。
这种情况,依旧不会触发删除该层的行为。直到没有任何层依赖当前层时,才会真实的删除当前层。
这就是为什么,有时候会奇怪,为什么命名没有别的标签指向这个镜像,但是它还是存在的原因,也是为什么有时候会发现所删除的成熟和自己docker pull看到的成熟不一样的原因。
除了镜像依赖外,还需要注意的是容器对镜像的依赖
如果有用这个镜像启动的容器存在(即使容器没有运行),那么同样不可以删除这个镜像
容器是以镜像为基础,再加一层容器存储层,组成这样的多层存储结构去运行的
因此该镜像如果被这个容器所依赖的,那么删除必然会导致故障
如果这些容器是不需要的,应该先将他们删除,然后再来删除镜像。
$ docker image rm $(docker image ls -q redis)
或者删除所有在 mongo:3.2 之前的镜像:
$ docker image rm $(docker image ls -q -f before=mongo:3.2)
本文作者:Eric
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!