镜像
容器镜像表示封装了应用程序及其所有软件依赖项的二进制数据。容器镜像是可以独立运行的可执行软件包,并且对其运行时环境做出非常明确的假设。
你通常会创建应用程序的容器镜像,并将其推送到注册表,然后在 Pod 中引用它。
本页面概述了容器镜像的概念。
注意
如果你正在寻找 Kubernetes 版本(例如 v1.32,最新次要版本)的容器镜像,请访问 下载 Kubernetes。镜像名称
容器镜像通常会给定一个名称,例如 pause
、example/mycontainer
或 kube-apiserver
。镜像还可以包含注册表主机名;例如:fictional.registry.example/imagename
,并且可能还包含端口号;例如:fictional.registry.example:10443/imagename
。
如果你未指定注册表主机名,则 Kubernetes 假定你指的是 Docker 公共注册表。你可以通过在 容器运行时 配置中设置默认镜像注册表来更改此行为。
在镜像名称部分之后,你可以添加一个*标签*或*摘要*(就像你使用 docker
或 podman
等命令时一样)。标签使你可以标识同一系列镜像的不同版本。摘要是镜像特定版本的唯一标识符。摘要是镜像内容的哈希值,并且是不可变的。标签可以移动以指向不同的镜像,但摘要是固定的。
镜像标签由小写和大写字母、数字、下划线 (_
)、句点 (.
) 和破折号 (-
) 组成。它的长度最多为 128 个字符。并且必须遵循下一个正则表达式模式:[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}
你可以在 OCI 分发规范中阅读更多相关信息并找到验证正则表达式。如果你未指定标签,则 Kubernetes 假定你指的是标签 latest
。
镜像摘要由哈希算法(例如 sha256
)和哈希值组成。例如:sha256:1ff6c18fbef2045af6b9c16bf034cc421a29027b800e4f9b68ae9b1cb3e9ae07
你可以在 OCI 镜像规范中找到有关摘要格式的更多信息。
Kubernetes 可以使用的一些镜像名称示例为
busybox
- 仅镜像名称,无标签或摘要。Kubernetes 将使用 Docker 公共注册表和 latest 标签。(与docker.io/library/busybox:latest
相同)busybox:1.32.0
- 带有标签的镜像名称。Kubernetes 将使用 Docker 公共注册表。(与docker.io/library/busybox:1.32.0
相同)registry.k8s.io/pause:latest
- 带有自定义注册表和 latest 标签的镜像名称。registry.k8s.io/pause:3.5
- 带有自定义注册表和非 latest 标签的镜像名称。registry.k8s.io/pause@sha256:1ff6c18fbef2045af6b9c16bf034cc421a29027b800e4f9b68ae9b1cb3e9ae07
- 带有摘要的镜像名称。registry.k8s.io/pause:3.5@sha256:1ff6c18fbef2045af6b9c16bf034cc421a29027b800e4f9b68ae9b1cb3e9ae07
- 带有标签和摘要的镜像名称。仅摘要将用于拉取。
更新镜像
首次创建 Deployment、StatefulSet、Pod 或其他包含 Pod 模板的对象时,如果未明确指定,则该 Pod 中所有容器的拉取策略默认设置为 IfNotPresent
。此策略导致 kubelet 跳过拉取已存在的镜像。
镜像拉取策略
容器的 imagePullPolicy
和镜像的标签会影响 kubelet 尝试拉取(下载)指定镜像的时间。
以下列出了你可以为 imagePullPolicy
设置的值以及这些值所产生的影响
IfNotPresent
- 仅当镜像在本地尚不存在时才拉取镜像。
Always
- 每次 kubelet 启动容器时,kubelet 都会查询容器镜像注册表以将名称解析为镜像 摘要。如果 kubelet 在本地缓存了具有该确切摘要的容器镜像,则 kubelet 将使用其缓存的镜像;否则,kubelet 将拉取具有已解析摘要的镜像,并使用该镜像启动容器。
Never
- kubelet 不会尝试获取镜像。如果镜像以某种方式已存在于本地,则 kubelet 将尝试启动容器;否则,启动将失败。有关更多详细信息,请参见预拉取的镜像。
只要注册表可以可靠地访问,底层镜像提供程序的缓存语义就会使 imagePullPolicy: Always
变得高效。你的容器运行时可以注意到镜像层已存在于节点上,因此无需再次下载它们。
注意
在生产环境中部署容器时,应避免使用 :latest
标签,因为它更难跟踪正在运行的镜像版本,并且更难正确回滚。
相反,应指定有意义的标签,例如 v1.42.0
和/或摘要。
为了确保 Pod 始终使用相同版本的容器镜像,你可以指定镜像的摘要;将 <image-name>:<tag>
替换为 <image-name>@<digest>
(例如,image@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2
)。
当使用镜像标签时,如果镜像注册表更改了该镜像上的标签所代表的代码,则你可能会最终得到混合运行旧代码和新代码的 Pod。镜像摘要唯一标识镜像的特定版本,因此 Kubernetes 每次启动具有该镜像名称和摘要指定的容器时都会运行相同的代码。通过摘要指定镜像可修复你运行的代码,以便注册表中的更改不会导致版本混合。
有一些第三方准入控制器在创建 Pod(和 Pod 模板)时对其进行修改,以便基于镜像摘要而不是标签定义正在运行的工作负载。如果你想确保所有工作负载都运行相同的代码,而无论注册表中的标签更改如何,这可能很有用。
默认镜像拉取策略
当你(或控制器)向 API 服务器提交新的 Pod 时,当满足特定条件时,集群会设置 imagePullPolicy
字段
- 如果你省略
imagePullPolicy
字段,并且你指定了容器镜像的摘要,则imagePullPolicy
会自动设置为IfNotPresent
。 - 如果你省略
imagePullPolicy
字段,并且容器镜像的标签为:latest
,则imagePullPolicy
会自动设置为Always
; - 如果你省略
imagePullPolicy
字段,并且你未指定容器镜像的标签,则imagePullPolicy
会自动设置为Always
; - 如果你省略
imagePullPolicy
字段,并且你为容器镜像指定了非:latest
的标签,则imagePullPolicy
会自动设置为IfNotPresent
。
注意
容器的 imagePullPolicy
值始终在首次创建对象时设置,如果镜像的标签或摘要稍后发生更改,则不会更新。
例如,如果你使用标签不是 :latest
的镜像创建 Deployment,然后稍后将该 Deployment 的镜像更新为 :latest
标签,则 imagePullPolicy
字段将不会更改为 Always
。你必须在对象初始创建后手动更改任何对象的拉取策略。
强制拉取镜像
如果您想始终强制拉取镜像,您可以执行以下操作之一:
- 将容器的
imagePullPolicy
设置为Always
。 - 省略
imagePullPolicy
,并使用:latest
作为要使用的镜像的标签;当您提交 Pod 时,Kubernetes 会将策略设置为Always
。 - 省略
imagePullPolicy
以及要使用的镜像的标签;当您提交 Pod 时,Kubernetes 会将策略设置为Always
。 - 启用 AlwaysPullImages 准入控制器。
ImagePullBackOff
当 kubelet 使用容器运行时为 Pod 创建容器时,容器可能会因 ImagePullBackOff
而处于等待状态。
状态 ImagePullBackOff
表示容器无法启动,因为 Kubernetes 无法拉取容器镜像(原因例如镜像名称无效,或从私有仓库拉取时没有 imagePullSecret
)。 BackOff
部分表示 Kubernetes 将持续尝试拉取镜像,并增加退避延迟。
Kubernetes 会增加每次尝试之间的延迟,直到达到内置的限制,即 300 秒(5 分钟)。
按运行时类拉取镜像
Kubernetes v1.29 [alpha]
(默认禁用:false)如果您启用 RuntimeClassInImageCriApi
特性门控,则 kubelet 将通过(镜像名称、运行时处理程序)的元组来引用容器镜像,而不仅仅是镜像名称或摘要。您的容器运行时可能会根据选定的运行时处理程序调整其行为。基于运行时类拉取镜像对于基于虚拟机的容器(如 Windows HyperV 容器)非常有用。
串行和并行镜像拉取
默认情况下,kubelet 串行拉取镜像。换句话说,kubelet 一次只向镜像服务发送一个镜像拉取请求。其他镜像拉取请求必须等到正在处理的请求完成后才能进行。
节点会独立做出镜像拉取决策。即使您使用串行镜像拉取,两个不同的节点也可以并行拉取相同的镜像。
如果您想启用并行镜像拉取,您可以在 kubelet 配置中将字段 serializeImagePulls
设置为 false。如果 serializeImagePulls
设置为 false,则镜像拉取请求将立即发送到镜像服务,并且将同时拉取多个镜像。
启用并行镜像拉取时,请确保您的容器运行时的镜像服务可以处理并行镜像拉取。
kubelet 永远不会代表一个 Pod 并行拉取多个镜像。例如,如果您有一个包含 init 容器和应用程序容器的 Pod,则两个容器的镜像拉取不会并行化。但是,如果您有两个 Pod 使用不同的镜像,则在启用并行镜像拉取时,kubelet 会代表这两个不同的 Pod 并行拉取镜像。
最大并行镜像拉取
Kubernetes v1.32 [beta]
当 serializeImagePulls
设置为 false 时,kubelet 默认不限制同时拉取的最大镜像数。如果您想限制并行镜像拉取的数量,您可以在 kubelet 配置中设置字段 maxParallelImagePulls
。如果 maxParallelImagePulls
设置为 *n*,则只能同时拉取 *n* 个镜像,并且任何超出 *n* 的镜像拉取都必须等到至少一个正在进行的镜像拉取完成后才能进行。
当启用并行镜像拉取时,限制并行镜像拉取的数量可以防止镜像拉取消耗过多的网络带宽或磁盘 I/O。
您可以将 maxParallelImagePulls
设置为大于或等于 1 的正数。如果将 maxParallelImagePulls
设置为大于或等于 2,则必须将 serializeImagePulls
设置为 false。kubelet 将无法以无效的 maxParallelImagePulls
设置启动。
具有镜像索引的多架构镜像
除了提供二进制镜像外,容器注册表还可以提供容器镜像索引。镜像索引可以指向容器特定架构版本的多个镜像清单。其思路是,您可以为镜像设置名称(例如:pause
、example/mycontainer
、kube-apiserver
),并允许不同的系统为它们使用的机器架构获取正确的二进制镜像。
Kubernetes 本身通常使用后缀 -$(ARCH)
命名容器镜像。为了向后兼容,请使用后缀生成旧镜像。其思路是生成具有所有 arch 的清单的 pause
镜像,并生成 pause-amd64
镜像,它与可能硬编码了带有后缀的镜像的旧配置或 YAML 文件向后兼容。
使用私有注册表
私有注册表可能需要密钥才能从中读取镜像。
可以通过多种方式提供凭据
- 配置节点以验证到私有注册表的身份
- 所有 Pod 都可以读取任何已配置的私有注册表
- 需要集群管理员进行节点配置
- Kubelet 凭据提供程序动态获取私有注册表的凭据
- 可以将 kubelet 配置为对相应的私有注册表使用凭据提供程序执行插件。
- 预拉取的镜像
- 所有 Pod 都可以使用节点上缓存的任何镜像
- 需要对所有节点具有 root 访问权限才能进行设置
- 在 Pod 上指定 ImagePullSecrets
- 只有提供自己的密钥的 Pod 才能访问私有注册表
- 供应商特定的或本地的扩展
- 如果您使用的是自定义节点配置,则您(或您的云提供商)可以实现您自己的机制,用于验证节点到容器注册表的身份。
以下将更详细地解释这些选项。
配置节点以验证到私有注册表的身份
设置凭据的具体说明取决于您选择使用的容器运行时和注册表。您应该参考您的解决方案的文档以获取最准确的信息。
有关配置私有容器镜像注册表的示例,请参阅从私有注册表拉取镜像任务。该示例使用 Docker Hub 中的私有注册表。
用于经过身份验证的镜像拉取的 Kubelet 凭据提供程序
注意
当 kubelet 需要动态获取注册表凭据时,此方法尤其适用。最常用于云提供商提供的注册表,其中身份验证令牌是短期的。您可以配置 kubelet 以调用插件二进制文件,从而动态获取容器镜像的注册表凭据。这是获取私有注册表凭据的最可靠和通用的方法,但也需要启用 kubelet 级别的配置。
有关更多详细信息,请参阅配置 kubelet 镜像凭据提供程序。
config.json 的解释
config.json
的解释在原始 Docker 实现和 Kubernetes 解释之间有所不同。在 Docker 中,auths
键只能指定根 URL,而 Kubernetes 允许使用 glob URL 以及前缀匹配的路径。唯一的限制是,glob 模式 (*
) 必须包含每个子域的点 (.
)。匹配的子域的数量必须等于 glob 模式 (*.
) 的数量,例如
*.kubernetes.io
将**不**匹配kubernetes.io
,但会匹配abc.kubernetes.io
*.*.kubernetes.io
将**不**匹配abc.kubernetes.io
,但会匹配abc.def.kubernetes.io
prefix.*.io
将匹配prefix.kubernetes.io
*-good.kubernetes.io
将匹配prefix-good.kubernetes.io
这意味着像这样的 config.json
是有效的
{
"auths": {
"my-registry.io/images": { "auth": "…" },
"*.my-registry.io/images": { "auth": "…" }
}
}
现在,镜像拉取操作会将凭据传递给 CRI 容器运行时,以用于每个有效模式。例如,以下容器镜像名称将成功匹配
my-registry.io/images
my-registry.io/images/my-image
my-registry.io/images/another-image
sub.my-registry.io/images/my-image
但不会匹配
a.sub.my-registry.io/images/my-image
a.b.sub.my-registry.io/images/my-image
kubelet 会为每个找到的凭据按顺序执行镜像拉取。这意味着,config.json
中可以为不同的路径提供多个条目
{
"auths": {
"my-registry.io/images": {
"auth": "…"
},
"my-registry.io/images/subpath": {
"auth": "…"
}
}
}
如果现在一个容器指定要拉取镜像 my-registry.io/images/subpath/my-image
,则如果其中一个身份验证源失败,kubelet 将尝试从这两个身份验证源下载它们。
预拉取的镜像
注意
如果您可以控制节点配置,则此方法适用。如果您的云提供商管理节点并自动替换它们,则此方法将无法可靠地工作。默认情况下,kubelet 尝试从指定的注册表拉取每个镜像。但是,如果容器的 imagePullPolicy
属性设置为 IfNotPresent
或 Never
,则会使用本地镜像(分别优先或排他地使用)。
如果您想依靠预拉取的镜像来代替注册表身份验证,则必须确保集群中的所有节点都具有相同的预拉取镜像。
这可用于预加载某些镜像以提高速度,或作为验证到私有注册表的替代方法。
所有 Pod 都将具有对任何预拉取镜像的读取权限。
在 Pod 上指定 imagePullSecrets
注意
这是运行基于私有注册表中镜像的容器的推荐方法。Kubernetes 支持在 Pod 上指定容器镜像注册表密钥。imagePullSecrets
必须全部与 Pod 位于同一命名空间中。引用的 Secret 的类型必须为 kubernetes.io/dockercfg
或 kubernetes.io/dockerconfigjson
。
使用 Docker 配置创建 Secret
您需要知道用于验证到注册表的用户名、注册表密码和客户端电子邮件地址,以及它的主机名。运行以下命令,替换为适当的大写值
kubectl create secret docker-registry <name> \
--docker-server=DOCKER_REGISTRY_SERVER \
--docker-username=DOCKER_USER \
--docker-password=DOCKER_PASSWORD \
--docker-email=DOCKER_EMAIL
如果您已经有 Docker 凭据文件,那么您可以导入凭据文件作为 Kubernetes Secrets,而不是使用上述命令。
基于现有 Docker 凭据创建 Secret 说明了如何进行设置。
如果您使用多个私有容器注册表,这将特别有用,因为 kubectl create secret docker-registry
创建的 Secret 仅适用于单个私有注册表。
注意
Pod 只能引用其自身命名空间中的镜像拉取 Secret,因此此过程需要在每个命名空间执行一次。在 Pod 上引用 imagePullSecrets
现在,您可以通过将 imagePullSecrets
部分添加到 Pod 定义来创建引用该 Secret 的 Pod。imagePullSecrets
数组中的每个项只能引用同一命名空间中的 Secret。
例如
cat <<EOF > pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: foo
namespace: awesomeapps
spec:
containers:
- name: foo
image: janedoe/awesomeapp:v1
imagePullSecrets:
- name: myregistrykey
EOF
cat <<EOF >> ./kustomization.yaml
resources:
- pod.yaml
EOF
对于每个使用私有注册表的 Pod,都需要执行此操作。
但是,可以通过在 ServiceAccount 资源中设置 imagePullSecrets 来自动设置此字段。
请查看 向服务账户添加 ImagePullSecrets 获取详细说明。
您可以将此方法与每个节点的 .docker/config.json
结合使用。凭据将会被合并。
使用场景
配置私有仓库有多种解决方案。以下是一些常见的使用场景和建议的解决方案。
- 集群仅运行非专有(例如,开源)镜像。无需隐藏镜像。
- 使用来自公共仓库的公共镜像
- 无需任何配置。
- 一些云服务提供商会自动缓存或镜像公共镜像,这可以提高可用性并减少拉取镜像的时间。
- 使用来自公共仓库的公共镜像
- 集群运行一些专有镜像,这些镜像应该对公司外部的人员隐藏,但对所有集群用户可见。
- 使用托管的私有仓库
- 可能需要在需要访问私有仓库的节点上手动配置
- 或者,在防火墙后运行内部私有仓库,并开放读取权限。
- 无需 Kubernetes 配置。
- 使用控制镜像访问权限的托管容器镜像仓库服务
- 与手动节点配置相比,它在集群自动缩放方面效果更好。
- 或者,在更改节点配置不方便的集群上,使用
imagePullSecrets
。
- 使用托管的私有仓库
- 集群包含专有镜像,其中一些镜像需要更严格的访问控制。
- 确保 AlwaysPullImages 准入控制器处于活动状态。否则,所有 Pod 都有可能访问所有镜像。
- 将敏感数据移入 "Secret" 资源,而不是将其打包在镜像中。
- 一个多租户集群,每个租户都需要自己的私有仓库。
- 确保 AlwaysPullImages 准入控制器处于活动状态。否则,所有租户的所有 Pod 都有可能访问所有镜像。
- 运行需要授权的私有仓库。
- 为每个租户生成仓库凭据,放入 secret 中,并将 secret 填充到每个租户命名空间。
- 租户将该 secret 添加到每个命名空间的 imagePullSecrets 中。
如果需要访问多个仓库,可以为每个仓库创建一个 secret。
旧版内置 kubelet 凭据提供程序
在旧版本的 Kubernetes 中,kubelet 与云提供商凭据进行了直接集成。这使其能够动态获取镜像仓库的凭据。
kubelet 凭据提供程序集成有三种内置实现:ACR(Azure 容器注册表)、ECR(弹性容器注册表)和 GCR(Google 容器注册表)。
有关旧版机制的更多信息,请阅读您正在使用的 Kubernetes 版本的文档。 Kubernetes v1.26 到 v1.32 不包含旧版机制,因此您需要执行以下操作之一:
- 在每个节点上配置 kubelet 镜像凭据提供程序
- 使用
imagePullSecrets
和至少一个 Secret 指定镜像拉取凭据
下一步
- 阅读 OCI 镜像清单规范。
- 了解 容器镜像垃圾回收。
- 详细了解 从私有仓库拉取镜像。