使用镜像流及附加磁盘为拉取镜像加速

  GKE的用户越来越多,近期很多大模型也开始部署在GKE。但是随着镜像越来越大,创建pod时,镜像的拉取速度成为瓶颈。
  去年,GKE推出了镜像流加速(ImageStreaming),结合Artifact Registry实现node pool层的镜像缓存加速,但是这种方式有一定限制,镜像必须保存在Artifact Registry,并且第一次拉取镜像时速度仍然较慢。前不久,GCP又推出了通过附加磁盘加速镜像的方式,解决了这两个限制。
  GCP官方文档相关说明和操作步骤,但是写的不够详细,并且有些小错误和小坑,下边做详细解释,并比较各种方式镜像拉取速度。

创建带Docker Image的附加磁盘镜像

  使用此功能前,需要先创建带有所需Docker Image的磁盘镜像,然后在创建nood pool时,指定在node挂接通过此镜像生成的附加磁盘。

环境准备

所需代码

  在Github上可以下载创建磁盘镜像所需的代码:
gke-disk-image-builder
  该代码为go语言编写,所以先要预备go的执行环境。
  此代码的功能是创建一台临时GCP VM,并附加一块磁盘,将指定的Docker Image下载到该磁盘后,将该磁盘做成磁盘镜像。

执行权限

  代码执行时,需要一定GCP权限:

  • compute.projects.get
  • storag.objects.create
  • serviceusage.services.use
  • iam.serviceAccountUser

  如果在具备gcloud环境,可以通过ADC赋予这些权限;如果在其他环境,可以创建Service Account并下载key,在执行创建磁盘镜像命令时通过参数指定key。

GCS Bucket

  需创建一个GCS Bucket,存放创建磁盘镜像过程中的日志。代码执行过程中,会创建一个临时GCP VM,该VM使用默认的GCE Default Service Account,所以需要给GCE Default Service Account绑定权限。在文档中,要求赋予storage.objectCreator的role,但实际运行时,需要有storage.objects.get权限,所以应该赋予storage.objectAdmin权限。
  如下图

  为GCE Default Service Account赋予权限:

创建磁盘镜像

使用默认参数创建磁盘镜像

  创建磁盘镜像最简单的方式是在CloudShell里执行。CloudShell已具备go执行环境,并且具备登录人员的对应权限,所以只需下载代码进入相关目录即可。
  从Github下载代码并进入相关目录:

1
2
git clone https://github.com/GoogleCloudPlatform/ai-on-gke.git
cd ai-on-gke/tools/gke-disk-image-builder/

  在CloudShell执行如下:

  创建磁盘镜像的必须参数如下:

1
2
3
4
5
6
7
go run ./cli \
--project-name=PROJECT_ID \
--image-name=DISK_IMAGE_NAME \
--zone=LOCATION \
--gcs-path=gs://LOG_BUCKET_NAME \
--disk-size-gb=10 \
--container-image=

  其中

  • project-name:项目的id(注意参数名是name,但实际需要id,是个小坑)
  • image-name:创建的磁盘镜像名字,不能和已有的重复,否则会报错
  • zone:临时GCP VM所在zone,随意即可
  • gcs-path:存放日志的GCS Bucker名字
  • disk-size-gb:镜像容量,GB为单位,整数,不需后缀
  • container-image:所需拉取的Docker Image名字(地址),可以放多个,每个Image均需要用此参数指定
      如下成功创建:

  创建磁盘镜像过程中的日志保存在预先创建的GCS Bucket,每次执行都会创建一个新目录:

  进入该目录,可以查看日志:

其他参数

  根据实际情况,该代码还支持更多参数,常用如下:

  • –gcp-oauth:如之前所述,如果没哟欧ADC环境或ADC环境权限不足,需要使用ServiceAccount的key认证,可以通过此参数设置key的路径,注意要绝对路径
  • –service-account:创建临时GCP VM时,为该VM配置的Service Account
  • –image-pull-auth:私有库的认证,下一节详细描述
  • –timeout:每个步骤的最大时长,默认20分钟,如果网络较慢,或者Docker Image较大,可以适当增加
  • –network&–subnet:创建GCP VM的vpc和subnet,默认值Default,可根据实际情况配置

从私有镜像库拉取Docker Image

  上个例子中,拉取的Docker Image(us-docker.pkg.dev/hy-tool/hy-docker-repo/hy-image-001:1.0)存放在公有镜像库us-docker.pkg.dev/hy-tool/hy-docker-repo/,所以无需任何认证。
  现在用一个私有库的Doceker Image(us-docker.pkg.dev/hy-tool/hy-docker-repo-private/hy-image-001:1.0)测试:

  由于权限问题,在拉取Docker Image时出错。这时候我们需要通过参数–image-pull-auth解决。
  –gcp-oauth有两个选项,默认是None,即没有任何认证。另一个选项是ServiceAccountToken,当设置为ServiceAccountToken时时,将使用临时VM的ServiceAccount作为鉴权去拉取Docker Image。
  现在使用Default GCE Service Account测试,将其赋予私有镜像库的viewer权限:

  然后在创建镜像的命令里加上参数–image-pull-auth=ServiceAccountToken后执行:

  这次拉取成功并创建了镜像。
  如前边所述,同一个磁盘镜像中可以拉取多个Docker Image,并且多个Docker Image可以存放在不同的镜像库。但因为参数–image-pull-auth是全局的,所以多个Docker Image必须全在公有镜像库或者全在私有镜像库。

Docker Image拉取速度比较

  现在GKE上拉取Docker Image有三种方式:

  • 直接从镜像库拉取
  • 采用镜像流传输加速
  • 通过本地磁盘加速

  现在对这三种方式进行比较。
  ImageStreaming是基于Region级别,所以为了避免相互干扰,将创建3个不同Region的Cluster,每个Cluster创建NodePool进行测试。每个NodePool配置2个Node,机型为e2-standard-4(16GB内存)。

测试环境准备

普通集群

在us-west1-a创建普通Cluster,及普通NodePool:

1
2
gcloud container clusters create cluster001 --zone=us-west1-a
gcloud container node-pools create np001 --cluster=cluster001 --zone=us-west1-a --num-nodes=2 --machine-type=e2-standard-4

启用ImageStreaming的集群

在us-west2-a创建启用ImageStreaming的Cluster,及启用ImageStreaming的NodePool

1
2
gcloud container clusters create cluster001 --zone=us-west2-a --enable-image-streaming
gcloud container node-pools create np001 --cluster=cluster001 --zone=us-west2-a --enable-image-streaming --num-nodes=2 --machine-type=e2-standard-4

启用附加磁盘的集群

在us-west3-a创建启用附加磁盘的Cluster,及启用附加磁盘的NodePool(使用附加磁盘功能需启用ImageStreaming):

1
2
gcloud container clusters create cluster001 --zone=us-west3-a --enable-image-streaming
gcloud beta container node-pools create np001 --cluster=cluster001 --zone=us-west3-a --enable-image-streaming --num-nodes=2 --machine-type=e2-standard-4 --secondary-boot-disk=disk-image=global/images/hy-image-001,mode=CONTAINER_IMAGE_CACHE

  查看Node配置,会发现多一块从磁盘镜像复制的附加磁盘:

拉取速度比较

  创建yaml文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: apps/v1
kind: Deployment
metadata:
name: hy-deploy001
spec:
replicas: 1
selector:
matchLabels:
app: hy-deploy
template:
metadata:
labels:
app: hy-deploy
spec:
nodeSelector:
cloud.google.com/gke-nodepool: np001
containers:
- name: hy-deploy
image: us-docker.pkg.dev/hy-tool/hy-docker-repo/hy-image-001:1.0
resources:
requests:
memory: "10000Mi"
cpu: "500m"

  这里使用了一个2GB的Docker Image,并且要求内存为10G,接近单个Node的内存上限,这样每个Node只能运行一个Pod。

普通NodePool拉取

  在普通NodePool创建pod,并通过kubectl get events查看Docker Image拉取时间:

  拉取时间大约55s。
  现将pod副本适量设置为2,由于内存要求,将部署到新的Node:

  新的Node需要再次从镜像库拉取,耗时43s。

启用ImageStreaming的NodePool拉取

  同样查看第一个Node的拉取时间:

  大约53s。
  在新Node拉取:

  可以看到,镜像通过ImageStreaming方式从本地缓存拉取,立刻完成。

启用附加磁盘的NodePool拉取

  查看Docker Image拉取时间:

  可以看到,即使第一次拉取,Docker Image从本地磁盘提供,只花了不到1s的时间。

总结

  通过以上实验可以看到,ImageStreaming和附加磁盘的方式可以显著提升Docker Image的拉取速度,特别是现在比较火热的AI模型,都会采用比较大的Docker Image,可以用这些方式加速。
  ImageStreaming和附加磁盘的实现方式不一样,使用场景也有区别,简单总结如下:

ImageStreaming 附加磁盘
第一次拉取 无加速 有加速
新Node拉取 同Region有加速 有加速
使用限制 只能对Artifact Registry的Docker Image加速 需要预先创建包含Docker Image的磁盘镜像,操作较为繁琐

  大家可以根据实际情况选择最适合自己的方式。