Amazon EKS默认开启的资源调度策略是LeastRequestedPriority,意味着消耗资源最少的节点会优先被调度,这样使得集群的资源在所有节点之间分配的相对均匀。但在一些特定的批处理负载场景下(例如机器学习、数据分析),当集群配置了弹性伸缩,作业发起的Pod总是默认均匀的分布在所有集群节点上,导致很多节点运行着少量独立pod,无法被Cluster Autoscaler组件及时回收,从而造成集群资源的浪费。目前亚马逊云科技推出了 Karpenter开源伸缩组件,该组件默认采用了binpack的机制分配适当的计算资源可以解决类似的问题,具体Karpenter的实现机制本文不作涉及,本文采用另外一种方式,通过在Amazon EKS上部署自定义调度器的方式来实现binpack的调度。
所谓binpack调度,原理是调度器在调度pod到节点的时候,预期在节点上保留最少的未使用 CPU 或内存。此策略最大限度地减少了正在使用的集群节点的数量,也降低了资源碎片。
LeastRequestedPriority和binpack的调度区别可以参考下图。
通过上图可以看到,在binpack的调度策略下,pod会集中使用节点资源。这样闲置的节点(上图中的node3)会被及时的回收掉以避免浪费。
我们要想在Amazon EKS上实现上述调度策略,因为Amazon EKS当前不支持直接修改kube-scheduler的配置增加自定义的KubeSchedulerConfiguration对象配置,所以本文指导用户在Amazon EKS部署自定义的调度器来实现。
实现原理
调度框架是面向 Kubernetes 调度器的一种插件架构, 它为现有的调度器添加了一组新的“插件” API。插件会被编译到调度器之中。插件可以实现一些自定义的调度策略。同时我们可以传递KubeSchedulerConfiguration配置给到Scheduler,通过配置文件定义调度框架所支持的不同阶段执行不同的调度行为。
自定义KubeSchedulerConfiguration配置
在配置文件中,为调度器定义扩展点使用哪些插件,禁用哪些插件。
apiVersion: kubescheduler.config.k8s.io/v1beta1
kind: KubeSchedulerConfiguration
leaderElection:
leaderElect: false
profiles:
- schedulerName: my-scheduler
利用Kubernetes Scheduler Framework实现binpack
我们可以在Kubernetes Scheduler Framework中使用Score插件RequestedToCapacityRatio,用于优选阶段给节点打分。将节点按我们希望的方式打分。我们可以将资源利用率为0的时候,得分0分,资源利用率为100时,得分10分,资源利用率越高,得分越高,则这个行为是Binpack的资源分配方式。
如图:在Score阶段应用RequestedToCapacityRatio插件
配置多个scheduler共存
Kubernetes集群可以有多个调度器存在,默认调度器是default-scheduler,我们需要在调度器的配置文件中KubeSchedulerProfile 字段schedulerName 取个单独的名字,例如后面的my-scheduler
部署架构
如上文所述,Kubernetes支持在集群中部署多个scheduler,我们将自定义的scheduler以pod的形式部署在Amazon EKS的node上,有需要的pod部署的时候指定使用自定义scheduler实现调度,没有指定scheduler的pod将继续使用集群默认提供的default scheduler进行调度,这样我们可以将自定义的调度策略配置在单独部署的scheduler上以实现更复杂的调度来满足业务需求。
前提条件
本文使用Amazon EKS 1.21版本,不同版本配置会有差异
本地安装docker环境,用来进行镜像打包操作
方案部署步骤
构建自定义scheduler镜像
本文以官方scheduler镜像为例,首先下载Kubernetes对应版本的源码进行编译
git clone https://github.com/kubernetes/kubernetes.git
cd kubernetes
make
构建scheduler 的Docker file和image
FROM busybox
ADD ./_output/local/bin/linux/amd64/kube-scheduler /usr/local/bin/kube-scheduler
docker build -t 1074006*****.dkr.ecr.ap-southeast-1.amazonaws.com/my-kube-scheduler:1.0 .
在Amazon ECR上创建repository,并将镜像push到Amazon ECR
aws ecr create-repository \
--repository-name my-kube-scheduler \
--image-scanning-configuration scanOnPush=true \
--region ap-southeast-1
aws ecr get-login-password --region ap-southeast-1 | docker login --username AWS --password-stdin 1074006*****.dkr.ecr.ap-southeast-1.amazonaws.com
docker push 1074006*****.dkr.ecr.ap-southeast-1.amazonaws.com/my-kube-scheduler:1.0
在Amazon EKS上部署自定义scheduler
创建my-scheduler.yaml文件,修改对应的image url路径
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-scheduler
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: my-scheduler-as-kube-scheduler
subjects:
- kind: ServiceAccount
name: my-scheduler
namespace: kube-system
roleRef:
kind: ClusterRole
name: system:kube-scheduler
apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: my-scheduler-as-volume-scheduler
subjects:
- kind: ServiceAccount
name: my-scheduler
namespace: kube-system
roleRef:
kind: ClusterRole
name: system:volume-scheduler
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: ConfigMap
metadata:
name: my-scheduler-config
namespace: kube-system
data:
my-scheduler-config.yaml: |
apiVersion: kubescheduler.config.k8s.io/v1beta1
kind: KubeSchedulerConfiguration
leaderElection:
leaderElect: false
profiles:
- schedulerName: my-scheduler
plugins:
score:
disabled:
- name: NodeResourcesLeastAllocated
enabled:
- name: RequestedToCapacityRatio
weight: 100
pluginConfig:
- name: RequestedToCapacityRatio
args:
shape:
- utilization: 0
score: 0
- utilization: 100
score: 10
resources:
- name: cpu
weight: 1
- name: memory
weight: 1
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
component: scheduler
tier: control-plane
name: my-scheduler
namespace: kube-system
spec:
selector:
matchLabels:
component: scheduler
tier: control-plane
replicas: 1
template:
metadata:
labels:
component: scheduler
tier: control-plane
version: second
spec:
serviceAccountName: my-scheduler
containers:
- command:
- /usr/local/bin/kube-scheduler
- --leader-elect=false
- --address=0.0.0.0
- --scheduler-name=my-scheduler
- --config=/etc/kubernetes/my-scheduler/my-scheduler-config.yaml
image: 1074006*****.dkr.ecr.ap-southeast-1.amazonaws.com/my-kube-scheduler:1.0
livenessProbe:
httpGet:
path: /healthz
port: 10259
scheme: HTTPS
initialDelaySeconds: 15
name: kube-second-scheduler
readinessProbe:
httpGet:
path: /healthz
port: 10259
scheme: HTTPS
resources:
requests:
cpu: '1'
securityContext:
privileged: false
volumeMounts:
- name: config-volume
mountPath: /etc/kubernetes/my-scheduler
hostNetwork: false
hostPID: false
volumes:
- name: config-volume
configMap:
name: my-scheduler-config
其中我们在调度器的配置文件中,关闭了NodeResourcesLeastAllocated调度策略,开启了RequestedToCapacityRatio调度策略,从而实现binpack调度。
在Amazon EKS集群中应用yaml文件
kubectl create -f my-scheduler.yaml
观察scheduler是否正确部署在集群中
kubectl get pods -n kube-system | grep my-scheduler
当显示my-scheduler的pod已经正常显示running状态为正常
NAME READY STATUS RESTARTS AGE
my-scheduler-567cf5487f-x8qwq 1/1 Running 0 2m
验证自定义scheduler
创建 my-app.yaml文件,添加字段schedulerName: my-scheduler,指定使用my-scheduler作为调度器
## nginx deployment yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: ops-test
labels:
app: ops-test
spec:
replicas: 60
selector:
matchLabels:
app: ops-test
template:
metadata:
labels:
app: ops-test
app2: ops-test1
spec:
schedulerName: my-scheduler
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
resources:
requests:
cpu: 0.5
memory: 1Gi
在集群中创建deployment
kubectl create -f my-app.yaml
观察调度效果(当前集群中有10台m5.4xlarge作为node,上面没有其它负载)。
ec2-user@ip-172-31-21-1:~/eks/sche$kubectl -n ops-test get pods -o jsonpath="{range .items[*]}{.metadata.labels.app}{'\t'}{.spec.nodeName}{'\n'}{end}" | sort| uniq -c
29 ops-test ip-192-168-42-120.ap-southeast-1.compute.internal
29 ops-test ip-192-168-57-79.ap-southeast-1.compute.internal
2 ops-test ip-192-168-61-90.ap-southeast-1.compute.internal
删除schedulerName: my-scheduler字段,重新部署后观察效果
ec2-user@ip-172-31-21-1:~/eks/sche$kubectl -n ops-test get pods -o jsonpath="{range .items[*]}{.metadata.labels.app}{'\t'}{.spec.nodeName}{'\n'}{end}" | sort| uniq -c
6 ops-test ip-192-168-32-80.ap-southeast-1.compute.internal
6 ops-test ip-192-168-35-23.ap-southeast-1.compute.internal
6 ops-test ip-192-168-39-133.ap-southeast-1.compute.internal
6 ops-test ip-192-168-42-120.ap-southeast-1.compute.internal
6 ops-test ip-192-168-53-166.ap-southeast-1.compute.internal
6 ops-test ip-192-168-57-79.ap-southeast-1.compute.internal
6 ops-test ip-192-168-58-66.ap-southeast-1.compute.internal
6 ops-test ip-192-168-59-58.ap-southeast-1.compute.internal
6 ops-test ip-192-168-61-90.ap-southeast-1.compute.internal
6 ops-test ip-192-168-62-151.ap-southeast-1.compute.internal
我们可以发现,在应用了自定义调度器的binpack调度策略后,pod会优先占满部分节点资源,而Kubernetes默认的调度策略,会将pod按最小资源使用率的方式均匀的分配在每个节点上。
需要留意的问题
维护scheduler增加了集群的复杂度,并且scheduler是个单点
EKS集群升级的时候scheduler也需要手工进行维护升级以避免兼容性问题
总结
目前Amazon EKS不支持直接修改kube-scheduler配置添加自定义的调度插件或配置,本文介绍了通过部署自定义scheduler的方式实现自定义的调度策略,针对本文中提到的binpack调度策略的需求,除了安装自定义scheduler的方式,我们也可以通过安装Karpenter、Apache yunikorn、Volcano等弹性伸缩或调度增强组件实现类似功能,具体可以参考相应的官方文档。
参考链接
https://v1-21.docs.kubernetes.io/docs/tasks/extend-kubernetes/configure-multiple-schedulers/
https://v1-21.docs.kubernetes.io/docs/concepts/scheduling-eviction/resource-bin-packing/
https://karpenter.sh/v0.11.1/
https://blog.cloudera.com/spark-on-kubernetes-gang-scheduling-with-yunikorn/
本篇作者