Kubernetes镜像预热——OpenKruise如何高效拉取镜像提升Pod启动速度

1、简介

在云原生架构中,容器化应用的使用已经成为一种趋势。然而,容器启动时间长短直接影响服务的可用性和用户体验。在高并发场景下,快速响应用户请求至关重要。为了解决这一问题,OpenKruise提供了镜像预热功能,旨在提高容器的启动速度和资源利用效率。本文将深入探讨OpenKruise的镜像预热功能,介绍其原理、配置方法和实际应用场景。

“镜像” 也算是 Docker 为容器领域带来的一大创新。在 Docker 之前,虽然 Linux 已经提供了 cgroup 隔离,尽管阿里巴巴从 2011 年已经逐渐基于 LXC 开始容器化,但都缺乏镜像这种对运行环境的封装。不过呢,尽管镜像为我们带来了诸多好处,但不可否认在实际场景中我们也面临各种各样拉镜像带来的问题,其中最常见的一点就是拉镜像的耗时。

镜像预热是指在实际容器启动之前,提前将所需的镜像拉取并缓存到节点上。这种方式能显著减少容器启动时的延迟,因为启动过程中无需再进行镜像下载。通过镜像预热,系统能够在负载高峰期快速扩展服务,从而保证稳定的用户体验。

我们过去听到过很多用户对容器化的期待和认识,比如 “极致弹性”、“秒级扩容”、“高效发布” 等等,但结合 Kubernetes 中一个标准的 Pod 创建过程来看,和用户的期望还是有一定差距的(假设 Pod 中包含 sidecar、app 两个容器):

正常来说,对于小规模集群中调度、分配/挂载远程盘、分配网络等操作耗时较小,对于大规模集群需要做一定优化,但都还在可控的范围内。然而对于拉镜像的耗时,在大规模弹性的集群中则尤为棘手,即使采用了 P2P 等技术手段来优化,对于一个较大的业务镜像还是可能需要较长的时间来拉取,与用户所期望的扩容、发布速度不符。

而我们如果能做到将 sidecar 容器的镜像、以及业务容器的基础镜像提前在节点上拉取好,则 Pod 创建过程可以大幅缩短,其中拉镜像的耗时甚至能优化 70% 以上:

而 Kubernetes 自身是没有提供任何面向镜像的操作能力的,围绕 Kubernetes 的生态来看,目前也没有比较成熟的规模化镜像预热产品。在云原生架构中,容器化应用的使用已经成为一种趋势。然而,容器启动时间长短直接影响服务的可用性和用户体验。在高并发场景下,快速响应用户请求至关重要。为了解决这一问题,OpenKruise提供了镜像预热功能,旨在提高容器的启动速度和资源利用效率。

2、原理

OpenKruise 实现镜像预热的原理,要先从它的运行架构看起:

从 v0.8.0 开始,安装了 Kruise 之后,有两个在 kruise-system 命名空间下的组件:kruise-manager 与 kruise-daemon。前者是一个由 Deployment 部署的中心化组件,一个 kruise-manager 容器(进程)中包含了多个 controller 和 webhook;后者则由 DaemonSet 部署到集群中的节点上,通过与 CRI 交互来绕过 Kubelet 完成一些扩展能力(比如拉取镜像、重启容器等)。

因此,Kruise 会为每个节点(Node)创建一个同名对应的自定义资源:NodeImage,而每个节点的 NodeImage 里写明了在这个节点上需要预热哪些镜像,因此这个节点上的 kruise-daemon 只要按照 NodeImage 来执行镜像的拉取任务即可:

apiVersion: apps.kruise.io/v1alpha1
kind: NodeImage
metadata:
name: my-nodeimage-pull
spec:
image: nodeimage:latest # 要拉取的镜像
imagePullPolicy: IfNotPresent # 镜像拉取策略
nodeSelector:
kubernetes.io/os: linux # 可选,指定在哪些节点拉取镜像
pullPolicy:
retries: 3 # 重试次数
backoffLimit: 5 # 拉取失败后的最大重试等待时间
retryIntervalSeconds: 30 # 每次重试之间的间隔时间(秒)
imagePullSecrets:

  • name: my-registry-secret # 如果需要从私有镜像仓库拉取镜像,可以指定拉取密钥
    如上图所示,我们在 NodeImage 中能指定要拉取的镜像名、tag、拉取的策略,比如单次拉取的超时、失败重试次数、任务的 deadline、TTL 时间等等。有了 NodeImage,我们也就拥有了最基本的镜像预热能力了,不过还不能完全满足大规模场景的预热需求。在一个有 5k 个节点的集群中,要用户去一个个更新 NodeImage 资源来做预热显然是不够友好的。因此,Kruise 还提供了一个更高抽象的自定义资源 ImagePullJob:

apiVersion: apps.kruise.io/v1alpha1
kind: ImagePullJob
metadata:
name: my-imagepulljob
spec:
image: nodeimage:latest # 要拉取的镜像
parallelism: 5 # 同时在多少个节点上并行拉取镜像
completionPolicy:
type: Always # 可以是 Always 或 Never,决定任务完成策略
backoffLimit: 3 # 最大重试次数
pullPolicy:
retries: 3 # 拉取失败后的重试次数
retryIntervalSeconds: 30 # 两次重试之间的间隔时间(秒)
selector:
matchLabels:
kubernetes.io/os: linux # 可选,选择要在哪些节点上拉取镜像
nodeSelector:
matchExpressions:
– key: kubernetes.io/arch
operator: In
values:
– amd64 # 可选,选择特定架构的节点
imagePullSecrets:

  • name: my-registry-secret # 如果需要从私有镜像仓库拉取镜像,可以指定拉取密钥

如上图所示,在 ImagePullJob 中用户可以指定一个镜像要在哪些范围的节点上批量做预热,以及这个 job 的拉取策略、生命周期等。一个 ImagePullJob 创建后,会被 kruise-manager 中的 imagepulljob-controller 接收到并处理,将其分解并写入到所有匹配节点的 NodeImage 中,以此来完成规模化的预热。

3、场景

在 Kubernetes 集群的管理和应用部署中,镜像预热是提升服务性能和稳定性的重要手段。不同的使用场景下,镜像预热能起到关键作用,确保集群在高并发、动态扩缩容、灾备恢复等关键场景下,能快速响应和高效运作。

高并发应用:对于需要快速响应的高并发应用,镜像预热可以在预期的流量高峰来临之前做好准备,确保服务的稳定性。

频繁扩缩容的服务:在使用Kubernetes进行动态扩缩容时,镜像预热能够减少由于镜像拉取导致的延迟,提升扩缩容的效率。

灾备恢复:在故障恢复场景中,预热的镜像能够加速服务恢复,减少对用户的影响。

4、主要功能

ImagePullJob 是 OpenKruise 专门为镜像预热设计的自定义资源,它允许用户定义需要拉取的镜像以及在哪些节点上执行这些拉取任务。通过 ImagePullJob,我们可以在应用启动之前就将镜像拉取到指定节点,确保应用启动时不会因为镜像下载而延迟。

并行拉取:可以并行地在多个节点上同时拉取镜像,提升镜像分发速度。

拉取重试:支持自定义重试次数和间隔,确保镜像拉取的成功率。

节点选择:支持根据节点标签和选择器精确指定需要拉取镜像的节点。

私有镜像仓库支持:可以配置镜像拉取密钥,从私有镜像仓库安全地拉取镜像。

5、基础镜像 – 集群维度预热

apiVersion: apps.kruise.io/v1alpha1
kind: ImagePullJob
metadata:
name: sidecar-image-job
spec:
image: sidecar-image:latest
parallelism: 20
completionPolicy:
type: Always
activeDeadlineSeconds: 1800
ttlSecondsAfterFinished: 300
pullPolicy:
backoffLimit: 3
timeoutSeconds: 300
[root@mast01 CloneSet]# kubectl apply -f 6.yaml
imagepulljob.apps.kruise.io/base-image-job created

[root@mast01 CloneSet]# kubectl get imagepulljobs -n default
NAME TOTAL ACTIVE SUCCEED FAILED AGE MESSAGE
sidecar-image-job 3 2 1 0 13s job is running, progress 33.3%
状态信息解释如下:

  • TOTAL: 表示预热任务中定义的总实例数量(这里是 3)。
  • ACTIVE: 表示当前正在运行的实例数量(这里是 2),说明有两个实例正在执行镜像拉取。
  • SUCCEED: 表示成功完成的实例数量(这里是 1)。
  • FAILED: 表示失败的实例数量(这里是 0)。
  • AGE: 表示这个任务已存在的时间(这里是 13 秒)。
  • MESSAGE: 显示当前的状态信息,job is running, progress 33.3% 表示任务正在运行,已完成 33.3%。
    如上述 ImagePullJob 有几个特征:

采用 Always 策略一次性预热:

所有节点做一次预热。

整个 job 预热超时时间 30min。

job 完成后过 5min 自动删除。

默认整个集群维度预热:

存量的节点上统一预热。

后续新增(导入)的节点上也会立即自动做预热。

当然,这里的 sidecar 预热也可以配置为 Never 策略,只有在新 Pod 启动并且没有本地镜像时,系统才会尝试拉取所需镜像。这样可以避免不必要的资源消耗,视场景而定。以我们的经验来看,尤其在 sidecar 做版本迭代、镜像升级的时候,提前做一次规模化的镜像预热,可以大幅提升后续 Pod 扩容、发布的速度。

6、特殊业务镜像 – 资源池维度预热

apiVersion: apps.kruise.io/v1alpha1
kind: ImagePullJob
metadata:
name: serverless-job
spec:
image: serverless-image:latest
parallelism: 10
completionPolicy:
type: Never
pullPolicy:
backoffLimit: 3
timeoutSeconds: 300
selector:
matchLabels:
resource-pool: serverless
如上述 ImagePullJob 有几个特征:

采用 Never 策略长期预热。

指定 selector 预热范围,是匹配 resource-pool=serverless 标签的节点。

对于一些多租的 Kubernetes 集群中会存在多个不同的业务资源池,其中可能需要将一些特定的业务镜像按资源池维度来预热。当然,这里只是以资源池为例,用户可以根据自身的场景来定义在哪些节点上预热某种镜像。

7、总结

如上图所示,在下个版本中 OpenKruise 的 CloneSet 将支持发布过程自动镜像预热。当用户还在灰度升级第一批 Pod 的时候,Kruise 会提前在后续 Pod 所在节点上把新版本的镜像预热好。这样一来,在后续批次的 Pod 做原地升级时候,新镜像都已经在节点上准备好了,也就节省了真正发布过程中的拉镜像耗时。

当然,这种 “发布+预热” 的模式也只适用于 OpenKruise 的原地升级场景。对于原生 workload 如 Deployment 而言,由于发布时 Pod 是新建出来的,我们无法提前预知到它会被调度到的节点,自然也就没办法提前把镜像预热好了。哔哩哔哩目前使用的是Kubernetes镜像预热——Dragonfly优化容器镜像分发。哔哩哔哩目前使用的是Kubernetes镜像预热——Dragonfly优化容器镜像分发。

声明:文中观点不代表本站立场。本文传送门:https://eyangzhen.com/422646.html

联系我们
联系我们
分享本页
返回顶部