亚马逊AWS官方博客

通过 OverProvisioning 提高EKS弹性伸缩效率

在管理EKS集群时,需要动弹调整集群内的资源,实现弹性伸缩。EKS的弹性伸缩可以通过两个维度实现,横线调整 Nodegroup 数量的 ClusterAutoscaler, 和横向调整 Pod 数量的 HPA。
当集群内资源足够时,通过 HPA 调整 Pod 数量,可以以秒级实现弹性伸缩。但是当集群资源不足时,Pod 的弹性伸缩就会 Pending,等待 Node 资源就绪。这时 Pod 灵活的优势就会失去。
有没有办法能够动态预置 Node 资源,帮助提高EKS集群的弹性伸缩效率?答案是肯定的!

什么是PriorityClass

PriorityClass 是一个无名称空间对象,它定义了从优先级类名称到优先级整数值的映射。 名称在 PriorityClass 对象元数据的 name 字段中指定。 值在必填的 value 字段中指定。值越大,优先级越高。
高优先级的 Pod 会抢占低优先级 Pod 的资源,那我们只需要创建一些优先级为 -1 的 Pod 在集群内“占位”,我们可以称之为”空泡“。
这样当集群资源即将用尽时,这些占位的”空泡“会被挤破,释放资源给应用 Pod。而后这些”空泡“会重新生成,进入 Pending状态,以申请新的 Node 资源。

对比测试

我们设定一个场景进行测试:
在一个EKS集群内,有一个Nodegroup,弹性伸缩范围是2-10个节点,实例类型m5.large, 2 vCPU,8G内存。
用 Nginx 模拟我们的应用,每个 Nginx 占用 0.5 vCPU。
假定,业务系统正在面临突增的访问量,业务系统设定每5分钟扩容一次 Pod。这里我们用手动伸缩模拟真实场景。

cat <<EoF> nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-to-scaleout
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        service: nginx
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx-to-scaleout
        resources:
          limits:
            cpu: 500m
            memory: 512Mi
          requests:
            cpu: 500m
            memory: 512Mi
      nodeSelector:
        alpha.eksctl.io/nodegroup-name: priorityclass
EoF

 

没有使用 PriorityClass 的对照组

首先先创建一个Deployment,用于弹性伸缩
可以看到Pod内的时间和本地几乎一致,没有秒级误差

第一次我们将Pod扩容至6个,这是一个在当前集群承受范围之内的值。

可以看到 Pod 在2秒之内完成了启动。

在此之后,我们等待5分钟,再进行一次扩容。


当集群内 Node 资源无法满足 Pod 需求时,扩容速度被 Node 启动时间拖慢,本地扩容耗时接近5分钟.

使用 PriorityClass 预置空泡

我们先创建两个 PriorityClass:

cat <<EoF > priorityclass_application.yaml
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: application-priority
value: 10000
globalDefault: false
description: "PriorityClass for application"
EoF

cat <<EoF > priorityclass_victims.yaml
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: victims-priority
value: -1
globalDefault: false
description: "PriorityClass for victims"
EOF

kubectl apply -fpriorityclass_application.yaml
kubectl apply -f priorityclass_victims.yaml

更新 Nginx Deployment, 让他带有高优先级:

cat <<EoF> nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-to-scaleout
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        service: nginx
        app: nginx
    spec:
      priorityClassName: "application-priority"
      containers:
      - image: nginx
        name: nginx-to-scaleout
        resources:
          limits:
            cpu: 500m
            memory: 512Mi
          requests:
            cpu: 500m
            memory: 512Mi
      nodeSelector:
        alpha.eksctl.io/nodegroup-name: priorityclass
EoF

kubectl apply -f nginx.yaml

再创建占位容器,这里设置了三个占用 0.5 vCPU 的 Pod,预留 1.5 vCPU 的资源

cat <<EoF> victims.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: victims
spec:
  replicas: 3
  selector:
    matchLabels:
      app: victims
  template:
    metadata:
      labels:
        service: victims
        app: victims
    spec:
      priorityClassName: "victims-priority"
      containers:
      - image: nginx
        name: victims
        resources:
          limits:
            cpu: 500m
            memory: 512Mi
          requests:
            cpu: 500m
            memory: 512Mi
      nodeSelector:
        alpha.eksctl.io/nodegroup-name: priorityclass
EoF

kubectl apply -f victims.yaml

此时我们进行第一次扩容

可以看到本来应该有足够资源的集群,因为占位空泡的加入可用资源变少,扩容应用没有足够的资源启动。
应用挤破占位空泡,抢占其资源。应用正常启动,占位空泡进入 Pending 状态,等待资源就绪。

因为设计的重新调度,本次启动时间20秒。
接下来,等待5分钟。在步进冷却期间,占位空泡已经得到了新的资源,并成功启动,集群资源充沛。

再次进行扩容

可以看到因为有上一次的调度经验,本次扩容的启动时间变为4秒。

弹性预留资源

当集群资源扩容时,预留同样的资源并不能很好地解决问题了。预留的资源在集群内占比变得更小。
这时我们就需要动态调整预留资源的多少。
这个容器会调用 cluster-proportional-autoscaler,动态的调整目标容器的数量,这里就是占位容器的数量。
通过 cluster-proportional-autoscaler 我们可以线性的(比如每个核心1个目标副本)或增量的(自定义每个核心目标容器的数量,1核心1个副本,3核心6个副本,5核心20个副本),并设置基于集群节点和核心数或实例弹性伸缩。
这里配置 cluster-proportional-autoscaler, 按照每核心1个 victims 副本的增加速度扩容, 就可以实现预留资源随集群资源线性增长。

cat <<EoF> autoscaler.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: victims-autoscaler
  labels:
    app: victims-autoscaler
spec:
  selector:
    matchLabels:
      app: victims-autoscaler
  replicas: 1
  template:
    metadata:
      labels:
        app: victims-autoscaler
    spec:
      containers:
        - image: k8s.gcr.io/cluster-proportional-autoscaler-amd64:1.1.2
          name: autoscaler
          command:
            - ./cluster-proportional-autoscaler
            - --namespace=default
            - --configmap=victims-autoscaler
            - --default-params={"linear":{"coresPerReplica":1}}
            - --target=deployment/victims
            - --logtostderr=true
            - --v=2
      serviceAccountName: cluster-proportional-autoscaler-service-account
EoF

kubectl create sa cluster-proportional-autoscaler-service-account

kubectl create clusterrolebinding \
    --clusterrole=cluster-admin \
    --serviceaccount=overprovisioning:cluster-proportional-autoscaler-service-account \
    cluster-proportional-autoscaler

kubectl apply -f autoscaler.yaml

 

总结

相比于直接使用 ASG 进行扩容,使用 Pod 优先级构建占位空泡可以有效提升扩容效率。在测试的情况下,启动时间从5分钟缩短到4秒,弹性伸缩耗时缩短98.7%。

 

本篇作者

王逸飞

亚马逊云科技解决方案架构师,负责基于亚马逊云科技的云计算方案的咨询与架构设计,同时致力于亚马逊云科技云服务知识体系的传播与普及。在微服务,容器等领域拥有多年经验。