# 资源模型与资源管理
# 字典:
Pod:最小的原子调度单位,所有和调度和资源管理相关的属性都是属于Pod对象的字段。最重要的就是Cpu和内存的配置。
apiVersion: v1 kind: Pod metadata: name: frontend spec: containers: - name: db image: mysql env: - name: MYSQL_ROOT_PASSWORD value: "passport" resources: requests: memory: 64Mi cpu: 250m limits: memory: 128Mi cpu: 500m - name: wp image: wordpress resources: requests: memory: 64Mi cpu: 250m limits: memory: 128Mi cpu: 500m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27资源
- 可压缩:cpu
- 不可压缩:内存,会被内核kill掉
配置,实际场景中,大多数作业使用的资源远小于它所请求的资源限额
- requests:kube-schedule 按它的值来计算
- limits:kubelet按它的值来配置限制
Qos模型 当宿主机资源紧张时,对Pod进行Eviction资源回收时用到,
- Guaranteed:如果每一个Containers都设置了requests和limits,并且值相等
- 资源不足最后被删除
- cpu绑定,减小上下文切换,性能提升
- Burstable:至少有一个Container设置了requests
- BestEffort:没有设置request和limits
- Guaranteed:如果每一个Containers都设置了requests和limits,并且值相等
Eviction 资源回收
阈值
memory.available<100Mi nodefs.available<10% nodefs.inodesFree<5% imagefs.available<15% # 配置方式 # kubelet --eviction-hard=imagefs.available<10%,memory.available<500Mi,nodefs.available<5%,nodefs.inodesFree<5% --eviction-soft=imagefs.available<30%,nodefs.available<10% --eviction-soft-grace-period=imagefs.available=2m,nodefs.available=2m --eviction-max-pod-grace-period=600
1
2
3
4
5
6
7
8阈值数据来源:Cgroups读取,以及cAdvisor监控到的数据
Soft模式:Soft模式允许有一段优雅时间,如 imagefs.available=2m,意味着当 imagefs 不足的阈值达到 2 分钟之后,kubelet 才会开始 Eviction 的过程
Hard模式
回收策略
- BestEffor类别
- Burstable类别、并且发生“饥饿”的资源使用量已经超过requests的Pod
- Guaranteed类别。并且k8s保证只有当Guaranteed类别的Pod资源使用量超过limits限制,或者宿主机本身正处理于Memory Pressure(内存压力)状态时,这样的Pod才会被Eviction操作
CPU绑定 DeamonSet的Pod都设置为Guaranteed类型的Qos类型 MemoryPressure或DiskPressure时会给宿主机打污点标记
- cpuset
- Pod必须是Guaranteed类型
- 将Pod的CPU资源的值设置为一个相等的整数值
- cpushare
- cpuset
# 默认调度器
# 字典
默认调度器:职责是为一个新创建的Pod,寻找合适的节点Node
- 从集群所有节点中,根据调度算法 挑选出所有可运行Pod的节点
- 从第一步的结果中,根据调度算法挑选出一个最符合条件的节点做为最终结果
包含两组核心的算法
- Predicate 断言,断定
- Priority 优先级
通过更新Pod的spec.nodeName来指定Pod最终调度到那个节点
Schduler Cache:通过一个双向链表和node的递增计数(etcd实现)实现增量更新
发展方向:轻量化、接口化和插件化
# 调度器执行逻辑
Informer Path:启动一系列Informer,监听Etcd中Pod、Node、Service等调度相关的API对象的变化;
- 监听到后通过Pod Informer的Handler将待调度Pod添加到调度队列,默认是一个PriorityQueue
- 维护调度器缓存,ScheduleCache
Scheduling Path:主循环,不断从队列中取出一个Pod
- 调用Predicates算法进行“过滤”,过滤后得到的一组Node,就是可运行的Pod的宿主机列表。
- 调用Priorities算法为Node打分,从0~10,分数最高的作为此次调度的结果
无锁化
- 启动多个Goroutine以节点为粒度并发执行Predicates算法
- Priorities算法以MapReduce方式并行计算然后汇总
Bind:修改Pod对象的nodeName值
- Assume:只会更新Schduler Cache中的Pod的Node信息
- Bind:异步ApiServer更新Pod,真正完成绑定
Admit:新的Pod完成调度需要在某个节点运行起来前,该节点上的kubelet还会通过一个Admit的操作再次验证是否能够在节点上运行
Admit GeneralPredicates算法:基本断言调度算法,资源是否可用,端口是否冲突等过一遍
# 42 调度策略解析
# 阶段
Predicates 按照调度策略,从当前集群的所有节点中,过滤出一系列符合条件的节点
GeneralPredicates 负责最基础的调度策略,比如计算宿主机资源是否够用;kubelet启动pod前还会再执行一遍,用于二次确认
PodFitsResources 计算cpu和内存、扩展资源是否够用。GPU等硬件资源没有定义具体资源类型,统一用Extended Resource的、kv格式扩展字段描述
requests: alpha.kubernetes.io/nvidia-gpu: 2 # node.capacity字段中,也要相应的添加上GPU的总数
1
2
3
4PodFitsHost 宿主机名字是否与Pod的spec.nodeName一致
PodFitsHostPorts 检查宿主机端口spec.nodePort是否已被使用
PodMatchNodeSelector 检查Pod的nodeSelector或nodeAffinity指定的节点,是否与待考察节点匹配
Volume相关的一组规则
- NoDiskConflict 多个Pod声明挂载持久化volumn是否有冲突(was、gce的volume不允许被2个pod同时使用)
- MaxPDVolumeCountPredicate 节点上某种类型的volume是否超出一定数据
- VolumeZonePredicate 检查持久化volume的zone(高可用域)标签是否匹配
- VolumeBindingPredicate 这Pod对应pv的nodeAffinity字段,是否与某个节点标签的匹配;如果pvc没有与具体pv绑定的话,调度器还要负责检查所有待绑定的PV,当有可用pv并且nodeAffinity与待考查节点一致,这规则才会返回成功
- CSIMaxVolumeLimitPredicate 检查csi volume相关的限制
apiVersion: v1 kind: PersistentVolume metadata: name: example-local-pv spec: capacity: storage: 500Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: local-storage local: path: /mnt/disks/vol1 nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostnamee operator: in value: - my-node
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21宿主机相关的规则 检查待调度的Pod是否满足Node本身的某些条件
- PodToleratesNodeTaints 检查Node的污点机制,只朋当Pod的Toleration字段与Node的Taint字段能匹配时,Pod才能调度到这个节点上
- NodeMemoryPressurePredicate 检查当前节点的内存是不是已经不够充足,如果是,则不能被调度到这个节点
- NodeConditionPredicate:检查Node是否还未准备好或处理NodeOutOfDisk、NodeNetworkUnavailable状态,或者spec.Unschedulable为true,则不可调度
- nodeDiskPreessurePredicate:检查磁盘是否不够用
- NodePIDPressurePredicate:检查节点pid是否不够用
与Pod相关的过滤规则
跟GeneralPredicates大多数是重合的 如:PodAffinityPredicate。检查待调度Pod与Node上已有的Pod之间的亲密与反亲密程度
apiVersion: v1 kind: Pod metadata: name: with-pod-antiaffinity spec: affinity: podAntiffinity: requiredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpresions: - key: security operator: In values: - S2 topologyKey: kuberenetes.io/hostname containers: - name: with-pod-affinity image: docker.io/ocpqe/hello-pod
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20InterPodAntiAffinity 反亲合性规则
Priorities 为节点打分,得分最高的就是最后被绑定的结点,分值 0 ~ 10
总分 = (权重1 * 打分函数1) + (权重2 * 打分函数2) + … + (权重n * 打分函数n)
- LeastRequestedPriority 选择空闲资源(cpu、内存)最多的机器
- BalancedResourceAllocation 平衡节点上的所有资源,避免资源不均衡的情况。根据Fraction(Pod请求资源/节点上可用资源),选择资源差距最小的
- NodeAffinityPriority
- TaintTolerationPriority
- InterPodAffinityPriority
- ImageLocalityPriority 镜像很大,并且已经存在某Node上,则得分就越高
- 计算得分时还会根据镜像的分布进行优化,对冲引起调度堆叠的风险
# 43 默认优先级及抢占策略
解决Pod调度失败的问题。
当一个高优先级的Pod调度失败后,不希望被“搁置”,而是会“挤走”某个节点上的一些低优先级的Pod。这样就保证了这个高优先级的Pod的调度成功。
Kubernetes 规定,优先级是一个 32 bit 的整数,最大值不超过 1000000000(10 亿,1 billion)
# 字典
- Priority
- Preemption
启动:
apiVersion: scheduling.k8s.io/v1beta1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
# true: 这个值会成为系统的默认值
# false:希望声明使用它的有这个优先级,没有声明的优先级为0
globalDefault: false
descriptiton: ""
---
apiVersion: v1
kind: Pod
meetadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
priorityClassName: high-priority
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
高优先级的先从调度队列中出队
抢占:当一个高优先级的Pod调度失败后,调度器试图从当前集群中找一个节点,使得当这个节点上的一个或多个低优先级的Pod被删除后,待调度的Pod就可被调度到这个节点上。
“抢占者”(Preemptor)
- 调度器将抢占者的spec.nominatedNodeName 设置为被抢占Node的名字
- 抢占者重新进入下一个调度周期,新的调度周期里来决定是不是要运行在被抢占的节点上
原因
- Pod有优雅退出时间,默认30s,在此期间,集群可调度性变化,在优雅退出期间
- 允许其它更高优先级的Pod抢占,也使抢占者有机会抢占其它节点
实现
- ActiveQ 下一个调度周期要调度的对象
- UnschedulableQ 调度失败的对象
逻辑
- 检查失败原因,确认是否可为其找一个新节点
- 如果抢占可以发生,调度器把自己缓存的所有节点信息复制一份,然后中用这个副本来模拟抢占过程
原则:尽量减小抢占对整个系统的影响
动作
- 检查牺牲者列表,清理这些Pod所带的nominatedNodeName字段
- 把抢占者的nominatedNodeName设置为被抢占者的Node名字(触发“重新做人”流程,让其进入下一个调度周期的调度流程)
- 开启一个Goroutine,同步地删除牺牲者
在执行Predicate算法时,如果待检查的Node是个即将被抢占的节点,调度器会进行两次Predicates算法,只需要检查优先级等于或大于当前Pod的抢占者
- 假设'抢占者'已经运行在这个节点上
- 正常执行,不考虑‘抢占者’ InterPodAntiAffinity 由于Pod之间有互斥性,所以要考虑抢占者存在于节点上时,待调度的Pod还能不能调度成功
参考资源:https://segmentfault.com/a/1190000018446829
# 44 GPU管理与Device Plugin机制
1. GPU 设备,比如 /dev/nvidia0;GPU
2. 驱动目录,比如 /usr/local/nvidia/*。
2
容器的CRI(Container Runtime Interface)参数里
Extended Resoure: 用户自定义资源
apiVersion: v1
kind: Pod
metadata:
name: cuda-vector-add
spec:
restartPolicy: OnFailure
containers:
- name: cuda-vector-add
image: "k8s.gcr.io/cuda-vector-add:v0.1"
resources:
limits:
nvidia.com/gpu: 1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Node
metadata:
name: node-1
Status:
Capacity:
cpu: 2
memory: 2049008Ki
nvidia.com/gpu: 1
2
3
4
5
6
7
8
9
path api更新Node节点,添加自定义资源的数量
# 启动k8s客户端
kubectl proxy
curl --header "Content-Type: application/json-patch+json" \
--request PATCH \
--data '[{"op":"add", "path":"/status/capacity/nvidia.com/gpu", "value":"1"}]' http://localhostt:8001/api/v1/nodes/<your-node-name>/status
2
3
4
5
6
**Device Plugin插件:**每一种硬件设备都需要有相对应的Device Plugin管理,它们通过gRPC方式,同kubelet连接起来
Device Plugin插件通过ListAndWatch的API,定期向kubelet汇报该Node上的GPU的列表,Kubelet拿到后,直接在向ApiServer发送的心跳里,以Extended Resource的方式,添加上GPU的数量
调度成功后,kubelet拿来进行容器操作,当它发现请求GPU时,会从自己持有的GPU列表中,为它分配,此时会向Device Plugin发起一个Allocate请求
Device Plugin收到后,会根据传来的设备ID,找到这些设备对应的设备路径和驱动目录。
kubelet拿到这些信息后,追回在创建容器对应的CRI请求中。
其它硬件插件:
- FPGA
- SRIOV
- RDMA
问题:
- 只能处理“设备个数”这一唯一情况
- 异构、不能简单肜“数据描述”具体需求时,就不能处理了
- 根据某种设备的全局分布,做最佳的调度选择也不支持
- 缺乏对Device描述的API对象,如果硬件设备复杂,并且Pod也关心硬件属性的话