【JS】Serverless 场景下 Pod 创建效率优化

Serverless 场景下 Pod 创建效率优化

阿里云云栖号发布于 今天 03:05

简介: 众所周知,Kubernetes 是云原生领域的基石,作为容器编排的基础设施,被广泛应用在 Serverless 领域。弹性能力是 Serverless 领域的核心竞争力,本次分享将重点介绍基于 Kubernetes 的 Serverless 服务中,如何优化 Pod 创建效率,提升弹性效率。

【JS】Serverless 场景下 Pod 创建效率优化

Serverless 计算简介

 
在进入主题之前,先简单回顾下 Serverless 计算的定义。

从维基百科可以了解到,Serverless 计算是云计算的一种形态,由云厂商管理服务器,向用户动态分配机器资源,基于实际使用的资源量计费。

用户构建和运行服务时,不用考虑服务器,降低了用户管理服务器的负担。在业务高峰期通过平台的弹性能力自动扩容实例,在业务低峰期自动缩容实例,降低资源成本。

Serverless 计算平台

 
下述是当前常见的 Serverless 计算产品的架构。
 

【JS】Serverless 场景下 Pod 创建效率优化

整个产品架构通常会有管控平面和数据平面两层,管控平面服务开发者,管理应用生命周期,满足开发者对应用管理的需求,数据平面服务应用的访问方,如开发者业务的用户,满足应用的流量管理和访问诉求。

管控平面通常采用 Kubernetes 做资源管理和调度,master 通常是 3 节点,满足对高可用的需求,节点通过内网 SLB 访问 K8s master。

在节点层面,通常会有两种类型的节点:

  • 一种是运行 kubelet 的节点,如裸金属服务器、虚拟机等,这类节点上会运行安全容器作为 Pod 运行时,每个 Pod 拥有独立的 kernel,降低共享宿主机 kernel 带来的安全风险。同时会通过云产品 VPC 网络或其他网络技术,在数据链路层隔离租户的网络访问。通过 安全容器+二层网络隔离,单个节点上可以提供可靠的多租运行环境。
  • 还有一种是虚拟节点,通过 VirtualKubelet 衔接 K8s 和弹性实例。弹性实例是云产品中类似虚拟机的一种轻量资源形态,提供无限资源池的容器组服务,该容器组的概念对应 K8s 中的 Pod 概念。AWS 提供有 Fargate 弹性实例,阿里云提供有 ECI 弹性实例。

Serverless 产品会提供基于 K8s 的 PaaS 层,负责向开发者提供部署、开发等相关的服务,屏蔽 K8s 相关的概念,降低开发者开发、运维应用的成本。

在数据平面,用户可通过 SLB 实现对应用实例的访问。PaaS 层也通常会在该平面提供诸如流量灰度、A/B 测试等流量管理服务,满足开发者对流量管理的需求。

弹性能力是 Serverless 计算平台的核心竞争力,需要满足开发者对 Pod 规模 的诉求,提供类似无限资源池的能力,同时还要满足创建 Pod 效率的诉求,及时响应请求。

Pod 规模可通过增加 IaaS 层资源来满足,接下来重点介绍提升 Pod 创建效率的技术。

Pod 创建相关场景

 
先了解下 Pod 创建相关的场景,这样可以更有效通过技术满足业务诉求。

业务中会有两种场景涉及到 Pod 创建:

  • 第一种是创建应用,这个过程会先经过调度,决策最适合 Pod 的节点,然后在节点上创建 Pod。
  • 第二种是升级应用,在这个过程中,通常是不断进行 创建新 Pod 和 销毁旧 Pod。

Serverless 服务中,开发者关心的重点在于应用的生命周期,尤其是创建和升级阶段,Pod 创建效率会影响这两个阶段的整体耗时,进而影响开发者的体验。面对突发流量时,创建效率的高低会对开发者服务的响应速度产生重要影响,严重者会使开发者的业务受损。

面对上述业务场景,接下来重点分析如何提升 Pod 创建效率。

创建 Pod 流程

 
整体分析下 Pod 创建的阶段,按照影响 Pod 创建效率的优先级来依次解决。

这是简化后的创建 Pod 流程:

【JS】Serverless 场景下 Pod 创建效率优化

当有 Pod 创建请求时,先进行调度,为 Pod 选取最合适的节点。在节点上,先进行拉取镜像的操作,镜像在本地准备好后,再进行创建容器组的操作。在拉取镜像阶段,又依次分为下载镜像和解压镜像两个步骤。

我们针对两种类型的镜像进行了测试,结果如下:

【JS】Serverless 场景下 Pod 创建效率优化

从测试结果可看到,解压镜像耗时在整个拉取镜像过程中的占比不容忽视,对于解压前 248MB 左右的 golang:1.10 镜像,解压镜像耗时竟然占到了拉取镜像耗时的 77.02%,对于节解压前 506MB 左右的 hadoop namenode 镜像,解压镜像耗时和下载镜像耗时各占 40% 和 60% 左右,即对于拉取镜像过程的总耗时也不容忽视。

接下来就分别针对上述过程的不同节点进行优化处理,分别从上述整个流程、解压镜像、下载镜像等方面进行探讨。
 

拉取镜像效率提升

镜像预热

 
可以快速想到的方法是进行镜像预热,在 Pod 调度到节点前预先在节点上准备好镜像,将拉取镜像从创建 Pod 的主链路中移除,如下图:

【JS】Serverless 场景下 Pod 创建效率优化

可以在调度前进行全局预热,在所有节点上行提前拉取镜像。也可以在调度过程中进行预热,在确定调度到的
节点后,在目标节点上拉取镜像。

两种方式无可厚非,可根据集群实际情况进行选择。

社区里 OpenKruise 项目即将推出镜像预热服务,可以关注下。下述是该服务的使用方式:

【JS】Serverless 场景下 Pod 创建效率优化

通过 ImagePullJob CRD 下发镜像预热任务,指定目标镜像和节点,可配置拉取的并发度、Job 处理的超时时间以及 Job Object 自动回收的时间。若是私有镜像,可指定拉取镜像时的 secret 配置。ImagePullJob 的 Events 会提镜任务的状态信息,可考虑适当增大 Job Object 自动回收的时间,便于通过 ImagePullJob Events 查看任务的处理状态。
 

提升解压效率

 
从刚才看到的拉取镜像的数据来看,解压镜像耗时会占拉取镜像总耗时很大的比例,测试的例子最大占比到了 77%,所以需要考虑如何提升解压效率。

先回顾下 docker pull 的技术细节:

【JS】Serverless 场景下 Pod 创建效率优化

在 docker pull 时,整体会进行两个阶段:

  • 并行下载 image 层
  • 拆解 image 层

在解压 image 层时,默认采用的 gunzip。

再简单了解下 docker push 的过程:

  • 先对 image 层进行打包操作,这个过程中会通过 gzip 进行压缩。
  • 然后并行上传。

gzip/gunzip 是单线程的压缩/解压工具,可考虑采用 pigz/unpigz 进行多线程的压缩/解压,充分利用多核优势。

containerd 从 1.2 版本开始支持 pigz,节点上安装 unpigz 工具后,会优先用其进行解压。通过这种方法,可通过节点多核能力提升镜像解压效率。

这个过程也需要关注 下载/上传 的并发度问题,docker daemon 提供了两个参数来控制并发度,控制并行处理的镜像层的数量,--max-concurrent-downloads 和 --max-concurrent-uploads。默认情况下,下载的并发度是 3,上传的并发度是 5,可根据测试结果调整到合适的值。

使用 unpigz 后的解压镜像效率:

【JS】Serverless 场景下 Pod 创建效率优化

在相同环境下,golang:1.10 镜像解压效率提升了 35.88%,hadoop namenode 镜像解压效率提升了 16.41%。

非压缩镜像

 
通常内网的带宽足够大,是否有可能省去 解压缩/压缩 的逻辑,将拉取镜像的耗时集中在下载镜像方面?即适量增大下载耗时,缩短解压耗时。

再回顾下 docker pull/push 的流程,在 unpack/pack 阶段,可以考虑将 gunzip 和 gzip 的逻辑去掉:
 

【JS】Serverless 场景下 Pod 创建效率优化

对于 docker 镜像,若 docker push 时的镜像是非压缩的,则 docker pull 时是无需进行解压缩操作,故要实现上述目标,就需要在 docker push 时去掉压缩逻辑。

docker daemon 暂时不支持上述操作,我们对 docker 进行了一番修改,在上传镜像时不进行压缩操作,测试结果如下:

【JS】Serverless 场景下 Pod 创建效率优化

这里重点关注解压镜像耗时,可看到 golang:1.10 镜像解压效率提升了 50% 左右,hadoop namenode 镜像解压效率替身挂了 28% 左右。在拉取镜像总耗时方面,该方案有一定的效果。
 

镜像分发

 
小规模集群中,提升拉取镜像效率的重点需要放在提升解压效率方面,下载镜像通常不是瓶颈。而在大规模集群中,由于节点数众多,中心式的 Image Registry 的带宽和稳定性也会影响拉取镜像的效率,如下图:

【JS】Serverless 场景下 Pod 创建效率优化

下载镜像的压力集中在中心式的 Image Registry 上。

这里介绍一种基于 P2P 的镜像分发系统来解决上述问题,以 CNCF 的 DragonFly 项目为例:
 

【JS】Serverless 场景下 Pod 创建效率优化

这里有几个核心组件:

ClusterManager

它本质上是一个中心式的 SuperNode,在 P2P 网络中作为 tracker 和 scheduler 协调节点的下载任务。同时它还是一个缓存服务,缓存从 Image Registry 中下载的镜像,降低节点的增加对 Image Registry 带来的压力。

Dfget

它既是节点上下载镜像的客户端,同时又充当向其他节点提供数据的能力,可以将本地已有的镜像数据按需提供给其他节点。

Dfdaemon

在每个节点上有个 Dfdaemon 组件,它本质上是一个 proxy,对 docker daemon 的拉取镜像的请求实现透明代理服务,使用 Dfget 下载镜像。

通过 P2P 网络,中心式的 Image Registry 数据被缓存到 ClusterManager 中,ClusterManager 协调节点对镜像的下载需求,将下载镜像的压力分摊到集群节点上,集群节点既是镜像数据的拉取方,又是镜像数据的提供方,充分利用内网带宽的能力进行镜像分发。

按需加载镜像

 
除了上述介绍到的方法,是否还有其他优化方法?

当前节点上创建容器时,是需要先把镜像全部数据拉取到本地,然后才能启动容器。再考虑下启动虚拟机的过程,即使是几百 GB 的虚拟机镜像,启动虚拟机也通常是在秒级别,几乎感受不到虚拟机镜像大小带来的影响。

那么容器领域是否也可以用到类似的技术?

再看一篇发表在 usenix 上的题为《Slacker: Fast Distribution with Lazy Docker Containers》 的 paper 描述:

该 paper 分析,在镜像启动耗时中,拉取镜像占比 76%,但是在启动时,仅有 6.4% 的数据被使用到,即镜像启动时需要的镜像数据量很少,需要考虑在镜像启动阶段按需加载镜像,改变对镜像的使用方式。

对于「Image 所有 layers 下载完后才能启动镜像」,需要改为启动容器时按需加载镜像,类似启动虚拟机的方式,仅对启动阶段需要的数据进行网络传输。

但当前镜像格式通常是 tar.gz 或 tar,而 tar 文件没有索引,gzip 文件不能从任意位置读取数据,这样就不能满足按需拉取时拉取指定文件的需求,镜像格式需要改为可索引的文件格式。

Google 提出了一种新的镜像格式,stargz,全称是 seeable tar.gz。它兼容当前的镜像格式,但提供了文件索引,可从指定位置读取数据。

传统的 .tar.gz 文件是这样生成的: Gzip(TarF(file1) + TarF(file2) + TarF(file3) + TarFooter))。分别对每个文件进行打包,然后对文件组进行压缩操作。

stargz 文件做了这样的创新:Gzip(TarF(file1)) + Gzip(TarF(file2)) + Gzip(TarF(file3_chunk1)) + Gzip(F(file3_chunk2)) + Gzip(F(index of earlier files in magic file), TarFooter)。针对每个文件进行打包和压缩操作,同时形成一个索引文件,和 TarFooter 一起进行压缩。

这样就可以通过索引文件快速定位要拉取的文件的位置,然后从指定位置拉取文件。

然后在 containerd 拉取镜像环节,对 containerd 提供一种 remote snapshotter,在创建容器 rootfs 层时,不通过先下载镜像层再构建的方式,而是直接 mount 远程存储层,如下图所示:

【JS】Serverless 场景下 Pod 创建效率优化

要实现这样的能力,一方面需要修改 containerd 当前的逻辑,在 filter 阶段识别远程镜像层,对于这样的镜像层不进行 download 操作,一方面需要实现一个 remote snapshotter,来支持对于远程层的管理。

当 containerd 通过 remote snapshotter 创建容器时,省去了拉取镜像的阶段,对于启动过程中需要的文件,可对 stargz 格式的镜像数据发起 HTTP Range GET 请求,拉取目标数据。

阿里云实现了名为 DADI 的加速器,类似上述的思想,目前应用在了阿里云容器服务,实现了 3.01s 启动  
10000 个容器,完美杜绝了冷启动的漫长等待。感兴趣的读者也参考该文章:https://developer.aliyun.com/...
 

原地升级

 
上述都是针对创建 Pod 过程提供的技术方案,对于升级场景,在现有的技术下,是否有效率提升的可能性?是否可以达到下述效果,即免去创建 Pod 的过程,实现 Pod 原地升级?

【JS】Serverless 场景下 Pod 创建效率优化

在升级场景中,占比较大的场景是仅升级镜像。针对这种场景,可使用 K8s 自身的 patch 能力。通过 patch image,Pod 不会重建,仅目标 container 重建,这样就不用完整经过 调度+新建 Pod 流程,仅对需要升级的容器进行原地升级。

在原地升级过程中,借助 K8s readinessGates 能力,可以控制 Pod 优雅下线,由 K8s Endpoint Controller 主动摘除即将升级的 Pod,在 Pod 原地升级后加入升级后的 Pod,实现升级过程中流量无损。

OpenKruise 项目中的 CloneSet Controller 提供了上述能力:

【JS】Serverless 场景下 Pod 创建效率优化

开发者使用 CloneSet 声明应用,用法类似 Deployment。在升级镜像时,由 CloneSet Controller 负责执行 patch 操作,同时确保升级过程中业务流量无损。

小结

 
从业务场景出发,我们了解了提升 Pod 创建效率带来收益的场景。然后通过分析 Pod 创建的流程,针对不同的阶段做相应的优化,有的放矢。

通过这样的分析处理流程,使得可以有效通过技术满足业务需求。

作者简介

张翼飞,就职于阿里云容器服务团队,主要专注 Serverless 领域的产品研发。
原文链接
本文为阿里云原创内容,未经允许不得转载

javascriptbootstrapreact.js

阅读 66发布于 今天 03:05

本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议


阿里云栖号

汇集阿里技术精粹-yq.aliyun.com

avatar

阿里云云栖号

阿里云官网内容平台!汇聚阿里云优质内容(入门、文档、案例、最佳实践、直播等)!如需转载或内容类合作,邮件[email protected] 秒级回复!

23.8k 声望

10.7k 粉丝

0 条评论

得票时间

avatar

阿里云云栖号

阿里云官网内容平台!汇聚阿里云优质内容(入门、文档、案例、最佳实践、直播等)!如需转载或内容类合作,邮件[email protected] 秒级回复!

23.8k 声望

10.7k 粉丝

宣传栏

简介: 众所周知,Kubernetes 是云原生领域的基石,作为容器编排的基础设施,被广泛应用在 Serverless 领域。弹性能力是 Serverless 领域的核心竞争力,本次分享将重点介绍基于 Kubernetes 的 Serverless 服务中,如何优化 Pod 创建效率,提升弹性效率。

【JS】Serverless 场景下 Pod 创建效率优化

Serverless 计算简介

 
在进入主题之前,先简单回顾下 Serverless 计算的定义。

从维基百科可以了解到,Serverless 计算是云计算的一种形态,由云厂商管理服务器,向用户动态分配机器资源,基于实际使用的资源量计费。

用户构建和运行服务时,不用考虑服务器,降低了用户管理服务器的负担。在业务高峰期通过平台的弹性能力自动扩容实例,在业务低峰期自动缩容实例,降低资源成本。

Serverless 计算平台

 
下述是当前常见的 Serverless 计算产品的架构。
 

【JS】Serverless 场景下 Pod 创建效率优化

整个产品架构通常会有管控平面和数据平面两层,管控平面服务开发者,管理应用生命周期,满足开发者对应用管理的需求,数据平面服务应用的访问方,如开发者业务的用户,满足应用的流量管理和访问诉求。

管控平面通常采用 Kubernetes 做资源管理和调度,master 通常是 3 节点,满足对高可用的需求,节点通过内网 SLB 访问 K8s master。

在节点层面,通常会有两种类型的节点:

  • 一种是运行 kubelet 的节点,如裸金属服务器、虚拟机等,这类节点上会运行安全容器作为 Pod 运行时,每个 Pod 拥有独立的 kernel,降低共享宿主机 kernel 带来的安全风险。同时会通过云产品 VPC 网络或其他网络技术,在数据链路层隔离租户的网络访问。通过 安全容器+二层网络隔离,单个节点上可以提供可靠的多租运行环境。
  • 还有一种是虚拟节点,通过 VirtualKubelet 衔接 K8s 和弹性实例。弹性实例是云产品中类似虚拟机的一种轻量资源形态,提供无限资源池的容器组服务,该容器组的概念对应 K8s 中的 Pod 概念。AWS 提供有 Fargate 弹性实例,阿里云提供有 ECI 弹性实例。

Serverless 产品会提供基于 K8s 的 PaaS 层,负责向开发者提供部署、开发等相关的服务,屏蔽 K8s 相关的概念,降低开发者开发、运维应用的成本。

在数据平面,用户可通过 SLB 实现对应用实例的访问。PaaS 层也通常会在该平面提供诸如流量灰度、A/B 测试等流量管理服务,满足开发者对流量管理的需求。

弹性能力是 Serverless 计算平台的核心竞争力,需要满足开发者对 Pod 规模 的诉求,提供类似无限资源池的能力,同时还要满足创建 Pod 效率的诉求,及时响应请求。

Pod 规模可通过增加 IaaS 层资源来满足,接下来重点介绍提升 Pod 创建效率的技术。

Pod 创建相关场景

 
先了解下 Pod 创建相关的场景,这样可以更有效通过技术满足业务诉求。

业务中会有两种场景涉及到 Pod 创建:

  • 第一种是创建应用,这个过程会先经过调度,决策最适合 Pod 的节点,然后在节点上创建 Pod。
  • 第二种是升级应用,在这个过程中,通常是不断进行 创建新 Pod 和 销毁旧 Pod。

Serverless 服务中,开发者关心的重点在于应用的生命周期,尤其是创建和升级阶段,Pod 创建效率会影响这两个阶段的整体耗时,进而影响开发者的体验。面对突发流量时,创建效率的高低会对开发者服务的响应速度产生重要影响,严重者会使开发者的业务受损。

面对上述业务场景,接下来重点分析如何提升 Pod 创建效率。

创建 Pod 流程

 
整体分析下 Pod 创建的阶段,按照影响 Pod 创建效率的优先级来依次解决。

这是简化后的创建 Pod 流程:

【JS】Serverless 场景下 Pod 创建效率优化

当有 Pod 创建请求时,先进行调度,为 Pod 选取最合适的节点。在节点上,先进行拉取镜像的操作,镜像在本地准备好后,再进行创建容器组的操作。在拉取镜像阶段,又依次分为下载镜像和解压镜像两个步骤。

我们针对两种类型的镜像进行了测试,结果如下:

【JS】Serverless 场景下 Pod 创建效率优化

从测试结果可看到,解压镜像耗时在整个拉取镜像过程中的占比不容忽视,对于解压前 248MB 左右的 golang:1.10 镜像,解压镜像耗时竟然占到了拉取镜像耗时的 77.02%,对于节解压前 506MB 左右的 hadoop namenode 镜像,解压镜像耗时和下载镜像耗时各占 40% 和 60% 左右,即对于拉取镜像过程的总耗时也不容忽视。

接下来就分别针对上述过程的不同节点进行优化处理,分别从上述整个流程、解压镜像、下载镜像等方面进行探讨。
 

拉取镜像效率提升

镜像预热

 
可以快速想到的方法是进行镜像预热,在 Pod 调度到节点前预先在节点上准备好镜像,将拉取镜像从创建 Pod 的主链路中移除,如下图:

【JS】Serverless 场景下 Pod 创建效率优化

可以在调度前进行全局预热,在所有节点上行提前拉取镜像。也可以在调度过程中进行预热,在确定调度到的
节点后,在目标节点上拉取镜像。

两种方式无可厚非,可根据集群实际情况进行选择。

社区里 OpenKruise 项目即将推出镜像预热服务,可以关注下。下述是该服务的使用方式:

【JS】Serverless 场景下 Pod 创建效率优化

通过 ImagePullJob CRD 下发镜像预热任务,指定目标镜像和节点,可配置拉取的并发度、Job 处理的超时时间以及 Job Object 自动回收的时间。若是私有镜像,可指定拉取镜像时的 secret 配置。ImagePullJob 的 Events 会提镜任务的状态信息,可考虑适当增大 Job Object 自动回收的时间,便于通过 ImagePullJob Events 查看任务的处理状态。
 

提升解压效率

 
从刚才看到的拉取镜像的数据来看,解压镜像耗时会占拉取镜像总耗时很大的比例,测试的例子最大占比到了 77%,所以需要考虑如何提升解压效率。

先回顾下 docker pull 的技术细节:

【JS】Serverless 场景下 Pod 创建效率优化

在 docker pull 时,整体会进行两个阶段:

  • 并行下载 image 层
  • 拆解 image 层

在解压 image 层时,默认采用的 gunzip。

再简单了解下 docker push 的过程:

  • 先对 image 层进行打包操作,这个过程中会通过 gzip 进行压缩。
  • 然后并行上传。

gzip/gunzip 是单线程的压缩/解压工具,可考虑采用 pigz/unpigz 进行多线程的压缩/解压,充分利用多核优势。

containerd 从 1.2 版本开始支持 pigz,节点上安装 unpigz 工具后,会优先用其进行解压。通过这种方法,可通过节点多核能力提升镜像解压效率。

这个过程也需要关注 下载/上传 的并发度问题,docker daemon 提供了两个参数来控制并发度,控制并行处理的镜像层的数量,--max-concurrent-downloads 和 --max-concurrent-uploads。默认情况下,下载的并发度是 3,上传的并发度是 5,可根据测试结果调整到合适的值。

使用 unpigz 后的解压镜像效率:

【JS】Serverless 场景下 Pod 创建效率优化

在相同环境下,golang:1.10 镜像解压效率提升了 35.88%,hadoop namenode 镜像解压效率提升了 16.41%。

非压缩镜像

 
通常内网的带宽足够大,是否有可能省去 解压缩/压缩 的逻辑,将拉取镜像的耗时集中在下载镜像方面?即适量增大下载耗时,缩短解压耗时。

再回顾下 docker pull/push 的流程,在 unpack/pack 阶段,可以考虑将 gunzip 和 gzip 的逻辑去掉:
 

【JS】Serverless 场景下 Pod 创建效率优化

对于 docker 镜像,若 docker push 时的镜像是非压缩的,则 docker pull 时是无需进行解压缩操作,故要实现上述目标,就需要在 docker push 时去掉压缩逻辑。

docker daemon 暂时不支持上述操作,我们对 docker 进行了一番修改,在上传镜像时不进行压缩操作,测试结果如下:

【JS】Serverless 场景下 Pod 创建效率优化

这里重点关注解压镜像耗时,可看到 golang:1.10 镜像解压效率提升了 50% 左右,hadoop namenode 镜像解压效率替身挂了 28% 左右。在拉取镜像总耗时方面,该方案有一定的效果。
 

镜像分发

 
小规模集群中,提升拉取镜像效率的重点需要放在提升解压效率方面,下载镜像通常不是瓶颈。而在大规模集群中,由于节点数众多,中心式的 Image Registry 的带宽和稳定性也会影响拉取镜像的效率,如下图:

【JS】Serverless 场景下 Pod 创建效率优化

下载镜像的压力集中在中心式的 Image Registry 上。

这里介绍一种基于 P2P 的镜像分发系统来解决上述问题,以 CNCF 的 DragonFly 项目为例:
 

【JS】Serverless 场景下 Pod 创建效率优化

这里有几个核心组件:

ClusterManager

它本质上是一个中心式的 SuperNode,在 P2P 网络中作为 tracker 和 scheduler 协调节点的下载任务。同时它还是一个缓存服务,缓存从 Image Registry 中下载的镜像,降低节点的增加对 Image Registry 带来的压力。

Dfget

它既是节点上下载镜像的客户端,同时又充当向其他节点提供数据的能力,可以将本地已有的镜像数据按需提供给其他节点。

Dfdaemon

在每个节点上有个 Dfdaemon 组件,它本质上是一个 proxy,对 docker daemon 的拉取镜像的请求实现透明代理服务,使用 Dfget 下载镜像。

通过 P2P 网络,中心式的 Image Registry 数据被缓存到 ClusterManager 中,ClusterManager 协调节点对镜像的下载需求,将下载镜像的压力分摊到集群节点上,集群节点既是镜像数据的拉取方,又是镜像数据的提供方,充分利用内网带宽的能力进行镜像分发。

按需加载镜像

 
除了上述介绍到的方法,是否还有其他优化方法?

当前节点上创建容器时,是需要先把镜像全部数据拉取到本地,然后才能启动容器。再考虑下启动虚拟机的过程,即使是几百 GB 的虚拟机镜像,启动虚拟机也通常是在秒级别,几乎感受不到虚拟机镜像大小带来的影响。

那么容器领域是否也可以用到类似的技术?

再看一篇发表在 usenix 上的题为《Slacker: Fast Distribution with Lazy Docker Containers》 的 paper 描述:

该 paper 分析,在镜像启动耗时中,拉取镜像占比 76%,但是在启动时,仅有 6.4% 的数据被使用到,即镜像启动时需要的镜像数据量很少,需要考虑在镜像启动阶段按需加载镜像,改变对镜像的使用方式。

对于「Image 所有 layers 下载完后才能启动镜像」,需要改为启动容器时按需加载镜像,类似启动虚拟机的方式,仅对启动阶段需要的数据进行网络传输。

但当前镜像格式通常是 tar.gz 或 tar,而 tar 文件没有索引,gzip 文件不能从任意位置读取数据,这样就不能满足按需拉取时拉取指定文件的需求,镜像格式需要改为可索引的文件格式。

Google 提出了一种新的镜像格式,stargz,全称是 seeable tar.gz。它兼容当前的镜像格式,但提供了文件索引,可从指定位置读取数据。

传统的 .tar.gz 文件是这样生成的: Gzip(TarF(file1) + TarF(file2) + TarF(file3) + TarFooter))。分别对每个文件进行打包,然后对文件组进行压缩操作。

stargz 文件做了这样的创新:Gzip(TarF(file1)) + Gzip(TarF(file2)) + Gzip(TarF(file3_chunk1)) + Gzip(F(file3_chunk2)) + Gzip(F(index of earlier files in magic file), TarFooter)。针对每个文件进行打包和压缩操作,同时形成一个索引文件,和 TarFooter 一起进行压缩。

这样就可以通过索引文件快速定位要拉取的文件的位置,然后从指定位置拉取文件。

然后在 containerd 拉取镜像环节,对 containerd 提供一种 remote snapshotter,在创建容器 rootfs 层时,不通过先下载镜像层再构建的方式,而是直接 mount 远程存储层,如下图所示:

【JS】Serverless 场景下 Pod 创建效率优化

要实现这样的能力,一方面需要修改 containerd 当前的逻辑,在 filter 阶段识别远程镜像层,对于这样的镜像层不进行 download 操作,一方面需要实现一个 remote snapshotter,来支持对于远程层的管理。

当 containerd 通过 remote snapshotter 创建容器时,省去了拉取镜像的阶段,对于启动过程中需要的文件,可对 stargz 格式的镜像数据发起 HTTP Range GET 请求,拉取目标数据。

阿里云实现了名为 DADI 的加速器,类似上述的思想,目前应用在了阿里云容器服务,实现了 3.01s 启动  
10000 个容器,完美杜绝了冷启动的漫长等待。感兴趣的读者也参考该文章:https://developer.aliyun.com/...
 

原地升级

 
上述都是针对创建 Pod 过程提供的技术方案,对于升级场景,在现有的技术下,是否有效率提升的可能性?是否可以达到下述效果,即免去创建 Pod 的过程,实现 Pod 原地升级?

【JS】Serverless 场景下 Pod 创建效率优化

在升级场景中,占比较大的场景是仅升级镜像。针对这种场景,可使用 K8s 自身的 patch 能力。通过 patch image,Pod 不会重建,仅目标 container 重建,这样就不用完整经过 调度+新建 Pod 流程,仅对需要升级的容器进行原地升级。

在原地升级过程中,借助 K8s readinessGates 能力,可以控制 Pod 优雅下线,由 K8s Endpoint Controller 主动摘除即将升级的 Pod,在 Pod 原地升级后加入升级后的 Pod,实现升级过程中流量无损。

OpenKruise 项目中的 CloneSet Controller 提供了上述能力:

【JS】Serverless 场景下 Pod 创建效率优化

开发者使用 CloneSet 声明应用,用法类似 Deployment。在升级镜像时,由 CloneSet Controller 负责执行 patch 操作,同时确保升级过程中业务流量无损。

小结

 
从业务场景出发,我们了解了提升 Pod 创建效率带来收益的场景。然后通过分析 Pod 创建的流程,针对不同的阶段做相应的优化,有的放矢。

通过这样的分析处理流程,使得可以有效通过技术满足业务需求。

作者简介

张翼飞,就职于阿里云容器服务团队,主要专注 Serverless 领域的产品研发。
原文链接
本文为阿里云原创内容,未经允许不得转载

以上是 【JS】Serverless 场景下 Pod 创建效率优化 的全部内容, 来源链接: utcz.com/a/114107.html

回到顶部