镜像

容器镜像代表封装了应用程序及其所有软件依赖项的二进制数据。容器镜像是一种可执行的软件捆绑包,可以独立运行,并对运行环境有非常明确的假设。

通常,您会创建应用程序的容器镜像并将其推送到仓库,然后在 Pod 中引用它。

本页面概述了容器镜像的概念。

说明

如果您正在寻找 Kubernetes 版本的容器镜像(例如最新的小版本 v1.36),请访问 下载 Kubernetes

镜像名称

容器镜像通常有一个名称,例如 pauseexample/mycontainerkube-apiserver。镜像还可以包含仓库主机名;例如:fictional.registry.example/imagename,可能还包含端口号;例如:fictional.registry.example:10443/imagename

如果您不指定仓库主机名,Kubernetes 会假设您指的是 Docker 公共仓库。您可以通过在容器运行时配置中设置默认镜像仓库来更改此行为。

在镜像名称部分之后,您可以添加标签 (tag)摘要 (digest)(就像使用 dockerpodman 等命令时那样)。标签允许您标识同一系列镜像的不同版本。摘要是特定镜像版本的唯一标识符。摘要是镜像内容的哈希值,且不可变。标签可以移动以指向不同的镜像,但摘要是固定的。

镜像标签由大小写字母、数字、下划线 (_)、句点 (.) 和短横线 (-) 组成。标签长度最多可达 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 — 同时带有标签和摘要的镜像名称。仅摘要将被用于拉取。

更新镜像

当您首次创建 DeploymentStatefulSet、Pod 或其他包含 PodTemplate 的对象时,如果未明确指定拉取策略,则默认情况下该 Pod 中所有容器的拉取策略将被设置为 IfNotPresent。此策略导致 kubelet 在镜像已存在时跳过拉取。

镜像拉取策略

容器的 imagePullPolicy 和镜像的标签都会影响 kubelet 何时尝试拉取(下载)指定镜像。

以下是您可以为 imagePullPolicy 设置的值及其影响的列表:

IfNotPresent
仅在本地不存在镜像时才拉取。
Always
每次 kubelet 启动容器时,kubelet 都会查询容器镜像仓库,将名称解析为镜像摘要。如果 kubelet 在本地缓存中拥有该精确摘要的容器镜像,kubelet 将使用其缓存的镜像;否则,kubelet 将拉取解析出的摘要对应的镜像,并使用该镜像启动容器。
永不
kubelet 不会尝试获取镜像。如果镜像以某种方式已经在本地存在,kubelet 会尝试启动容器;否则,启动失败。详见预拉取的镜像

只要仓库可可靠访问,底层镜像提供程序的缓存语义甚至使 imagePullPolicy: Always 也变得高效。您的容器运行时可以检测到镜像层已存在于节点上,因此无需再次下载。

说明

在生产环境中部署容器时,您应该避免使用 :latest 标签,因为这更难追踪正在运行的镜像版本,也更难进行正确的版本回滚。

相反,请指定一个有意义的标签(例如 v1.42.0)和/或摘要。

为了确保 Pod 始终使用相同版本的容器镜像,您可以指定镜像的摘要;将 <image-name>:<tag> 替换为 <image-name>@<digest>(例如,image@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2)。

使用镜像标签时,如果镜像仓库更改了该标签所代表的代码,您最终可能会得到混合运行旧代码和新代码的 Pod。镜像摘要唯一标识镜像的特定版本,因此 Kubernetes 每次启动具有该镜像名称和摘要的容器时都会运行相同的代码。通过摘要指定镜像可以锁定您运行的代码,从而防止仓库端的更改导致版本混合。

有一些第三方准入控制器可以在 Pod(及 PodTemplate)创建时对其进行变更,使得运行的工作负载基于镜像摘要而非标签定义。如果您想确保整个工作负载在仓库标签发生更改时始终运行相同的代码,这非常有用。

默认镜像拉取策略

当您(或控制器)向 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 而处于 Waiting 状态。

状态 ImagePullBackOff 意味着由于 Kubernetes 无法拉取容器镜像(原因可能是无效的镜像名称,或者在没有 imagePullSecret 的情况下从私有仓库拉取),容器无法启动。BackOff 部分表明 Kubernetes 将继续尝试拉取镜像,且退避延迟会逐渐增加。

Kubernetes 会增加每次尝试之间的延迟,直到达到编译后的限制,即 300 秒(5 分钟)。

基于运行时类的镜像拉取

特性状态: Kubernetes v1.29 [alpha](默认禁用)
Kubernetes 包含基于 Pod 的 RuntimeClass 执行镜像拉取的 Alpha 支持。

如果您启用 RuntimeClassInImageCriApi 特性门控,kubelet 将通过镜像名称和运行时处理程序的元组(而非仅仅是镜像名称或摘要)来引用容器镜像。您的容器运行时可能会根据所选的运行时处理程序调整其行为。基于运行时类拉取镜像对于基于 VM 的容器(例如 Windows Hyper-V 容器)非常有用。

串行与并行镜像拉取

默认情况下,kubelet 串行拉取镜像。换句话说,kubelet 一次只向镜像服务发送一个镜像拉取请求。其他镜像拉取请求必须等待当前处理的请求完成。

节点独立做出镜像拉取决策。即使您使用串行镜像拉取,两个不同的节点也可以并行拉取同一个镜像。

如果您想启用并行镜像拉取,可以在 kubelet 配置中将 serializeImagePulls 字段设置为 false。当 serializeImagePulls 设置为 false 时,镜像拉取请求将立即发送到镜像服务,并同时拉取多个镜像。

在启用并行镜像拉取时,请确保您的容器运行时的镜像服务能够处理并行拉取。

kubelet 永远不会代表同一个 Pod 并行拉取多个镜像。例如,如果您的 Pod 包含初始化容器和应用程序容器,这两个容器的镜像拉取操作不会并行化。但是,如果您有两个使用不同镜像的 Pod,并且启用了并行镜像拉取功能,kubelet 将会代表这两个不同的 Pod 并行拉取镜像。

最大并行镜像拉取数

特性状态: Kubernetes v1.35 [stable]

serializeImagePulls 设置为 false 时,kubelet 默认不对同时拉取的镜像数量设限。如果您想限制并行镜像拉取的数量,可以在 kubelet 配置中设置 maxParallelImagePulls 字段。将 maxParallelImagePulls 设置为 n 时,同一时间最多只能拉取 n 个镜像,超过 n 的任何镜像拉取都必须等待至少一个正在进行的镜像拉取完成。

在启用并行镜像拉取时,限制并行拉取的数量可以防止镜像拉取消耗过多的网络带宽或磁盘 I/O。

您可以将 maxParallelImagePulls 设置为大于或等于 1 的正数。如果您将 maxParallelImagePulls 设置为大于或等于 2,则必须将 serializeImagePulls 设置为 false。如果 maxParallelImagePulls 设置无效,kubelet 将无法启动。

使用镜像索引的多架构镜像

除了提供二进制镜像外,容器仓库还可以提供 容器镜像索引。镜像索引可以指向多个用于特定架构容器版本的镜像清单。其核心思想是,您可以为镜像设定一个名称(例如:pauseexample/mycontainerkube-apiserver),并允许不同的系统获取其所使用的机器架构对应的二进制镜像。

Kubernetes 项目通常为其发布版本创建镜像,镜像名称后缀包含 -$(ARCH)。为了向后兼容,会生成带有后缀的旧镜像。例如,名为 pause 的镜像将是一个包含所有受支持架构清单的多架构镜像,而 pause-amd64 将是一个向后兼容的版本,用于旧配置或在 YAML 文件中硬编码了包含后缀的镜像名称。

使用私有仓库

私有仓库可能需要认证才能发现和/或从中拉取镜像。可以通过多种方式提供凭据:

  • 在定义 Pod 时指定 imagePullSecrets

    只有提供自身密钥的 Pod 才能访问私有仓库。

  • 配置节点以认证私有仓库

    • 所有 Pod 都可以读取任何已配置的私有仓库。
    • 需要由集群管理员进行节点配置。
  • 使用 kubelet 凭据提供程序插件动态获取私有仓库凭据

    可以将 kubelet 配置为对相应的私有仓库使用凭据提供程序 exec 插件。

  • 预拉取的镜像

    • 所有 Pod 都可以使用节点上缓存的任何镜像。
    • 需要对所有节点拥有 root 访问权限进行设置。
  • 供应商特定或本地扩展

    如果您使用的是自定义节点配置,您(或您的云提供商)可以实现自己的机制,将节点认证到容器仓库。

以下是对这些选项的详细解释。

在 Pod 上指定 imagePullSecrets

说明

这是运行基于私有仓库镜像的容器的推荐方法。

Kubernetes 支持在 Pod 上指定容器镜像仓库密钥。所有 imagePullSecrets 必须是与 Pod 处于同一 命名空间中的 Secret。这些 Secret 必须是 kubernetes.io/dockercfgkubernetes.io/dockerconfigjson 类型。

配置节点以认证私有仓库

设置凭据的具体说明取决于您选择使用的容器运行时和仓库。您应该查阅解决方案的文档以获取最准确的信息。

有关配置私有容器镜像仓库的示例,请参阅 从私有仓库拉取镜像 任务。该示例使用了 Docker Hub 中的私有仓库。

用于认证镜像拉取的 Kubelet 凭据提供程序

您可以配置 kubelet 调用插件二进制文件以动态获取容器镜像的仓库凭据。这是获取私有仓库凭据最强大、最通用的方法,但也需要进行 kubelet 级别的配置才能启用。

此技术对于运行需要私有仓库中托管的容器镜像的 静态 Pod 特别有用。在静态 Pod 的规范中无法使用 ServiceAccountSecret 来提供私有仓库凭据,因为它不能在规范中引用其他 API 资源。

详情请参阅 配置 kubelet 镜像凭据提供程序

config.json 的解析

config.json 的解析在原始 Docker 实现和 Kubernetes 解析之间有所不同。在 Docker 中,auths 键只能指定根 URL,而 Kubernetes 允许通配符 URL 和前缀匹配路径。唯一的限制是通配符模式 (*) 必须包含每个子域的句点 (.)。匹配的子域数量必须等于通配符模式 (*.) 的数量,例如:

  • *.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.example/images": { "auth": "…" },
        "*.my-registry.example/images": { "auth": "…" }
    }
}

镜像拉取操作会将凭据传递给 CRI 容器运行时以匹配每个有效的模式。例如,以下容器镜像名称将成功匹配:

  • my-registry.example/images
  • my-registry.example/images/my-image
  • my-registry.example/images/another-image
  • sub.my-registry.example/images/my-image

但是,这些容器镜像名称将不会匹配:

  • a.sub.my-registry.example/images/my-image
  • a.b.sub.my-registry.example/images/my-image

kubelet 会为每个找到的凭据按顺序执行镜像拉取。这意味着 config.json 中针对不同路径的多个条目也是可能的:

{
    "auths": {
        "my-registry.example/images": {
            "auth": "…"
        },
        "my-registry.example/images/subpath": {
            "auth": "…"
        }
    }
}

如果现在一个容器指定拉取 my-registry.example/images/subpath/my-image,kubelet 将尝试使用这两种认证源进行下载,如果其中一个失败,则尝试另一个。

预拉取的镜像

说明

如果可以控制节点配置,此方法非常适用。如果您的云提供商自动管理和替换节点,则此方法无法可靠工作。

默认情况下,kubelet 尝试从指定的仓库拉取每个镜像。但是,如果容器的 imagePullPolicy 属性设置为 IfNotPresentNever,则使用本地镜像(分别优先使用或仅使用)。

如果您想依赖预拉取的镜像作为仓库认证的替代方案,必须确保集群中的所有节点都具有相同的预拉取镜像。

这可用于预加载某些镜像以提高速度,或作为认证私有仓库的替代方法。

使用 kubelet 凭据提供程序类似,预拉取的镜像也适用于启动依赖托管在私有仓库中镜像的 静态 Pod

说明

特性状态: Kubernetes v1.35 [beta](默认启用)

对预拉取镜像的访问可以根据镜像拉取凭据验证进行授权。

确保镜像拉取凭据验证

特性状态: Kubernetes v1.35 [beta](默认启用)

如果为您的集群启用了 KubeletEnsureSecretPulledImages 特性门控,Kubernetes 将为每个需要凭据才能拉取的镜像验证其凭据,即使该镜像已存在于节点上。此验证确保 Pod 请求中那些未通过所提供凭据成功拉取的镜像必须重新从仓库拉取。此外,重复使用先前导致成功拉取的凭据的镜像拉取请求,将无需重新从仓库拉取,而是通过本地验证(前提是镜像已在本地可用)。这由 Kubelet 配置中的 imagePullCredentialsVerificationPolicy 字段控制。

此配置控制在镜像已存在于节点上时必须在何时验证镜像拉取凭据:

  • NeverVerify:模拟禁用此特性门控的行为。如果镜像存在于本地,则不验证镜像拉取凭据。
  • NeverVerifyPreloadedImages:不验证在 kubelet 外部拉取的镜像,但所有其他镜像都将进行凭据验证。这是默认行为。
  • NeverVerifyAllowListedImages:不验证在 kubelet 外部拉取且在 kubelet 配置中指定的 preloadedImagesVerificationAllowlist 中提到的镜像。
  • AlwaysVerify:所有镜像在使用前都将对其凭据进行验证。

此验证适用于预拉取的镜像、使用节点级 Secret 拉取的镜像以及使用 Pod 级 Secret 拉取的镜像。

说明

在凭据轮换的情况下,先前用于拉取镜像的凭据将继续验证,而无需访问仓库。新的或已轮换的凭据将需要从仓库重新拉取镜像。

首次启用 KubeletEnsureSecretPulledImages

当首次启用 KubeletEnsureSecretPulledImages 时(无论是通过 kubelet 升级还是显式启用特性),如果 kubelet 在此时能够访问任何镜像,这些镜像都将被视为预拉取。这是因为在这种情况下,kubelet 没有关于镜像被拉取的记录。kubelet 将仅在镜像首次被拉取时开始制作拉取记录。

如果这引起关注,建议在启用该特性之前清理节点上所有不应被视为预拉取的镜像。

请注意,移除保存镜像拉取记录的目录在 kubelet 重启时会有同样的效果,特别是当前容器运行时在节点中缓存的镜像都将被视为预拉取。

创建包含 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 Secret基于现有 Docker 凭据创建 Secret 说明了如何设置此项。

如果您正在使用多个私有容器仓库,这特别有用,因为 kubectl create secret docker-registry 创建的 Secret 仅适用于单个私有仓库。

说明

Pod 只能引用其所在命名空间中的镜像拉取 Secret,因此每个命名空间需要执行一次此过程。

在 Pod 上引用 imagePullSecrets

现在,您可以通过在 Pod 定义中添加 imagePullSecrets 部分来创建引用该 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 结合使用。凭据将会合并。

使用场景

配置私有仓库有多种解决方案。以下是一些常见用例和建议的解决方案。

  1. 集群仅运行非专有(例如开源)镜像。无需隐藏镜像。
    • 使用公共仓库中的公共镜像。
      • 无需配置。
      • 一些云提供商会自动缓存或镜像公共镜像,这可以提高可用性并缩短拉取镜像的时间。
  2. 集群运行一些专有镜像,这些镜像应对公司外部隐藏,但对所有集群用户可见。
    • 使用托管的私有仓库。
      • 在需要访问私有仓库的节点上可能需要手动配置。
    • 或者,在您的防火墙后运行一个具有开放读取访问权限的内部私有仓库。
      • 无需 Kubernetes 配置。
    • 使用控制镜像访问权限的托管容器镜像仓库服务。
      • 它与节点自动扩缩容配合得比手动节点配置更好。
    • 或者,在更改节点配置不方便的集群上,使用 imagePullSecrets
  3. 集群带有专有镜像,其中少数需要更严格的访问控制。
    • 确保 AlwaysPullImages 准入控制器处于活动状态。否则,所有 Pod 都有可能访问所有镜像。
    • 将敏感数据移入 Secret 资源,而不是将其打包在镜像中。
  4. 每个租户需要自己的私有仓库的多租户集群。
    • 确保 AlwaysPullImages 准入控制器处于活动状态。否则,所有租户的所有 Pod 都有可能访问所有镜像。
    • 运行需要授权的私有仓库。
    • 为每个租户生成仓库凭据,存储在 Secret 中,并将该 Secret 分发到每个租户命名空间。
    • 然后,租户将该 Secret 添加到每个命名空间的 imagePullSecrets 中。

如果您需要访问多个仓库,可以为每个仓库创建一个 Secret。

遗留的内置 Kubelet 凭据提供程序

在较旧的 Kubernetes 版本中,kubelet 与云提供商凭据有直接集成。这提供了动态获取镜像仓库凭据的能力。

kubelet 凭据提供程序集成有三种内置实现:ACR(Azure 容器仓库)、ECR(Elastic 容器仓库)和 GCR(Google 容器仓库)。

从 Kubernetes 1.26 版本开始,该遗留机制已被移除,因此您需要:

  • 在每个节点上配置一个 kubelet 镜像凭据提供程序;或者
  • 使用 imagePullSecrets 和至少一个 Secret 指定镜像拉取凭据。

接下来