一. Docker镜像简介

Docker镜像就像停止运行的容器;我们可以将镜像理解为类(Class)​。镜像需要从镜像仓库服务中拉取镜像。常见的镜像仓库服务是DockerHub,但是也存在其他镜像仓库服务。拉取操作会将镜像下载到本地 Docker主机。

镜像是多层的:

镜像由多个层组成,每层叠加之后,从外部看来就如一个独立的对象。镜像内部是一个精简的操作系统(OS)​,同时还包含应用运行所必须的文件和依赖包。因为容器的设计初衷就是快速和小巧,所以镜像通常都比较小。

构建时结构

我们可以停止某个容器的运行,并从中创建新的镜像(会保存当前执行的环境状况)。在该前提下,镜像可以理解为一种构建时(build-time)结构,而容器可以理解为一种运行时(run-time)结构,如图:
 
在这里插入图片描述

 

 

二. Docker镜像详解

1. 镜像和容器的关系

我们可以使用docker container run命令从某个镜像启动一个或多个容器。一旦容器从镜像启动后,二者之间就变成了互相依赖的关系,并且在镜像上启动的容器全部停止之前,镜像是无法被删除的。

尝试删除镜像而不停止或销毁使用它的容器,会导致下面的错误。

$ docker image rm 

Error response from daemon: conflict: unable to remove repository reference \ "" (must force) - container is using its referenc\ ed image

 

2. 镜像通常比较小

容器追求快速和小巧,这意味着构建镜像的时候通常需要裁剪掉不必要的部分,保持较小的体积,比如:

  1. Docker镜像通常不会包含6个不同的Shell让读者选择:通常Docker镜像中只有一个精简的Shell,甚至没有Shell。
  2. 镜像不包含内核:容器都是共享所在Docker主机的内核(如cgroups和namespaces)。所以容器仅包含必要的操作系统(通常只有操作系统文件和文件系统对象)​。

 

3. 拉取镜像

查看主机镜像列表
Docker主机安装之后,本地并没有镜像。使用以下命令检查Docker主机的本地仓库中是否包含镜像。

% docker image ls 

REPOSITORY                  TAG         IMAGE ID       CREATED         SIZE
langgenius/dify-web         0.6.11      03ed4f2b51ea   8 weeks ago     229MB
langgenius/dify-api         0.6.11      38532784de04   8 weeks ago     2.55GB
...

通过下面的命令可以将镜像拉取到本地。

$ docker image pull ubuntu:latest

latest: Pulling from library/ubuntu

 

4. 镜像命名

4.1. 镜像仓库服务

Docker镜像存储在镜像仓库服务(Image Registry)当中。Docker客户端的镜像仓库服务是可配置的,默认使用Docker Hub。

镜像仓库服务包含多个镜像仓库(Image Repository)​。同样,一个镜像仓库中可以包含多个镜像。如下图展示了包含3个镜像仓库的镜像仓库服务,其中每个镜像仓库都包含一个或多个镜像。

 

4.2. 官方和非官方镜像仓库

Docker Hub也分为官方仓库(Official Repository)和非官方仓库(Unofficial Repository)​。

在这里插入图片描述

官方仓库中的镜像是由Docker公司审查的。这意味着其中的镜像会及时更新,由高质量的代码构成​。这些代码是安全的,有完善的文档和最佳实践。

大部分流行的操作系统和应用在Docker Hub的官方仓库中都有其对应镜像。这些镜像很容易找到,基本都在Docker Hub命名空间的顶层。

 

4.3. 镜像的命名和标签

只需要给出镜像的名字和标签,就能在官方仓库中定位一个镜像(采用“:”分隔)​。docker image pull命令的格式如下。

docker image pull <repository>:<tag>

$ docker image pull mongo:3.3.11 //该命令会从官方Mongo库拉取标签为3.3.11的镜像 
$ docker image pull redis:latest //该命令会从官方Redis库拉取标签为latest的镜像 
$ docker image pull alpine //该命令会从官方Alpine库拉取标签为latest的镜像
$ docker image pull nigelpoulton/tu-demo:v2 
//该命令会从以我自己的Docker Hub账号为命名空间的tu-demo库中下载标签为v2的镜像

注意:如果没有指定镜像标签,则Docker会拉取标签为latest的镜像。但标有latest标签的镜像不保证这是仓库中最新的镜像!例如,Alpine仓库中最新的镜像通常标签是edge。通常来讲,使用latest标签时需要谨慎!

 

5. 为镜像打多个标签

一个镜像可以根据用户需要设置多个标签,如下

# 指定 -a 参数来拉取仓库中的全部镜像。
$ docker image pull -a nigelpoulton/tu-demo

...

$ docker image ls
REPOSITORY              TAG     IMAGE ID       CREATED     SIZE
nigelpoulton/tu-demo     v2     6ac21e..bead   1 yr ago    211.6 MB
nigelpoulton/tu-demo     latest 9b915a..1e29   1 yr ago    211.6 MB
nigelpoulton/tu-demo     v1     9b915a..1e29   1 yr ago    211.6 MB

# 该命令从nigelpoulton/tu-demo仓库拉取了3个镜像:latest、v1以及v2。

注意看IMAGE ID这一列。只有两个不同的Image ID。这里实际只下载了两个镜像,其中有两个标签指向了相同的镜像。换句话说,其中一个镜像拥有两个标签。

 

6. 过滤镜像内容

Docker提供–filter参数过滤镜像内容。如下只会返回悬虚(dangling)镜像。

docker image ls --filter dangling=true

REPOSITORY    TAG       IMAGE ID       CREATED      SIZE
<none>        <none>    4fd34165afe0   7 days ago   14.5MB

 

6.1. 虚空镜像

那些没有标签的镜像被称为悬虚镜像,在列表中展示为<none>:<none>。通常出现这种情况,是因为构建了一个新镜像,然后为该镜像打了一个已经存在的标签。当此情况出现,Docker会构建新的镜像,接着移除旧镜像上面的标签,将该标签标在新的镜像之上。

例如

  1. 基于alpine:3.4构建一个新的镜像,并打上dodge:challenger标签。
  2. 更新Dockerfile,并构建一个新的镜像,并且标签为dodge:challenger,这时docker会移除旧镜像上面对应的标签,旧镜像就变成了悬虚镜像。

 

6.2. 删除虚空镜像

可以通过docker image prune命令移除全部的悬虚镜像。如果添加了-a参数,Docker会额外移除没有被使用的镜像(那些没有被任何容器使用的镜像)​。

 

6.3. 过滤器与格式化输出

  • dangling:可以指定true或者false,仅返回悬虚镜像(true)​,或者非悬虚镜像(false)​。
  • before:返回指定镜像之前被创建的全部镜像。
  • since:返回指定镜像之后创建的全部镜像。
  • label:根据标注(label)的名称或者值,对镜像进行过滤。
docker image ls --filter reference="*:latest"
REPOSITORY   TAG      IMAGE ID        CREATED      SIZE
alpine       latest   3fd9065eaf02    8 days ago   4.15MB
test         latest   8426e7efb777    3 days ago   122MB

 

可以使用--format对输出内容进行格式化。

# 例如,只返回Docker主机上镜像的大小属性。
 docker image ls --format "{{.Size}}"
99.3MB
111MB
82.6MB
88.8MB
4.15MB
108MB

# 返回仓库名,标签、大小
docker image ls --format "{{.Repository}}: {{.Tag}}: {{.Size}}"
dodge:  challenger: 99.3MB
ubuntu: latest:     111MB
python: 3.4-alpine: 82.6MB
python: 3.5-alpine: 88.8MB
alpine: latest:     4.15MB
nginx:  latest:     108MB

 

7. 搜索Docker Hub中的仓库

简单模式下,该命令会搜索所有“NAME”字段中包含特定字符串的仓库。例如,下面的命令会查找所有“NAME”包含“nigelpoulton”的仓库。

$ docker search nigelpoulton
NAME                         DESCRIPTION                STARS    AUTOMATED
nigelpoulton/pluralsight..   Web app used in...         8        [OK]
nigelpoulton/tu-demo                                    7
nigelpoulton/k8sbook         Kubernetes Book web app    1
nigelpoulton/web-fe1         Web front end example      0
nigelpoulton/hello-cloud     Quick hello-world image    0

 

8. 镜像分层

8.1. 镜像和分层

一个Docker镜像由一些松耦合的只读镜像层组成。Docker负责堆叠这些镜像层,并且将它们表示为单个统一的对象:即Docker镜像。

在这里插入图片描述

 

通过pull来查看仓库的分层。可以看到,这个镜像包含5个镜像层。

$ docker image pull ubuntu:latest
latest: Pulling from library/ubuntu
952132ac251a: Pull  complete
82659f8f1b76: Pull  complete
c19118ca682d: Pull  complete
8296858250fe: Pull  complete
24e0251a0e2c: Pull complete
...
# 在上面输出内容中,以Pull complete结尾的每一行都代表了镜像中某个被拉取的镜像层。

 

8.2. 共享镜像层

多个镜像之间会共享镜像层。这样可以有效节省空间并提升性能。如下例子

$ docker image pull -a nigelpoulton/tu-demo

latest: Pulling from nigelpoulton/tu-demo
...

v2: Pulling from nigelpoulton/tu-demo
237d5fcd25cf: Already exists
a3ed95caeb02: Already exists
<Snip>
eab5aaac65de: Pull complete
Digest: sha256:d3c0d8c9d5719d31b79c...fef58a7e038cf0ef2ba5eb74c

Status: Downloaded newer image for nigelpoulton/tu-demo

$ docker image ls
REPOSITORY             TAG      IMAGE ID       CREATED        SIZE
nigelpoulton/tu-demo   v2       6ac...ead   4 months ago   211.6 MB
nigelpoulton/tu-demo   latest   9b9...e29   4 months ago   211.6 MB
nigelpoulton/tu-demo   v1       9b9...e29   4 months ago   211.6 MB

Docker很聪明,可以识别出要拉取的镜像中,哪几层已经在本地存在。

Docker在Linux上支持很多存储引擎(Snapshotter)​。每个存储引擎都有自己的镜像分层、镜像层共享以及写时复制(CoW)技术的具体实现。但是,其最终效果和用户体验是完全一致的。

 

9. 镜像散列值

9.1. 根据摘要拉取镜像

如果出现给镜像打错标签的情况,有时甚至会给新镜像打一个已经存在的标签。

如下描述一个存在的问题

假设镜像golftrack:1.5存在一个已知的Bug。因此可以拉取该镜像后修复它,并使用相同的标签将更新的镜像重新推送回仓库。此时原镜像被覆盖,但在生产环境中遗留了大量运行中的容器,没有什么好办法区分正在使用的镜像版本是修复前还是修复后的,因为两个镜像的标签是相同的!

 

摘要
每一个镜像都会有有一个基于其内容的密码散列值即摘要。所以当镜像内容变更时,散列值一定会改变。这种方式可以解决前面讨论的问题。

在docker image ls命令之后添加--digests参数即可在本地查看镜像摘要。如下

# 拉取镜像
$ docker image pull alpine
Using default tag: latest
。。。
Digest: sha256:3dcdb92d7432d56604d...6d99b889d0626de158f73a

# 显示摘要
$ docker image ls --digests alpine
REPOSITORY  TAG     DIGEST              IMAGE ID      CREATED        SIZE
alpine      latest  sha256:3dcd...f73a  4e38e38c8ce0  10 weeks ago   4.8 MB

目前原生Docker命令不支持从远端镜像仓库服务(如Docker Hub)中获取镜像签名了。这意味着只能先通过标签方式拉取镜像到本地,然后自己维护镜像的摘要列表。
 

下面的例子首先在Docker主机上删除alpine:latest镜像,然后显示如何通过摘要来再次拉取该镜像。

$ docker image rm alpine:latest
Untagged: alpine:latest
Untagged: alpine@sha256:c0537...7c0a7726c88e2bb7584dc96
Deleted: sha256:02674b9cb179d...abff0c2bf5ceca5bad72cd9
Deleted: sha256:e154057080f40...3823bab1be5b86926c6f860

$ docker image pull alpine@sha256:c0537...7c0a7726c88e2bb7584dc96
sha256:c0537...7726c88e2bb7584dc96: Pulling from library/alpine
cfc728c1c558: Pull complete
Digest: sha256:c0537ff6a5218...7c0a7726c88e2bb7584dc96
Status: Downloaded newer image for alpine@sha256:c0537...bb7584dc96

 

9.2. 镜像散列值(摘要)

镜像本身是一个配置对象,其中包含了镜像层的列表以及一些元数据信息。镜像层才是实际数据存储的地方(比如文件等,镜像层之间是完全独立的,并没有从属于某个镜像集合的概念)​。

镜像的唯一标识是一个加密ID,即配置对象本身的散列值。每个镜像层也由一个加密ID区分,其值为镜像层本身内容的散列值。

这意味着修改镜像的内容或其中任意的镜像层,都会导致加密散列值的变化。

 

9.3. 压缩散列值

我们再看一个场景:在推送和拉取镜像的时候,都会对镜像层进行压缩来节省网络带宽以及仓库二进制存储空间。

例如,在推送镜像层到Docker Hub的时候,Docker Hub会尝试确认接收到的镜像没有在传输过程中被篡改。为了完成校验,Docker Hub会根据镜像层重新计算散列值,并与原散列值进行比较。因为镜像在传输过程中被压缩​,所以散列值的校验也会失败。为避免该问题,每个镜像层同时会包含一个分发散列值(DistributionHash)​。这是一个压缩版镜像的散列值,当从镜像仓库服务拉取或者推送镜像的时候,其中就包含了分发散列值,该散列值会用于校验拉取的镜像是否被篡改过

这个内容寻址存储模型极大地提升了镜像的安全性,因为在拉取和推送操作后提供了一种方式来确保镜像和镜像层数据是一致的。

 

10. 删除镜像

删除操作会在当前主机上删除该镜像以及相关的镜像层。

$ docker image rm 02674b9cb179
Untagged: alpine@sha256:c0537ff6a5218...c0a7726c88e2bb7584dc96
Deleted: sha256:02674b9cb179d57...31ba0abff0c2bf5ceca5bad72cd9
Deleted: sha256:e154057080f4063...2a0d13823bab1be5b86926c6f860

注意:

  1. 如果某个镜像层被多个镜像共享,那只有当全部依赖该镜像层的镜像都被删除后,该镜像层才会被删除。
  2. 如果被删除的镜像上存在运行状态的容器,那么删除操作不会被允许。

 

一种删除某Docker主机上全部镜像的快捷方式是在docker image rm命令中传入当前系统的全部镜像ID,可以通过docker image ls获取全部镜像ID(使用-q参数)​。

$ docker image rm $(docker image ls -q) -f
# -q:只返回了系统中本地拉取的全部镜像的ID列表

谨慎使用!!!

 

三. 多架构镜像:支持多架构和多平台

读者会突然发现,在拉取镜像并运行之前,需要考虑镜像是否与当前运行环境的架构匹配,这破坏了Docker的流畅体验。多架构镜像(Multi-architecture Image)的出现解决了这个问题!这意味着某个镜像仓库标签(repository:tag)下的镜像可以同时支持64位Linux、PowerPC Linux、64位Windows和ARM等多种架构。简单地说,就是一个镜像标签之下可以支持多个平台和架构

 

为了实现这个特性,镜像仓库服务API支持两种重要的结构:Manifest列表(新)和Manifest。

Manifest列表是指某个镜像标签支持的架构列表。其支持的每种架构,都有自己的Mainfest定义,其中列举了该镜像的构成。

如下图:使用Golang官方镜像作为示例。图左侧是Manifest列表,其中包含了该镜像支持的每种架构。Manifest列表的每一项都有一个箭头,指向具体的Manifest,其中包含了镜像配置和镜像层数据。
在这里插入图片描述

这里:不需要告知Docker具体的镜像版本是64位Linux还是64位Windows。

$ docker container run --rm golang go version

Unable to find image 'golang:latest' locally
latest: Pulling from library/golang
723254a2c089: Pull complete
<Snip>
39cd5f38ffb8: Pull complete
Digest: sha256:947826b5b6bc4...
Status: Downloaded newer image for golang:latest
go version go1.9.2 linux/amd64

 

四. 镜像:命令

命令说明
docker image pull1. 镜像从远程镜像仓库服务的仓库中下载。
2. 默认情况下,镜像会从Docker Hub的仓库中拉取。
docker image ls列出了本地Docker主机上存储的镜像。可以通过–digests参数来查看镜像的SHA256签名。
docker image inspect展示了镜像的细节,包括镜像层数据和元数据。
docker image rm用于删除镜像。docker image rm alpine:latest命令的含义是删除alpine:latest镜像。当镜像存在关联的容器,并且容器处于运行(Up)或者停止(Exited)状态时,不允许删除该镜像。
Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐