• 生产环境中的kubernetes 优先级与抢占


    kubernetes 中的抢占功能是调度器比较重要的feature,但是真正使用起来还是比较危险,否则很容易把低优先级的pod给无辜kill。为了提高GPU集群的资源利用率,决定勇于尝试一番该featrue。当然使用之前还是得阅读一下相关的代码做到心里有数,出了问题也方便定位修复。

    基本原理

    优先级与抢占是为了确保一个高优先级的pod在调度失败后,可以通过"挤走" 低优先级的pod,腾出空间后保证它可以调度成功。 我们首先需要在集群中声明PriorityClass来定义优先等级数值和抢占策略,

    apiVersion: scheduling.k8s.io/v1
    kind: PriorityClass
    metadata:
      name: high
    value: 10000
    preemptionPolicy: Never
    globalDefault: false
    description: "This priority class should be used for high priority service pods."
    ---
    apiVersion: scheduling.k8s.io/v1
    kind: PriorityClass
    metadata:
      name: low
    value: -999
    globalDefault: false
    description: "This priority class should be used for log priority service pods."
    

    如上所示定义了两个PriorityClass对象。然后就可以在pod中声明使用它了:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        run: nginx
      name: high-nginx
    spec:
      replicas: 1
      selector:
        matchLabels:
          run: nginx
      template:
        metadata:
          labels:
            run: nginx
        spec:
          containers:
          - image: nginx
            imagePullPolicy: Always
            name: nginx
            resources:
              limits:
                cpu: "500m"
          priorityClassName: high
    

    这个 Pod 通过 priorityClassName 字段。声明了要使用名叫 high-priority 的 PriorityClass。当这个 Pod 被提交给 Kubernetes 之后,Kubernetes 的 Priority AdmissionController 就会自动将这个 Pod 的spec.priority 字段设置为10000。
    如下:

      preemptionPolicy: Never
      priority: 10000
      priorityClassName: high
    

    Pod创建好之后,调度器就会根据创建的priority进行调度的决策,首先会在等待队列中优先调度,如果调度失败就会进行抢占: 依次遍历所有的node找出最适合的node,将该nodename填充在spec.nominatedNodeName字段上,然后等待被抢占的pod全都退出后再次尝试调度到该node之上。具体的逻辑请自行阅读相关代码,此处不在赘述。

    生产环境使用方式

    • v1.14版本的kubernetes该feature已经GA,默认开启,但此时我们往往没有做好准备,如果直接给pod设置优先级会导致很多意料之外的抢占,造成故障。 (参见How a Production Outage Was Caused Using Kubernetes Pod Priorities)。所以建议在初次使用的时候还是先显式关闭抢占,只设置优先级,等集群中所有的pod都有了各自的优先级之后再开启,此时所有的抢占都是可预期的。可以通过kube-scheduler 配置文件中的disablePreemption: true进行关闭
    • 调度器是根据优先级pod.spec.priority数值来决定优先级的,而用户是通过指定pod.sepc.priorityclass的名字来为pod选择优先级的,此时就需要Priority AdmissionController根据priorityclass name为pod自动转换并设置对应的priority数值。我们需要确保该admissionController开启,如果你的kube-apiserver中还是通过--admission-control flag来指定admissionoController的话需要手动添加Priority admissonController,如果是通过--enable-admission-plugins来指定的话,无需操作,该admissionController默认开启。
    • 按照集群规划创建对应的PriorityClass及其对应的抢占策略,目前支持两种策略: Never, PreemptLowerPriority。 Never可以指定不抢占其他pod, 即使该pod优先级特别高,这对于一些离线任务较为友好。 非抢占调度在v1.15中为alpha, 需要通过--feature-gates=NonPreemptingPriority=true 进行开启。
    • 在创建好了PriorityClass之后,需要防止高优先级的pod过分占用太多资源,使用resourceQuota机制来限制其使用量,避免低优先级的pod总是被高优先级的pod压制,造成资源饥饿。resoueceQuote可以通过指定scope为PriorityClass来限定某个优先级能使用的资源量:
    apiVersion: v1
    kind: ResourceQuota
    metadata:
      name: high-priority
    spec:
      hard:
        pods: "10"
      scopeSelector:
        matchExpressions:
        - operator : In
          scopeName: PriorityClass
          values: ["high"]
    

    如上即为限制高优先级的pod最多能创建10个。operator指定了作用的对象,operator: In可以显式指定作用于的哪些priorityClass,operator: Exists则指定作用于该namespace下的所有priorityClass。

    • 有时候我们想要只有priorityClass对应的resourceQuota存在之后才能创建pod,确保所有的priorityClass的pod资源都是受控的。 如果那个namespacew没有该resourceQuota则拒绝该pod的创建,该操作可以通过指定--admission-control-config-file文件来设置,内容如下:
    apiVersion: apiserver.k8s.io/v1alpha1
    kind: AdmissionConfiguration
    plugins:
    - name: "ResourceQuota"
      configuration:
        apiVersion: resourcequota.admission.k8s.io/v1beta1
        kind: Configuration
        limitedResources:
        - resource: pods
          matchScopes:
          - scopeName: PriorityClass 
            operator: In
            values: ["high"]
    

    该配置定义了: "high"优先级的pod只能在指定的namespaces下创建,该namespaces有作用于"high"优先级的resouceQuota,上面第四步中的resouceQuota即可满足要求。
    scopeName定义了作用的对象为priorityClass, operator指定了作用的范围,可以是In操作符,指定某几个value, 也可以是Exits操作符,指定所有的PriorityClass必须有对应的quota存在, 否则该namespace就无法创建该优先级的pod,这些操作符与上面resouceQuota中定义的一一对应。通过这样就限制了一些优先级只能在有资源约束的namespace下创建。

    • 如果没有显式指定优先级,则默认的优先级值为0,需要结合业务规划决定是否有必要调整默认优先级。
    • 对于一些daemonset需要显式设置较高的优先级来防止被抢占,在部署一个新的daemonset的时候需要考虑是否会造成大规模pod的抢占。
    • 等到所有的优先级设置完毕之后就可以开启抢占功能了,此时集群中所有pending 的高优先级pod就会瞬间抢占,还是需要额外小心,确保集群中高优先级的pod不会导致低优先级的pod大规模被kill,如果我们提前设置了对应的resource quota值,则会有一定的资源约束。
    • 优先级和抢占对于资源的精细化运营考验很大,对于resource quota的设置需要十分精细,需要考虑两个维度来设置: namespace层面和priority层面,我们既希望限制namespace使用的资源,有希望某个priority使用的资源,防止低优先级的pod资源饥饿。 可以在初期只考虑namespace层面的限制,priority层面通过上层业务来保证,例如创建任务的时候保证集群中高优先级的资源使用量不超过50%等。

    其他思考

    笔者的线上环境中, 有些再跑的模型训练任务业务对于自动failove实现不是很好,如果中途被抢占了只能从头开始计算,需要占用额外的GPU资源,这种工作类型不允许被抢占,但是如果把他设置为高优先级又不太合适,因为它确实不是最高的优先级,优先级最高的还是在线业务,不能让它抢占在线业务。 它属于中间优先级,可以抢占低优先级的pod。 经过探索发现目前kubernetes并不支持该中类型,当前支持的抢占策略为: Never, PreemptLowerPriority都无法满足需求。所以在此基础上开发了NonPreemptible类型的抢占策略,该优先级的pod是不允许被其他人抢占的,调度还是按照优先级在队列里排队,但是一旦调度上去就无法被抢占。
    这种抢占策略略显"霸道",所以需要谨慎使用,设置resouceQuota,并且只能由特定的任务使用。 并且为了不影响deamonset等优先级最高的任务,允许被某个指定Priority数值之上的pod抢占。并且随着业务的发展,这部分逻辑需要逐步去掉,之所有存在这部分逻辑是因为pod不能被中断,不能被抢占,所以还是需要使这些任务支持重启与挂起,具体来说就是: pod挂载远程磁盘并自动checkpoint,重启之后从以前的恢复点继续执行。等这些业务改造完成之后,逐步去掉这种工作抢占策略

    reference

    Pod Priority and Preemption
    Priority in ResourceQuota
    Allow PriorityClasses To Be Non-Preempting

  • 相关阅读:
    Hbase学习记录(2)| Shell操作
    Hbase学习记录(1)|伪分布式安装
    Zookeeper集群安装详解
    防范xss的正确姿势
    怎么样通过编写Python小程序来统计测试脚本的关键字
    XSS报警机制(前端防火墙:第二篇)
    XSS姿势——文件上传XSS
    MySQL防范SQL注入风险
    SQL注入—我是如何一步步攻破一家互联网公司的
    通过BurpSuite和sqlmap配合对dvwa进行sql注入测试和用户名密码暴力破解
  • 原文地址:https://www.cnblogs.com/gaorong/p/11318328.html
Copyright © 2020-2023  润新知