
Docker 镜像概念
可以把镜像理解为 VM 模板,VM 模板就像停止运行的 VM,而 Docker 就像停止运行的容器。就像一个静态的 OS 镜像。
首先需要从镜像仓库拉取镜像,然后使用**镜像可以启动多个容器。**镜像在内部看来有很多层级,但是在外部看来就是一个整体。在镜像中像是集成了一个小 OS,其中镜像中包含了许多必须依赖的内容。
在该前提下,镜像可以理解为一个构建时(build-time)结构,而容器可以理解为一个运行时(run-time)结构

镜像和容器
上图反映了镜像与容器之间的关系,如果镜像没有使用 docker container run 命令运行在容器中时,那么将可以随时删除。但是使用 docker container run 命令运行镜像到容器后,那么两个就会像绑定到一起一样。这时删除镜像将会有错误。
因为镜像正在容器中运行,所以时删除不了的,就类似于你的 OS 镜像在 VMware workstation 中挂载运行时,是无法删除挂载后的镜像的。
镜像的大小
镜像一般都比较小,一般的镜像会包含多个 shell 环境或者是一个 shell 环境,有些镜像为了节省空间只会创建一个 shell,并不会创建多个 shell。
并且镜像中是不包含内核的,会与宿主机共享内核来使用并运行该容器。
例如 ubuntu 的镜像,在官方网站上要近 10G,但是在 docker 镜像中,只需要 110M 就可以下载 Ubuntu 并可以使用该 Ubuntu 的环境。
Hyper-V 容器是个特例,他会运行在轻量级的 VM 上,同时利用 VM 内部的操作系统内核。
拉取镜像
Docker 主机安装之后,本地没有镜像。
通常情况下,镜像会从 Docker Hub 的仓库中拉取。docker image pull alpine:latest 命令会从 docker hub 的 alpine 仓库中拉取标签为 latest 的镜像。默认镜像会存储在 /var/lib/docker/
[root@centos7 docker]# docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest d70eaf7277ea 31 hours ago 72.9MB
tomcat latest 625b734f984e 47 hours ago 648MB
镜像仓库服务
Docker 镜像促成农户在 镜像仓库服务中(image Registry)当中。
docker 客户端的镜像仓库服务是可以配置的,默认使用 docker hub。
镜像仓库服务包含多个镜像仓库(image repository)

Docker 镜像仓库的选择
Docker 的镜像仓库分为官方镜像仓库和非官方镜像仓库两种,官方镜像仓库都是一些经过官方认证过的镜像。使用起来安全一些,这样并不意味着非官方镜像仓库中的数据就是一定不好的。非官方镜像仓库中也有一部分镜像是非常好的,并且使用起来也非常 very good。
Docker Hub 就是一个典型的官方仓库代表,其中的镜像也不乏是有一些危险性的。所以在调用官方或者非官方镜像时都要小心使用。
Docker Hub 基本上已经包含了当前主流系统和应用的镜像了。
镜像的命名和标签
只需要给出镜像的命令和标签,就能在官方仓库中定位一个镜像(使用 : 分割)。在官方仓库拉取镜像时,docker image pull 命令的格式如下:
docker image pull <repoitory>:<tag>
拉取官方仓库中不同的镜像
docker image pull mongo:3.3.11
//该命令会从官方Mongo库拉取标签为3.3.11的镜像
$ docker image pull redis:latest
//该命令会从官方Redis库拉取标签为latest的镜像
# latest 表示为最新的镜像内容
$ docker image pull alpine
//该命令会从官方Alpine库拉取标签为latest的镜像
注意事项
- 没有在仓库名称后指定固定版本或者标签时,那么将会自动拉取 latest
- 有些官方网站上最新的镜像标签并不是 latest,而是其他的标签,这也就意味着,从官方下载的 latest 镜像并不一定是 latest 最新版本的!
为镜像打标签
一个镜像可以根据用户需要设置多个标签。因为标签是存放在镜像元数据中的任意数字或字符串。
在 docker image pull 命令中指定 -a 参数来拉取仓库中的全部经i想,接下来可以使用 docker image ls 查看到镜像。
如果拉取的镜像仓库中包含多个平台或者架构的镜像,那么命令可能会执行失败。
悬虚镜像
首先基于 alpine:3.4 构建一个新的镜像,并打上 dodge:challenger 标签。然后更新 Dockerfile,将 alpine:3.4 替换为 alpine:3.5,并且再次执行 docker image build 命令,该命令会构建一个新的镜像,并且标签为 dodge:challenger,同时移除了旧镜像上面对应的标签,旧镜像就变成了悬虚镜像。
可以通过 docker image prune 命令移除全部的悬虚镜像。如果添加了 -a 参数,Docker 会额外移除没有被使用的镜像(那些没有被任何容器使用的镜像)。
筛选本地镜像方式
- dangling:指定 true(仅返回悬虚镜像) 或者 false(进返回非悬虚镜像)
- before:需要镜像名称或者 ID 作为参数,返回在之前被创建的全部镜像
- since:与 before 类似,不过返回的是指定镜像之后创建的全部镜像
- lablel:根据标注(label的名称或者值对镜像进行过滤)
其他过滤方式
使用 reference 过滤
docker image ls --filter=reference="*:latest"
[root@centos7 docker]# docker image ls --filter=reference="*:latest"
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest d70eaf7277ea 32 hours ago 72.9MB
tomcat latest 625b734f984e 2 days ago 648MB
使用 –format 参数来过滤显示字段
[root@centos7 docker]# docker image ls --format "{{.Repository}}: {{.Tag}}: {{.Size}}"
ubuntu: latest: 72.9MB
tomcat: latest: 648MB
筛选 Docker Hub(Repository)方式
docker search [image_name]方式会通过 CLI 方式搜索 Docker Hub。可以通过 name 名称进行过滤。
[root@centos7 docker]# docker search tomcat
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
tomcat Apache Tomcat is an open source implementati… 2861 [OK]
tomee Apache TomEE is an all-Apache Java EE certif… 83 [OK]
name 字段是仓库名称,包含了 docker id,或者非官方仓库的组织名称。例如,上面的命令会列出所有仓库名称中包含 tomcat 的镜像。
仅显示官方镜像
上面内容中显示的镜像有官方的,也有非官方的。可以使用 --filter "is-official=true",使命令返回内容只显示官方的镜像。
[root@centos7 docker]# docker search tomcat --filter "is-official=true"
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
tomcat Apache Tomcat is an open source implementati… 2861 [OK]
tomee Apache TomEE is an all-Apache Java EE certif… 83 [OK]
仅显示个人制作创建的镜像
[root@centos7 docker]# docker search tomcat --filter "is-automated=true"
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
dordoka/tomcat Ubuntu 14.04, Oracle JDK 8 and Tomcat 8 base… 55 [OK]
bitnami/tomcat Bitnami Tomcat Docker Image 36 [OK]
consol/tomcat-7.0 Tomcat 7.0.57, 8080, "admin/admin" 17 [OK]
更改显示的镜像行数
默认只会 docker serach 只会显示 25 行,如果想让 docker 显示更多的镜像,那么就需要使用 --limit 参数来定义。使用命令如下。
[root@centos7 docker]# docker search tomcat --filter "is-automated=true" --limit 100
镜像的分层
镜像由一些松耦合的只读镜像层组成。

docker 负责堆叠这些镜像,并且将他们表示为单个统一的对象。查看镜像分层的方式可以通过 docker image inspect 命令来查看。
[root@centos7 docker]# docker image inspect ubuntu
[
{
"Id": "sha256:d70eaf7277eada08fca944de400e7e4dd97b1262c06ed2b1011500caa4decaf1",
"RepoTags": [
"ubuntu:latest"
],
"RepoDigests": [
"ubuntu@sha256:fff16eea1a8ae92867721d90c59a75652ea66d29c05294e6e2f898704bdb8cf1"
],
"Parent": "",
"Comment": "",
"Created": "2020-10-23T17:32:36.438324921Z",
"Container": "917be43da4a1ceb52416d44076c5f99b0d20ceca3f5f9a2918a020e05ec3616a",
"ContainerConfig": {
"Hostname": "917be43da4a1",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ",
"CMD [\"/bin/bash\"]"
],
"ArgsEscaped": true,
"Image": "sha256:8d5c6d6340d4365a65dc5becb6ecb055dcf505527c94d2dfa167f9b99a48a985",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": {}
},
"DockerVersion": "18.09.7",
"Author": "",
"Config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/bash"
],
"ArgsEscaped": true,
"Image": "sha256:8d5c6d6340d4365a65dc5becb6ecb055dcf505527c94d2dfa167f9b99a48a985",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"Architecture": "amd64",
"Os": "linux",
"Size": 72879481,
"VirtualSize": 72879481,
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/8bc26bcb466f9a132a1fc8f9e7b47c9e283f06718e98a7641e92e16698d60fc2/diff:/var/lib/docker/overlay2/b27e2da29a54923bb91495c87446b64cc2b44871a1c270ea0235d9509843766d/diff",
"MergedDir": "/var/lib/docker/overlay2/c3fce46a5f7fbcd7dbf04327e6c267543e3392293a3433eb2352cd4c2fee46a3/merged",
"UpperDir": "/var/lib/docker/overlay2/c3fce46a5f7fbcd7dbf04327e6c267543e3392293a3433eb2352cd4c2fee46a3/diff",
"WorkDir": "/var/lib/docker/overlay2/c3fce46a5f7fbcd7dbf04327e6c267543e3392293a3433eb2352cd4c2fee46a3/work"
},
"Name": "overlay2"
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:47dde53750b4a8ed24acebe52cf31ad131e73a9611048fc2f92c9b6274ab4bf3",
"sha256:0c2689e3f9206b1c4adfb16a1976d25bd270755e734588409b31ef29e3e756d6",
"sha256:cc9d18e90faad04bc3893cfaa50b7846ee75f48f5b8377a213fa52af2189095c"
]
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
]
由上得知,就算是一个小型的系统,那么镜像层数也有三层。只不过输出的内容使用了镜像的 SHA256 散列值来标识镜像层。
也可以使用 docker history ubuntu 查看镜像的构建历史记录,但并不是严格意义上的镜像分层。有些 dockerfile 中的指令,并不会创建新的镜像层。比如ENV、EXPOSE、CMD 以及 ENTRY- POINT。不过,这些命令会在镜像中添加元数据。
oot@centos7 docker]# docker history ubuntu
IMAGE CREATED CREATED BY SIZE COMMENT
d70eaf7277ea 32 hours ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 32 hours ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B
<missing> 32 hours ago /bin/sh -c [ -z "$(apt-get indextargets)" ] 0B
<missing> 32 hours ago /bin/sh -c set -xe && echo '#!/bin/sh' > /… 811B
<missing> 32 hours ago /bin/sh -c #(nop) ADD file:435d9776fdd3a1834… 72.9MB
所有的 docker 镜像都起始于一个基础镜像曾,当进行修改或增加新的内容时,就会在当前的镜像曾之上,创建新的镜像层。
例如在一个基础镜像层上安装了一个 python 环境,那么就出现了一个新的镜像层,如果为这个 python 层打上了补丁,那么就会再次新创建一个镜像层。

添加镜像时,那么当第一层有三个文件,而第二层也有三个文件**(都是不同的文件)**那么在用户看来就会有六个文件,因为是看到的第一、二层整合。

稍微复杂的三层镜像,在外部看来整个镜像只有 6 个文件,因为最上层的文件与下一层的文件重合并且覆盖了,或者说是更新到了最新版本。

Docker 通过存储引擎(新版本采用快照机制)的方式来实现镜像层堆栈。保证多镜像层对外展示为统一的文件系统。
显示与系统显示相同的三层镜像。所有镜像层堆叠并合并,对外提供统一的视图。

共享镜像层
多个镜像之间会共享镜像曾。这样可以有效节省空间并提高性能。
在镜像拉取时 docker 会聪明的辨识哪些镜像层已经存在并且可以共享,那么 docker 将不会继续下载这些镜像,会跳过这些镜像并且共享这些已经存在的镜像层。
Docker 在 linux 支持很多存储引擎。每个存储引擎都有自己的镜像分层,镜像层共享以及写时复制技术具体实现。
根据摘要拉取镜像
拉取镜像时如果只是通过上面提的标签拉取镜像,可能会出现标签名相同的现象,这时就会出现拉取镜像错误。可以使用摘要拉取镜像,这样就不会再出现相同的现象了。docker image pull 命令拉取镜像,然后使用 docker image ls --digests 参数即可在本地查看镜像摘要。
[root@centos7 docker]# docker image ls --digests
REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE
ubuntu latest sha256:fff16eea1a8ae92867721d90c59a75652ea66d29c05294e6e2f898704bdb8cf1 d70eaf7277ea 33 hours ago 72.9MB
tomcat latest sha256:30dd6da4bc6b290da345cd8a90212f358b6a094f6197a59fe7f2ba9b8a261b4f 625b734f984e 2 days ago 648MB
[root@centos7 docker]# docker image ls --format "{{.Repository}}:{{.Digest}}"
ubuntu:sha256:fff16eea1a8ae92867721d90c59a75652ea66d29c05294e6e2f898704bdb8cf1
tomcat:sha256:30dd6da4bc6b290da345cd8a90212f358b6a094f6197a59fe7f2ba9b8a261b4f
没有原生 docker 命令支持查看远程仓库 docker hub 上镜像的 sha 校验码
镜像散列值(摘要)
镜像本省就是一个配置对象,其中包含了镜像曾的列表以及一些元数据信息。
镜像层才是实际数据存储的地方(比如文件等,镜像层之间是完全独立的,并没有从属于某个镜像集合的概念)。
镜像的唯一标识是一个加密 ID,即配置对象本身的散列值,每个镜像层也由一个加密 ID 区分,其值为镜像层本身内容的散列值。
内容散列
因为散列算法的雪崩性,当修改镜像的内容或者其中任意的镜像层,都会导致加密散列值的变化。镜像和其镜像层都是不可变的,任何改变都能很轻松的被辨别。这就是所谓的内容散列(Content Hash)
分发散列
因为镜像在 cli 发送 docker pull 时,那么会进行压缩后发送到本地,这时 sha 值就会产生变化。所以分发散列就要解决这个问题。
分发散列在计算时,会将镜像中的镜像层分层计算 sha 值并插入到镜像层中发送给客户端,这样当客户端验证时,就可以结算每个镜像层的 sha 值然后进行对比。这个内容寻址极大的提高了镜像的安全性,因为在拉取和推送操作后提供了一种方式来确保镜像和镜像层数据是一致的。
该模型也及绝了随机生成镜像和镜像曾 ID 这种方式可能导致的 ID 冲突问题。

多层架构的镜像
Docker 的优点就是使用方便,运行一个应用就像拉取镜像并运行容器那么简单。无需担心安装、依赖或者配置的问题。
随着 Docker 的发展,事情开始复杂,尤其是添加了新平台和架构后。
这时就会发现,在拉取镜像并运行时,需要考虑镜像是否与当前运行环境的架构匹配,这就破坏了 Docker 的流畅体验。
多架构镜像(Multi-archittecture image)的出现解决了这个问题。
Docker(镜像和镜像仓库服务)规范目前支持多架构镜像。意味着某个镜像仓库标签(repository.tag)下的镜像可以同时支持多种架构。就是一个镜像标签可以支持多个平台和架构。
实现这个特性,镜像仓库服务 API 支持两种重要的结构:Manifest 列表和 Manifest。
Manifest 列表
是指某个镜像标签支持的架构列表。其支持的每种架构,都有自己的 Mainfest 定义,其中列举了该镜像的构成。
就像 NAT 地址表中对应的 IP 地址一样,在 Manifest 列表中会有多种架构对应的镜像系统。
图中的左侧就是 Manifest 列表,其中包含了该镜像支持的每种架构。
Mainfest 列表的每一项都有一个箭头,指向具体的 Manifest,其中包含了镜像配置和镜像层数据。

Manifest 列表原理
如果要拉取一个 golang 镜像,大概分为以下几步
- 客户端发送拉取请求
- docker hub 查看该拉取的镜像是否有 Manifest 列表
- 如果有列表,那么就会根据请求信息中的客户端主机架构,查看 Manifest 列表中对应的镜像
- 将镜像压缩并做分发散列发送给客户端
[root@centos7 docker]# docker container run --rm golang go version
Unable to find image 'golang:latest' locally
latest: Pulling from library/golang
e4c3d3e4f7b0: Already exists
101c41d0463b: Already exists
8275efcd805f: Already exists
751620502a7a: Already exists
aaabf962c4fc: Pull complete
7883babec904: Pull complete
1791d366c848: Pull complete
Digest: sha256:1ba0da74b20aad52b091877b0e0ece503c563f39e37aa6b0e46777c4d820a2ae
Status: Downloaded newer image for golang:latest
go version go1.15.3 linux/amd64 ---显示对应的镜像属性信息
删除镜像
当不需要一个镜像时,可以直接通过 docker image rm 命令从 Docker 主机中删除该镜像。
删除操作会在当前主机上删除该镜像以及相关的镜像层。这意味着无法通过 docker image ls 命令看到删除后的镜像,并且对应的包含镜像层数据的目录会被删除。
也可以通过镜像 ID 来删除镜像
[root@centos7 docker]# docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest d70eaf7277ea 35 hours ago 72.9MB
tomcat latest 625b734f984e 2 days ago 648MB
golang latest 4a581cd6feb1 10 days ago 839MB
[root@centos7 docker]# docker image rm 4a581cd6feb1
Untagged: golang:latest
Untagged: golang@sha256:1ba0da74b20aad52b091877b0e0ece503c563f39e37aa6b0e46777c4d820a2ae
Deleted: sha256:4a581cd6feb1725d06da3b506e6e1d8ac47bf5ef087a9a80a0d36fb6cecb42b4
Deleted: sha256:313dfbc438286a8bed1dceaefce82b322cee8736182038236afc8bd63e6e9629
Deleted: sha256:76ec05416c3a5b49430b5f23d660190c10cab232cf080443f0a37f11d4abf8d4
Deleted: sha256:b33ac968aa6621045e995addabb58c290be4a7ad793c7c753c5794d75d962fa3
[root@centos7 docker]# docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest d70eaf7277ea 35 hours ago 72.9MB
tomcat latest 625b734f984e 2 days ago 648MB
如果是删除在容器中运行的镜像,那么系统将会报出不允许的操作。因为这时镜像已经在容器中运行了。
[root@centos7 docker]# docker image rm d70eaf7277ea
Error response from daemon: conflict: unable to delete d70eaf7277ea (cannot be forced) - image is being used by running container db79ab1b333a
可以看到 docker image ls -q 命令只返回了系统中本地拉取的全部镜像的 ID 列表。将这个列表作为参数传给 docker image rm会删除本地系统中的全部镜像。
管理镜像常用命令
docker image pull是下载镜像的命令。镜像从远程镜像仓库服务的仓库中下载。
默认情况下,镜像会从 Docker Hub 的仓库中拉取。
docker image pull alpine:latest命令会从 Docker Hub 的 alpine 仓库中拉取标签为 latest 的镜像。
docker image ls列出了本地 Docker 主机上存储的镜像。可以通过 –digests 参数来查看镜像的 SHA256 签名。
docker image inspect命令非常有用!该命令完美展示了镜像的细节,包括镜像层数据和元数据。
docker image rm用于删除镜像。
docker image rm alpine:latest命令的含义是删除 alpine:latest 镜像。当镜像存在关联的容器,并且容器处于运行(Up)或者停止(Exited)状态时,不允许删除该镜像。
docker ls --filter "{{.Prepository}}:{{Size}}"筛选指定的内容