# 资源模型与资源管理

# 字典:

  1. 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
  2. 资源

    • 可压缩:cpu
    • 不可压缩:内存,会被内核kill掉
  3. 配置,实际场景中,大多数作业使用的资源远小于它所请求的资源限额

    • requests:kube-schedule 按它的值来计算
    • limits:kubelet按它的值来配置限制
  4. Qos模型 当宿主机资源紧张时,对Pod进行Eviction资源回收时用到,

    • Guaranteed:如果每一个Containers都设置了requests和limits,并且值相等
      • 资源不足最后被删除
      • cpu绑定,减小上下文切换,性能提升
    • Burstable:至少有一个Container设置了requests
    • BestEffort:没有设置request和limits
  5. 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模式

  6. 回收策略

    • BestEffor类别
    • Burstable类别、并且发生“饥饿”的资源使用量已经超过requests的Pod
    • Guaranteed类别。并且k8s保证只有当Guaranteed类别的Pod资源使用量超过limits限制,或者宿主机本身正处理于Memory Pressure(内存压力)状态时,这样的Pod才会被Eviction操作
  7. CPU绑定 DeamonSet的Pod都设置为Guaranteed类型的Qos类型 MemoryPressure或DiskPressure时会给宿主机打污点标记

    • cpuset
      • Pod必须是Guaranteed类型
      • 将Pod的CPU资源的值设置为一个相等的整数值
    • cpushare

# 默认调度器

# 字典

  1. 默认调度器:职责是为一个新创建的Pod,寻找合适的节点Node

    • 从集群所有节点中,根据调度算法 挑选出所有可运行Pod的节点
    • 从第一步的结果中,根据调度算法挑选出一个最符合条件的节点做为最终结果

    包含两组核心的算法

    • Predicate 断言,断定
    • Priority 优先级

    通过更新Pod的spec.nodeName来指定Pod最终调度到那个节点

  2. Schduler Cache:通过一个双向链表和node的递增计数(etcd实现)实现增量更新

  3. 发展方向:轻量化、接口化和插件化

# 调度器执行逻辑

  1. Informer Path:启动一系列Informer,监听Etcd中Pod、Node、Service等调度相关的API对象的变化;

    • 监听到后通过Pod Informer的Handler将待调度Pod添加到调度队列,默认是一个PriorityQueue
    • 维护调度器缓存,ScheduleCache
  2. Scheduling Path:主循环,不断从队列中取出一个Pod

    • 调用Predicates算法进行“过滤”,过滤后得到的一组Node,就是可运行的Pod的宿主机列表。
    • 调用Priorities算法为Node打分,从0~10,分数最高的作为此次调度的结果

    无锁化

    • 启动多个Goroutine以节点为粒度并发执行Predicates算法
    • Priorities算法以MapReduce方式并行计算然后汇总
  3. Bind:修改Pod对象的nodeName值

    • Assume:只会更新Schduler Cache中的Pod的Node信息
    • Bind:异步ApiServer更新Pod,真正完成绑定
  4. Admit:新的Pod完成调度需要在某个节点运行起来前,该节点上的kubelet还会通过一个Admit的操作再次验证是否能够在节点上运行

  5. Admit GeneralPredicates算法:基本断言调度算法,资源是否可用,端口是否冲突等过一遍

# 42 调度策略解析

# 阶段

  1. Predicates 按照调度策略,从当前集群的所有节点中,过滤出一系列符合条件的节点

    • GeneralPredicates 负责最基础的调度策略,比如计算宿主机资源是否够用;kubelet启动pod前还会再执行一遍,用于二次确认

      • PodFitsResources 计算cpu和内存、扩展资源是否够用。GPU等硬件资源没有定义具体资源类型,统一用Extended Resource的、kv格式扩展字段描述

        requests:
          alpha.kubernetes.io/nvidia-gpu: 2
        
        # node.capacity字段中,也要相应的添加上GPU的总数
        
        1
        2
        3
        4
      • PodFitsHost 宿主机名字是否与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
        20
      • InterPodAntiAffinity 反亲合性规则

  2. 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)

# 字典

  1. Priority
  2. 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

高优先级的先从调度队列中出队

  1. 抢占:当一个高优先级的Pod调度失败后,调度器试图从当前集群中找一个节点,使得当这个节点上的一个或多个低优先级的Pod被删除后,待调度的Pod就可被调度到这个节点上。

  2. “抢占者”(Preemptor)

    • 调度器将抢占者的spec.nominatedNodeName 设置为被抢占Node的名字
    • 抢占者重新进入下一个调度周期,新的调度周期里来决定是不是要运行在被抢占的节点上

    原因

    • Pod有优雅退出时间,默认30s,在此期间,集群可调度性变化,在优雅退出期间
    • 允许其它更高优先级的Pod抢占,也使抢占者有机会抢占其它节点

    实现

    • ActiveQ 下一个调度周期要调度的对象
    • UnschedulableQ 调度失败的对象

    逻辑

    • 检查失败原因,确认是否可为其找一个新节点
    • 如果抢占可以发生,调度器把自己缓存的所有节点信息复制一份,然后中用这个副本来模拟抢占过程

    原则:尽量减小抢占对整个系统的影响

    动作

    • 检查牺牲者列表,清理这些Pod所带的nominatedNodeName字段
    • 把抢占者的nominatedNodeName设置为被抢占者的Node名字(触发“重新做人”流程,让其进入下一个调度周期的调度流程)
    • 开启一个Goroutine,同步地删除牺牲者

在执行Predicate算法时,如果待检查的Node是个即将被抢占的节点,调度器会进行两次Predicates算法,只需要检查优先级等于或大于当前Pod的抢占者

  1. 假设'抢占者'已经运行在这个节点上
  2. 正常执行,不考虑‘抢占者’ InterPodAntiAffinity 由于Pod之间有互斥性,所以要考虑抢占者存在于节点上时,待调度的Pod还能不能调度成功

参考资源:https://segmentfault.com/a/1190000018446829

# 44 GPU管理与Device Plugin机制

1. GPU 设备,比如 /dev/nvidia0;GPU 
2. 驱动目录,比如 /usr/local/nvidia/*。
1
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
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
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
1
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请求中。

其它硬件插件:

  1. FPGA
  2. SRIOV
  3. RDMA

问题:

  1. 只能处理“设备个数”这一唯一情况
  2. 异构、不能简单肜“数据描述”具体需求时,就不能处理了
  3. 根据某种设备的全局分布,做最佳的调度选择也不支持
  4. 缺乏对Device描述的API对象,如果硬件设备复杂,并且Pod也关心硬件属性的话
上次更新: : 7 months ago