Kubernetes v1.33:镜像拉取策略终于按你的预期工作了!
镜像拉取策略终于按你的预期工作了!
Kubernetes 中有些东西让人感到奇怪,imagePullPolicy
的行为就是其中之一。
Kubernetes 作为一个专注于运行 Pod 的平台,居然在限制 Pod 访问经认证的镜像方面,存在一个长达十余年的问题,
详见 Issue 18787!
v1.33 解决了这个十年前的老问题,这真是一个有纪念意义的版本。
说明:
在本博文中,“Pod 凭据”这个术语将被频繁使用。 在这篇博文的上下文中,这一术语通常指的是 Pod 拉取容器镜像时可用于身份认证的认证材料。
IfNotPresent:即使我本不该有这个镜像
问题的本质在于,imagePullPolicy: IfNotPresent
策略正如其字面意义所示,仅此而已。
我们来设想一个场景:Pod A 运行在 Namespace X 中,被调度到 Node 1,
此 Pod 需要从某个私有仓库拉取镜像 Foo。此 Pod 在 imagePullSecrets
中引用
Secret 1 来作为镜像拉取认证材料。Secret 1 包含从私有仓库拉取镜像所需的凭据。
kubelet 将使用 Pod A 提供的 Secret 1 来拉取 镜像 Foo,这是预期的(也是安全的)行为。
但现在情况变得奇怪了。如果 Namespace Y 中的 Pod B 也被调度到 Node 1,就会出现意外(甚至是不安全)的情况。
Pod B 可以引用同一个私有镜像,指定 IfNotPresent
镜像拉取策略。
Pod B 未在其 imagePullSecrets
中引用 Secret 1(甚至未引用任何 Secret)。
当 kubelet 尝试运行此 Pod 时,它会采用 IfNotPresent
策略。
kubelet 发现本地已存在镜像 Foo,会将镜像 Foo 提供给 Pod B。
即便 Pod B 一开始并未提供授权拉取镜像的凭据,却依然能够运行此镜像。
使用由另一个 Pod 拉取的私有镜像
虽然 IfNotPresent
不应在节点上已存在镜像 Foo 的情况下再拉取此镜像,
但允许将所有 Pod 调度到有权限访问之前已拉取私有镜像的节点上,这从安全态势讲是不正确的做法。
因为这些 Pod 从开始就未被授权拉取此镜像。
IfNotPresent:但前提是我有权限
在 Kubernetes v1.33 中,SIG Auth 和 SIG Node 终于开始修复这个(非常古老的)难题,并经过验证可行! 基本的预期行为没有变。如果某镜像不存在,kubelet 会尝试拉取此镜像。 利用每个 Pod 提供的凭据来完成此拉取任务。这与 v1.33 之前的行为相匹配。
但如果镜像存在,kubelet 的行为就变了。 kubelet 现在先要验证 Pod 的凭据,然后才会允许 Pod 使用镜像。
在修缮此特性时,我们也考虑到了性能和服务稳定性。 如果多个 Pod 使用相同的凭据,则无需重复认证。 即使这些 Pod 使用的是相同的 Kubernetes Secret 对象(即便其凭据已轮换),也同样适用。
Never:永不拉取,但使用前仍需鉴权
采用 imagePullPolicy: Never
选项时,不会获取镜像。
但如果节点上已存在此容器镜像,任何尝试使用此私有镜像的 Pod 都需要提供凭据,并且这些凭据需要经过验证。
使用相同凭据的 Pod 无需重新认证。未提供之前已成功拉取镜像所用凭据的 Pod,将不允许使用此私有镜像。
Always:鉴权通过后始终拉取
imagePullPolicy: Always
一直以来都能按预期工作。
每次某镜像被请求时,请求会流转到镜像仓库,镜像仓库将执行身份认证检查。
过去,为了确保你的私有容器镜像不会被节点上已拉取过镜像的其他 Pod 重复使用,
通过 Pod 准入来强制执行 Always
镜像拉取策略是唯一的方式。
幸运的是,这个过程相对高效:仅拉取镜像清单,而不是镜像本体。 但这依然带来代价与风险。每当发布新版本、扩容或重启 Pod 时, 提供镜像的镜像仓库必须可以接受认证检查,从而将镜像仓库放到关键路径中确保集群中所运行的服务的稳定性。
工作原理
此特性基于每个节点上存在的持久化文件缓存。以下简要说明了此特性的工作原理。 完整细节请参见 KEP-2535。
首次请求某镜像的流程如下:
- 请求私有仓库中某镜像的 Pod 被调度到某节点。
- 此镜像在节点上不存在。
- kubelet 记录一次拉取镜像的意图。
- kubelet 从 Pod 引用的 Kubernetes Secret 中提取凭据作为镜像拉取 Secret,并使用这些凭据从私有仓库拉取镜像。
- 镜像已成功拉取后,kubelet 会记录这次成功的拉取。 记录包括所使用的凭据细节(哈希格式)以及构成这些凭据的原始 Secret。
- kubelet 移除原始意图记录。
- kubelet 保留成功拉取的记录供后续使用。
当以后调度到同一节点的 Pod 请求之前拉取过的私有镜像:
- kubelet 检查新 Pod 为拉取镜像所提供的凭据。
- 如果这些凭据的哈希或其源 Secret 与之前成功拉取记录的哈希或源 Secret 相匹配,则允许此 Pod 使用之前拉取的镜像。
- 如果在该镜像的成功拉取记录中找不到这些凭据或其源 Secret,则 kubelet 将尝试使用这些新的凭据从远程仓库进行拉取,同时触发认证流程。
试用
在 Kubernetes v1.33 中,我们发布了此特性的 Alpha 版本。
要想试用,在 kubelet v1.33 上启用 KubeletEnsureSecretPulledImages
特性门控。
你可以在 Kubernetes 官方文档中的镜像概念页中了解此特性和更多可选配置的细节。
下一步工作
在未来的版本中,我们将:
- 使此特性与 kubelet 镜像凭据提供程序的投射服务账号令牌协同工作, 后者能够添加新的、特定于工作负载的镜像拉取凭据源。
- 编写基准测试套件,以评估此特性的性能并衡量后续变更的影响。
- 实现内存中的缓存层,因此我们不需要为每个镜像拉取请求都读取文件。
- 添加对凭据过期的支持,从而强制重新认证之前已验证过的凭据。
如何参与
阅读 KEP-2535 是深入理解这些变更的绝佳方式。
如果你想进一步参与,可以加入 Kubernetes Slack 频道 #sig-auth-authenticators-dev (如需邀请链接,请访问 https://slack.k8s.io/)。 欢迎你参加每隔一周在星期三举行的 SIG Auth 双周例会。