Serverless 场景下 Pod 建立效率优化

头图.png

做者 | 张翼飞  阿里云技术专家 来源|阿里巴巴云原生公众号node

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

Serverless 计算简介

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

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

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

Serverless 计算平台

  下述是当前常见的 Serverless 计算产品的架构。   1.jpg服务器

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

管控平面一般采用 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 流程:

2.jpg

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

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

3.jpg

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

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

拉取镜像效率提高

 

镜像预热

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

4.jpg

能够在调度前进行全局预热,在全部节点上行提早拉取镜像。也能够在调度过程当中进行预热,在肯定调度到的 节点后,在目标节点上拉取镜像。

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

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

5.png

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

提高解压效率

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

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

6.jpg

在 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 后的解压镜像效率:

7.jpg

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

非压缩镜像

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

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

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

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

9.jpg

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

镜像分发

  小规模集群中,提高拉取镜像效率的重点须要放在提高解压效率方面,下载镜像一般不是瓶颈。而在大规模集群中,因为节点数众多,中心式的 Image Registry 的带宽和稳定性也会影响拉取镜像的效率,以下图:

10.jpg

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

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

这里有几个核心组件:

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 描述:

Our analysis shows that pulling packages accounts for 76% of container start time, but only 6.4% of   that data is read.

该 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 远程存储层,以下图所示:

12.jpg

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

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

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

原地升级

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

13.jpg

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

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

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

14.png

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

小结

  从业务场景出发,咱们了解了提高 Pod 建立效率带来收益的场景。而后经过分析 Pod 建立的流程,针对不一样的阶段作相应的优化,有的放矢。

经过这样的分析处理流程,使得能够有效经过技术知足业务需求。

做者简介

张翼飞,就任于阿里云容器服务团队,主要专一 Serverless 领域的产品研发。

**Serverless 对春联领双肩包活动开始啦!**云开发平台与函数计算联合推出新年活动,极速迁移和部署春联神器等应用,领阿里云定制牛年背包和代金券。新老用户都可参加!参加连接:workbench.aliyun.com/activities/…

相关文章
相关标签/搜索