OCI 容器镜像的 ID 与标签
在本地开发中使用 Docker 时,一个常用的命令是 docker pull
,用来拉取镜像到本地。如下面代码所示:
$ docker pull nginx
上面的代码仅包含了 nginx
,这是一个最简略的形式。完整的名称可以由 4 个部分组成:
组成部分 | 默认值 |
---|---|
注册中心(registry) | docker.io |
分组(group) | library |
镜像名称(name) | |
ID 或标签(tag) | latest |
除了镜像名称之外,其他部分都可以省略。当省略时,将使用默认值。比如 bitnami/postgresql:11
仅省略了注册中心,而 registry.access.redhat.com/ubi8/ubi-minimal:8.5
则提供了全部组成部分。nginx
实际上等同于 docker.io/library/nginx:latest
。
镜像标签
对于镜像的标签,大部分人并不陌生。镜像一般使用语义化的版本号作为标签。除了标签,每个镜像都有唯一的 ID。这个 ID 实际上是镜像的配置文件的摘要。标签则可以指向不同的镜像 ID。这种使用模式可以与 Git 中的 commit hash 和标签来进行类比。在 Git 中,每个 commit 有唯一的 hash 作为 ID,而标签则指向不同的 hash 值。标签所对应的 commit hash 是可以发生变化的。
容器镜像的 ID 和标签也有同样的特点。同一个标签所指向的镜像 ID 可能是变化的。这一点对标签 latest
来说,尤为明显。在每一次推送镜像时,如果使用默认的 latest
标签,则 latest
标签会移动到新的镜像 ID。即使使用语义化版本号的标签,如 1.2.0
,同样的标签也可能指向不同的镜像 ID。作为镜像的发布者,应该避免这样的情况。当某个版本发布完成,并产生对应的镜像标签之后,就不应该使用同样的标签再次发布。
理论上来说,在应用部署时,使用镜像 ID 比标签更加稳定,因为镜像 ID 才是唯一确定不变的。不过,镜像 ID 的可读性太差。在实践中,一般仍然是使用标签。通常的标签采用语义化版本号,并添加持续集成服务器提供的构建号(Build ID)作为后缀。比如,1.2.0-b123
表示版本 1.2.0
的第 123
次构建。
在拉取镜像时,既可以使用标签,也可以使用镜像 ID。
$ docker pull nginx:1.21.4
$ docker pull nginx@sha256:2f14a471f2c2819a3faf88b72f56a0372ff5af4cb42ec45aab00c03ca5c9989f
Kubernetes 镜像拉取策略
在 Kubernetes 上,容器声明的 imagePullPolicy
属性可以指定镜像拉取策略。该策略有 3 个可选值:
IfNotPresent
:当镜像在本地不可用时拉取。Always
:每次启动容器时拉取。Never
:从不拉取。
当没有指定 imagePullPolicy
时,如果没有指定镜像的标签,或是标签为 latest
,imagePullPolicy
的默认值是 Always
;否则默认值是 IfNotPresent
。
由于镜像标签所指定的实际镜像 ID 可能产生变化,当启动新的 Pod 时,新的 Pod 中的容器可能使用的是新的镜像。举例来说,容器镜像的标签是 1.0.0
,并已经有 Pod 运行。之后使用同样的标签再次发布新的镜像。当因为水平扩展而创建新的 Pod 时,如果 imagePullPolicy
的值是 Always
,新的 Pod 使用的是新的镜像,即使标签没有变化。新旧版本的混用可能产生问题。
为了避免这种情况,可以使用镜像 ID。可以使用 Kubernetes 的准入控制器来修改 Pod 的声明,把其中的容器镜像的标签,替换成镜像 ID。这样可以避免由于标签修改造成的不一致。
当在 Kubernetes 上运行时,Pod 的状态可以显示容器使用的镜像 ID,也就是下面代码中的 imageID
字段。
containerStatuses:
- containerID: containerd://119cd2bbd8670aeef1a515df4af429aff405c1bff48b8370d503efe577b5a505
image: docker.io/rancher/coredns-coredns:1.8.3
imageID: docker.io/rancher/coredns-coredns@sha256:9d4f5d7968c432fbd4123f397a2d6ab666fd63d13d510d9728d717c3e002dc72