运维锅总详解Kubernetes之Scheduler

2024-07-29 15:29:13 浏览数 (4)

本文尝试从Kubernetes Scheduler的功能介绍、交互逻辑、伪代码实现、最佳实践、自定义Scheduler举例及其历史演进6个方面进行详细阐述。希望对您有所帮助!

一、Kubernetes Scheduler 功能

Kubernetes Scheduler 是 Kubernetes 集群的核心组件之一,负责将新创建的 Pod 分配到合适的节点上。它根据一系列规则和策略,确保集群资源的最佳利用和应用的高效运行。以下是 Kubernetes Scheduler 的功能详解。

1. Scheduler 工作流程

Kubernetes Scheduler 的工作流程主要包括以下几个步骤:

  1. 监听未分配的 Pod:Scheduler 监听 API Server,当有新的 Pod 被创建且未绑定到任何节点时,Scheduler 会将其加入调度队列中。
  2. 预选节点(Filtering):Scheduler 通过一系列过滤规则筛选出符合条件的节点。例如,节点是否有足够的资源(CPU、内存等)、节点是否满足 Pod 的亲和性/反亲和性规则等。
  3. 优选节点(Scoring):对通过过滤的节点进行打分,依据不同的优选规则对节点进行评分(如节点资源的剩余情况、Pod 分布等),最终选择得分最高的节点。
  4. 绑定 Pod 到节点:Scheduler 将 Pod 绑定到选定的节点,API Server 会更新 Pod 的状态。

2. Scheduler 的主要功能

节点过滤(Predicates)

节点过滤是调度过程中的第一步,主要包括以下几种过滤规则:

  • PodFitsResources:检查节点是否有足够的资源(CPU、内存等)供 Pod 使用。
  • PodFitsHostPorts:检查节点上是否有指定的主机端口可用。
  • PodFitsHost:检查 Pod 是否要求运行在特定的节点上。
  • NoDiskConflict:检查节点上是否有 Pod 使用了同一持久卷。
  • MatchNodeSelector:检查节点标签是否满足 Pod 的节点选择器要求。
  • CheckNodeUnschedulable:检查节点是否可调度。
  • PodToleratesNodeTaints:检查 Pod 是否容忍节点的污点。
节点优选(Priorities)

在通过过滤规则后,Scheduler 会对符合条件的节点进行打分,依据不同的优选规则对节点进行评分:

  • LeastRequestedPriority:优先选择资源请求最少的节点。
  • BalancedResourceAllocation:优先选择资源分配最平衡的节点。
  • NodePreferAvoidPodsPriority:优先选择没有被其他 Pod 标记为“避开”的节点。
  • NodeAffinityPriority:依据节点亲和性规则对节点进行打分。
  • TaintTolerationPriority:依据污点容忍规则对节点进行打分。
Pod 亲和性和反亲和性(Pod Affinity and Anti-Affinity)

Pod 亲和性和反亲和性允许用户定义更复杂的调度规则,以确保 Pod 能够调度到特定的节点或避免调度到某些节点。例如,可以定义 Pod 必须与某些标签的 Pod 在同一节点上,或者必须与某些标签的 Pod 不在同一节点上。

节点亲和性(Node Affinity)

节点亲和性允许用户定义 Pod 只能调度到某些特定的节点。它是节点选择器的更灵活和表达力更强的形式,可以基于节点的标签进行配置。

Taints 和 Tolerations

Taints 和 Tolerations 用于避免 Pod 被调度到不适合的节点。节点可以有污点(Taints),只有容忍这些污点(Tolerations)的 Pod 才能被调度到这些节点上。

3. 自定义 Scheduler

Kubernetes 允许用户编写和部署自定义的调度器,替代默认的调度器或与之共存。以下是编写自定义调度器的基本步骤:

  1. 创建调度器插件:基于 Kubernetes 提供的调度框架,创建新的调度插件。调度插件可以是过滤插件、打分插件、预绑定插件等。
  2. 配置调度器:在 kube-scheduler 配置文件中添加或修改调度插件。
  3. 部署自定义调度器:将自定义调度器部署到 Kubernetes 集群中,并在 Pod 定义中指定使用自定义调度器。

4. 调试和监控

Kubernetes Scheduler 提供了多种调试和监控工具,以便用户了解调度过程并解决调度问题。例如:

  • 调度事件日志:Scheduler 会记录调度过程中的事件日志,可以通过查看这些日志来了解调度过程。
  • 调度指标:Scheduler 提供了一些 Prometheus 指标,可以用来监控调度性能和调度决策。

通过上述功能和工具,Kubernetes Scheduler 能够实现灵活、高效的调度策略,满足不同应用的需求。

二、Kubernetes Scheduler与其他组件交互示意图

下图表示 Kubernetes Scheduler 与其他组件交互过程 。图示包括 API Server、Scheduler、Controller Manager、etcd、Kubelet 和节点。

示意图

交互流程解释

  1. 用户创建 Pod
    • 用户向 API Server 提交 Pod 创建请求。
    • API Server 将 Pod 的配置信息存储在 etcd 中,并确认创建成功。
  2. 调度 Pod
    • API Server 通知 Scheduler 有新的待调度 Pod。
    • Scheduler 从 API Server 获取 Pod 的配置信息,并从 etcd 获取集群中节点的信息。
    • Scheduler 过滤和打分节点,选择最合适的节点。
    • Scheduler 将 Pod 绑定到选定的节点,并通知 API Server。
  3. 更新 Pod 绑定信息
    • API Server 将 Pod 的绑定信息更新到 etcd。
    • Scheduler 确认绑定成功。
  4. 控制器管理 Pod
    • API Server 通知 Controller Manager Pod 的更新信息。
    • Controller Manager 从 etcd 获取 Pod 配置信息,并更新 Pod 的状态。
  5. Kubelet 启动 Pod
    • API Server 通知 Kubelet Pod 已分配到其管理的节点上。
    • Kubelet 从 etcd 获取 Pod 的配置信息。
    • Kubelet 在节点上创建并启动 Pod。
    • 节点返回 Pod 的运行状态给 Kubelet。
    • Kubelet 更新 Pod 的状态到 API Server。
  6. 存储 Pod 状态并通知用户
    • API Server 将 Pod 的运行状态存储到 etcd。
    • API Server 通知用户 Pod 处于运行状态。

通过这个交互示意图和解释,可以清晰地了解 Kubernetes Scheduler 与其他组件的交互流程和机制。

三、Kubernetes Scheduler伪代码实现

下面是一个使用 Go 语言编写的 Kubernetes Scheduler 功能的伪代码示例。此示例包括基本的调度流程,包括从 API Server 获取未调度的 Pod,筛选节点,评分节点,并将 Pod 绑定到选定的节点。

Kubernetes Scheduler 伪代码

代码语言:javascript复制
package main

import (
    "context"
    "fmt"
    "log"
    "time"

    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/tools/clientcmd"
    "k8s.io/client-go/util/homedir"
    "path/filepath"
    "k8s.io/kubernetes/pkg/scheduler"
    "k8s.io/kubernetes/pkg/scheduler/framework/plugins/noderesources"
    "k8s.io/kubernetes/pkg/scheduler/framework/plugins/registry"
    "k8s.io/kubernetes/pkg/scheduler/framework/runtime"
)

func main() {
    // 加载Kubernetes配置
    var kubeconfig string
    if home := homedir.HomeDir(); home != "" {
        kubeconfig = filepath.Join(home, ".kube", "config")
    }

    config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
    if err != nil {
        log.Fatalf("Error building kubeconfig: %s", err.Error())
    }

    // 创建Kubernetes客户端
    clientset, err := kubernetes.NewForConfig(config)
    if err != nil {
        log.Fatalf("Error creating Kubernetes client: %s", err.Error())
    }

    // 注册调度插件
    registry := registry.NewRegistry()
    pluginFactory := noderesources.NewFit
    registry.Register("NodeResourcesFit", pluginFactory)

    // 创建调度框架
    schedulerFramework, err := runtime.NewFramework(registry, nil, nil)
    if err != nil {
        log.Fatalf("Error creating scheduler framework: %s", err.Error())
    }

    // 创建自定义调度器
    sched := scheduler.New(
        clientset,
        schedulerFramework,
        nil,
        context.TODO(),
        "custom-scheduler",
    )

    // 主调度循环
    for {
        pod := getPendingPod(clientset)
        if pod != nil {
            nodes := getNodes(clientset)
            filteredNodes := filterNodes(pod, nodes)
            if len(filteredNodes) > 0 {
                bestNode := scoreNodes(pod, filteredNodes)
                bindPodToNode(clientset, pod, bestNode)
            }
        }
        time.Sleep(1 * time.Second)
    }
}

// 获取未调度的Pod
func getPendingPod(clientset *kubernetes.Clientset) *v1.Pod {
    pods, err := clientset.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{})
    if err != nil {
        log.Fatalf("Error listing pods: %s", err.Error())
    }
    for _, pod := range pods.Items {
        if pod.Spec.NodeName == "" {
            return &pod
        }
    }
    return nil
}

// 获取所有节点
func getNodes(clientset *kubernetes.Clientset) []v1.Node {
    nodes, err := clientset.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
    if err != nil {
        log.Fatalf("Error listing nodes: %s", err.Error())
    }
    return nodes.Items
}

// 节点过滤
func filterNodes(pod *v1.Pod, nodes []v1.Node) []v1.Node {
    var filteredNodes []v1.Node
    for _, node := range nodes {
        if nodeHasSufficientResources(pod, &node) {
            filteredNodes = append(filteredNodes, node)
        }
    }
    return filteredNodes
}

// 检查节点是否有足够资源
func nodeHasSufficientResources(pod *v1.Pod, node *v1.Node) bool {
    // 实现资源检查逻辑,如CPU和内存是否足够
    return true
}

// 节点评分
func scoreNodes(pod *v1.Pod, nodes []v1.Node) *v1.Node {
    var bestNode *v1.Node
    var highestScore int
    for _, node := range nodes {
        score := calculateNodeScore(pod, &node)
        if score > highestScore {
            highestScore = score
            bestNode = &node
        }
    }
    return bestNode
}

// 计算节点分数
func calculateNodeScore(pod *v1.Pod, node *v1.Node) int {
    // 实现评分逻辑,如节点的资源利用率、亲和性等
    return 0
}

// 绑定Pod到节点
func bindPodToNode(clientset *kubernetes.Clientset, pod *v1.Pod, node *v1.Node) {
    binding := &v1.Binding{
        ObjectMeta: metav1.ObjectMeta{Name: pod.Name, Namespace: pod.Namespace},
        Target: v1.ObjectReference{
            Kind: "Node",
            Name: node.Name,
        },
    }
    err := clientset.CoreV1().Pods(pod.Namespace).Bind(context.TODO(), binding, metav1.CreateOptions{})
    if err != nil {
        log.Fatalf("Error binding pod: %s", err.Error())
    } else {
        fmt.Printf("Successfully bound pod %s to node %sn", pod.Name, node.Name)
    }
}

伪代码解释

  1. 主函数
    • 加载 Kubernetes 配置并创建 Kubernetes 客户端。
    • 注册调度插件并创建调度框架。
    • 创建自定义调度器,并进入主调度循环。
  2. 主调度循环
    • 获取未调度的 Pod。
    • 获取所有节点。
    • 过滤节点,找出符合条件的节点。
    • 对符合条件的节点进行评分,选择得分最高的节点。
    • 将 Pod 绑定到选定的节点。
  3. 辅助函数
    • getPendingPod:获取未调度的 Pod。
    • getNodes:获取所有节点。
    • filterNodes:根据资源要求过滤节点。
    • nodeHasSufficientResources:检查节点是否有足够资源。
    • scoreNodes:对符合条件的节点进行评分。
    • calculateNodeScore:计算节点的分数。
    • bindPodToNode:将 Pod 绑定到选定的节点。

这个伪代码示例展示了一个基本的自定义调度器的工作流程和关键函数。实际实现中,你可以根据具体需求扩展和修改这些函数。

下面是表示上述自定义 Kubernetes Scheduler 伪代码的调用图。这张图展示了调度器的主要流程,包括获取未调度的 Pod、获取节点、过滤节点、评分节点以及将 Pod 绑定到选定节点的过程。

图解说明

  1. Start Scheduler
    • 调度器启动并进入主循环。
  2. Get Pending Pod
    • 从 API Server 获取未调度的 Pod。
  3. Pod Found?
    • 判断是否找到未调度的 Pod,如果没有找到则返回主循环继续检查。
  4. Get Nodes
    • 从 API Server 获取所有节点的信息。
  5. Filter Nodes
    • 根据资源要求和调度规则过滤节点。
  6. Filtered Nodes Found?
    • 判断是否有符合条件的节点,如果没有找到则返回主循环。
  7. Score Nodes
    • 对符合条件的节点进行评分。
  8. Select Best Node
    • 选择得分最高的节点。
  9. Bind Pod to Node
    • 将 Pod 绑定到选定的节点,并更新 API Server。
  10. Log Success
    • 记录绑定成功的日志。
  11. Communicates with Kubernetes API
    • 调度器通过 API Server 与 Kubernetes 交互,获取 Pod 和节点信息,更新绑定状态。
  12. etcd
    • API Server 将 Pod 和节点信息存储到 etcd 中。
  13. Nodes
    • 显示集群中的节点,调度器对这些节点进行过滤和评分。

该图直观地展示了自定义 Kubernetes Scheduler 的主要工作流程和与其他组件的交互。

四、自定义 Kubernetes Scheduler最佳实践

自定义 Kubernetes Scheduler 是一个高级主题,需要仔细考虑和实现,确保它能够有效地管理和调度 Pod 到集群中的节点。以下是一些关于自定义 Kubernetes Scheduler 的最佳实践:

1. 确定需求和场景

在开始之前,确保明确你的需求和场景。了解你想要实现的特定调度逻辑和策略,例如特定的资源分配、亲和性/反亲和性需求、定制化的节点选择算法等。理解这些需求将有助于指导你设计和实现自定义 Scheduler。

2. 使用 Kubernetes 提供的调度框架和插件

Kubernetes 提供了灵活的调度框架和各种插件,例如节点资源调度、亲和性/反亲和性、污点和容忍等。在自定义 Scheduler 时,可以基于现有的调度框架进行扩展和定制化,使用已有的调度插件或编写新的插件以满足特定需求。

3. 编写调度器代码

编写自定义 Scheduler 的代码时,考虑以下几点:

  • 初始化 Kubernetes 客户端:确保能够与 Kubernetes API Server 进行通信。
  • 注册调度插件:根据需要注册适合的调度插件,如节点资源调度插件、亲和性/反亲和性插件等。
  • 实现调度逻辑:在主循环中实现从 API Server 获取未调度的 Pod、获取节点列表、过滤和评分节点、绑定 Pod 到节点等核心调度逻辑。
  • 错误处理和日志记录:添加适当的错误处理机制,并记录调度过程中的关键事件和决策。

4. 测试和验证

在部署自定义 Scheduler 之前,进行充分的测试和验证是必不可少的步骤。确保你的 Scheduler 能够正确地处理各种场景和边界条件,例如不同类型的 Pod、不同的节点状态、并发调度等。

5. 部署和监控

部署自定义 Scheduler 到 Kubernetes 集群后,确保能够有效地监控其性能和行为。使用 Kubernetes 提供的监控工具(如 Prometheus)或自定义指标来收集调度器的运行数据,并及时调整和优化调度策略。

6. 考虑安全性和可扩展性

在设计和实现自定义 Scheduler 时,要考虑安全性和可扩展性。确保 Scheduler 的操作是安全的,并且可以在集群的规模和负载增加时保持稳定和高效。

示例实现

以下是一个简单的示例实现,展示了如何使用 Go 和 Kubernetes 客户端库来创建自定义 Scheduler:

  • 初始化 Kubernetes 客户端和调度器框架。
  • 注册节点资源调度插件。
  • 实现调度逻辑,包括节点过滤、评分和绑定 Pod 到节点。
  • 在主循环中运行调度器,并持续监视未调度的 Pod。
代码语言:javascript复制
// 请参考前面给出的Go语言伪代码实现。

通过遵循这些最佳实践,你可以成功地设计、开发和部署自定义 Kubernetes Scheduler,以满足特定的业务需求和调度策略。

五、自定义Kubernetes Scheduler示例

自定义 Kubernetes Scheduler 允许你定义调度逻辑以满足特定需求。下面是如何创建、配置和部署自定义 Scheduler 的详细步骤。

步骤 1:编写自定义调度器

你可以使用 Go 语言编写自定义调度器。以下是一个基本的自定义调度器示例:

代码语言:javascript复制
package main

import (
    "context"
    "fmt"
    "log"
    "time"

    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/tools/clientcmd"
    "k8s.io/client-go/util/homedir"
    "path/filepath"
    "k8s.io/kubernetes/pkg/scheduler"
    "k8s.io/kubernetes/pkg/scheduler/framework/plugins/noderesources"
    "k8s.io/kubernetes/pkg/scheduler/framework/plugins/registry"
    "k8s.io/kubernetes/pkg/scheduler/framework/runtime"
)

func main() {
    var kubeconfig string
    if home := homedir.HomeDir(); home != "" {
        kubeconfig = filepath.Join(home, ".kube", "config")
    }

    config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
    if err != nil {
        log.Fatalf("Error building kubeconfig: %s", err.Error())
    }

    clientset, err := kubernetes.NewForConfig(config)
    if err != nil {
        log.Fatalf("Error creating Kubernetes client: %s", err.Error())
    }

    registry := registry.NewRegistry()
    pluginFactory := noderesources.NewFit
    registry.Register("NodeResourcesFit", pluginFactory)

    schedulerFramework, err := runtime.NewFramework(registry, nil, nil)
    if err != nil {
        log.Fatalf("Error creating scheduler framework: %s", err.Error())
    }

    sched := scheduler.New(
        clientset,
        schedulerFramework,
        nil,
        context.TODO(),
        "custom-scheduler",
    )

    for {
        err := sched.Run(context.TODO())
        if err != nil {
            log.Printf("Error running scheduler: %s", err.Error())
        }
        time.Sleep(1 * time.Second)
    }
}

步骤 2:构建 Docker 镜像

为你的自定义调度器创建一个 Docker 镜像。首先,创建一个 Dockerfile:

代码语言:javascript复制
FROM golang:1.17 as builder

WORKDIR /go/src/custom-scheduler
COPY . .

RUN go get -d -v ./...
RUN go build -o custom-scheduler .

FROM alpine:latest
RUN apk --no-cache add ca-certificates

WORKDIR /root/
COPY --from=builder /go/src/custom-scheduler/custom-scheduler .

CMD ["./custom-scheduler"]

然后构建镜像:

代码语言:javascript复制
docker build -t custom-scheduler:latest .

步骤 3:创建 Kubernetes Deployment

创建一个 Deployment 来运行自定义调度器:

代码语言:javascript复制
apiVersion: apps/v1
kind: Deployment
metadata:
  name: custom-scheduler
  labels:
    app: custom-scheduler
spec:
  replicas: 1
  selector:
    matchLabels:
      app: custom-scheduler
  template:
    metadata:
      labels:
        app: custom-scheduler
    spec:
      containers:
      - name: custom-scheduler
        image: custom-scheduler:latest
        imagePullPolicy: IfNotPresent
        command: ["./custom-scheduler"]
      serviceAccountName: system:scheduler

步骤 4:配置调度器名称

为你的 Pod 指定使用自定义调度器。在 Pod 定义中设置 schedulerName

代码语言:javascript复制
apiVersion: v1
kind: Pod
metadata:
  name: example-pod
spec:
  schedulerName: custom-scheduler
  containers:
  - name: example-container
    image: nginx

步骤 5:部署和验证

部署自定义调度器:

代码语言:javascript复制
kubectl apply -f custom-scheduler-deployment.yaml

创建一个使用自定义调度器的 Pod:

代码语言:javascript复制
kubectl apply -f example-pod.yaml

检查 Pod 是否使用自定义调度器成功调度:

代码语言:javascript复制
kubectl get pod example-pod -o jsonpath='{.spec.schedulerName}'

总结

通过以上步骤,你可以编写、构建、部署和使用自定义 Kubernetes Scheduler。这使你能够实现更灵活和特定需求的调度逻辑。

六、Kubernetes Scheduler历史演进

Kubernetes Scheduler 是 Kubernetes 集群管理系统的核心组件之一,它负责将未分配节点的 Pod 分配到合适的节点上。自 Kubernetes 项目启动以来,Kubernetes Scheduler 也经历了显著的演进。以下是 Kubernetes Scheduler 的历史演进:

1. Kubernetes 早期版本(2014-2015)

1.1. 初始设计
  • Kubernetes 于 2014 年开源,最初的调度器设计非常简单,主要基于静态调度策略,如 CPU 和内存资源。
  • 调度算法主要基于 “First Fit” 策略,即选择第一个满足资源要求的节点。
1.2. 调度器插件机制
  • 初始版本中,调度逻辑内嵌在调度器代码中,缺乏灵活性和扩展性。

2. Kubernetes 1.0(2015)

2.1. 初步稳定
  • Kubernetes 1.0 版本标志着 Kubernetes 的首次正式发布。
  • 调度器引入了更复杂的调度策略,如节点亲和性和反亲和性。

3. Kubernetes 1.2-1.5(2016-2017)

3.1. Pod 优先级和抢占
  • 引入 Pod 优先级和抢占机制,以便更好地处理资源紧张的场景。
  • 调度器能够根据优先级调整 Pod 的调度顺序。
3.2. 多调度器支持
  • Kubernetes 支持用户自定义调度器,使得在同一个集群中可以运行多个调度器实例。

4. Kubernetes 1.6-1.9(2017-2018)

4.1. 调度器扩展点
  • 引入调度器扩展点机制,使得用户可以插入自定义的调度策略和插件,增强调度器的可扩展性。
4.2. 事件驱动的调度
  • 调度器逐步从定期扫描变为事件驱动,更高效地响应 Pod 和节点状态的变化。

5. Kubernetes 1.10-1.13(2018-2019)

5.1. 调度框架(Scheduling Framework)
  • Kubernetes 1.11 引入了调度框架(Scheduling Framework),允许用户通过插件扩展调度器的行为。
  • 这一框架提供了多个扩展点,如预过滤、过滤、优选、绑定等。
5.2. 调度器性能优化
  • 持续的性能优化,提升调度器在大规模集群中的表现。

6. Kubernetes 1.14-1.19(2019-2020)

6.1. 调度框架逐步稳定
  • 调度框架功能逐步完善,用户可以更加灵活地自定义调度逻辑。
  • 增加了更多内置插件,提升了调度器的默认能力。
6.2. 调度器配置文件(Scheduler Configuration)
  • 引入调度器配置文件,允许用户通过配置文件定制调度器行为,而无需修改代码。

7. Kubernetes 1.20-1.23(2020-2021)

7.1. 调度框架的广泛采用
  • 调度框架被广泛采用,并逐渐成为定制调度逻辑的标准方式。
  • 更多的社区插件和企业插件被开发出来,满足不同场景的需求。
7.2. 性能和稳定性提升
  • 持续的性能和稳定性改进,使调度器能够在更大规模和更复杂的环境中运行。

8. Kubernetes 1.24 (2022-至今)

8.1. 提升调度器的智能化
  • 引入更多智能化的调度策略和算法,如机器学习驱动的调度。
  • 增加对异构资源(如 GPU、FPGA)的更好支持。
8.2. 多集群调度
  • 支持多集群环境中的调度需求,使得调度器能够跨多个 Kubernetes 集群进行资源调度。
8.3. 提升调度器的可观测性
  • 增加更多调度器的可观测性特性,使得管理员能够更好地了解调度决策和调度过程。

总结

Kubernetes Scheduler 的演进反映了 Kubernetes 社区对调度需求的不断理解和优化。随着 Kubernetes 的发展,调度器变得更加智能、灵活和高效,以应对现代应用程序的复杂需求和大规模集群的挑战。未来,调度器将继续演进,以支持更多新兴的计算资源和调度策略。

完。

希望对您有所帮助!关注锅总,及时获得更多花里胡哨的运维实用操作!

0 人点赞