简介
镜像、Docker镜像、容器镜像以及OCI镜像都指的是同一件事情,我们将会交替使用这些术语。
容器镜像是一个只读的包,其中包含了运行一个应用程序所需的一切内容。它包括应用程序代码、应用程序依赖、最小化的操作系统构件以及元数据。一个单独的镜像可以用来启动一个或多个容器。
如果您熟悉VMware,可以将镜像类比为VM模板。一个VM模板就像一个已停止的虚拟机,而容器镜像则类似于一个已停止的容器。如果您是开发人员,可以将它们类比为类。您可以从一个类创建一个或多个对象,就像您可以从一个镜像创建一个或多个容器。
您可以通过从镜像仓库中拉取镜像来获取容器镜像。最常见的仓库是Docker Hub,但也存在其他仓库。拉取操作将镜像下载到本地的Docker主机上,在那里Docker可以使用它来启动一个或多个容器。
镜像由多个层次组成,这些层次堆叠在一起,表示为一个单一对象。镜像内部包含了一个精简的操作系统(OS)以及运行应用程序所需的所有文件和依赖项。因为容器旨在快速轻量,所以镜像往往是小型的(Windows镜像往往巨大)。
这就是一个简要的介绍。让我们深入一点。
深入
我们已经多次提到过镜像就像是已停止的容器。实际上,您可以停止一个容器并从中创建一个新的镜像。考虑到这一点,镜像被认为是构建时的结构,而容器则是运行时的结构。
镜像和容器
上图显示了镜像与容器之间关系的高级视图。我们使用docker run
和docker service create
命令从单个镜像启动一个或多个容器。一旦从镜像启动了一个容器,这两个结构就会相互依赖,直到使用该镜像的最后一个容器停止并销毁之前,您不能删除该镜像。
镜像通常很小
容器的整个目的是运行单个应用程序或服务。这意味着它只需要运行的应用程序的代码和依赖项 - 不需要其他任何内容。因此,镜像也是小巧且精简的,剔除了所有非必要的部分。
举例来说,在我撰写这段内容时,官方的 Alpine Linux 镜像只有 7MB。这是因为它不包含6种不同的shell,三种不同的软件包管理器等等... 实际上,很多镜像甚至不包含shell或软件包管理器 - 如果应用程序不需要,就不会包含在镜像中。
镜像不包含内核。这是因为容器共享它们运行所在主机的内核。通常,在镜像中只包含一些重要的文件系统组件和其他基本结构。这也是为什么有时会听到人们说"镜像包含足够的操作系统"。
基于Windows的镜像通常比基于Linux的镜像要大得多,这是因为Windows操作系统的工作方式不同。Windows镜像往往几个GB大小,推送和拉取需要较长时间。
拉取镜像
干净安装的Docker主机在其本地仓库中没有任何镜像。
Linux主机上的本地镜像仓库通常位于 /var/lib/docker/。如果您在Mac或PC上使用Docker Desktop,一切都在一个虚拟机中运行。
您可以使用以下命令检查您的Docker主机是否在其本地仓库中有任何镜像。
ruby
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
将镜像传送到Docker主机的过程称为拉取(pulling)。因此,如果您想在Docker主机上获取最新的Busybox镜像,您就需要拉取它。使用以下命令来拉取一些镜像,然后检查它们的大小。
如果您正在Linux上进行操作,并且尚未将您的用户帐户添加到本地docker Unix组中,您可能需要在以下所有命令前面添加sudo。
Linux示例:
makefile
$ docker pull redis:latest
latest: Pulling from library/redis
b5d25b35c1db: Pull complete
6970efae6230: Pull complete
fea4afd29d1f: Pull complete
7977d153b5b9: Pull complete
7945d827bd72: Pull complete
b6aa3d1ce554: Pull complete
Digest: sha256:ea30bef6a1424d032295b90db20a869fc8db76331091543b7a80175cede7d887
Status: Downloaded newer image for redis:latest
docker.io/library/redis:latest
$ docker pull alpine:latest
latest: Pulling from library/alpine
08409d417260: Pull complete
Digest: sha256:02bb6f428431fbc2809c5d1b41eab5a68350194fb508869a33cb1af4444c9b11
Status: Downloaded newer image for alpine:latest
docker.io/library/alpine:latest
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine latest 44dd6f223004 9 days ago 7.73MB
redis latest 2334573cc576 2 weeks ago 111MB
Windows 示例:
makefile
> docker pull mcr.microsoft.com/powershell:latest
latest: Pulling from powershell
5b663e3b9104: Pull complete
9018627900ee: Pull complete
133ab280ee0f: Pull complete
084853899645: Pull complete
399a2a3857ed: Pull complete
6c1c6d29a559: Pull complete
d1495ba41b1c: Pull complete
190bd9d6eb96: Pull complete
7c239384fec8: Pull complete
21aee845547a: Pull complete
f951bda9026b: Pull complete
Digest: sha256:fbc9555...123f3bd7
Status: Downloaded newer image for mcr.microsoft.com/powershell:latest
mcr.microsoft.com/powershell:latest
> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mcr.microsoft.com/powershell latest 73175ce91dff 2 days ago 495MB
mcr.microsoft.com/.../iis latest 6e5c6561c432 3 days ago 5.05GB
正如您所见,这些镜像现在位于Docker主机的本地存储库中。您还可以看到Windows镜像要大得多,由许多更多的层组成。
镜像命名
在拉取镜像时,您必须指定要拉取的镜像名称。让我们花点时间了解镜像命名。为了做到这一点,我们需要了解镜像是如何存储的一些背景知识。
镜像注册表
我们将镜像存储在称为注册表的集中位置。大多数现代注册表实现了OCI分发规范,我们有时称之为OCI注册表。注册表的任务是安全地存储容器镜像,并使它们易于从不同的环境中访问。一些注册表提供高级服务,如镜像扫描和与构建流水线的集成。
最常见的注册表是Docker Hub,但还有其他注册表,包括第三方注册表和安全的本地注册表。然而,Docker客户端是有偏见的,并默认使用Docker Hub。在本书的剩余部分中,我们将使用Docker Hub。
以下命令的输出被剪辑了,但您可以看到Docker配置为使用index.docker.io/v1/作为其默认注册表。这会自动重定向到index.docker.io/v2/。
yaml
$ docker info
<Snip>
Default Runtime: runc
containerd version: 2806fc1057397dbaeefbea0e4e17bddfbd388f38
runc version: v1.1.5-0-gf19387a
Registry: https://index.docker.io/v1/
<Snip>
镜像注册表包含一个或多个镜像仓库。反过来,镜像仓库包含一个或多个镜像。这可能有点令人困惑,因此图6.2展示了一个包含3个仓库的镜像注册表的图片,每个仓库都有一个或多个镜像。
官方仓库
Docker Hub具有官方仓库的概念。
正如名称所示,官方仓库是经过应用程序供应商和Docker公司审核和策划的图像的家园。这意味着它们应该包含最新、高质量的代码,安全、有文档,符合最佳实践。
如果一个仓库不是官方仓库,它可能就像野西一样 ------ 您不应该假设它们是安全的、有文档的,或者根据最佳实践构建的。这并不是说它们包含的图像是坏的。在非官方仓库中有一些优秀的东西。您只需要在信任其代码之前要非常小心。说实话,您永远不应该相信来自互联网的软件 ------ 即使是来自官方仓库的图像也是如此。
大多数受欢迎的应用程序和操作系统在Docker Hub上都有官方仓库。它们很容易辨认,因为它们位于Docker Hub命名空间的顶级,并带有绿色的"Docker official image"徽章。以下列表显示了一些位于Docker Hub命名空间顶级的官方仓库及其URL:
- nginx: hub.docker.com/_/nginx/
- busybox: hub.docker.com/_/busybox/
- redis: hub.docker.com/_/redis/
- mongo: hub.docker.com/_/mongo/ 另一方面,我的个人图像位于非官方仓库的野西地区,不应受到信任。以下是我仓库中图像的一些示例:
- nigelpoulton/tu-demo --- hub.docker.com/r/nigelpoul...
- nigelpoulton/pluralsight-docker-ci --- hub.docker.com/r/nigelpoul... 我的仓库中的图像不仅未经审核、未保持更新、不安全、没有良好的文档,而且它们也不位于Docker Hub命名空间的顶级。我的仓库都位于nigelpoulton的第二级命名空间内。
在所有这些之后,我们终于可以看一下如何在Docker命令行中寻址图像了。
镜像命名和标记
从官方仓库中获取镜像就像提供仓库名称和标签,中间用冒号(:)分隔一样简单。当使用来自官方仓库的镜像时,docker pull的格式为:
$ docker pull <仓库名称>:<标签>
在前面的Linux示例中,我们使用以下两个命令获取了Alpine和Redis镜像:
ruby
$ docker pull alpine:latest
$ docker pull redis:latest
这些命令从顶级的"alpine"和"redis"仓库中拉取了标记为"latest"的镜像。
以下示例展示了如何从官方仓库中拉取不同的镜像:
arduino
$ docker pull mongo:4.2.24
//This will pull the image tagged as `4.2.24` from the official `mongo` repository.
$ docker pull busybox:glibc
//This will pull the image tagged as `glibc` from the official `busybox` repository.
$ docker pull alpine
//This will pull the image tagged as `latest` from the official `alpine` repository.
关于这些命令有几点要注意的事项。
首先,如果在仓库名称后没有指定镜像标签,Docker 将默认您正在引用标记为"latest"的镜像。如果仓库中没有标记为最新的镜像,则命令将失败。
其次,"latest"标签没有任何神奇的能力。仅仅因为一个镜像被标记为"latest"并不能保证它是仓库中最新的镜像!
从非官方仓库中拉取镜像本质上是相同的,您只需要在仓库名称之前加上 Docker Hub 的用户名或组织名。以下示例显示了如何从一个不值得信任的用户(Docker Hub 账户名为 nigelpoulton)拥有的 tu-demo 仓库中拉取 v2 镜像。
arduino
$ docker pull nigelpoulton/tu-demo:v2
//This will pull the image tagged as `v2`
//from the `tu-demo` repository within the `nigelpoulton` namespace
如果您想从第三方注册表(非 Docker Hub)拉取镜像,只需在仓库名称前加上注册表的 DNS 名称。例如,以下命令从 Google Container Registry(gcr.io)上的 google-containers/git-sync 仓库拉取 3.1.5 镜像。
makefile
$ docker pull gcr.io/google-containers/git-sync:v3.1.5
v3.1.5: Pulling from google-containers/git-sync
597de8ba0c30: Pull complete
b263d8e943d1: Pull complete
a20ed723abc0: Pull complete
49535c7e3a51: Pull complete
4a20d0825f07: Pull complete
Digest: sha256:f38673f25b8...b5f8f63c4da7cc6
Status: Downloaded newer image for gcr.io/google-containers/git-sync:v3.1.5
gcr.io/google-containers/git-sync:v3.1.5
请注意,从 Docker Hub 和其他注册表拉取镜像的体验完全相同。
具有多个标签的镜像
关于镜像标签的最后一点... 一个单独的镜像可以有任意多个标签。这是因为标签是任意的字母数字值,存储在镜像的元数据中。
乍一看,以下输出似乎显示了三个镜像。然而,仔细观察后,实际上是两个镜像 -- 带有 c610c6a38555 ID 的镜像被标记为 latest 和 v1。
bash
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nigelpoulton/tu-demo latest c610c6a38555 22 months ago 58.1MB
nigelpoulton/tu-demo v1 c610c6a38555 22 months ago 58.1MB
nigelpoulton/tu-demo v2 6ba12825d092 16 months ago 58.6MB
这是一个关于之前所提到的关于 latest 标签的警告的完美例子。在这个例子中,latest 标签指的是与 v1 标签相同的镜像。这意味着它指向两个镜像中较旧的那个!故事的寓意是,latest 是一个任意的标签,不能保证它指向仓库中最新的镜像!
过滤 docker images 的输出
Docker提供了--filter标志来过滤docker images返回的镜像列表。
以下示例只会返回悬空的镜像。
css
$ docker images --filter dangling=true
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 4fd34165afe0 7 days ago 14.5MB
悬空图像是不再具有标签并在列表中显示为:的图像。它们通常在使用已经存在的标签构建新图像时发生。在这种情况下,Docker将构建新图像,注意到现有图像已经具有相同的标签,然后从现有图像中删除该标签并将其分配给新图像。
考虑以下示例,您基于alpine:3.4构建一个新的应用程序图像,并将其标记为dodge:challenger。然后,您将图像更新为使用alpine:3.5而不是alpine:3.4。当您构建新图像时,该操作将创建一个新的带有dodge:challenger标签的图像,并从旧图像中删除标签。旧图像将变为悬空图像。
您可以使用docker image prune命令删除系统中的所有悬空图像。如果添加了-a标志,Docker还将删除所有未使用的图像(即不被任何容器使用的图像)。
Docker目前支持以下过滤器:
- dangling:接受true或false,并仅返回悬空图像(true)或非悬空图像(false)。
- before:需要图像名称或ID作为参数,并返回在其之前创建的所有图像。
- since:与上述相同,但返回在指定图像之后创建的图像。
- label:基于标签或标签和值的存在对图像进行过滤。docker images命令不会在其输出中显示标签。
- 对于其他所有过滤操作,您可以使用reference。
以下是一个使用reference的示例,仅显示标记为"latest"的图像。撰写本文时,此功能在某些Docker安装上有效,但在其他安装上可能无效(可能不适用于使用containerd进行图像管理的系统)。
ini
$ docker images --filter=reference="*:latest"
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox latest 3596868f4ba8 7 days ago 3.72MB
alpine latest 44dd6f223004 9 days ago 7.73MB
redis latest 2334573cc576 2 weeks ago 111MB
您还可以使用--format标志使用Go模板格式化输出。例如,以下命令仅会返回Docker主机上图像的大小属性。
perl
$ docker images --format "{{.Size}}"
3.72MB
7.73MB
111MB
265MB
58.1MB
使用以下命令返回所有图像,但仅显示仓库、标签和大小。
yaml
$ docker images --format "{{.Repository}}: {{.Tag}}: {{.Size}}"
busybox: latest: 3.72MB
alpine: latest: 7.73MB
redis: latest: 111MB
portainer/portainer-ce: latest: 265MB
nigelpoulton/tu-demo: latest: 58.1MB
<Snip>
如果您需要更强大的过滤功能,您总是可以使用操作系统和Shell提供的工具,比如grep和awk。您还可以找到一个有用的Docker Desktop扩展。
从命令行界面搜索Docker Hub
docker search命令允许您从命令行界面搜索Docker Hub。它的价值有限,因为您只能针对"名称"字段中的字符串进行模式匹配。然而,您可以根据返回的任何列来过滤输出。
在其最简单的形式中,它会搜索所有包含特定字符串的存储库在"名称"字段中。例如,以下命令搜索所有在"名称"字段中包含"nigelpoulton"的存储库。
bash
$ docker search nigelpoulton
NAME DESCRIPTION STARS AUTOMATED
nigelpoulton/pluralsight.. Web app used in... 22 [OK]
nigelpoulton/tu-demo 12
nigelpoulton/k8sbook Kubernetes Book web app 2
nigelpoulton/workshop101 Kubernetes 101 Workshop 0
<Snip>
"名称"字段是存储库名称。这包括非官方存储库的Docker ID或组织名称。例如,以下命令列出所有包含字符串"alpine"的名称的存储库。
bash
$ docker search alpine
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
alpine A minimal Docker.. 9962 [OK]
rancher/alpine-git 1
grafana/alpine Alpine Linux with.. 4
<Snip>
注意返回的一些存储库是官方的,而另一些是非官方的。您可以使用 --filter "is-official=true" 以便只显示官方存储库。
css
$ docker search alpine --filter "is-official=true"
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
alpine A minimal Docker.. 9962 [OK]
关于 docker search 还有最后一点。默认情况下,Docker 只会显示 25 行结果。但是,您可以使用 --limit 标志将其增加到最多 100 行。
镜像和层级
Docker 镜像是一个由松散连接的只读层级组成的集合,每个层级由一个或多个文件组成。图 6.3 展示了一个具有 5 个层级的镜像。
Docker 负责将这些层级堆叠起来,并将它们表示为一个单一的统一对象。
有几种方法可以查看和检查构成镜像的层级。实际上,我们之前在拉取镜像时就见过其中一种。下面的示例更详细地查看了镜像拉取操作。
makefile
$ docker pull ubuntu:latest
latest: Pulling from library/ubuntu
952132ac251a: Pull complete
82659f8f1b76: Pull complete
c19118ca682d: Pull complete
8296858250fe: Pull complete
24e0251a0e2c: Pull complete
Digest: sha256:f4691c96e6bbaa99d...28ae95a60369c506dd6e6f6ab
Status: Downloaded newer image for ubuntu:latest
docker.io/ubuntu:latest
上面输出中以"Pull complete"结尾的每一行代表了被拉取的镜像中的一个层级。正如我们所见,这个镜像有5个层级,并且在图6.4中显示了层级的ID。
查看镜像层级的另一种方法是使用docker inspect命令检查镜像。以下示例检查了相同的ubuntu:latest镜像。
css
$ docker inspect ubuntu:latest
[
{
"Id": "sha256:bd3d4369ae.......fa2645f5699037d7d8c6b415a10",
"RepoTags": [
"ubuntu:latest"
<Snip>
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:c8a75145fc...894129005e461a43875a094b93412",
"sha256:c6f2b330b6...7214ed6aac305dd03f70b95cdc610",
"sha256:055757a193...3a9565d78962c7f368d5ac5984998",
"sha256:4837348061...12695f548406ea77feb5074e195e3",
"sha256:0cad5e07ba...4bae4cfc66b376265e16c32a0aae9"
]
}
}
]
精简后的输出再次显示了5个图层。但这一次它们是使用它们的SHA256哈希值显示的。
docker inspect命令是查看镜像详细信息的好方法。
docker history命令是检查镜像并查看层数据的另一种方法。但是,它显示的是镜像的构建历史,不是最终镜像的严格层列表。例如,一些Dockerfile指令("ENV"、"EXPOSE"、"CMD"和"ENTRYPOINT")会向镜像添加元数据,而不会创建图层。
所有的Docker镜像都以基础图层开始,随着所做的更改和添加新内容,新的图层会被添加在顶部。
考虑以下过于简化的示例,构建一个简单的Python应用程序。您可能会有一个公司政策,要求所有应用程序都基于官方的Ubuntu 22:04镜像。这将成为您镜像的基础图层。添加Python软件包将在基础图层之上添加第二个图层。如果您稍后添加源代码文件,这些文件将作为附加图层添加。最终镜像将有三个图层,如图6.5所示(请记住,这是为了演示目的而过于简化的示例)。
重要的是要理解,随着添加额外的图层,镜像始终是按照添加的顺序堆叠的所有图层的组合。以图6.6中显示的两个图层的简单示例为例。每个图层都有3个文件,但总体上的镜像有6个文件,因为它是两个图层的组合。
在图6.7中稍微复杂一点的三层图像示例中,总体镜像在统一视图中只显示6个文件。这是因为顶层中的文件7是下面的文件5(内联)的更新版本。在这种情况下,高层中的文件会遮挡其下方的文件。这允许将文件的更新版本作为新的图层添加到镜像中。
Docker采用了一个存储驱动程序,负责堆叠层并将它们呈现为一个统一的文件系统/镜像。在Linux上的存储驱动程序示例包括overlay2、devicemapper、btrfs和zfs。正如它们的名称所示,每个存储驱动程序都基于Linux文件系统或块设备技术,每个都具有自己独特的性能特点。
无论使用哪个存储驱动程序,用户体验都是相同的。
图6.8显示了与系统显示相同的3层图像。即所有三个层都堆叠和合并,形成一个单一的统一视图。
共享镜像层次
多个镜像可以共享层。这导致了空间和性能上的效率。
下面的示例显示了带有 -a 标志的 docker pull 命令的输出。这可以用来下载仓库中的所有镜像。该命令有限制,如果仓库中有多个平台和架构(例如 Linux 和 Windows,或 amd64 和 arm64)的镜像,可能会失败。
vbnet
$ docker pull -a nigelpoulton/tu-demo
latest: Pulling from nigelpoulton/tu-demo
aad63a933944: Pull complete
f229563217f5: Pull complete
<Snip>>
Digest: sha256:c9f8e18822...6cbb9a74cf
v1: Pulling from nigelpoulton/tu-demo
aad63a933944: Already exists
f229563217f5: Already exists
<Snip>
fc669453c5af: Pull complete
Digest: sha256:674cb03444...f8598e4d2a
v2: Pulling from nigelpoulton/tu-demo
Digest: sha256:c9f8e18822...6cbb9a74cf
Status: Downloaded newer image for nigelpoulton/tu-demo
docker.io/nigelpoulton/tu-demo
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nigelpoulton/tu-demo latest d5e1e48cf932 2 weeks ago 104MB
nigelpoulton/tu-demo v2 d5e1e48cf932 2 weeks ago 104MB
nigelpoulton/tu-demo v1 6852022de69d 2 weeks ago 104MB
注意那些以 Already exists 结尾的行。
这些行告诉我们,Docker 足够智能,能够识别当被要求拉取一个已经有本地副本的镜像层时。在这个示例中,Docker 首先拉取了标记为 latest 的镜像。然后,在拉取 v1 和 v2 镜像时,它注意到已经有了组成这些镜像的一些层。这是因为该仓库中的三个镜像几乎完全相同,因此它们共享许多层。实际上,v1 和 v2 之间唯一的区别就是最顶层。
正如之前提到的,Linux 上的 Docker 支持许多存储驱动。每个驱动都可以以自己的方式实现镜像分层、层共享和写时复制(Copy-On-Write)行为。然而,最终的结果和用户体验是相同的。
通过摘要拉取镜像
到目前为止,我们已经向您展示了如何使用名称(标签)来拉取镜像。这绝对是最常见的方法,但它有一个问题 - 标签是可变的!这意味着有可能意外地使用错误的标签(名称)给镜像打标签。有时,甚至可能将镜像与现有但不同的镜像打上相同的标签。这可能会引发问题!
例如,想象一下,您有一个名为golftrack:1.5的镜像,其中有一个已知的错误。您拉取镜像,应用修复,然后使用相同的标签将更新后的镜像推送回其仓库。
花点时间考虑一下发生了什么......您有一个名为golftrack:1.5的镜像,其中存在一个错误。这个镜像被您的生产环境中的容器使用。您创建了一个包含修复的新版本镜像。然后是错误...您构建并将修复后的镜像使用与易受攻击的镜像相同的标签推送回其仓库!这会覆盖原始镜像,并且没有很好的方法来知道哪些生产容器使用易受攻击的镜像,哪些使用了修复的镜像 - 它们都有相同的标签!
这就是图像摘要的用武之地。
Docker支持内容寻址存储模型。作为该模型的一部分,所有镜像都会获得一个密码学内容哈希。为了讨论方便,我们将这个哈希称为摘要。由于摘要是镜像内容的哈希,因此不可能在不创建新的唯一摘要的情况下更改镜像的内容。换句话说,您无法更改镜像的内容并保留旧的摘要。这意味着摘要是不可变的,并且提供了解决刚才提到的问题的方法。
每次拉取镜像时,docker pull命令都会将镜像的摘要作为返回的信息的一部分。您还可以通过在docker images命令中添加--digests标志来查看Docker主机本地仓库中的镜像摘要。以下是这两个示例。
vbnet
$ docker pull alpine
Using default tag: latest
latest: Pulling from library/alpine
08409d417260: Pull complete
Digest: sha256:02bb6f42...44c9b11
Status: Downloaded newer image for alpine:latest
docker.io/library/alpine:latest
$ docker images --digests alpine
REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE
alpine latest sha256:02bb6f42...44c9b11 44dd6f223004 9 days ago 7.73MB
上面截取的输出显示了alpine镜像的摘要,格式为 -
sha256:02bb6f42...44c9b11
现在我们知道了镜像的摘要,我们可以在再次拉取镜像时使用它。这将确保我们获得我们期望的完全相同的镜像!
在撰写本文时,没有本地的Docker命令可以从远程仓库(如Docker Hub)中检索镜像的摘要。这意味着确定镜像的摘要的唯一方法是按标签拉取它,然后记下其摘要。这可能会在将来发生改变。
以下示例删除了您的Docker主机上的alpine:latest镜像,然后显示了如何使用其摘要而不是其标签再次拉取它。实际的摘要在书中被截断,以适应一行。请将其替换为您在自己系统上拉取的版本的真实摘要。
makefile
$ docker rmi alpine:latest
Untagged: alpine:latest
Untagged: alpine@sha256:02bb6f428431fbc2809c5d1b41eab5a68350194fb508869a33cb1af4444c9b11
Deleted: sha256:44dd6f2230041eede4ee5e792728313e43921b3e46c1809399391535c0c0183b
Deleted: sha256:94dd7d531fa5695c0c033dcb69f213c2b4c3b5a3ae6e497252ba88da87169c3f
$ docker pull alpinesha256:02bb6f42...44c9b11
docker.io/library/alpine@sha256:02bb6f42...44c9b11: Pulling from library/alpine
08409d417260: Pull complete
Digest: sha256:02bb6f428431...9a33cb1af4444c9b11
Status: Downloaded newer image for alpine@sha256:02bb6f428431...9a33cb1af4444c9b11
docker.io/library/alpine@sha256:02bb6f428431...9a33cb1af4444c9b11
再多说一些关于镜像哈希(摘要)的内容
正如之前提到的,镜像是由独立的多个层组成的松散集合。
在某种程度上,镜像只是一个清单文件,列出了各个层和一些元数据。应用程序和依赖项存在于这些层中,每个层都是完全独立的,没有构建成更大整体的概念。
每个镜像由一个加密 ID 标识,这是清单文件的哈希值。每个层由一个加密 ID 标识,这是层内容的哈希值。
这意味着改变镜像或其任何层的内容将导致关联的加密哈希发生变化。因此,镜像和层是不可变的,我们可以轻松地识别是否进行了更改。
到目前为止,事情还相当简单。但接下来会变得稍微复杂一些。
当我们推送和拉取镜像时,为了节省网络带宽和存储空间,层会被压缩。然而,压缩的内容与未压缩的内容是不同的。因此,在推送或拉取操作之后,内容哈希不再匹配。
这带来了各种问题。例如,Docker Hub会验证每个推送的层,以确保其在传输过程中没有被篡改。为了做到这一点,它会对层内容进行哈希运算,并将其与发送的哈希进行比较。由于层被压缩,哈希验证将失败。
为了解决这个问题,每个层还会得到一个称为分发哈希的东西。这是层压缩版本的哈希,每次推送和拉取到注册表时都会包含在每个层中。这用于验证层是否在没有被篡改的情况下到达。
多架构镜像
Docker最好的一点就是其简单性。然而,随着技术的发展,它们变得越来越复杂。当Docker开始支持不同的平台和架构,例如Windows和Linux,在不同变体的ARM、x64、PowerPC和s390x上,突然之间,流行的镜像在不同的平台和架构上都有版本,作为用户,我们必须添加额外的步骤,以确保我们拉取适用于我们环境的正确版本。这破坏了顺畅的Docker体验。
注意:我们使用术语"架构"来指代CPU架构,如x64和ARM。我们使用术语"平台"来指代操作系统(Linux或Windows)或操作系统和架构的组合。
多架构镜像来拯救!
幸运的是,有一种巧妙的方法来支持多架构镜像。这意味着一个单一的镜像,比如golang:latest,可以有适用于x64上的Linux、PowerPC上的Linux、Windows x64、不同版本ARM上的Linux等多个平台和架构的镜像。清楚起见,我们说的是一个单一的镜像标签支持多个平台和架构。我们马上会看到它的实际操作,但这意味着您可以在任何平台或架构上运行一个简单的docker pull golang:latest命令,Docker将会拉取正确的镜像。
为了实现这一点,Registry API支持两个重要的结构:
- manifest列表
- manifests
Manifest列表的含义正如它的名字所示:它是一个特定镜像标签支持的架构列表。然后,每个受支持的架构都有自己的清单,其中列出了用于构建它的层。
图6.9以官方的golang镜像为例。左边是manifest列表,其中包含了镜像支持的每个架构的条目。箭头表示manifest列表中的每个条目指向一个包含图像配置和层数据的manifest。
让我们先在实际操作之前了解一下理论。
假设您正在树莓派上运行Docker(ARM上的Linux)。当您拉取一个镜像时,Docker会向Docker Hub发出相关的调用。如果镜像存在manifest列表,将会解析该列表,以查看是否存在适用于ARM上的Linux的条目。如果存在,将检索Linux ARM镜像的manifest,并解析其各个层的加密ID。然后,每个层都将从Docker Hub拉取并在Docker主机上组装起来。
现在我们来看看实际操作。
以下示例分别来自Linux ARM系统和Windows x64系统。两者都基于官方的golang镜像启动一个新的容器,并运行go version命令。输出显示了Go的版本以及主机的平台和CPU架构。注意这两个命令完全相同,Docker会负责获取适用于该平台和架构的正确镜像!
ARM64上的Linux示例:
bash
$ docker run --rm golang go version
<Snip>
go version go1.20.4 linux/arm64
x64上的Windows示例:
bash
> docker run --rm golang go version
<Snip>
go version go1.20.4 windows/amd64
Windows上的Golang镜像目前大小超过2GB,可能需要很长时间才能下载。
'docker manifest'命令允许您检查Docker Hub上任何镜像的清单列表。以下示例检查了Golang镜像在Docker Hub上的清单列表。您可以看到Linux和Windows支持多种CPU架构。您可以在不使用grep过滤器的情况下运行相同的命令,以查看完整的JSON清单列表。
perl
$ docker manifest inspect golang | grep 'architecture\|os'
"architecture": "amd64",
"os": "linux"
"architecture": "arm",
"os": "linux",
"architecture": "arm64",
"os": "linux",
"architecture": "386",
"os": "linux"
"architecture": "mips64le",
"os": "linux"
"architecture": "ppc64le",
"os": "linux"
"architecture": "s390x",
"os": "linux"
"architecture": "amd64",
"os": "windows",
"os.version": "10.0.20348.1726"
"architecture": "amd64",
"os": "windows",
"os.version": "10.0.17763.4377"
所有官方镜像都有清单列表。
您可以使用docker buildx为不同的平台和架构创建自己的构建,然后使用docker manifest create创建自己的清单列表。
以下命令从当前目录构建一个名为myimage:arm-v7的ARMv7镜像。它基于github.com/nigelpoulto...中的代码。
dart
$ docker buildx build --platform linux/arm/v7 -t myimage:arm-v7 .
[+] Building 43.5s (11/11) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 368B 0.0s
<Snip>
=> => exporting manifest list sha256:2a621c3d06...84f9395d6 0.0s
=> => naming to docker.io/library/myimage:arm-v7 0.0s
=> => unpacking to docker.io/library/myimage:arm-v7 0.8s
这个命令的美妙之处在于您不必从一个ARMv7的Docker节点运行它。实际上,上面显示的示例是在x64硬件上的Linux上运行的。
删除镜像
当您不再需要Docker主机上的镜像时,您可以使用docker rmi命令删除它。rmi是remove image的缩写,意为删除镜像。
删除一个镜像将从您的Docker主机中删除镜像及其所有的层。这意味着它将不再出现在docker images命令中,并且包含层数据的Docker主机上的所有目录都将被删除。然而,如果一个镜像层被其他镜像共享,它只有在所有引用它的镜像都被删除后才会被删除。
使用docker rmi命令删除前面步骤中拉取的镜像。以下示例通过其ID删除镜像,这在您的系统上可能不同。
makefile
$ docker rmi 44dd6f223004
Untagged: alpine@sha256:02bb6f428431fbc2809c5d1...9a33cb1af4444c9b11
Deleted: sha256:44dd6f2230041eede4ee5e7...09399391535c0c0183b
Deleted: sha256:94dd7d531fa5695c0c033dc...97252ba88da87169c3f
您可以在同一条命令中列出多个图像,用空格分隔,如下所示。
ruby
$ docker rmi f70734b6a266 a4d3716dbb72
如果图像正在被正在运行的容器使用,您将无法删除该图像。您需要在删除图像之前停止并删除任何容器。
删除Docker主机上所有图像的一个方便快捷方法是运行docker rmi命令,并通过调用带有-q标志的docker images来传递系统上所有图像ID的列表,如下所示。
ruby
$ docker rmi $(docker images -q)
如果您正在Windows系统上进行操作,这将只在PowerShell终端中起作用。在CMD提示符下将不起作用。
为了理解这是如何工作的,请下载一些图像,然后运行docker images -q命令。
vbnet
$ docker pull alpine
Using default tag: latest
latest: Pulling from library/alpine
08409d417260: Pull complete
Digest: sha256:02bb6f428431fbc2809c5...a33cb1af4444c9b11
Status: Downloaded newer image for alpine:latest
docker.io/library/alpine:latest
$ docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
79d0ea7dc1a8: Pull complete
Digest: sha256:dfd64a3b4296d8c9b62aa3...ee20739e8eb54fbf
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest
$ docker images -q
44dd6f223004
3f5ef9003cef
可以看到,docker images -q返回一个仅包含所有本地图像的图像ID列表。将此列表传递给docker rmi将删除系统上的所有图像,如下所示。
makefile
$ docker rmi $(docker images -q) -f
Untagged: alpine:latest
Untagged: alpine@sha256:02bb6f428431fb...a33cb1af4444c9b11
Deleted: sha256:44dd6f2230041...09399391535c0c0183b
Deleted: sha256:94dd7d531fa56...97252ba88da87169c3f
Untagged: ubuntu:latest
Untagged: ubuntu@sha256:dfd64a3b4296d8...9ee20739e8eb54fbf
Deleted: sha256:3f5ef9003cefb...79cb530c29298550b92
Deleted: sha256:b49483f6a0e69...f3075564c10349774c3
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
让我们回顾一下处理 Docker 镜像的主要命令。
镜像 - 命令
docker pull
命令用于从远程镜像仓库下载镜像。默认情况下,镜像会从Docker Hub下载,但你也可以从其他镜像仓库下载。例如,以下命令会从Docker Hub的alpine仓库下载标记为latest的镜像:docker pull alpine:latest
。docker images
命令列出了存储在你的Docker主机本地镜像缓存中的所有镜像。添加--digests
标志可以查看SHA256摘要。docker inspect
命令可以说是一个美丽的工具!它提供了镜像的所有细节,包括层数据和元数据。docker manifest inspect
命令允许你检查存储在Docker Hub上的任何镜像的清单列表。以下命令将显示redis镜像的清单列表:docker manifest inspect redis
。docker buildx
是一个扩展了Docker CLI以支持多平台构建的Docker CLI插件。docker rmi
命令用于删除镜像。例如,以下命令将删除alpine:latest镜像:docker rmi alpine:latest
。你不能删除与正在运行(Up)或已停止(Exited)状态的容器相关联的镜像。