《深入剖析kubernetes》学习笔记(7)——作业调度和资源管理

《深入剖析kubernetes》学习笔记(7)——作业调度和资源管理

40. kubernetes的资源模型和资源管理

  • Pod是kubernetes中最小的调度单元,与调度和资源管理相关的属性都属于Pod对象的字段(实际上定义在Pod的各容器下),例如
    apiVersion: v1
    kind: Pod
    metadata:
      name: frontend
    spec:
      containers:
      - name: db
        image: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: "password"
        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"
    
  • 资源的分类
    • 可压缩资源,例如CPU,当其不足时,Pod只会“饥饿”等待,而不会退出
    • 不可压缩资源,例如内存,当其不足时,Pod会被内核kill
  • yaml中资源的单位
    • CPU:一般写作cpu:“500m”,表示500milicpu,意味着0.5cpu,也可以直接写成cpu:0.5,但这种写法不通用,仍建议使用m为单位
    • 内存:默认单位时bytes,例如memory:500Mi,意味着内存为500*1024*1024bytes;memory:100M意味着内存为500*1000*1000bytes
  • 定义资源限制时,还分limits和requests两种
    • 在调度的时候,kube-scheduler 只会按照 requests 的值进行计算
    • 在真正设置 Cgroups 限制的时候,kubelet 则会按照 limits 的值来进行设置
    • 类似**Borg 论文中对“动态资源边界”**的思想(刚提交作业时,其所需资源一般远小于他的资源限额,所以刚提交时会主动减小其资源限额,以提升效率,当资源用量提升到一定阈值时再“快速恢复”)
  • kubernetes的QoS模型,给Pod划分为几个QoS级别:
    • Guaranteed: Pod 里的每一个 Container 都同时设置了 requests 和 limits,并且 requests 和 limits 值相等(若仅设置了limits没有设置requests,kubernetes会自动设置与limits取值相同的requests)
    • Burstable:Pod 不满足 Guaranteed 的条件,但至少有一个 Container 设置了 requests
    • BestEffort:Pod 既没有设置 requests,也没有设置 limits
  • 当宿主机资源紧张的时候,kubelet 会对 Pod 进行 Eviction(即资源回收)
  • 比如,可用内存(memory.available)、可用的宿主机磁盘空间(nodefs.available),以及容器运行时镜像存储空间(imagefs.available)等等
  • Eviction 在 Kubernetes 里分为 Soft 和 Hard 两种模式
    • Soft Eviction 允许设置一段“优雅时间”,达到阈值持续一段时间才会触发Eviction
    • Hard Eviction 模式下,Eviction 过程就会在阈值达到之后立刻开始。
  • Eviction时,kubelet根据Pod的QoS级别来决定回收哪些Pod:
    • 首先选择 BestEffort 类别的 Pod
    • 其次是Burstable 类别中资源使用量超过requestes的Pod
    • 最后是Guaranteed类别中资源使用量超过limits的,或者宿主机内存紧张的Pod
      • 建议将DaemonSet的Pod都设置为Guaranteed级别,不然一旦被回收又会立刻重建,令资源回收失去意义
  • cpuset的设置
    • 通过cpuset的设置,可将容器绑定到某个CPU核心上,无需共享CPU,减小OS在CPU之间切换的开销,提高容器中应用性能
    • 设置方式:保证Pod是Guaranteed类别,且CPU的requests和limits是相同的整数

41.kubernetes的默认调度器

  • 调度器职责在于为一个新创建出来的 Pod,寻找一个最合适的节点(Node)。
  • 调度器的核心在于两个相互独立的控制循环:
    • Informer Path
      • 启动一系列Informer,监听Etcd中与Pod、Node、Service 等与调度相关的 API 对象的变化
      • 将待调度的Pod加入调度队列(默认是优先级队列)
      • 更新调度器缓存(scheduler cache)中的信息
    • Scheduling Path
      • 是实际负责调度的主循环
      • 从调度队列中取出一个Pod,调用Predicates算法“过滤”得到一组Node(“过滤”所需的Node信息都是从调度器缓存中获取的),再调用Priority算法对这组Node进行打分,得分最高的作为调度的结果
      • 执行“乐观绑定”(也叫做Assume),真正的绑定操作需要访问APIServer,修改Pod对象中nodeName字段的值,而在本步骤中直接修改调度器缓存中的Pod和Node的信息
      • 创建一个Goroutine来异步地向APIServer发起更新Pod的请求,完成真正的Bind操作
  • Admit操作
    • 由于“乐观绑定”的操作,所以当新Pod完成调度要在某个节点上启动时,该节点kubelet还要执行Admit操作再次验证Pod是否确实能够在本节点上运行。
    • 实际就是再执行了一遍GeneralPredicates的基本调度算法

42.默认调度器策略

Predicates算法过滤Node的策略

  • 最基础的调度策略GeneralPredicates
    • PodFitsResources检查Pod的requests字段,计算宿主机的CPU和内存资源是否足够
    • PodFitsHost检查宿主机名字是否和Pod的sepc.nodeName一致(应只在admit操作中生效?)
    • PodFitsHostPort检查Pod申请的宿主机端口spec.nodePort是否已被占用
    • PodMatchNodeSelector检查Pod的nodeSelector或者nodeAffinity指定的节点是否与当前考察节点匹配
    • 该组策略检查了Pod能否运行在一个Node上的基本条件,在Pod启动之后前,kubelet还会执行Admit操作,即再执行一遍GeneralPredicates
  • 与Volume相关的过滤策略
    • NoDiskConflict检查多个Pod声明挂载的持久化Volume是否冲突
    • MaxPDVolumeCountPredicate检查某类型的持久化Volume是否超过一定数目
    • VolumeZonePredicate检查持久化 Volume 的 Zone(高可用域)标签,是否与待考察节点的 Zone 标签相匹配
    • VolumeBindingPredicate检查该Pod对应的PV的spec.nodeAffinity字段是否和该节点匹配【本地持久化卷“延迟绑定”发生的地方,若Pod的PVC还没有和PV绑定,调度器负责遍历所有待绑定的PV,当出现可用PV、且宿主机满足nodeAffinity要求一致时,该规则才返回“成功”】
  • 与宿主机相关的过滤策略
    • 主要检查待调度的Pod是否满足Node自身的要求,例如
    • PodToleratesNodeTaints检查Pod的Toleration字段是否和Node的Taint字段匹配
    • NodeMemoryPressurePredicate检查当前节点内存是否充足
  • 与Pod相关的过度策略
    • 与GeneralPredicates的规则大多数重合
    • PodAffinityPredicate检查待调度的Pod和当前节点已有Pod之间的亲密和反亲密关系
  • 当开始调度一个 Pod 时,Kubernetes 调度器会同时启动 16 个 Goroutine,来并发地为集群里的所有 Node 计算 Predicates,最后返回可以运行这个 Pod 的宿主机列表

Priority算法

  • LeastRequestedPriority:选择空闲资源(CPU 和 Memory)最多的宿主机
    • score = (cpu((capacity-sum(requested))10/capacity) + memory((capacity-sum(requested))10/capacity))/2
  • BalancedResourceAllocation:选择资源占用最均衡的宿主机
    • score = 10 - variance(cpuFraction,memoryFraction,volumeFraction)*10
  • ImageLocalityPriority:如果Pod所需镜像很大,则已有其所需镜像的node的得分会更高
    • 为了避免引起调度堆叠,当大镜像分布的节点很少时,则适当调低这些节点的权重
  • 还有NodeAffinityPriority、TaintTolerationPriority 和 InterPodAffinityPriority等

43.调度优先级和抢占机制

  • 优先级(Priority)和抢占(Preemption)解决的是调度失败时怎么办的问题
  • 基本思路
    • 当某个高优先级的Pod调度失败,则“挤走“某个Node上的一些低优先级Pod,从而保证高优先级Pod的成功调度
  • PriorityClass的定义
    apiVersion: scheduling.k8s.io/v1beta1
    kind: PriorityClass
    metadata:
      name: high-priority
    value: 1000000
    globalDefault: false
    description: "This priority class should be used for high priority service pods only."
    
    • Kubernetes 规定,优先级是一个 32 bit 的整数,且不超过10亿,超出的部分分配给了系统Pod
  • 具体实现
    • 调度器维护了两个调度队列
      • activeQ,存放下一个调度周期需要调度的Pod
      • unschedulableQ,存放调度失败的Pod。当其中的Pod更新后,就将其移动到activeQ中
    • 如何寻找Victims(应该被”挤走“的Pod)
      • 第一步,检查调度失败原因,确认抢占是否可以帮抢占者找到新节点。(比如,若因没有和nodeAffinity匹配的节点而调度失败,则不会触发抢占)
      • 第二步,复制一份缓存的节点信息,用该副本模拟抢占操作。
    • 模拟抢占操作的过程
      • 遍历缓存副本中的所有节点,在每个节点上,从最低优先级开始逐一删除Pod,一旦抢占者可以运行,则记录该Node和需要删除的Pod列表
      • 从所有可能的抢占结果中选择一个,找到最优结果,以尽量减小对现有系统的影响(被删除的Pod数量最小,或者优先级最低等等)
    • 实际抢占过程
      • 第一步,清理牺牲者Pod所携带的nominatedNodeName字段
      • 第二步,将抢占者Pod的nominatedNodeName字段设置为被抢占的Node名字,抢占者Pod重新进入activeQ,进入下一个调度周期【需要注意,调度器不能保证该Pod一定会被调度到nominatedNodeName指定的Node上】
      • 第三步,开启一个Goroutine,同步删除牺牲者
    • 当activeQ中存在有比当前待调度的Pod优先级更高的Pod携带了nominatedNodeName字段时,则对当前待调度Pod,调度器会对所有Node执行两遍Predicates算法
      • 第一遍, 调度器会假设上述“潜在的抢占者”已经运行在这个节点上,然后执行 Predicates 算法;
        • 原因在于,考虑InterPodAntiAffinity的规则,若抢占者已经存在待考察Node上了,当前Pod还能否成功调度到该Node上
      • 第二遍, 调度器会正常执行 Predicates 算法,即:不考虑任何“潜在的抢占者”。
  • 当整个集群发生可能会影响调度结果的变化(比如,添加或者更新 Node,添加和更新 PV、Service 等)时,调度器会执行一个被称为 MoveAllToActiveQueue 的操作,把所调度失败的 Pod 从 unscheduelableQ 移动到 activeQ 里面
  • 当一个已经调度成功的 Pod 被更新时,调度器则会将 unschedulableQ 里所有跟这个 Pod 有 Affinity/Anti-affinity 关系的 Pod,移动到 activeQ 里面

44.GPU管理和device plugin

  • 基本使用诉求:只要在 Pod 的 YAML 里面,声明某容器需要的 GPU 个数,那么 Kubernetes 为我创建的容器里就应该出现对应的 GPU 设备,以及它对应的驱动目录
  • Kubernetes 在 Pod 的 API 对象里,没有为 GPU 专门设置一个资源类型字段,而是使用了一种叫作 Extended Resource(ER)的特殊字段来负责传递 GPU 的信息,例如
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
  • 宿主机需要向kubernetes的APIServer汇报自身的GPU资源情况,这是通过device plugin机制实现的
  • Device Plugin示意图
    img
  • Device Plugin工作流程
    • 每一种硬件设备,都有对应的Device Plugin管理,通过gRPC的方法与kubelet连接
    • ListAndWatch:定期向kubelet汇报该Node上的GPU列表
    • Allocate:Pod调度到本机后,kubelet从自己持有的GPU中为该容器分配其所需的GPU,即通过Allocate()方法向Device Plugin传递待分配的GPU ID列表
    • Device Plugin根据Allocate()方法提供的GPU ID列表,查找设备的路径和驱动目录,返回给kubelet
    • kubelet将设备的信息追加在CRI请求中,发送给Docker,则Docker创建的容器就会有这个设备,并挂载了其所需的驱动目录
  • Device Plugin机制的缺点
    • Allocate 和 ListAndWatch API 无法添加可扩展性的参数,所以对硬件设备的管理只能停留在“设备个数”这一层面,无法完成更加复杂的操作
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值