Kubernetes v1.33:镜像拉取策略如你所愿!

镜像拉取策略终于如你所愿!

Kubernetes 中的有些事情令人惊讶,imagePullPolicy 的行为方式可能就是其中之一。鉴于 Kubernetes 的核心是运行 Pod,你可能会觉得奇怪,10 多年来,以 issue 18787 的形式存在的、限制 Pod 访问需身份验证的镜像一直存在一个警告!当你能够解决一个存在了十年的问题时,这真是一个激动人心的版本。

即使我不该拥有它,也要 IfNotPresent

问题的症结在于 imagePullPolicy: IfNotPresent 策略只做了它所说的,仅此而已。让我们设置一个场景。首先,位于 Namespace X 中的 Pod A 被调度到 Node 1,并需要来自私有仓库的 image Foo。对于其镜像拉取身份验证材料,该 Pod 在其 imagePullSecrets 中引用了 Secret 1Secret 1 包含从私有仓库拉取所需的凭据。Kubelet 将利用 Pod A 提供的 Secret 1 中的凭据,并从镜像仓库拉取 container image Foo。这是预期的(也是安全的)行为。

但现在事情变得奇怪了。如果位于 Namespace Y 中的 Pod B 恰好也被调度到 Node 1,就会发生意想不到的(并且可能不安全的)事情。Pod B 可能引用了相同的私有镜像,指定了 IfNotPresent 镜像拉取策略。Pod B 在其 imagePullSecrets 中没有引用 Secret 1(或者在我们的例子中,任何 Secret)。当 Kubelet 尝试运行该 Pod 时,它会遵循 IfNotPresent 策略。Kubelet 看到 image Foo 已经本地存在,就会将 image Foo 提供给 Pod BPod B 得以运行该镜像,尽管它一开始并未提供授权其拉取该镜像的凭据。

Illustration of the process of two pods trying to access a private image, the first one with a pull secret, the second one without it

使用由不同 Pod 拉取的私有镜像

虽然 IfNotPresentimage Foo 已经存在于节点上时不应该拉取它,但允许调度到节点上的所有 Pod 访问先前拉取的私有镜像是一种不正确的安全态势。这些 Pod 从未被授权拉取该镜像。

IfNotPresent,但前提是我应该拥有它

在 Kubernetes v1.33 中,我们 —— SIG Auth 和 SIG Node —— 终于开始着手解决这个(非常古老)的问题,并确保验证的正确性!基本的预期行为没有改变。如果镜像不存在,Kubelet 将尝试拉取镜像。每个 Pod 提供的凭据将用于此任务。这与 1.33 之前的行为一致。

如果镜像存在,那么 Kubelet 的行为会发生变化。Kubelet 现在将在允许 Pod 使用该镜像之前验证 Pod 的凭据。

在修订此功能时,性能和服务稳定性一直是考虑的因素。使用相同凭据的 Pod 不需要重新进行身份验证。当 Pod 从同一个 Kubernetes Secret 对象中获取凭据时,即使凭据被轮换,情况也是如此。

永不拉取,但若已授权则使用

imagePullPolicy: Never 选项不会获取镜像。但是,如果容器镜像已经存在于节点上,任何试图使用私有镜像的 Pod 都需要提供凭据,并且这些凭据需要验证。

使用相同凭据的 Pod 不需要重新进行身份验证。未提供先前用于成功拉取镜像的凭据的 Pod 将不被允许使用私有镜像。

如果已授权,则始终拉取

imagePullPolicy: Always 一直都按预期工作。每次请求镜像时,请求都会发送到镜像仓库,镜像仓库将执行身份验证检查。

过去,通过 Pod 准入强制使用 Always 镜像拉取策略是确保你的私有容器镜像不会被已经拉取了这些镜像的节点上的其他 Pod 重复使用的唯一方法。

幸运的是,这在某种程度上是高效的。只拉取镜像清单,而不是镜像本身。然而,仍然存在成本和风险。在新的发布、扩容或 Pod 重启期间,提供镜像的镜像仓库必须可用以进行身份验证检查,这使得镜像仓库处于集群内运行服务稳定性的关键路径上。

它是如何工作的

该功能基于存在于每个节点上的持久性、基于文件的缓存。以下是该功能工作原理的简化描述。有关完整版本,请参阅 KEP-2535

首次请求镜像的过程如下

  1. 请求来自私有仓库的镜像的 Pod 被调度到一个节点上。
  2. 镜像在节点上不存在。
  3. Kubelet 记录下准备拉取镜像的意图。
  4. Kubelet 从 Pod 作为镜像拉取 Secret 引用的 Kubernetes Secret 中提取凭据,并使用它们从私有仓库中拉取镜像。
  5. 镜像成功拉取后,Kubelet 记录成功的拉取。该记录包括所用凭据的详细信息(以哈希形式)以及它们来自的 Secret。
  6. Kubelet 删除原始的意图记录。
  7. Kubelet 保留成功拉取的记录以备后用。

当将来调度到同一节点的 Pod 请求先前拉取的私有镜像时

  1. Kubelet 检查新 Pod 为拉取提供的凭据。
  2. 如果这些凭据的哈希值或凭据的源 Secret 与先前成功拉取记录的哈希值或源 Secret 匹配,则允许该 Pod 使用先前拉取的镜像。
  3. 如果在该镜像的成功拉取记录中未找到凭据或其源 Secret,Kubelet 将尝试使用这些新凭据向远程仓库请求拉取,从而触发授权流程。

立即试用

在 Kubernetes v1.33 中,我们发布了此功能的 Alpha 版本。要试用它,请为你的 1.33 Kubelet 启用 KubeletEnsureSecretPulledImages 特性门控。

你可以在 Kubernetes 官方文档的“镜像”概念页面上了解有关该功能和其他可选配置的更多信息。

接下来是什么?

在未来的版本中,我们计划:

  1. 使此功能与用于 Kubelet 镜像凭据提供程序的投射服务账号令牌协同工作,这将增加一个新的、特定于工作负载的镜像拉取凭据来源。
  2. 编写一个基准测试套件,以衡量此功能的性能并评估未来任何更改的影响。
  3. 实现一个内存缓存层,这样我们就不需要为每个镜像拉取请求读取文件。
  4. 增加对凭据过期的支持,从而强制重新验证先前已验证的凭据。

如何参与

阅读 KEP-2535 是深入了解这些变化的好方法。

如果你有兴趣进一步参与,请在 Kubernetes Slack 的 #sig-auth-authenticators-dev 频道上与我们联系(如需邀请,请访问 https://slack.k8s.io/)。也欢迎你参加每两周一次的 SIG Auth 会议,会议在每隔一个星期三举行。