一、背景

咱们晓得,容器运行起来的工夫是十分快的,然而如果节点上容器的镜像不存在,那么在运行容器时要先拉取镜像,拉取镜像在容器启动的过程中占用的工夫比拟长,这个过程要将容器所有的镜像层都拉取到本地磁盘中。据统计,拉镜像操作要占用容器启动工夫的76%。这在容器数量少的状况下问题不大,但容器数量比拟多并且都是冷启动的时候会十分的慢。

如何解决容器冷启动过程中拉取镜像慢这个问题?有这样的一种解决思路:在容器启动过程中,容器要用的镜像通过高速网络按需从镜像仓库中读取,而不是将镜像所有的层都拉下来。Stargz-snapshotter是containerd上面的一个子项目,以Proxy Plugin的形式扩大containerd的性能,是Containerd的一个remote-snapshotter实现。

版本变迁

二、应用

Stargz-snapshotter在kuberentes中应用比较简单:应用Containerd作为kuberentes的CRI运行时。在本地起一个stargz-snapshotter的服务,作为containerd的一个remote snapshotter。

镜像转换

在应用前须要将咱们的一般的镜像转换成stargz-snapshotter能够辨认的镜像,应用ctr-remote工具进行转换,上面示例是将本地一个centos镜像进行转换,转换实现后推送到镜像仓库中:

$ ctr-remote image optimize --plain-http --entrypoint='[ "sleep" ]' --args='[ "3000" ]' centos:7 centos:7-eg复制代码

比照

应用crictl工具在本地拉取转换前和转换后的镜像,做一下比照,通过lazy的形式拉取镜像的速度更快:

失常拉取镜像

$ time crictl pull centos:7
Image is up to date for sha256:ef8f4eaacef5da519df36f0462e280c77f8125870dbb77e85c898c87fdbbea27real  
0m5.967suser  0m0.009ssys  0m0.012s

拉取优化后的镜像

$ time crictl pull centos:7-eg
Image is up to date for sha256:36edf0c0bb4daca572ba284057960f1441b14e0d21d5e497cb47646a22f653d6real  
0m0.624suser  0m0.012ssys  0m0.010s复制代码

查看镜像层

应用crictl创立一个pod,进入容器中,查看/.stargz-snapshotter/目录下各个镜像层在本地缓存的状况:

$ cat /.stargz-snapshotter/*{"digest":"sha256:857949cb596c96cc9e91156bf7587a105a2e1bc1e6db1b9507596a24a80f351a","size":80005845,"fetchedSize":3055845,"fetchedPercent":3.819527185794988}{"digest":"sha256:8c57b1a6bef1480562bc27d145f6d371955e1f1901ebdea590f38bfedd6e17d0","size":33614550,"fetchedSize":64550,"fetchedPercent":0.19202993941611593}复制代码

三、原理

上图是stargz-snapshotter的实现概览,通常的咱们在拉取镜像时,要将镜像的每一层拉取下来,而应用stargz-snapshotter后containerd不再是拉取镜像的层,而是为存储在镜像仓库中镜像的每一层在容器运行节点上创立一个目录,通过近程挂载的形式挂到各个目录上。容器启动前再将各个目录做overlay挂载,为容器提供一个rootfs。当须要读取某个文件时,通过网络读取镜像仓库中镜像层中的文件。

上面再看一下镜像层是怎么近程挂载和如何从镜像层中按需读取文件的。

用户态文件系统

Stargz-snapshotter应用FUSE实现了用户态的文件系统。FUSE(Filesystem in userspace)框架是一个内核模块,可能让用户在用户空间实现文件系统并且挂载到某个目录,就像在内核实现文件系统一样。

如上图所示,stargz-snapshotter是一个实现了用户态文件系统的程序(golang语言,应用go-fuse作为实现的依赖)。当有拉取镜像的操作产生时,stargz-snapshotter会为镜像的每一层在${stargz-root}/snapshotter/snapshots/下创立一个目录,执行实现一个文件系统的逻辑,并将这个文件系统挂载到刚创立的目录上,例如图中的/dcos/snapshotter/snapshots/1。当有用户读取目录下的文件时,申请的流向是这样的:

① 操作申请经VFS到FUSE

② FUSE内核模块依据申请类型,调用stargz-snapshotter的逻辑,stargz-snapshotter从镜像仓库中读取该层中的文件

③ Stargz-snapshotter将文件的内容通过VFS返回给零碎调用

(e)stargz格局

a. stargz格局

通常寄存在镜像仓库中的镜像层都是应用gzip压缩过的,咱们不能从这个压缩后的文件中提取单个文件。那stargz-snapshotter是怎么做到从单个镜像层中读取单个文件的呢?

Stargz-snapshotter应用了另一种压缩镜像层的格局,它也是gzip包,一种可seekable的gzip包,图3是targz和stargz的比照。压缩包里的文件能够被检索和抽取,但仍是zip格局的文件;镜像层中的每个文件都会被打成一个zip包,最初再组成一个大的zip包;整个zip包中有一个TOC文件,它记录了包中每个文件的偏移量;Footer占最初47个字节,记录了TOC在整个zip包中的偏移量。

这样就能够通过镜像层最初47个字节的Footer,找到TOC的偏移量,而后读取TOC的内容就能失去整个镜像层中有哪些文件,每个文件的偏移量是多少。Stargz-snapshotter就是通过这个TOC文件去按需检索整个镜像层中文件的。

b. estartgz格局

默认状况下,将镜像的某一层近程挂载到指标主机后,stargz-snapshotter默认会创立一个后台任务去缓存镜像层。在容器启动过程中,如果容器启动须要的文件没有在本地缓存那么stargz-snapshotter就须要通过网络去镜像仓库中读取,这会导致容器启动速度比较慢。

estargz是对stargz格局进行了优化,如上图所示。它多了一个landmark文件,这个文件将镜像层中的文件分成了两类:一类是容器运行时最有可能用到的,另一类是可能用不到的。这样后台任务会优先去缓存那些容器运行时须要的文件,这样会减少本地缓存的命中率,放慢容器的启动速度。

下图是三种镜像层的比照状况,legacy是一般镜像层,stargz是stargz格局的镜像层,estargz是优化后stargz格局的镜像层。

c. 分层拉取镜像

镜像层应用estargz格局能够做到从压缩包中检索文件,那stargz是如何从镜像仓库中依照分片获取文件全副或者局部数据的?

在OCI标准中有对于如何从仓库中获取局部数据的形容,而docker registry也有对应接口实现。

Registry中获取镜像层部署数据的接口如下:

其中,name就是指标repository的名称,digest就是镜像层blob的digest的值,Host就是镜像仓库的地址,Range形容的就是要获取的blob分片。

返回的响应如下:

其中,start是开始的字节,end是完结的字节,size是层大小,length是本次申请的层分片。

lazy-pulling流程

Containerd应用stargz-snapshotter拉取镜像的流程如下:

① 依据镜像名称和tag解析出镜像manifest的digest的值

② 依据镜像manifest的digest的值,从镜像仓库中下载manifest,保留在content store中

③ 依据manifest的内容获取镜像config的digest的值,从镜像仓库中下载config,保留在content store中

④ 解析镜像的每一层,创立snapshot,如果containerd应用的stargz-snapshotter,它会返回一个snapshot曾经存在的谬误。Stargz-snapshotter PrepareSnapshot的逻辑就是为以后层筹备文件系统并挂载到本地的一个过程。

⑤ 所有镜像层解析实现后会保留镜像的元数据

四、小结

创立容器时,拉取镜像过程在容器启动工夫的占比高,通常咱们会应用多种办法去制作尽量小一点的镜像,或者通过P2P网络去散发镜像。镜像lazy pull是另一种进步镜像散发速度的形式。

应用stargz-snapshotter在镜像拉取时,仅将镜像的manifest和config下载下来,并镜像每一层通过近程挂载的形式挂到以后主机上,容器运行时达到按需读取文件的成果。而传统形式是将镜像的每一层都下载到本地进行解压。相比而言前者能放慢镜像的拉取速度,放慢容器冷启动的速度。但须要留神,文件是按需加载的,它依赖于一个比拟好的网络环境。