跳转至

调度器配置

官方文档地址:

调度器配置

当前配置

当前控制平面信息如下

> kubectl get nodes

Alias tip: kgno
NAME         STATUS   ROLES           AGE   VERSION
devmaster1   Ready    control-plane   96d   v1.25.4
devmaster2   Ready    control-plane   96d   v1.25.4
devmaster3   Ready    control-plane   96d   v1.25.4

当前 scheduler 配置如下:cat /etc/kubernetes/manifests/kube-scheduler.yaml

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    component: kube-scheduler
    tier: control-plane
  name: kube-scheduler
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-scheduler
    - --authentication-kubeconfig=/etc/kubernetes/scheduler.conf
    - --authorization-kubeconfig=/etc/kubernetes/scheduler.conf
    - --bind-address=127.0.0.1
    - --kubeconfig=/etc/kubernetes/scheduler.conf
    - --leader-elect=true
    image: k8s.gcr.io/kube-scheduler:v1.24.4
    imagePullPolicy: IfNotPresent
    livenessProbe:
      failureThreshold: 8
      httpGet:
        host: 127.0.0.1
        path: /healthz
        port: 10259
        scheme: HTTPS
      initialDelaySeconds: 10
      periodSeconds: 10
      timeoutSeconds: 15
    name: kube-scheduler
    resources:
      requests:
        cpu: 100m
    startupProbe:
      failureThreshold: 24
      httpGet:
        host: 127.0.0.1
        path: /healthz
        port: 10259
        scheme: HTTPS
      initialDelaySeconds: 10
      periodSeconds: 10
      timeoutSeconds: 15
    volumeMounts:
    - mountPath: /etc/kubernetes/scheduler.conf
      name: kubeconfig
      readOnly: true
  hostNetwork: true
  priorityClassName: system-node-critical
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  volumes:
  - hostPath:
      path: /etc/kubernetes/scheduler.conf
      type: FileOrCreate
    name: kubeconfig
status: {}

当期 kube-scheduler 进程信息

> ps -p $(pidof kube-scheduler) -o cmd -www | sed -e 's/--/\n--/g'

kube-scheduler
--authentication-kubeconfig=/etc/kubernetes/scheduler.conf
--authorization-kubeconfig=/etc/kubernetes/scheduler.conf
--bind-address=127.0.0.1
--kubeconfig=/etc/kubernetes/scheduler.conf
--leader-elect=true

应用最简单的配置

可以通过编写配置文件,并将其路径传给 kube-scheduler 的命令行参数,定制 kube-scheduler 的行为

调度模板(Profile)允许你配置 kube-scheduler 中的不同调度阶段。每个阶段都暴露于某个扩展点中。插件通过实现一个或多个扩展点来提供调度行为。

通过运行 kube-scheduler --config <filename> 来设置调度模板

在每个 master 节点上,新建文件夹存放配置

mkdir -p /etc/kubernetes/custom-config/kube-scheduler

新建一个文件 /etc/kubernetes/custom-config/kube-scheduler/scheduler.conf

apiVersion: kubescheduler.config.k8s.io/v1beta3
kind: KubeSchedulerConfiguration
clientConnection:
  kubeconfig: /etc/kubernetes/scheduler.conf
leaderElection:
  leaderElect: true

修改 /etc/kubernetes/manifests/kube-scheduler.yaml 文件中的启动命令和挂载为:

spec:
  containers:
  - command:
    - kube-scheduler
    - --config=/etc/kubernetes/custom-config/kube-scheduler/scheduler.conf
    - --authorization-kubeconfig=/etc/kubernetes/scheduler.conf
    - --authentication-kubeconfig=/etc/kubernetes/scheduler.conf
...
    volumeMounts:
    - mountPath: /etc/kubernetes/scheduler.conf
      name: kubeconfig
      readOnly: true
    - mountPath: /etc/kubernetes/custom-config/kube-scheduler
      name: kube-custom-config
      readOnly: true
...
  volumes:
  - hostPath:
      path: /etc/kubernetes/scheduler.conf
      type: FileOrCreate
    name: kubeconfig
  - hostPath:
      path: /etc/kubernetes/custom-config/kube-scheduler
      type: DirectoryOrCreate
    name: kube-custom-config

重启后的进程如下:

> ps -p $(pidof kube-scheduler) -o cmd -hww | sed -e 's/--/\n--/g'
kube-scheduler
--config=/etc/kubernetes/custom-config/kube-scheduler/scheduler.conf
--authentication-kubeconfig=/etc/kubernetes/scheduler.conf
--authorization-kubeconfig=/etc/kubernetes/scheduler.conf

配置文件

扩展点

调度行为发生在一系列阶段中,这些阶段是通过以下扩展点公开的:

  1. queueSort:这些插件对调度队列中的悬决的 Pod 排序。 一次只能启用一个队列排序插件。
  2. preFilter:这些插件用于在过滤之前预处理或检查 Pod 或集群的信息。 它们可以将 Pod 标记为不可调度。
  3. filter:这些插件相当于调度策略中的断言(Predicates),用于过滤不能运行 Pod 的节点。 过滤器的调用顺序是可配置的。 如果没有一个节点通过所有过滤器的筛选,Pod 将会被标记为不可调度。
  4. postFilter:当无法为 Pod 找到可用节点时,按照这些插件的配置顺序调用他们。 如果任何 postFilter 插件将 Pod 标记为**可调度**,则不会调用其余插件。
  5. preScore:这是一个信息扩展点,可用于预打分工作。
  6. score:这些插件给通过筛选阶段的节点打分。调度器会选择得分最高的节点。
  7. reserve:这是一个信息扩展点,当资源已经预留给 Pod 时,会通知插件。 这些插件还实现了 Unreserve 接口,在 Reserve 期间或之后出现故障时调用。
  8. permit:这些插件可以阻止或延迟 Pod 绑定。
  9. preBind:这些插件在 Pod 绑定节点之前执行。
  10. bind:这个插件将 Pod 与节点绑定。bind 插件是按顺序调用的,只要有一个插件完成了绑定,其余插件都会跳过。bind 插件至少需要一个。
  11. postBind:这是一个信息扩展点,在 Pod 绑定了节点之后调用。
  12. multiPoint:这是一个仅配置字段,允许同时为所有适用的扩展点启用或禁用插件。

对每个扩展点,你可以禁用默认插件或者是启用自己的插件,例如:

apiVersion: kubescheduler.config.k8s.io/v1beta3
kind: KubeSchedulerConfiguration
profiles:
  - plugins:
      score:
        disabled:
        - name: PodTopologySpread
        enabled:
        - name: MyCustomPluginA
          weight: 2
        - name: MyCustomPluginB
          weight: 1

调度插件

下面默认启用的插件实现了一个或多个扩展点:

  • ImageLocality:选择已经存在 Pod 运行所需容器镜像的节点。

实现的扩展点:score

实现的扩展点:filterpreScorescore

  • NodeName:检查 Pod 指定的节点名称与当前节点是否匹配。

实现的扩展点:filter

  • NodePorts:检查 Pod 请求的端口在节点上是否可用。

实现的扩展点:preFilterfilter

实现的扩展点:filterscore

实现的扩展点:preFilterfilterpreScorescore

  • NodeUnschedulable:过滤 .spec.unschedulable 值为 true 的节点。

实现的扩展点:filter

  • NodeResourcesFit:检查节点是否拥有 Pod 请求的所有资源。 得分可以使用以下三种策略之一:LeastAllocated(默认)、MostAllocatedRequestedToCapacityRatio

实现的扩展点:preFilterfilterscore

  • NodeResourcesBalancedAllocation:调度 Pod 时,选择资源使用更为均衡的节点。

实现的扩展点:score

  • VolumeBinding:检查节点是否有请求的卷,或是否可以绑定请求的。 实现的扩展点:preFilterfilterreservepreBindscore

**说明:**当 VolumeCapacityPriority 特性被启用时,score 扩展点也被启用。 它优先考虑可以满足所需卷大小的最小 PV。

  • VolumeRestrictions:检查挂载到节点上的卷是否满足卷提供程序的限制。

实现的扩展点:filter

  • VolumeZone:检查请求的卷是否在任何区域都满足。

实现的扩展点:filter

  • NodeVolumeLimits:检查该节点是否满足 CSI 卷限制。

实现的扩展点:filter

  • EBSLimits:检查节点是否满足 AWS EBS 卷限制。

实现的扩展点:filter

  • GCEPDLimits:检查该节点是否满足 GCP-PD 卷限制。

实现的扩展点:filter

  • AzureDiskLimits:检查该节点是否满足 Azure 卷限制。

实现的扩展点:filter

实现的扩展点:preFilterfilterpreScorescore

  • PrioritySort:提供默认的基于优先级的排序。

实现的扩展点:queueSort

  • DefaultBinder:提供默认的绑定机制。

实现的扩展点:bind

  • DefaultPreemption:提供默认的抢占机制。

实现的扩展点:postFilter

多配置文件

可以配置 kube-scheduler 运行多个配置文件。 每个配置文件都有一个关联的调度器名称,并且可以在其扩展点中配置一组不同的插件。

使用下面的配置样例,调度器将运行两个配置文件:一个使用默认插件,另一个禁用所有打分插件。

apiVersion: kubescheduler.config.k8s.io/v1beta3
kind: KubeSchedulerConfiguration
profiles:
  - schedulerName: default-scheduler
  - schedulerName: no-scoring-scheduler
    plugins:
      preScore:
        disabled:
        - name: '*'
      score:
        disabled:
        - name: '*'

对于那些希望根据特定配置文件来进行调度的 Pod,可以在 .spec.schedulerName 字段指定相应的调度器名称。

默认情况下,将创建一个调度器名为 default-scheduler 的配置文件。 这个配置文件包括上面描述的所有默认插件。 声明多个配置文件时,每个配置文件中调度器名称必须唯一。

如果 Pod 未指定调度器名称,kube-apiserver 将会把调度器名设置为 default-scheduler。 因此,应该存在一个调度器名为 default-scheduler 的配置文件来调度这些 Pod。

应用于多个扩展点的插件

kubescheduler.config.k8s.io/v1beta3 开始,配置文件配置中有一个附加字段 multiPoint,它允许跨多个扩展点轻松启用或禁用插件。 multiPoint 配置的目的是简化用户和管理员在使用自定义配置文件时所需的配置。

示例

在示例中,希望 ImagePullPolicy 成为唯一的调度影响因素

修改 /etc/kubernetes/custom-config/kube-scheduler/pull-policy-only-scheduler.conf 内容如下:

apiVersion: kubescheduler.config.k8s.io/v1beta3
kind: KubeSchedulerConfiguration
clientConnection:
  kubeconfig: /etc/kubernetes/scheduler.conf
leaderElection:
  leaderElect: true
profiles:
- schedulerName: default-scheduler
  # If a QueueSort plugin is specified
  # the same QueueSort Plugin and PluginConfig must be specified for all profiles.
  plugins:
    queueSort:
      disabled:
      - name: '*'
      enabled:
      - name: PrioritySort
- schedulerName: pull-policy-only-scheduler
  plugins:
    queueSort:
      disabled:
      - name: '*'
      enabled:
      - name: PrioritySort
    preFilter:
      disabled:
      - name: '*'
    filter:
      disabled:
      - name: '*'
      enabled:
      - name: TaintToleration
    postFilter:
      disabled:
      - name: '*'
    preScore:
      disabled:
      - name: '*'
    score:
      disabled:
      - name: '*'
      enabled:
      - name: ImageLocality
        weight: 100
    reserve:
      disabled:
      - name: '*'
    permit:
      disabled:
      - name: '*'
    preBind:
      disabled:
      - name: '*'
    bind:
      disabled:
      - name: '*'
      enabled:
      - name: DefaultBinder
    postBind:
      disabled:
      - name: '*'

修改 /etc/kubernetes/manifests/kube-scheduler.yaml 指定使用新的配置文件(注意不改文件名有可能不生效,暂时不知道为什么)

在 devmaster2 节点拉取镜像

nerdctl --namespace k8s.io image pull nginx:1.10

在 devmaster3 节点拉取镜像

nerdctl --namespace k8s.io image pull nginx:1.20

创建命名空间

kubectl create ns scheduler-test

切换到对应的命名空间

kubectl ns scheduler-test

分别创建 10 个容器

kubectl get pods -o=custom-columns=NAME:.metadata.name,Namespace:.metadata.namespace,NODE:.spec.nodeName,LABELS:.metadata.labels | sort

for i in {0..20..1}
do
  kubectl apply -f - << EOF
apiVersion: v1
kind: Pod
metadata:
  name: nx1-10-$i
  labels:
    name: nx1-10-$i
spec:
  schedulerName: pull-policy-only-scheduler
  containers:
  - name: nx
    image: nginx:1.10
EOF

  kubectl apply -f - << EOF
apiVersion: v1
kind: Pod
metadata:
  name: nx1-20-$i
  labels:
    name: nx1-20-$i
spec:
  schedulerName: pull-policy-only-scheduler
  containers:
  - name: nx
    image: nginx:1.20
EOF

  sleep 1
done

kubectl get pods -o=custom-columns=NAME:.metadata.name,Namespace:.metadata.namespace,NODE:.spec.nodeName,LABELS:.metadata.labels | sort

注意:这个调度器还和镜像大小有关

代码:https://github.com/chendave/kubernetes/blob/master/pkg/scheduler/framework/plugins/imagelocality/image_locality.go

// calculatePriority returns the priority of a node. Given the sumScores of requested images on the node, the node's
// priority is obtained by scaling the maximum priority value with a ratio proportional to the sumScores.
func calculatePriority(sumScores int64, numContainers int) int64 {
    maxThreshold := maxContainerThreshold * int64(numContainers)
    if sumScores < minThreshold {
        sumScores = minThreshold
    } else if sumScores > maxThreshold {
        sumScores = maxThreshold
    }

    return int64(framework.MaxNodeScore) * (sumScores - minThreshold) / (maxThreshold - minThreshold)
}

// sumImageScores returns the sum of image scores of all the containers that are already on the node.
// Each image receives a raw score of its size, scaled by scaledImageScore. The raw scores are later used to calculate
// the final score. Note that the init containers are not considered for it's rare for users to deploy huge init containers.
func sumImageScores(nodeInfo *framework.NodeInfo, containers []v1.Container, totalNumNodes int) int64 {
    var sum int64
    for _, container := range containers {
        if state, ok := nodeInfo.ImageStates[normalizedImageName(container.Image)]; ok {
            sum += scaledImageScore(state, totalNumNodes)
        }
    }
    return sum
}

// scaledImageScore returns an adaptively scaled score for the given state of an image.
// The size of the image is used as the base score, scaled by a factor which considers how much nodes the image has "spread" to.
// This heuristic aims to mitigate the undesirable "node heating problem", i.e., pods get assigned to the same or
// a few nodes due to image locality.
func scaledImageScore(imageState *framework.ImageStateSummary, totalNumNodes int) int64 {
    spread := float64(imageState.NumNodes) / float64(totalNumNodes)
    return int64(float64(imageState.Size) * spread)
}

// normalizedImageName returns the CRI compliant name for a given image.
// TODO: cover the corner cases of missed matches, e.g,
// 1. Using Docker as runtime and docker.io/library/test:tag in pod spec, but only test:tag will present in node status
// 2. Using the implicit registry, i.e., test:tag or library/test:tag in pod spec but only docker.io/library/test:tag
// in node status; note that if users consistently use one registry format, this should not happen.
func normalizedImageName(name string) string {
    if strings.LastIndex(name, ":") <= strings.LastIndex(name, "/") {
        name = name + ":latest"
    }
    return name
}