图像

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

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

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

镜像名称

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

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

在镜像名称部分之后,您可以添加标签摘要(与使用 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 或包含 Pod 模板的其他对象时,默认情况下,如果未明确指定,该 Pod 中所有容器的拉取策略将设置为 IfNotPresent。此策略导致kubelet 在镜像已存在的情况下跳过拉取镜像。

镜像拉取策略

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

以下是您可以为 imagePullPolicy 设置的值以及这些值产生的效果

IfNotPresent
仅当镜像不存在本地时才拉取镜像。
Always
每次 kubelet 启动容器时,kubelet 都会查询容器镜像仓库以将名称解析为镜像摘要。如果 kubelet 在本地缓存了具有完全相同摘要的容器镜像,kubelet 将使用其缓存的镜像;否则,kubelet 将使用解析后的摘要拉取镜像,并使用该镜像启动容器。
Never
kubelet 不尝试获取镜像。如果镜像以某种方式已存在于本地,kubelet 会尝试启动容器;否则,启动会失败。有关详细信息,请参见预拉取镜像

底层镜像提供程序的缓存语义使得即使 imagePullPolicy: Always 也能保持效率,只要镜像仓库能够可靠地访问。您的容器运行时可以注意到镜像层已存在于节点上,因此不需要再次下载它们。

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

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

有一些第三方准入控制器会在创建 Pod(和 Pod 模板)时对其进行变异,以便运行的工作负载根据镜像摘要而不是标签进行定义。这在您希望确保所有工作负载都运行相同的代码,无论仓库中的标签发生什么变化时都很有用。

默认镜像拉取策略

当您(或控制器)将新的 Pod 提交到 API 服务器时,您的集群会在满足特定条件时设置 imagePullPolicy 字段

  • 如果您省略 imagePullPolicy 字段,并且指定了容器镜像的摘要,则 imagePullPolicy 会自动设置为 IfNotPresent
  • 如果您省略 imagePullPolicy 字段,并且容器镜像的标签是 :latest,则 imagePullPolicy 会自动设置为 Always
  • 如果您省略 imagePullPolicy 字段,并且没有指定容器镜像的标签,则 imagePullPolicy 会自动设置为 Always
  • 如果您省略 imagePullPolicy 字段,并且指定了容器镜像的非 :latest 标签,则 imagePullPolicy 会自动设置为 IfNotPresent

强制拉取镜像

如果您希望始终强制拉取,您可以执行以下操作之一

  • 将容器的 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]
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.27 [alpha]

serializeImagePulls 设置为 false 时,kubelet 默认情况下对同时拉取的最大镜像数量没有限制。如果您希望限制并行镜像拉取的数量,可以在 kubelet 配置中设置 maxParallelImagePulls 字段。当 maxParallelImagePulls 设置为 n 时,一次只能拉取 n 个镜像,任何超过 n 个的镜像拉取请求必须等到至少一个正在进行的镜像拉取完成才能进行。

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

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

具有镜像索引的多体系结构镜像

除了提供二进制镜像之外,容器注册表还可以提供 容器镜像索引。镜像索引可以指向多个针对特定体系结构版本的 镜像清单。这样,您就可以为镜像命名(例如:pauseexample/mycontainerkube-apiserver),并允许不同的系统为其所使用的机器体系结构获取正确的二进制镜像。

Kubernetes 本身通常使用后缀 -$(ARCH) 为容器镜像命名。为了向后兼容,请使用后缀生成旧镜像。这样,您可以生成一个名为 pause 的镜像,该镜像包含所有体系结构的清单,以及一个名为 pause-amd64 的镜像,该镜像与旧配置或可能硬编码了后缀镜像的 YAML 文件向后兼容。

使用私有注册表

私有注册表可能需要密钥才能从其读取镜像。
凭据可以通过几种方式提供

  • 配置节点以验证私有注册表
    • 所有 Pod 都可以读取任何已配置的私有注册表
    • 需要集群管理员对节点进行配置
  • Kubelet 凭据提供程序可动态获取私有注册表的凭据
    • kubelet 可以配置为使用相应的私有注册表的凭据提供程序 exec 插件。
  • 预拉取的镜像
    • 所有 Pod 都可以使用缓存到节点上的任何镜像
    • 需要对所有节点具有 root 访问权限才能进行设置
  • 在 Pod 上指定 ImagePullSecrets
    • 只有提供自身密钥的 Pod 才能访问私有注册表
  • 供应商特定或本地扩展
    • 如果您使用的是自定义节点配置,则您(或您的云提供商)可以实施您自己的机制来对节点进行容器注册表的身份验证。

这些选项将在下面详细介绍。

配置节点以验证私有注册表

设置凭据的具体说明取决于您选择的容器运行时和注册表。您应该参考解决方案的文档以获取最准确的信息。

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

Kubelet 凭据提供程序用于经过身份验证的镜像拉取

您可以配置 kubelet 以调用插件二进制文件来动态获取容器镜像的注册表凭据。这是获取私有注册表凭据最稳健且用途最广的方法,但它也需要在 kubelet 级别进行配置才能启用。

有关更多详细信息,请参阅 配置 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.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 属性设置为 IfNotPresentNever,则使用本地镜像(分别优先使用或仅使用)。

如果您希望依赖预拉取的镜像来代替注册表身份验证,您必须确保集群中的所有节点都具有相同的预拉取镜像。

这可以用于预加载某些镜像以提高速度,或作为对私有注册表进行身份验证的替代方法。

所有 Pod 都可以读取任何预拉取的镜像。

在 Pod 上指定 ImagePullSecrets

Kubernetes 支持在 Pod 上指定容器镜像注册表密钥。imagePullSecrets 必须全部与 Pod 位于同一个命名空间。引用的 Secrets 必须是 kubernetes.io/dockercfgkubernetes.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 上引用 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 来自动执行此字段的设置。

有关详细说明,请查看 向 ServiceAccount 添加 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 Container Registry)和 GCR(Google Container Registry)。

有关旧版机制的更多信息,请阅读您正在使用的 Kubernetes 版本的文档。Kubernetes v1.26 到 v1.31 不包含旧版机制,因此您需要:

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

下一步

上次修改时间:2024 年 4 月 14 日下午 6:13 PST:修复评论 (9105838778)