一、k8s 基础架构

在分析 k8s pod 调度之前,我们先来回顾一下 k8s 的架构

二、k8s pod 调度相关的参数

当用户通过 kubectl 或者 api 创建了一个 deployment 或者 其它 workload 的时候, k8s 会创建对应的 pod 信息,pod 信息最终会传递给 kubelet,实例化成具体的 docker 容器, 那么 k8s 是根据什么条件,怎么把 pod “安排”到 node 上去的呢? 有哪些因数会影响 pod 的调度呢?

我找了一下 pod 的参数,大概会有以下几个参数影响 pod 的调度

  • NodeName
  • NodeSelector
  • Affinity
  • Tolerations
  • PriorityClassName
  • Priority

1、nodeName

一旦你在 pod 里指定了 nodeName ,那么 pod 一定会调度到该节点上, 其实 pod 已经跳过了调度过程了

2、Priority

pod 是可以有优先级的,这个优先级是相对于其它 pod 的,也就是说,如果 pod 的优先级高于其它 pod,这个 pod 会被优先调度。

3、PriorityClass

和 Priority 类似,PriorityClass 只是定义了一个名字而已,这样可以多个 pod 公用一个 优先级。

apiVersion: scheduling.k8s.io/v1alpha1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for XYZ service pods only."
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  priorityClassName: high-priority

4. 其它

其它的请参考 阳总的文章
https://blog.qikqiak.com/post/understand-kubernetes-affinity/

三、k8s 调度之节点的筛选

pod 请求的资源都是有一定要求的,比如 cpu 多少, memory 多少,或者存在 nodeSelector 之类的选择器,亲和性参数等等。

我们可以用以下伪代码来描述

func checkNode(pod v1.Pod, node v1.Node) (bool, err) {
    // 1. cpu check
    if pod.cpu > node.availableCPU {
        // 没有足够的 cpu ,该节点不满足
        return false, nil
    }
    // 2. memory check
    if pod.memory > node.availableMemory {
        // 没有足够的内存,该节点不满足
        return false, nil
    }
    // 3. nodeSelector check
    if !node.MatchSelector(pod.Spec.NodeSelector) {
        // label 条件不满足
        return false, nil
    }
    // ....
    // 3. other check

    // 检查结束,该节点满足
    return true, nil
}

func getFliterdNodeList(pod *v1.Pod) ([]*v1.Node, error) {
    allNode := getKubernetesAllNode()
    var filterdNodeList []*v1.Node
    for _, node := range allNode {
        if ok, err := checkNode(pod, node); err != nil {
            // 报错处理
        }
        if ok {
            // 符合条件,添加到数组中
            filterdNodeList = append(filterdNodeList, node)
        }
    }

    // 返回筛选后的 node 列表
    return filterdNodeList
} 

经过一轮筛选之后,会留下一些 node,我暂时称之为 filterdNodeList

四、k8s 调度之节点的排序

我们从多个 node 中筛选出适合 pod 的节点 filterdNodeList 之后,我们要做一个最终选择, 因为我们的 pod 只能运行在一个节点上,那么到底选谁呢?

那么我们的相亲对象 pod 一定会从众多 node 选择一个最高,最帅,最富,而且还爱自己的 node,这一环节,是众多 node 之间的比拼。

我们用以下伪代码表示

type NodeList []v1.Node
func (nodes NodeList) Len() int { return len(nodes) }
func (nodes NodeList) Swap(i, j int){ s[i], s[j] = s[j], s[i] }
func (nodes NodeList) Less(i, j int) bool { 
    if nodes[i].availableCPU > nodes[j].availableCPU {
        // cpu 资源多,优先级高, pod 优先考虑
        // ...
    }
    if nodes[i].availableMemory > nodes[j].availableMemory {
        // memory 资源多,优先级高, pod 优先考虑
        // 比较帅,这个也不错
        // ...
    }
    // 其它方面的比较
}

我大概总结一下:

  1. 节点上可用资源越多,优先级越高 (越有钱越好)
  2. 节点 cpu 和 memory 使用比较均衡 (相对帅且有钱,而不是只帅而没有钱的)
  3. 亲和性 (比如最好是公务员)
  4. 节点上已经存在镜像,且镜像越大越好,这样就能节约拉取镜像的时间 (家里已经买了车了,越贵越好)
  5. 其它

五、 kubelet 实例化 pod 为容器

之前我提到过,kubelet 创建的 pod 有三个来源,分别是

  1. 从 apiserver 获取 pod 信息
  2. 从 url 获取 pod 信息
  3. 从本地文件夹获取 pod 信息,这就是通常我们使用的 manifest

kubelet 获取 pod 信息之后,会通过调用 容器运行时,通常是 docker,将 pod 实例化为具体的容器。