k8s调度原理解析

306 阅读5分钟

背景/分享目标

  • 简单介绍k8s的基础知识
  • 深度介绍k8s调度的相关知识

“细节是魔鬼” vs “好读书,不求甚解”

  • k8s是一个技术深度和广度都很高的系统;

    • 广度上:本文只会覆盖到一些基本的原理和概念,并不关注具体的交互;
    • 深度上:本文只会聚焦于调度本身,具体会介绍一些整理后的代码/伪代码;

K8S整体概览

  1. 部署发展历程

与传统的部署方式相比,k8s容器化部署,主要有两个方面的变化:

  • 单机上的容器技术:把我们需要部署的程序打包成镜像/通信;

    • 在本篇文章里,暂时以docker作为容器技术代名词;
  • k8s:找到合适的物理机去部署容器;
  1. docker基础知识介绍

docker 的角色:实现部署业务程序在单机层面的各种隔离;

  • “软件层面”的隔离:

    • Namespace: 看不到物理机上的其他程序;
  • “硬件层面”的隔离:

    • Cgroup: 限制了硬件资源的使用的隔离;
    • AUFS/DeviceMapper: 文件的隔离;
  1. k8s介绍

k8s架构的作用:

  • 容器的初始化-运行-消亡;

    • 初始化:需要挑选合适的物理机部署容器;
    • 运行:需要挑选出新的物理机部署容器;

挑选即是调度;

聚焦一下:只关注调度,也就是如何找到一个合适的物理机;其他全部都不关注;

K8S的调度逻辑分析

在 Kubernetes 中,调度

是指将 Pod 放置到合适的 Node 上,然后对应 Node 上的 Kubelet 才能够运行这些 pod

基础理论:

  • 调度概览

调度器通过 kubernetes 的监测(Watch)机制来发现集群中新创建且尚未被调度到 Node 上的 Pod。 调度器会将发现的每一个未调度的 Pod 调度到一个合适的 Node 上来运行。

  • kube-scheduler 调度流程
  • kube-scheduler 给一个 pod 做调度选择包含两个步骤:
    • 过滤

      1. 过滤阶段会将所有满足 Pod 调度需求/资源请求的 Node 选出来。 在过滤之后,得出一个 Node 列表,里面包含了所有可调度节点;通常情况下, 这个 Node 列表包含不止一个 Node。如果这个列表是空的,代表这个 Pod 不可调度。
    • 打分

      1. 在打分阶段,调度器会为 Pod 从所有可调度节点中选取一个最合适的 Node。 根据当前启用的打分规则,调度器会给每一个可调度节点进行打分。
      2. 最后,kube-scheduler 会将 Pod 调度到得分最高的 Node 上。 如果存在多个得分最高的 Node,kube-scheduler 会从中随机选取一个。
    • 个人补充的一点:

      1. 选定Node之后,单机的绑定/部署异步进行;调度根据结果再决定是绑定成功或者是绑定失败,重新调度;
queueSort:这些插件对调度队列中的悬决的 Pod 排序。 一次只能启用一个队列排序插件。
preFilter:这些插件用于在过滤之前预处理或检查 Pod 或集群的信息。 它们可以将 Pod 标记为不可调度。
filter:这些插件相当于调度策略中的断言(Predicates),用于过滤不能运行 Pod 的节点。 过滤     器的调用顺序是可配置的。 如果没有一个节点通过所有过滤器的筛选,Pod 将会被标记为不可调度。
postFilter:当无法为 Pod 找到可用节点时,按照这些插件的配置顺序调用他们。 如果任何 postFilter 插件将 Pod 标记为“可调度”,则不会调用其余插件。
preScore:这是一个信息扩展点,可用于预打分工作。
score:这些插件给通过筛选阶段的节点打分。调度器会选择得分最高的节点。
reserve:这是一个信息扩展点,当资源已经预留给 Pod 时,会通知插件。 这些插件还实现了 Unreserve 接口,在 Reserve 期间或之后出现故障时调用。
permit:这些插件可以阻止或延迟 Pod 绑定。
preBind:这些插件在 Pod 绑定节点之前执行。
bind:这个插件将 Pod 与节点绑定。绑定插件是按顺序调用的,只要有一个插件完成了绑定,其余插件都会跳过。绑定插件至少需要一个。
postBind:这是一个信息扩展点,在 Pod 绑定了节点之后调用。

Talk is cheap, let's talk code

  1. “yaml工程师”
声明式:业务负责描述目标状态,而k8s负责更改实际状态,使其变为期望状态。
kubectl apply -f https://k8s.io/examples/controllers/nginx-deployment.yaml
apiVersion: v1
kind: Deployment
metadata:
  name: nginx-deployment   
  labels:     
     app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.14.2
    resources:
      requests:
        memory: "100Mi"
      limits:
        memory: "200Mi"
type FilterPlugin interface {
    Plugin
    Filter(ctx context.Context, state *CycleState, pod *v1.Pod, 
    nodeInfo *NodeInfo) *Status
}

// scheduleOne does the entire scheduling workflow for a single pod. It is serialized on the scheduling algorithm's host fitting.

func (sched *Scheduler) scheduleOne() {
    // Run "queueSort" plguins
    podInfo := sched.NextPod()
    fwk, err := sched.frameworkForPod(pod)

    // Run "prefilter"/"filter" plugins【filter】
    // Run "prescore"/"score"/"Normalize" plugins【score】
    scheduleResult, err := sched.Algorithm.Schedule(fwk, state, pod)
    If err != nil {
        fwk.RunPostFilterPlugins()
        return
    }
 
    // Run "reserve" plugins.
    err := fwk.RunReservePluginsReserve(Pod, scheduleResult.SuggestedHost); 
    if err != nil {
        return
    }
    
    // Run "permit" plugins.
    err := fwk.RunPermitPlugins(Pod, scheduleResult.SuggestedHost)
    if err != nil {
        return
    }

    // bind the pod to its host asynchronously (we can do this b/c of the assumption step above).
    go func() { 
        waitOnPermitStatus := fwk.WaitOnPermit(Pod)
        if !waitOnPermitStatus.IsSuccess() {
            return
        }

        // Run "prebind" plugins.
        preBindStatus := fwk.RunPreBindPlugins(Pod, scheduleResult.SuggestedHost)
        if !preBindStatus.IsSuccess() {
            return
        }

        // Run "bind" plugins
        err := sched.bind(fwk, Pod, scheduleResult.SuggestedHost)

        // Run "postbind" plugins.
        fwk.RunPostBindPlugins(assumedPod, scheduleResult.SuggestedHost)
        }
    }()
}

参考文档:

为容器和 Pod 分配内存资源

调度框架

pod生命流程