前序
集群当中,我们很少直接创建一个 pod 来启动应用服务,而是通过控制器来创建 pod 从而运行应用实例,比如: Deployment、DaemonSet、Job 等控制器完成对一组 Pod 副本的创建。
在大多数情况下我们使用 Deployment 控制创建 pod 的时候 pod 的副本集会被成功的调度在集群中任意一个可用的节点上运行,而忽略了副本集具体会被调度到那个节点上,但是在真正的生产环境中也会存在一定的需求,比如希望某个 pod 运行在指定的节点上,或者集群资源的利用率过高无法再运行 pod,但是这个 pod 又非常重要必须要运行到节点当中,此时就可以通过 pod 的调度策略来解决以上问题。
Scheduler 是 kubernetes 的调度器,主要的任务是把定义的 pod 分配到集群的节点上。听起来非常简单,但有很多要考虑的问题:
(1)公平:如何保证每个节点都能被分配资源
(2)资源高效利用:集群所有资源最大化被使用
(3)效率:调度的性能要好,能够尽快地对大批量的 pod 完成调度工作
(4)灵活:允许用户根据自己的需求控制调度的逻辑
Kubernetes 调度原理概述
Kubernetes 调度过程可以分为以下几个阶段:
调度的基本过程
Pod 提交到 API Server
当一个 Pod 被创建时,它会通过 Kubernetes API Server 进入调度队列,等待调度器选择合适的节点来运行。调度器选择节点
Kubernetes 使用调度器(Scheduler)来选择节点。调度器需要根据 Pod 的要求(如资源、亲和性、反亲和性、污点和容忍等)选择一个合适的节点。Pod 到节点的绑定
调度器选择节点后,会将 Pod 绑定到该节点,并向 API Server 更新该 Pod 的状态。容器调度执行
节点上的 kubelet 会接收到该 Pod,并开始拉取容器镜像,最终启动容器。
调度器的决策过程
Kubernetes 调度器的决策过程主要包括以下几个步骤:
- 筛选阶段(Filtering)
调度器根据 Pod 的资源需求、节点的资源容量以及其他约束条件,筛选出一组符合要求的节点。这些条件包括:
(1)节点的资源(CPU、内存、存储等)是否足够;
(2)节点是否满足 Pod 的节点选择器(Node Selector)要求;
(3)节点是否满足亲和性(Affinity)和反亲和性(Anti-Affinity)等规则;
(4)节点是否具备指定的标签;
(5)节点是否有足够的网络带宽等。
筛选有一系列的算法可以使用:
PodFitsResources:节点上剩余的资源是否大于 pod 请求的资源
PodFitsHost:如果 pod 指定了 NodeName,检查节点名称是否和 NodeName 匹配
PodFitsHostPorts:节点上已经使用的 port 是否和 pod 申请的 port 冲突
PodSelectorMatches:过滤掉和 pod 指定的 label 不匹配的节点
-
NoDiskConflict:已经 mount 的 volume 和 pod 指定的 volume 不冲突,除非它们都是只读
如果在预选过程中没有合适的节点,pod 会一直在
pending
状态,不断重试调度,直到有节点满足条件。经过这个步骤,如果有多个节点满足条件,就继续优选
过程: 按照优先级大小对节点排序。 优选阶段(Ranking)
调度器会对筛选出来的节点进行排序,选择最优的节点。
优选的标准包括:
(1)最小化资源的浪费;
(2)最大化资源的利用率;
(3)节点的负载均衡;
(4)节点的健康状态;
(5)节点的可扩展性等。
对应的优先级选项包括:
(1)LeastRequestedPriority:通过计算 CPU 和 Memory 的使用率来决定权重,使用率越低权重越高。换句话说,这个优先级指标倾向于资源使用比例更低的节点
(2)BalancedResourceAllocation:节点上 CPU 和 Memory 使用率越接近,权重越高。这个应该和上面的一起使用,不应该单独使用
(3)ImageLocalityPriority:倾向于已经有要使用镜像的节点,镜像总大小值越大,权重越高
通过算法对所有的优先级项目和权重进行计算,得出最终的结果,调度器将选择一个节点来运行 Pod,并更新该 Pod 的状态。
调度策略和调度器扩展
Kubernetes 允许用户通过多种方式定制和扩展调度策略,以满足不同的需求。
亲和性和反亲和性
节点亲和性(Node Affinity)
节点亲和性允许用户根据节点的标签来约束 Pod 运行的节点。例如,可以配置某个 Pod 必须运行在具有特定标签(如 zone=us-west-1)的节点上。Pod 亲和性与反亲和性(Pod Affinity and Anti-Affinity)
Pod 亲和性允许用户指定某些 Pod 必须或不能与其他 Pod 一起运行。反亲和性通常用于确保某些服务的高可用性,例如将副本部署在不同的节点上。
污点和容忍性(Taints and Tolerations)
- 污点:节点可以设置污点,标识节点不能接受某些 Pod。污点通常用于标记节点的特殊性或不可用状态(例如:维护状态、硬件故障等)。
- 容忍性:Pod 可以声明容忍某些污点,这使得它能够在这些节点上调度。
资源请求和限制
- 请求(Requests):Pod 对资源的最低要求。当一个 Pod 被调度时,Kubernetes 会确保节点上有足够的资源来满足这些请求。
- 限制(Limits):Pod 可以设置资源的最大限制,防止容器消耗过多资源导致节点资源耗尽。
调度策略的扩展
Kubernetes 允许通过 调度器插件 或 自定义调度器 来扩展调度逻辑。例如,可以通过调度器插件来处理复杂的调度需求,如节点的资源预测、负载均衡优化、不同工作负载的优先级管理等。
大规模集群中的性能优化
在大规模 Kubernetes 集群中,调度器的性能至关重要。随着节点数和 Pod 数量的增加,调度的复杂性急剧上升,可能导致调度延迟、资源浪费甚至系统不稳定。因此,优化 Kubernetes 调度器的性能变得尤为重要。
优化
优化调度延迟
调度器高可用和扩展性:可以通过增加调度器副本数量(通过 kube-scheduler 的高可用部署)来提高调度器的吞吐量和容错性。此外,可以通过水平扩展多个调度器来分担负载。
调度器的缓存机制:Kubernetes 调度器需要频繁访问集群的状态信息,包括节点信息、Pod 信息、资源使用情况等。优化调度器的缓存机制,减少不必要的查询,可以显著降低调度延迟。
Pod 分配优先级:为了避免调度延迟过长,可以根据 Pod 的优先级和紧急性进行排序,优先调度那些需要及时启动的 Pod。
优化资源利用
资源需求精确化:合理配置 Pod 的资源请求和限制,以避免资源的过度分配和浪费。可以通过使用 Vertical Pod Autoscaler(VPA) 来自动调整 Pod 的资源请求和限制。
负载均衡:合理的负载均衡策略能够确保节点负载均衡,避免某些节点过载,而其他节点闲置。可以使用 Pod Affinity/Anti-Affinity 或 Node Affinity 来优化调度,提高资源利用率。
混合负载调度:在大规模集群中,不同类型的工作负载(如批处理、实时处理、Web 服务等)可能有不同的资源需求和时效性。使用不同的调度策略,如优先级队列、QoS(服务质量)等,来区分并优化不同类型的 Pod 调度。
集群管理与监控
集群监控与自动化调度:结合 Prometheus 和 Grafana 等监控工具,对集群资源和调度过程进行实时监控。可以基于监控数据动态调整调度策略,避免某些节点资源过度或不足,确保集群的高效运行。
集群规模化管理:当集群规模增大时,可以通过将集群划分为多个小的子集群(如多租户环境)来降低调度复杂度。使用 Cluster Federation 或 Multi-cluster 策略来管理跨集群的调度和资源。
调度器优化策略
改进调度算法:调度器的核心是调度算法,改进调度算法(如动态调度、基于成本的调度等)可以减少调度的复杂度。例如,针对大规模集群,可以使用基于启发式算法、机器学习或预测模型的调度算法来优化 Pod 的调度决策。
延迟控制:大规模集群中的调度延迟可能会影响系统响应时间。通过减少不必要的调度决策、合并调度任务和预先分配资源来减少调度延迟。
资源监控与自动扩展
Horizontal Pod Autoscaler (HPA):Kubernetes 提供了 HPA 用于基于 CPU、内存等资源的使用情况自动调整 Pod 的副本数。通过合理配置 HPA,可以确保 Pod 在负载高峰期有足够的副本来处理请求。
Cluster Autoscaler:Cluster Autoscaler 用于自动调整集群节点数,根据集群负载的变化自动增加或减少节点数量。这可以在保证资源充足的情况下减少不必要的节点开销。
总结
Kubernetes 的调度系统是集群运行的核心部分,确保了容器的高效调度和资源的合理分配。在大规模集群中,调度的复杂性和性能要求更高,因此优化调度器的策略至关重要。通过灵活的调度策略、优化调度算法、精确的资源管理和集群监控,可以有效提高集群的性能,保证系统的稳定性与高效运行。
调度原理与性能优化的结合,是 Kubernetes 在大规模集群中顺利运行的关键所在。随着集群规模的扩大,对调度器的优化需求将不断增长,未来的 Kubernetes 调度器可能会更加智能化、自动化,适应不同的业务需求和资源变化。