|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
1. Kubernetes调度器概述
Kubernetes作为当今最流行的容器编排平台,其调度器(Scheduler)是整个系统的核心组件之一。调度器负责将新创建的Pod分配到集群中的合适节点上运行,这一过程看似简单,实则涉及复杂的决策逻辑和资源管理策略。在云原生环境中,调度器的效率和智能程度直接影响整个集群的资源利用率、应用性能和系统稳定性。
Kubernetes调度器的主要职责是监视新创建尚未分配节点的Pod,并根据一系列规则和策略为它们选择最合适的运行节点。这一过程需要考虑多方面因素,包括资源需求、硬件/软件/策略约束、亲和性和反亲和性规范、数据位置、工作负载间的干扰以及截止时间等。
调度器在Kubernetes架构中扮演着至关重要的角色。它作为控制平面的核心组件,与API Server、etcd、Controller Manager和kubelet等其他组件紧密协作,共同维持集群的正常运行。一个高效、智能的调度器能够显著提升集群资源利用率,降低运营成本,并确保应用性能和可靠性。
2. 调度器工作原理详解
Kubernetes调度器的工作流程可以概括为两个主要阶段:过滤(Filtering)和打分(Scoring)。让我们深入了解这两个阶段的具体实现。
2.1 调度流程概述
调度器的工作流程如下:
1. 监视Pod:调度器持续监视Kubernetes API Server,查找尚未分配节点的Pod(即spec.nodeName为空的Pod)。
2. 过滤阶段:对于每个待调度的Pod,调度器会遍历集群中的所有节点,通过一系列谓词(Predicates)规则筛选出满足Pod基本运行需求的节点。
3. 打分阶段:对过滤阶段筛选出的节点,调度器会应用一系列优先级(Priorities)规则进行打分,选出最适合运行该Pod的节点。
4. 绑定阶段:调度器将Pod与选定的节点进行绑定,更新Pod的spec.nodeName字段,然后kubelet会负责在目标节点上创建并运行Pod。
2.2 过滤阶段详解
过滤阶段是调度决策的第一步,其目的是快速排除那些明显不适合运行Pod的节点。Kubernetes提供了一系列内置的谓词规则,包括:
• PodFitsResources:检查节点是否有足够的CPU、内存等资源满足Pod的需求。
• PodFitsHostPorts:检查Pod请求的端口是否已被节点上的其他Pod占用。
• PodMatchNodeSelector:检查节点的标签是否匹配Pod的nodeSelector。
• NoVolumeZoneConflict:检查Pod请求的卷是否在节点所在的可用区。
• CheckNodeMemoryPressure:检查节点是否处于内存压力状态。
• CheckNodeDiskPressure:检查节点是否处于磁盘压力状态。
• MatchInterPodAffinity:检查Pod是否满足与其他Pod的亲和性/反亲和性规则。
让我们通过一个示例来了解过滤阶段的具体实现:
- // 伪代码示例:过滤阶段实现
- func (g *GenericScheduler) Schedule(pod *v1.Pod, nodeLister algorithm.NodeLister) (result ScheduleResult, err error) {
- // 获取所有节点
- nodes, err := nodeLister.List()
- if err != nil {
- return result, err
- }
-
- // 过滤阶段:应用谓词规则
- filteredNodes, failedPredicateMap, err := g.findNodesThatFit(pod, nodes)
- if err != nil {
- return result, err
- }
-
- // 如果没有节点满足条件,返回错误
- if len(filteredNodes) == 0 {
- return result, &FitError{
- Pod: pod,
- NumAllNodes: len(nodes),
- FailedPredicates: failedPredicateMap,
- }
- }
-
- // 打分阶段:应用优先级规则
- priorityList, err := g.prioritizeNodes(pod, filteredNodes)
- if err != nil {
- return result, err
- }
-
- // 选择得分最高的节点
- host, err := g.selectHost(priorityList)
- if err != nil {
- return result, err
- }
-
- return ScheduleResult{
- SuggestedHost: host,
- EvaluatedNodes: len(filteredNodes),
- FeasibleNodes: len(filteredNodes),
- }, nil
- }
复制代码
2.3 打分阶段详解
打分阶段是调度决策的第二步,其目的是从满足基本条件的节点中选出最优节点。每个优先级规则会为节点打分,最终得分是所有规则得分的加权和。Kubernetes提供了一系列内置的优先级规则,包括:
• LeastRequestedPriority:优先选择资源请求量较少的节点,以平衡资源使用。
• BalancedResourceAllocation:优先选择CPU和内存资源使用更均衡的节点。
• NodeAffinityPriority:优先选择与Pod的节点亲和性规则匹配度更高的节点。
• TaintTolerationPriority:优先选择与Pod的容忍规则匹配度更高的节点。
• InterPodAffinityPriority:优先选择与其他Pod的亲和性规则匹配度更高的节点。
• NodePreferAvoidPodsPriority:优先选择那些没有标记要避免Pod的节点。
• ImageLocalityPriority:优先选择已经存在Pod所需容器镜像的节点。
以下是一个打分阶段的示例实现:
- // 伪代码示例:打分阶段实现
- func (g *GenericScheduler) prioritizeNodes(pod *v1.Pod, nodes []*v1.Node) (schedulerapi.HostPriorityList, error) {
- // 初始化节点得分列表
- result := make(schedulerapi.HostPriorityList, 0, len(nodes))
-
- // 并行计算每个节点在每个优先级规则下的得分
- for _, node := range nodes {
- // 计算节点在所有优先级规则下的总得分
- score := 0
- for _, priorityConfig := range g.prioritizers {
- // 调用优先级函数计算得分
- nodeScore, err := priorityConfig.Function(pod, node)
- if err != nil {
- return nil, err
- }
- // 加权求和
- score += nodeScore * priorityConfig.Weight
- }
-
- result = append(result, schedulerapi.HostPriority{
- Host: node.Name,
- Score: score,
- })
- }
-
- return result, nil
- }
复制代码
2.4 绑定阶段详解
绑定阶段是调度决策的最后一步,调度器将Pod与选定的节点进行绑定。这个过程包括:
1. 更新Pod的spec.nodeName字段为选定的节点名称。
2. 将更新后的Pod对象写入etcd。
3. 目标节点上的kubelet监视到分配给自己的Pod后,负责创建并运行该Pod。
绑定阶段的示例代码如下:
- // 伪代码示例:绑定阶段实现
- func (b *Binder) Bind(binding *v1.Binding) error {
- // 创建绑定请求
- err := b.Client.CoreV1().Pods(binding.Namespace).Bind(binding)
- if err != nil {
- return err
- }
- return nil
- }
- // 调度器调用绑定
- func (sched *Scheduler) bind(binding *v1.Binding) error {
- // 尝试绑定
- err := sched.Binder.Bind(binding)
- if err != nil {
- // 绑定失败,记录错误并重试
- return err
- }
-
- // 绑定成功,记录事件
- sched.Recorder.Eventf(&v1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: binding.Name,
- Namespace: binding.Namespace,
- },
- }, v1.EventTypeNormal, "Scheduled", "Successfully assigned %v/%v to %v", binding.Namespace, binding.Name, binding.Target.Name)
-
- return nil
- }
复制代码
3. 资源感知机制
Kubernetes调度器的资源感知机制是其核心功能之一,它使调度器能够根据节点的资源状况做出智能决策。本节将深入探讨调度器如何感知、评估和利用集群资源。
3.1 资源模型
Kubernetes使用两种主要的资源度量方式:请求(Request)和限制(Limit)。
• 请求(Request):Pod需要的最小资源量,用于调度决策和节点资源分配。
• 限制(Limit):Pod可以使用的最大资源量,用于运行时资源限制。
调度器主要关注资源请求量,因为它决定了Pod能否在节点上运行。节点的可分配资源(Allocatable)是其总资源容量减去系统预留资源后的值,调度器会确保节点上所有Pod的资源请求总和不超过节点的可分配资源。
以下是一个资源模型的示例:
- apiVersion: v1
- kind: Pod
- metadata:
- name: resource-pod
- spec:
- containers:
- - name: resource-container
- image: nginx
- resources:
- requests:
- cpu: "500m" # 0.5 CPU核心
- memory: "512Mi" # 512 MiB内存
- limits:
- cpu: "1000m" # 1 CPU核心
- memory: "1Gi" # 1 GiB内存
复制代码
3.2 资源监控与报告
Kubernetes通过kubelet组件收集节点资源使用情况,并定期向API Server报告。调度器利用这些信息了解节点的资源状况,做出合理的调度决策。
kubelet通过cAdvisor(Container Advisor)收集容器资源使用数据,包括CPU、内存、磁盘和网络等。这些数据通过节点状态(NodeStatus)更新到API Server,调度器可以访问这些信息进行调度决策。
以下是节点资源状态的示例:
- apiVersion: v1
- kind: Node
- metadata:
- name: node-1
- status:
- capacity:
- cpu: "4"
- memory: 16Gi
- pods: "110"
- allocatable:
- cpu: "3900m"
- memory: 15Gi
- pods: "110"
- conditions:
- - type: MemoryPressure
- status: "False"
- - type: DiskPressure
- status: "False"
- - type: PIDPressure
- status: "False"
- - type: Ready
- status: "True"
复制代码
3.3 资源感知调度策略
Kubernetes调度器提供了多种资源感知的调度策略,帮助优化集群资源利用率。
正确设置Pod的资源请求和限制是资源感知调度的基础。请求值用于调度决策,确保节点有足够资源运行Pod;限制值用于运行时资源控制,防止Pod消耗过多资源影响其他应用。
以下是一个资源请求与限制的最佳实践示例:
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: resource-aware-deployment
- spec:
- replicas: 3
- selector:
- matchLabels:
- app: resource-aware
- template:
- metadata:
- labels:
- app: resource-aware
- spec:
- containers:
- - name: app
- image: myapp:1.0
- resources:
- requests:
- cpu: "500m"
- memory: "512Mi"
- limits:
- cpu: "1000m"
- memory: "1Gi"
- - name: sidecar
- image: sidecar:1.0
- resources:
- requests:
- cpu: "200m"
- memory: "256Mi"
- limits:
- cpu: "500m"
- memory: "512Mi"
复制代码
Kubernetes提供了资源配额(ResourceQuota)和限制范围(LimitRange)两种机制,帮助管理员控制命名空间的资源使用。
• 资源配额:限制命名空间中可以创建的对象数量以及计算资源总量。
• 限制范围:为命名空间中的Pod或容器设置默认资源请求和限制,以及最小/最大资源限制。
以下是一个资源配额和限制范围的示例:
- # 资源配额示例
- apiVersion: v1
- kind: ResourceQuota
- metadata:
- name: compute-resources
- namespace: dev-namespace
- spec:
- hard:
- requests.cpu: "4"
- requests.memory: 8Gi
- limits.cpu: "8"
- limits.memory: 16Gi
- pods: "10"
- # 限制范围示例
- apiVersion: v1
- kind: LimitRange
- metadata:
- name: resource-limits
- namespace: dev-namespace
- spec:
- limits:
- - default:
- cpu: "500m"
- memory: "512Mi"
- defaultRequest:
- cpu: "250m"
- memory: "256Mi"
- max:
- cpu: "2000m"
- memory: "2Gi"
- min:
- cpu: "100m"
- memory: "128Mi"
- type: Container
复制代码
Kubernetes允许配置资源过量使用(Overcommitment),即节点上所有Pod的资源请求总和可以超过节点的可分配资源。这种策略可以提高资源利用率,但也增加了资源耗尽的风险。
资源过量使用通过kubelet的--kube-reserved和--system-reserved参数配置,它们分别指定为Kubernetes系统和系统进程预留的资源。
以下是一个kubelet资源配置示例:
- kubelet --kube-reserved=cpu=500m,memory=1Gi --system-reserved=cpu=200m,memory=500Mi
复制代码
3.4 扩展资源与设备插件
除了CPU和内存等标准资源,Kubernetes还支持扩展资源(Extended Resources),如GPU、FPGA、高性能网卡等。这些资源通过设备插件(Device Plugin)机制集成到Kubernetes中。
设备插件负责:
1. 发现节点上的特定硬件设备。
2. 将这些设备作为扩展资源广告给Kubelet。
3. 监控设备健康状况。
4. 在Pod需要时分配设备。
以下是一个使用GPU资源的Pod示例:
- apiVersion: v1
- kind: Pod
- metadata:
- name: gpu-pod
- spec:
- containers:
- - name: cuda-container
- image: nvidia/cuda:10.0-base
- resources:
- limits:
- nvidia.com/gpu: 2 # 请求2个GPU
复制代码
4. 智能调度策略
Kubernetes提供了多种智能调度策略,使调度器能够根据业务需求和应用特性做出更精细的调度决策。这些策略包括节点亲和性、Pod亲和性/反亲和性、污点和容忍等。
4.1 节点亲和性调度
节点亲和性(Node Affinity)允许Pod根据节点标签选择性地调度到特定节点上。它分为两种类型:requiredDuringSchedulingIgnoredDuringExecution(硬性要求)和preferredDuringSchedulingIgnoredDuringExecution(软性偏好)。
硬性节点亲和性规则必须满足,否则Pod不会被调度到节点上。它使用nodeSelector的扩展形式,提供更丰富的匹配语法。
以下是一个硬性节点亲和性的示例:
- apiVersion: v1
- kind: Pod
- metadata:
- name: affinity-pod
- spec:
- affinity:
- nodeAffinity:
- requiredDuringSchedulingIgnoredDuringExecution:
- nodeSelectorTerms:
- - matchExpressions:
- - key: kubernetes.io/e2e-az-name
- operator: In
- values:
- - e2e-az1
- - e2e-az2
- containers:
- - name: nginx
- image: nginx
复制代码
软性节点亲和性规则是偏好而非强制要求,调度器会尝试满足这些规则,但在无法满足时仍会调度Pod。
以下是一个软性节点亲和性的示例:
- apiVersion: v1
- kind: Pod
- metadata:
- name: affinity-pod
- spec:
- affinity:
- nodeAffinity:
- preferredDuringSchedulingIgnoredDuringExecution:
- - weight: 1 # 权重,范围1-100
- preference:
- matchExpressions:
- - key: another-node-label-key
- operator: In
- values:
- - another-node-label-value
- containers:
- - name: nginx
- image: nginx
复制代码
4.2 Pod亲和性与反亲和性调度
Pod亲和性(Pod Affinity)和反亲和性(Pod Anti-Affinity)允许Pod根据其他Pod的标签选择性地调度到特定节点上。这种策略对于需要部署在一起或分开的应用非常有用。
Pod亲和性确保Pod被调度到与具有特定标签的Pod相同的节点上。它同样分为硬性要求和软性偏好两种类型。
以下是一个Pod亲和性的示例:
- apiVersion: v1
- kind: Pod
- metadata:
- name: pod-affinity-pod
- spec:
- affinity:
- podAffinity:
- requiredDuringSchedulingIgnoredDuringExecution:
- - labelSelector:
- matchExpressions:
- - key: security
- operator: In
- values:
- - S1
- topologyKey: topology.kubernetes.io/zone
- containers:
- - name: nginx
- image: nginx
复制代码
Pod反亲和性确保Pod不被调度到与具有特定标签的Pod相同的节点上。这对于避免单点故障和提高应用可用性非常重要。
以下是一个Pod反亲和性的示例:
- apiVersion: v1
- kind: Pod
- metadata:
- name: pod-antiaffinity-pod
- spec:
- affinity:
- podAntiAffinity:
- requiredDuringSchedulingIgnoredDuringExecution:
- - labelSelector:
- matchExpressions:
- - key: app
- operator: In
- values:
- - web-frontend
- topologyKey: kubernetes.io/hostname
- containers:
- - name: nginx
- image: nginx
复制代码
4.3 污点与容忍调度
污点(Taints)和容忍(Tolerations)是另一种控制Pod调度的机制。污点应用于节点,表示节点不能接受某些Pod;容忍应用于Pod,表示Pod可以容忍具有特定污点的节点。
节点污点由键、值和效果组成。效果可以是NoSchedule、PreferNoSchedule或NoExecute。
以下是为节点添加污点的示例:
- kubectl taint nodes node1 key=value:NoSchedule
复制代码
Pod可以通过容忍来接受具有特定污点的节点。容忍必须与污点的键、值和效果匹配。
以下是一个Pod容忍的示例:
- apiVersion: v1
- kind: Pod
- metadata:
- name: toleration-pod
- spec:
- tolerations:
- - key: "key"
- operator: "Equal"
- value: "value"
- effect: "NoSchedule"
- containers:
- - name: nginx
- image: nginx
复制代码
4.4 自定义调度器
除了默认调度器,Kubernetes还允许用户开发自定义调度器,满足特定业务需求。自定义调度器可以与默认调度器并行运行,或者完全替代默认调度器。
自定义调度器通常作为独立的进程运行,通过监视Kubernetes API Server获取待调度Pod和节点信息,然后应用自定义调度逻辑,最后将Pod绑定到选定节点。
以下是一个简单的自定义调度器框架:
- package main
- import (
- "flag"
- "fmt"
- "time"
- corev1 "k8s.io/api/core/v1"
- "k8s.io/apimachinery/pkg/labels"
- "k8s.io/client-go/informers"
- "k8s.io/client-go/kubernetes"
- "k8s.io/client-go/tools/cache"
- "k8s.io/client-go/tools/clientcmd"
- "k8s.io/klog/v2"
- )
- type CustomScheduler struct {
- clientset kubernetes.Interface
- podInformer cache.SharedIndexInformer
- nodeInformer cache.SharedIndexInformer
- unscheduledPods chan *corev1.Pod
- }
- func NewCustomScheduler(clientset kubernetes.Interface) *CustomScheduler {
- factory := informers.NewSharedInformerFactory(clientset, 30*time.Second)
-
- podInformer := factory.Core().V1().Pods().Informer()
- nodeInformer := factory.Core().V1().Nodes().Informer()
-
- scheduler := &CustomScheduler{
- clientset: clientset,
- podInformer: podInformer,
- nodeInformer: nodeInformer,
- unscheduledPods: make(chan *corev1.Pod, 100),
- }
-
- // 设置Pod事件处理器
- podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
- AddFunc: func(obj interface{}) {
- pod := obj.(*corev1.Pod)
- if pod.Spec.NodeName == "" && pod.Status.Phase == corev1.PodPending {
- scheduler.unscheduledPods <- pod
- }
- },
- UpdateFunc: func(oldObj, newObj interface{}) {
- oldPod := oldObj.(*corev1.Pod)
- newPod := newObj.(*corev1.Pod)
- if oldPod.Spec.NodeName == "" && newPod.Spec.NodeName == "" && newPod.Status.Phase == corev1.PodPending {
- scheduler.unscheduledPods <- newPod
- }
- },
- })
-
- return scheduler
- }
- func (s *CustomScheduler) Run(stopCh <-chan struct{}) {
- // 启动informer
- go s.podInformer.Run(stopCh)
- go s.nodeInformer.Run(stopCh)
-
- // 等待缓存同步
- if !cache.WaitForCacheSync(stopCh, s.podInformer.HasSynced, s.nodeInformer.HasSynced) {
- klog.Error("Timed out waiting for caches to sync")
- return
- }
-
- // 开始调度循环
- for {
- select {
- case pod := <-s.unscheduledPods:
- s.Schedule(pod)
- case <-stopCh:
- return
- }
- }
- }
- func (s *CustomScheduler) Schedule(pod *corev1.Pod) {
- // 获取所有节点
- nodes, err := s.nodeInformer.GetIndexer().ByIndex(cache.NamespaceIndex, "")
- if err != nil {
- klog.Errorf("Failed to get nodes: %v", err)
- return
- }
-
- // 自定义调度逻辑
- selectedNode := s.selectNode(pod, nodes)
- if selectedNode == nil {
- klog.Infof("No available node for pod %s/%s", pod.Namespace, pod.Name)
- return
- }
-
- // 绑定Pod到节点
- binding := &corev1.Binding{
- ObjectMeta: metav1.ObjectMeta{Name: pod.Name, UID: pod.UID},
- Target: corev1.ObjectReference{Kind: "Node", Name: selectedNode.Name},
- }
-
- err = s.clientset.CoreV1().Pods(pod.Namespace).Bind(binding)
- if err != nil {
- klog.Errorf("Failed to bind pod %s/%s to node %s: %v", pod.Namespace, pod.Name, selectedNode.Name, err)
- return
- }
-
- klog.Infof("Successfully bound pod %s/%s to node %s", pod.Namespace, pod.Name, selectedNode.Name)
- }
- func (s *CustomScheduler) selectNode(pod *corev1.Pod, nodes []interface{}) *corev1.Node {
- // 这里实现自定义的节点选择逻辑
- // 可以基于资源、标签、亲和性等因素进行选择
-
- for _, nodeObj := range nodes {
- node := nodeObj.(*corev1.Node)
-
- // 检查节点是否可调度
- if !isNodeSchedulable(node) {
- continue
- }
-
- // 检查节点是否有足够资源
- if !nodeHasEnoughResources(node, pod) {
- continue
- }
-
- // 检查节点是否匹配Pod的节点选择器
- if !nodeMatchesSelector(node, pod) {
- continue
- }
-
- // 如果所有检查都通过,选择该节点
- return node
- }
-
- return nil
- }
- func isNodeSchedulable(node *corev1.Node) bool {
- return !node.Spec.Unschedulable
- }
- func nodeHasEnoughResources(node *corev1.Node, pod *corev1.Pod) bool {
- // 实现资源检查逻辑
- return true
- }
- func nodeMatchesSelector(node *corev1.Node, pod *corev1.Pod) bool {
- // 实现节点选择器匹配逻辑
- return true
- }
- func main() {
- var kubeconfig string
- var masterURL string
-
- flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
- flag.StringVar(&masterURL, "master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.")
- flag.Parse()
-
- // 创建Kubernetes客户端
- config, err := clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)
- if err != nil {
- klog.Fatalf("Error building kubeconfig: %v", err)
- }
-
- clientset, err := kubernetes.NewForConfig(config)
- if err != nil {
- klog.Fatalf("Error building kubernetes clientset: %v", err)
- }
-
- // 创建并运行自定义调度器
- scheduler := NewCustomScheduler(clientset)
-
- stopCh := make(chan struct{})
- defer close(stopCh)
-
- go scheduler.Run(stopCh)
-
- // 等待终止信号
- select {}
- }
复制代码
要在Pod中使用自定义调度器,需要在Pod规范中指定调度器名称:
- apiVersion: v1
- kind: Pod
- metadata:
- name: custom-scheduler-pod
- spec:
- schedulerName: my-custom-scheduler
- containers:
- - name: nginx
- image: nginx
复制代码
4.5 多调度器协同工作
Kubernetes支持多个调度器同时工作,每个调度器可以负责调度特定的Pod。这种模式允许集群管理员根据不同的业务需求使用不同的调度策略。
要配置多调度器,需要:
1. 部署自定义调度器。
2. 在Pod中指定使用哪个调度器。
以下是一个部署自定义调度器的示例:
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: custom-scheduler
- spec:
- replicas: 1
- selector:
- matchLabels:
- app: custom-scheduler
- template:
- metadata:
- labels:
- app: custom-scheduler
- spec:
- serviceAccountName: custom-scheduler
- containers:
- - name: custom-scheduler
- image: my-custom-scheduler:1.0
- imagePullPolicy: Always
- command:
- - /bin/custom-scheduler
- - --kubeconfig=/etc/kubernetes/scheduler.conf
- - --v=3
- volumeMounts:
- - name: kubeconfig
- mountPath: /etc/kubernetes
- readOnly: true
- volumes:
- - name: kubeconfig
- secret:
- secretName: custom-scheduler-kubeconfig
复制代码
为Pod指定调度器只需在Pod规范中设置schedulerName字段:
- apiVersion: v1
- kind: Pod
- metadata:
- name: custom-scheduled-pod
- spec:
- schedulerName: custom-scheduler
- containers:
- - name: nginx
- image: nginx
复制代码
5. 调度器优化技巧
优化Kubernetes调度器可以显著提高集群资源利用率、应用性能和系统稳定性。本节将介绍一系列实用的调度器优化技巧。
5.1 资源分配优化
合理的资源分配是调度优化的基础。通过优化资源请求和限制,可以提高集群资源利用率,同时确保应用性能。
准确设置Pod的资源请求和限制是优化调度的第一步。资源请求过低可能导致节点资源过载,影响应用性能;资源请求过高则会导致资源浪费。
以下是一些设置资源请求和限制的最佳实践:
1. 基于监控数据设置资源:使用应用的历史监控数据确定合理的资源请求和限制。
2. 使用垂直Pod自动伸缩(VPA):VPA可以根据实际资源使用情况自动调整Pod的资源请求和限制。
3. 为不同环境设置不同资源:开发、测试和生产环境可以使用不同的资源配置。
以下是一个基于VPA的资源配置示例:
- apiVersion: autoscaling.k8s.io/v1
- kind: VerticalPodAutoscaler
- metadata:
- name: my-app-vpa
- spec:
- targetRef:
- apiVersion: "apps/v1"
- kind: Deployment
- name: my-app
- updatePolicy:
- updateMode: "Auto"
复制代码
资源配额和限制范围可以帮助控制命名空间的资源使用,防止单个应用或团队占用过多资源。
以下是一个资源配额和限制范围的组合示例:
- # 资源配额
- apiVersion: v1
- kind: ResourceQuota
- metadata:
- name: compute-resources
- namespace: dev-namespace
- spec:
- hard:
- requests.cpu: "10"
- requests.memory: 20Gi
- limits.cpu: "20"
- limits.memory: 40Gi
- pods: "50"
- persistentvolumeclaims: "10"
- # 限制范围
- apiVersion: v1
- kind: LimitRange
- metadata:
- name: resource-limits
- namespace: dev-namespace
- spec:
- limits:
- - default:
- cpu: "500m"
- memory: "512Mi"
- defaultRequest:
- cpu: "250m"
- memory: "256Mi"
- max:
- cpu: "2000m"
- memory: "2Gi"
- min:
- cpu: "100m"
- memory: "128Mi"
- type: Container
复制代码
资源过量使用可以提高资源利用率,但也增加了资源耗尽的风险。以下是一些优化资源过量使用的技巧:
1. 合理设置kubelet预留资源:为Kubernetes系统和系统进程预留足够资源。
2. 使用Pod优先级和抢占:确保关键Pod在资源不足时能够获得资源。
3. 监控资源使用情况:密切监控节点资源使用情况,避免资源耗尽。
以下是一个kubelet资源配置示例:
- kubelet --kube-reserved=cpu=500m,memory=1Gi,ephemeral-storage=1Gi \
- --system-reserved=cpu=200m,memory=500Mi,ephemeral-storage=1Gi \
- --eviction-hard=memory.available<500Mi,nodefs.available<10%
复制代码
5.2 调度性能调优
调度性能直接影响Pod的启动速度和集群的响应能力。通过优化调度器性能,可以提高集群的整体效率。
Kubernetes调度器支持并发调度多个Pod,通过调整并发度可以提高调度性能。
以下是一个调度器配置文件示例,展示了如何调整并发度:
- apiVersion: kubescheduler.config.k8s.io/v1beta2
- kind: KubeSchedulerConfiguration
- parallelism: 16 # 默认为16,可根据集群规模调整
复制代码
过多的谓词和优先级规则会增加调度决策的时间。以下是一些优化技巧:
1. 禁用不必要的规则:根据集群需求禁用不必要的谓词和优先级规则。
2. 自定义规则顺序:将计算成本高但过滤效果好的规则放在前面。
3. 使用自定义调度器:对于特定场景,开发专用的高效调度器。
以下是一个自定义调度器配置示例,展示了如何选择特定的谓词和优先级规则:
- apiVersion: kubescheduler.config.k8s.io/v1beta2
- kind: KubeSchedulerConfiguration
- profiles:
- - schedulerName: default-scheduler
- plugins:
- filter:
- enabled:
- - name: NodeUnschedulable
- - name: NodeResourcesFit
- - name: NodePorts
- - name: VolumeBinding
- score:
- enabled:
- - name: NodeResourcesBalancedAllocation
- - name: NodeResourcesLeastAllocated
- - name: ImageLocality
复制代码
Kubernetes调度器使用缓存来存储节点和Pod信息,减少API Server的访问次数。优化缓存配置可以提高调度性能。
以下是一些缓存优化技巧:
1. 增加缓存大小:对于大型集群,增加缓存大小可以提高性能。
2. 调整缓存过期时间:根据集群变化频率调整缓存过期时间。
3. 使用分布式缓存:对于多调度器部署,考虑使用分布式缓存。
以下是一个调度器缓存配置示例:
- apiVersion: kubescheduler.config.k8s.io/v1beta2
- kind: KubeSchedulerConfiguration
- percentageOfNodesToScore: 50 # 默认为50,表示只对50%的节点进行打分
复制代码
5.3 避免调度热点
调度热点是指某些节点被过度分配资源,而其他节点资源利用率较低的情况。避免调度热点可以提高集群资源利用率和应用性能。
Pod反亲和性可以确保应用实例分布在不同的节点上,避免单点过载。
以下是一个使用Pod反亲和性避免热点的示例:
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: web-frontend
- spec:
- replicas: 5
- selector:
- matchLabels:
- app: web-frontend
- template:
- metadata:
- labels:
- app: web-frontend
- spec:
- affinity:
- podAntiAffinity:
- requiredDuringSchedulingIgnoredDuringExecution:
- - labelSelector:
- matchExpressions:
- - key: app
- operator: In
- values:
- - web-frontend
- topologyKey: kubernetes.io/hostname
- containers:
- - name: web
- image: nginx
- resources:
- requests:
- cpu: "500m"
- memory: "512Mi"
- limits:
- cpu: "1000m"
- memory: "1Gi"
复制代码
在多可用区部署中,使用区域感知调度可以确保应用实例均匀分布在各个可用区,提高可用性。
以下是一个使用节点亲和性实现区域感知调度的示例:
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: zone-aware-app
- spec:
- replicas: 6
- selector:
- matchLabels:
- app: zone-aware
- template:
- metadata:
- labels:
- app: zone-aware
- spec:
- affinity:
- nodeAffinity:
- requiredDuringSchedulingIgnoredDuringExecution:
- nodeSelectorTerms:
- - matchExpressions:
- - key: topology.kubernetes.io/zone
- operator: In
- values:
- - us-east-1a
- - us-east-1b
- - us-east-1c
- containers:
- - name: app
- image: myapp:1.0
- resources:
- requests:
- cpu: "500m"
- memory: "512Mi"
- limits:
- cpu: "1000m"
- memory: "1Gi"
复制代码
Kubernetes Descheduler是一个用于重新平衡集群资源的工具,它可以识别并重新调度那些可能导致不均衡的Pod。
以下是一个Descheduler配置示例:
- apiVersion: policy/v1
- kind: PodDisruptionBudget
- metadata:
- name: descheduler-pdb
- spec:
- selector:
- matchLabels:
- app: descheduler
- maxUnavailable: 1
- ---
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: descheduler
- namespace: kube-system
- spec:
- replicas: 1
- selector:
- matchLabels:
- app: descheduler
- template:
- metadata:
- labels:
- app: descheduler
- spec:
- serviceAccountName: descheduler-sa
- containers:
- - name: descheduler
- image: k8s.gcr.io/descheduler/descheduler:v0.21.0
- imagePullPolicy: Always
- command:
- - "/bin/descheduler"
- args:
- - "--policy-config-file=/policy-dir/policy.yaml"
- - "--v=3"
- volumeMounts:
- - mountPath: /policy-dir
- name: policy-volume
- volumes:
- - name: policy-volume
- configMap:
- name: descheduler-policy
- ---
- apiVersion: v1
- kind: ConfigMap
- metadata:
- name: descheduler-policy
- namespace: kube-system
- data:
- policy.yaml: |
- apiVersion: "descheduler/v1alpha1"
- kind: "DeschedulerPolicy"
- strategies:
- "RemoveDuplicates":
- enabled: true
- "RemovePodsViolatingInterPodAntiAffinity":
- enabled: true
- "LowNodeUtilization":
- enabled: true
- params:
- nodeResourceUtilizationThresholds:
- thresholds:
- "cpu" : 20
- "memory": 20
- "pods": 20
- targetThresholds:
- "cpu" : 50
- "memory": 50
- "pods": 50
复制代码
5.4 高级调度优化
除了基本的调度优化技巧,还有一些高级优化策略可以进一步提高调度效率。
Kubernetes调度器支持通过Webhook扩展调度功能,允许在调度决策过程中调用外部服务。
以下是一个调度器扩展的示例:
- apiVersion: kubescheduler.config.k8s.io/v1beta2
- kind: KubeSchedulerConfiguration
- profiles:
- - schedulerName: default-scheduler
- plugins:
- filter:
- enabled:
- - name: NodeResourcesFit
- - name: MyCustomFilter
- score:
- enabled:
- - name: NodeResourcesBalancedAllocation
- - name: MyCustomScore
- pluginConfig:
- - name: MyCustomFilter
- args:
- favoriteColor: blue
- favoriteNumber: 7
- - name: MyCustomScore
- args:
- favoriteAnimal: dog
- favoriteFood: pizza
复制代码
Kubernetes调度框架(Scheduling Framework)是一组插件API,允许开发自定义调度插件,扩展调度器功能。
以下是一个自定义调度插件的示例:
- package main
- import (
- "context"
- "fmt"
- "time"
- "k8s.io/api/core/v1"
- "k8s.io/apimachinery/pkg/runtime"
- "k8s.io/klog/v2"
- "k8s.io/kubernetes/pkg/scheduler/framework"
- "k8s.io/kubernetes/pkg/scheduler/framework/plugins/defaultbinder"
- "k8s.io/kubernetes/pkg/scheduler/framework/plugins/queuesort"
- )
- const (
- Name = "custom-plugin"
- )
- type CustomPlugin struct {
- handle framework.Handle
- }
- var _ framework.FilterPlugin = &CustomPlugin{}
- var _ framework.ScorePlugin = &CustomPlugin{}
- func (cp *CustomPlugin) Name() string {
- return Name
- }
- func (cp *CustomPlugin) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
- // 实现过滤逻辑
- node := nodeInfo.Node()
- if node.Labels["custom-label"] != "enabled" {
- return framework.NewStatus(framework.Unschedulable, "Node does not have custom-label=enabled")
- }
- return nil
- }
- func (cp *CustomPlugin) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {
- // 实现打分逻辑
- nodeInfo, err := cp.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
- if err != nil {
- return 0, framework.AsStatus(err)
- }
-
- node := nodeInfo.Node()
- score := int64(0)
-
- // 根据节点标签计算得分
- if node.Labels["priority"] == "high" {
- score = 100
- } else if node.Labels["priority"] == "medium" {
- score = 50
- } else {
- score = 10
- }
-
- return score, nil
- }
- func (cp *CustomPlugin) ScoreExtensions() framework.ScoreExtensions {
- return cp
- }
- func (cp *CustomPlugin) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, scores framework.NodeScoreList) *framework.Status {
- // 实现得分归一化逻辑
- var highest int64 = 0
- for _, nodeScore := range scores {
- if nodeScore.Score > highest {
- highest = nodeScore.Score
- }
- }
-
- if highest == 0 {
- return nil
- }
-
- for i, nodeScore := range scores {
- scores[i].Score = nodeScore.Score * framework.MaxNodeScore / highest
- }
-
- return nil
- }
- type CustomArgs struct {
- FavoriteColor string `json:"favoriteColor,omitempty"`
- FavoriteNumber int `json:"favoriteNumber,omitempty"`
- }
- func New(_ runtime.Object, handle framework.Handle) (framework.Plugin, error) {
- return &CustomPlugin{handle: handle}, nil
- }
- func main() {
- // 这里是插件注册逻辑,通常在scheduler初始化时调用
- // scheme := runtime.NewScheme()
- // utilruntime.Must(scheme.AddUnversionedTypes(corev1.SchemeGroupVersion, &CustomArgs{}))
- // framework.Register(Name, func(_ runtime.Object, handle framework.Handle) (framework.Plugin, error) {
- // return New(nil, handle)
- // })
-
- // 在实际使用中,插件会被编译成独立的二进制文件,并通过--plugins参数加载
- fmt.Println("Custom plugin compiled successfully")
- }
复制代码
对于批量任务,使用批处理调度器如Volcano可以提高资源利用率和任务完成速度。
以下是一个Volcano作业的示例:
- apiVersion: batch.volcano.sh/v1alpha1
- kind: Job
- metadata:
- name: test-job
- spec:
- minAvailable: 3
- schedulerName: volcano
- priorityClassName: high-priority
- policies:
- - event: PodEvicted
- action: RestartJob
- plugins:
- ssh: []
- env: []
- svc: []
- maxRetry: 5
- queue: default
- tasks:
- - replicas: 3
- name: "default-nginx"
- template:
- metadata:
- name: web
- spec:
- containers:
- - image: nginx
- imagePullPolicy: IfNotPresent
- name: nginx
- resources:
- requests:
- cpu: "1"
- memory: "1Gi"
- restartPolicy: OnFailure
复制代码
6. 实际案例分析
通过实际案例,我们可以更好地理解Kubernetes调度器的工作原理和优化技巧的应用。本节将介绍几个典型的调度优化案例。
6.1 大规模微服务集群调度优化
某互联网公司拥有一个包含数千个微服务的Kubernetes集群,面临资源利用率低、Pod启动慢、节点负载不均等问题。通过一系列调度优化,他们显著提高了集群效率和稳定性。
通过监控数据分析,团队发现以下问题:
1. 资源请求设置不合理:许多Pod的资源请求远高于实际使用量,导致节点资源利用率低。
2. 调度热点:某些节点负载过高,而其他节点资源闲置。
3. Pod启动慢:由于调度决策复杂,Pod启动时间过长。
4. 关键应用受干扰:关键应用与批处理任务混部,导致性能不稳定。
针对上述问题,团队实施了以下优化措施:
1. 资源请求优化:使用VPA自动调整Pod资源请求和限制。为不同类型的应用设置不同的资源请求策略。
2. 使用VPA自动调整Pod资源请求和限制。
3. 为不同类型的应用设置不同的资源请求策略。
• 使用VPA自动调整Pod资源请求和限制。
• 为不同类型的应用设置不同的资源请求策略。
- apiVersion: autoscaling.k8s.io/v1
- kind: VerticalPodAutoscaler
- metadata:
- name: web-app-vpa
- spec:
- targetRef:
- apiVersion: "apps/v1"
- kind: Deployment
- name: web-app
- updatePolicy:
- updateMode: "Auto"
- resourcePolicy:
- containerPolicies:
- - containerName: "*"
- minAllowed:
- cpu: "100m"
- memory: "128Mi"
- maxAllowed:
- cpu: "2000m"
- memory: "2Gi"
复制代码
1. 避免调度热点:使用Pod反亲和性确保应用实例分布在不同节点。使用Descheduler定期重新平衡集群资源。
2. 使用Pod反亲和性确保应用实例分布在不同节点。
3. 使用Descheduler定期重新平衡集群资源。
• 使用Pod反亲和性确保应用实例分布在不同节点。
• 使用Descheduler定期重新平衡集群资源。
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: web-app
- spec:
- replicas: 10
- template:
- spec:
- affinity:
- podAntiAffinity:
- requiredDuringSchedulingIgnoredDuringExecution:
- - labelSelector:
- matchExpressions:
- - key: app
- operator: In
- values:
- - web-app
- topologyKey: kubernetes.io/hostname
复制代码
1. 调度性能优化:增加调度器并发度。优化谓词和优先级规则。
2. 增加调度器并发度。
3. 优化谓词和优先级规则。
• 增加调度器并发度。
• 优化谓词和优先级规则。
- apiVersion: kubescheduler.config.k8s.io/v1beta2
- kind: KubeSchedulerConfiguration
- parallelism: 32
- percentageOfNodesToScore: 30
- profiles:
- - schedulerName: default-scheduler
- plugins:
- filter:
- enabled:
- - name: NodeUnschedulable
- - name: NodeResourcesFit
- - name: NodePorts
- score:
- enabled:
- - name: NodeResourcesBalancedAllocation
- - name: NodeResourcesLeastAllocated
复制代码
1. 应用隔离:使用命名空间和资源配额隔离不同类型的应用。使用污点和容忍分离关键应用和批处理任务。
2. 使用命名空间和资源配额隔离不同类型的应用。
3. 使用污点和容忍分离关键应用和批处理任务。
• 使用命名空间和资源配额隔离不同类型的应用。
• 使用污点和容忍分离关键应用和批处理任务。
- # 为批处理节点添加污点
- apiVersion: v1
- kind: Node
- metadata:
- name: batch-node-1
- labels:
- role: batch
- spec:
- taints:
- - key: batch
- value: "true"
- effect: NoSchedule
-
- # 批处理任务容忍污点
- apiVersion: batch/v1
- kind: Job
- metadata:
- name: batch-job
- spec:
- template:
- spec:
- tolerations:
- - key: batch
- operator: Equal
- value: "true"
- effect: NoSchedule
复制代码
通过上述优化,团队取得了显著效果:
1. 资源利用率提高:CPU利用率从30%提升到65%,内存利用率从40%提升到70%。
2. Pod启动时间缩短:平均Pod启动时间从45秒减少到15秒。
3. 节点负载均衡:节点间CPU和内存使用差异从±40%降低到±15%。
4. 应用性能稳定:关键应用响应时间波动从±200ms降低到±50ms。
6.2 多可用区高可用调度优化
某金融企业需要在多个可用区部署关键应用,确保高可用性和灾难恢复能力。他们通过优化调度策略,实现了应用在多可用区间的智能分布和故障自动转移。
企业的关键需求包括:
1. 应用实例必须分布在至少3个可用区。
2. 每个可用区至少运行一个应用实例。
3. 某个可用区故障时,应用能自动在其他可用区恢复。
4. 跨可用区网络延迟最小化。
针对上述需求,团队实施了以下解决方案:
1. 区域感知调度:使用节点亲和性确保Pod分布在多个可用区。使用Pod反亲和性避免单点故障。
2. 使用节点亲和性确保Pod分布在多个可用区。
3. 使用Pod反亲和性避免单点故障。
• 使用节点亲和性确保Pod分布在多个可用区。
• 使用Pod反亲和性避免单点故障。
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: critical-app
- spec:
- replicas: 6
- template:
- spec:
- affinity:
- nodeAffinity:
- requiredDuringSchedulingIgnoredDuringExecution:
- nodeSelectorTerms:
- - matchExpressions:
- - key: topology.kubernetes.io/zone
- operator: In
- values:
- - us-east-1a
- - us-east-1b
- - us-east-1c
- podAntiAffinity:
- requiredDuringSchedulingIgnoredDuringExecution:
- - labelSelector:
- matchExpressions:
- - key: app
- operator: In
- values:
- - critical-app
- topologyKey: topology.kubernetes.io/zone
- preferredDuringSchedulingIgnoredDuringExecution:
- - weight: 100
- podAffinityTerm:
- labelSelector:
- matchExpressions:
- - key: app
- operator: In
- values:
- - critical-app
- topologyKey: kubernetes.io/hostname
复制代码
1. 区域故障恢复:使用Pod Disruption Budget确保最小可用实例数。使用自定义控制器监控可用区状态,自动重新调度Pod。
2. 使用Pod Disruption Budget确保最小可用实例数。
3. 使用自定义控制器监控可用区状态,自动重新调度Pod。
• 使用Pod Disruption Budget确保最小可用实例数。
• 使用自定义控制器监控可用区状态,自动重新调度Pod。
- apiVersion: policy/v1
- kind: PodDisruptionBudget
- metadata:
- name: critical-app-pdb
- spec:
- minAvailable: 3
- selector:
- matchLabels:
- app: critical-app
复制代码
1. 网络优化:使用拓扑感知路由优化跨可用区网络流量。使用本地存储减少跨可用区数据访问。
2. 使用拓扑感知路由优化跨可用区网络流量。
3. 使用本地存储减少跨可用区数据访问。
• 使用拓扑感知路由优化跨可用区网络流量。
• 使用本地存储减少跨可用区数据访问。
- apiVersion: v1
- kind: Service
- metadata:
- name: critical-app-service
- annotations:
- service.kubernetes.io/topology-aware-hints: "auto"
- spec:
- selector:
- app: critical-app
- ports:
- - protocol: TCP
- port: 80
- targetPort: 8080
复制代码
通过上述优化,企业实现了以下目标:
1. 高可用性:应用实例均匀分布在3个可用区,每个可用区运行2个实例。
2. 故障恢复:模拟可用区故障时,应用在2分钟内自动恢复到其他可用区。
3. 网络性能:跨可用区网络延迟降低30%,应用响应时间提升25%。
4. 运营效率:自动化故障恢复减少了70%的人工干预。
6.3 GPU资源调度优化
某AI公司需要高效调度GPU资源,支持多种深度学习训练和推理任务。他们通过自定义调度策略,实现了GPU资源的高效利用和任务优先级管理。
公司面临的主要挑战包括:
1. GPU资源昂贵且有限,需要最大化利用率。
2. 不同任务对GPU资源的需求差异大。
3. 训练任务通常需要长时间独占GPU,而推理任务可以共享GPU。
4. 关键任务需要优先获得GPU资源。
针对上述挑战,团队实施了以下优化方案:
1. GPU资源管理:使用设备插件管理GPU资源。自定义GPU资源分配策略。
2. 使用设备插件管理GPU资源。
3. 自定义GPU资源分配策略。
• 使用设备插件管理GPU资源。
• 自定义GPU资源分配策略。
- # GPU设备插件配置
- apiVersion: v1
- kind: DaemonSet
- metadata:
- name: nvidia-device-plugin-daemonset
- namespace: kube-system
- spec:
- template:
- spec:
- containers:
- - image: nvcr.io/nvidia/k8s-device-plugin:v0.8.0
- name: nvidia-device-plugin-ctr
- env:
- - name: FAIL_ON_INIT_ERROR
- value: "false"
- volumeMounts:
- - name: device-plugin
- mountPath: /var/lib/kubelet/device-plugins
- volumes:
- - name: device-plugin
- hostPath:
- path: /var/lib/kubelet/device-plugins
复制代码
1. 自定义调度器:开发专门的GPU调度器,支持GPU共享和抢占。实现任务优先级和资源预留机制。
2. 开发专门的GPU调度器,支持GPU共享和抢占。
3. 实现任务优先级和资源预留机制。
• 开发专门的GPU调度器,支持GPU共享和抢占。
• 实现任务优先级和资源预留机制。
- // 自定义GPU调度器伪代码
- type GPUScheduler struct {
- // 调度器状态
- gpuNodes []*GPUNode
- pendingQueue []*Task
- runningTasks map[string]*Task
- priorityClasses map[string]int
- }
-
- func (s *GPUScheduler) Schedule() {
- // 按优先级排序待调度任务
- sort.Slice(s.pendingQueue, func(i, j int) bool {
- return s.priorityClasses[s.pendingQueue[i].PriorityClass] >
- s.priorityClasses[s.pendingQueue[j].PriorityClass]
- })
-
- // 尝试调度每个任务
- for _, task := range s.pendingQueue {
- for _, node := range s.gpuNodes {
- if s.canSchedule(task, node) {
- s.assignTask(task, node)
- break
- }
- }
- }
- }
-
- func (s *GPUScheduler) canSchedule(task *Task, node *GPUNode) bool {
- // 检查节点是否有足够的GPU资源
- if task.RequiresExclusiveGPU && node.AvailableExclusiveGPUs == 0 {
- return false
- }
-
- if !task.RequiresExclusiveGPU && node.AvailableSharedGPUMemory < task.RequiredGPUMemory {
- return false
- }
-
- // 检查节点是否满足其他约束
- if !node.Labels.Match(task.NodeSelector) {
- return false
- }
-
- return true
- }
-
- func (s *GPUScheduler) assignTask(task *Task, node *GPUNode) {
- // 分配GPU资源
- if task.RequiresExclusiveGPU {
- node.AvailableExclusiveGPUs--
- task.AssignedGPUs = []string{node.AllocateExclusiveGPU()}
- } else {
- allocatedMemory := task.RequiredGPUMemory
- task.AssignedGPUs = node.AllocateSharedGPU(allocatedMemory)
- node.AvailableSharedGPUMemory -= allocatedMemory
- }
-
- // 更新任务状态
- task.Status = "Running"
- task.Node = node.Name
- s.runningTasks[task.ID] = task
-
- // 从待调度队列中移除
- for i, t := range s.pendingQueue {
- if t.ID == task.ID {
- s.pendingQueue = append(s.pendingQueue[:i], s.pendingQueue[i+1:]...)
- break
- }
- }
-
- // 创建Pod
- s.createPod(task)
- }
复制代码
1. 任务优先级管理:使用Pod优先级和抢占机制。实现任务排队和超时处理。
2. 使用Pod优先级和抢占机制。
3. 实现任务排队和超时处理。
• 使用Pod优先级和抢占机制。
• 实现任务排队和超时处理。
- # 优先级类定义
- apiVersion: scheduling.k8s.io/v1
- kind: PriorityClass
- metadata:
- name: high-priority
- value: 1000000
- globalDefault: false
- description: "This priority class should be used for critical GPU training jobs only."
- ---
- apiVersion: scheduling.k8s.io/v1
- kind: PriorityClass
- metadata:
- name: medium-priority
- value: 500000
- globalDefault: false
- description: "This priority class should be used for regular GPU training jobs."
- ---
- apiVersion: scheduling.k8s.io/v1
- kind: PriorityClass
- metadata:
- name: low-priority
- value: 100000
- globalDefault: true
- description: "This priority class should be used for GPU inference jobs."
-
- # 使用优先级类的Pod
- apiVersion: v1
- kind: Pod
- metadata:
- name: high-priority-gpu-job
- spec:
- priorityClassName: high-priority
- containers:
- - name: gpu-container
- image: tensorflow/tensorflow:latest-gpu
- resources:
- limits:
- nvidia.com/gpu: 2
复制代码
通过上述优化,公司取得了显著效果:
1. GPU利用率提升:从60%提升到90%,支持更多并发任务。
2. 任务完成时间缩短:高优先级任务平均等待时间从30分钟减少到5分钟。
3. 资源成本降低:通过GPU共享和智能调度,节省了30%的GPU资源成本。
4. 运营效率提升:自动化调度减少了80%的人工干预。
7. 构建稳定云原生环境的最佳实践
通过优化Kubernetes调度器,我们可以构建更加稳定、高效的云原生环境。本节将总结一些构建稳定云原生环境的最佳实践。
7.1 资源管理最佳实践
有效的资源管理是构建稳定云原生环境的基础。以下是一些资源管理的最佳实践:
为所有容器设置资源请求和限制,确保调度器能够做出合理的决策,防止资源耗尽。
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: resource-managed-app
- spec:
- template:
- spec:
- containers:
- - name: app
- image: myapp:1.0
- resources:
- requests:
- cpu: "500m"
- memory: "512Mi"
- limits:
- cpu: "1000m"
- memory: "1Gi"
复制代码
在命名空间级别使用资源配额和限制范围,防止单个应用或团队占用过多资源。
- # 资源配额
- apiVersion: v1
- kind: ResourceQuota
- metadata:
- name: namespace-quota
- spec:
- hard:
- requests.cpu: "10"
- requests.memory: "20Gi"
- limits.cpu: "20"
- limits.memory: "40Gi"
- pods: "50"
- # 限制范围
- apiVersion: v1
- kind: LimitRange
- metadata:
- name: container-limits
- spec:
- limits:
- - default:
- cpu: "500m"
- memory: "512Mi"
- defaultRequest:
- cpu: "250m"
- memory: "256Mi"
- type: Container
复制代码
使用资源监控和自动伸缩机制,确保应用能够根据负载动态调整资源使用。
- # 水平Pod自动伸缩(HPA)
- apiVersion: autoscaling/v2
- kind: HorizontalPodAutoscaler
- metadata:
- name: app-hpa
- spec:
- scaleTargetRef:
- apiVersion: apps/v1
- kind: Deployment
- name: app
- minReplicas: 2
- maxReplicas: 10
- metrics:
- - type: Resource
- resource:
- name: cpu
- target:
- type: Utilization
- averageUtilization: 50
- - type: Resource
- resource:
- name: memory
- target:
- type: Utilization
- averageUtilization: 70
- # 垂直Pod自动伸缩(VPA)
- apiVersion: autoscaling.k8s.io/v1
- kind: VerticalPodAutoscaler
- metadata:
- name: app-vpa
- spec:
- targetRef:
- apiVersion: "apps/v1"
- kind: Deployment
- name: app
- updatePolicy:
- updateMode: "Auto"
复制代码
7.2 调度策略最佳实践
合理的调度策略可以提高资源利用率,确保应用性能和稳定性。以下是一些调度策略的最佳实践:
使用节点和Pod的亲和性、反亲和性规则,控制Pod的分布,提高应用可用性和性能。
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: distributed-app
- spec:
- template:
- spec:
- affinity:
- # 节点亲和性:选择特定类型的节点
- nodeAffinity:
- requiredDuringSchedulingIgnoredDuringExecution:
- nodeSelectorTerms:
- - matchExpressions:
- - key: node-role.kubernetes.io/worker
- operator: In
- values:
- - "true"
-
- # Pod反亲和性:避免Pod集中在同一节点
- podAntiAffinity:
- requiredDuringSchedulingIgnoredDuringExecution:
- - labelSelector:
- matchExpressions:
- - key: app
- operator: In
- values:
- - distributed-app
- topologyKey: kubernetes.io/hostname
-
- # Pod亲和性:将相关应用部署在一起
- podAffinity:
- preferredDuringSchedulingIgnoredDuringExecution:
- - weight: 100
- podAffinityTerm:
- labelSelector:
- matchExpressions:
- - key: app
- operator: In
- values:
- - cache-service
- topologyKey: kubernetes.io/hostname
复制代码
使用污点和容忍机制隔离不同类型的工作负载,确保关键应用不受干扰。
- # 为关键节点添加污点
- apiVersion: v1
- kind: Node
- metadata:
- name: critical-node-1
- spec:
- taints:
- - key: workload-type
- value: critical
- effect: NoSchedule
- # 关键应用容忍污点
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: critical-app
- spec:
- template:
- spec:
- tolerations:
- - key: workload-type
- operator: Equal
- value: critical
- effect: NoSchedule
- containers:
- - name: app
- image: critical-app:1.0
复制代码
对于有特殊调度需求的应用,使用自定义调度器实现更精细的调度控制。
- apiVersion: v1
- kind: Pod
- metadata:
- name: custom-scheduled-pod
- spec:
- schedulerName: custom-scheduler
- containers:
- - name: app
- image: special-app:1.0
复制代码
7.3 高可用性和容灾最佳实践
构建高可用、容灾能力强的云原生环境是确保业务连续性的关键。以下是一些高可用性和容灾的最佳实践:
将应用部署在多个可用区,确保单个可用区故障时应用仍能正常运行。
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: multi-az-app
- spec:
- replicas: 6
- template:
- spec:
- affinity:
- nodeAffinity:
- requiredDuringSchedulingIgnoredDuringExecution:
- nodeSelectorTerms:
- - matchExpressions:
- - key: topology.kubernetes.io/zone
- operator: In
- values:
- - us-east-1a
- - us-east-1b
- - us-east-1c
- podAntiAffinity:
- requiredDuringSchedulingIgnoredDuringExecution:
- - labelSelector:
- matchExpressions:
- - key: app
- operator: In
- values:
- - multi-az-app
- topologyKey: topology.kubernetes.io/zone
复制代码
使用Pod中断预算(PDB)确保在维护或故障期间始终有足够的应用实例运行。
- apiVersion: policy/v1
- kind: PodDisruptionBudget
- metadata:
- name: app-pdb
- spec:
- minAvailable: 2
- selector:
- matchLabels:
- app: multi-az-app
复制代码
为应用配置适当的健康检查,确保不健康的容器能够自动重启或替换。
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: health-checked-app
- spec:
- template:
- spec:
- containers:
- - name: app
- image: health-checked-app:1.0
- livenessProbe:
- httpGet:
- path: /health
- port: 8080
- initialDelaySeconds: 30
- periodSeconds: 10
- timeoutSeconds: 5
- failureThreshold: 3
- readinessProbe:
- httpGet:
- path: /ready
- port: 8080
- initialDelaySeconds: 5
- periodSeconds: 5
- timeoutSeconds: 3
- failureThreshold: 1
复制代码
7.4 安全性最佳实践
确保云原生环境的安全性是保护应用和数据的关键。以下是一些安全性的最佳实践:
使用Pod安全策略(Pod Security Policy)限制Pod的安全配置,防止不安全的设置。
- apiVersion: policy/v1beta1
- kind: PodSecurityPolicy
- metadata:
- name: restricted-psp
- spec:
- privileged: false
- allowPrivilegeEscalation: false
- requiredDropCapabilities:
- - ALL
- volumes:
- - 'configMap'
- - 'emptyDir'
- - 'projected'
- - 'secret'
- - 'downwardAPI'
- - 'persistentVolumeClaim'
- runAsUser:
- rule: 'MustRunAsNonRoot'
- seLinux:
- rule: 'RunAsAny'
- fsGroup:
- rule: 'RunAsAny'
复制代码
使用网络策略(Network Policy)控制Pod之间的网络通信,防止未授权访问。
- apiVersion: networking.k8s.io/v1
- kind: NetworkPolicy
- metadata:
- name: app-network-policy
- spec:
- podSelector:
- matchLabels:
- app: secure-app
- policyTypes:
- - Ingress
- - Egress
- ingress:
- - from:
- - podSelector:
- matchLabels:
- app: frontend
- ports:
- - protocol: TCP
- port: 80
- egress:
- - to:
- - podSelector:
- matchLabels:
- app: database
- ports:
- - protocol: TCP
- port: 5432
复制代码
使用基于角色的访问控制(RBAC)限制用户和服务账户对Kubernetes资源的访问权限。
- # 角色
- apiVersion: rbac.authorization.k8s.io/v1
- kind: Role
- metadata:
- namespace: default
- name: app-operator
- rules:
- - apiGroups: [""]
- resources: ["pods", "services", "configmaps"]
- verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- - apiGroups: ["apps"]
- resources: ["deployments", "replicasets"]
- verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- # 角色绑定
- apiVersion: rbac.authorization.k8s.io/v1
- kind: RoleBinding
- metadata:
- name: app-operator-binding
- namespace: default
- subjects:
- - kind: ServiceAccount
- name: app-operator-sa
- namespace: default
- roleRef:
- kind: Role
- name: app-operator
- apiGroup: rbac.authorization.k8s.io
复制代码
7.5 监控和日志最佳实践
有效的监控和日志系统是确保云原生环境稳定运行的关键。以下是一些监控和日志的最佳实践:
使用Prometheus、Grafana等工具实施全面监控,覆盖基础设施、Kubernetes组件和应用性能。
- # Prometheus监控配置示例
- apiVersion: v1
- kind: ConfigMap
- metadata:
- name: prometheus-config
- data:
- prometheus.yml: |
- global:
- scrape_interval: 15s
- scrape_configs:
- - job_name: 'kubernetes-apiservers'
- kubernetes_sd_configs:
- - role: endpoints
- scheme: https
- tls_config:
- ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
- bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
- relabel_configs:
- - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
- action: keep
- regex: default;kubernetes;https
-
- - job_name: 'kubernetes-nodes'
- kubernetes_sd_configs:
- - role: node
- scheme: https
- tls_config:
- ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
- bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
- relabel_configs:
- - action: labelmap
- regex: __meta_kubernetes_node_label_(.+)
- - target_label: __address__
- replacement: kubernetes.default.svc:443
- - source_labels: [__meta_kubernetes_node_name]
- regex: (.+)
- target_label: __metrics_path__
- replacement: /api/v1/nodes/${1}/proxy/metrics
复制代码
使用Elasticsearch、Fluentd、Kibana(EFK)或Loki等工具实施集中化日志管理,便于故障排查和分析。
- # Fluentd日志收集配置示例
- apiVersion: v1
- kind: ConfigMap
- metadata:
- name: fluentd-config
- data:
- fluent.conf: |
- <source>
- @type tail
- path /var/log/containers/*app*.log
- pos_file /var/log/fluentd-containers.log.pos
- tag kubernetes.*
- format json
- time_format %Y-%m-%dT%H:%M:%S.%NZ
- </source>
-
- <match kubernetes.**>
- @type elasticsearch
- host elasticsearch-logging
- port 9200
- index_name fluentd
- type_name _doc
- </match>
复制代码
基于监控指标设置合理的告警规则,确保在问题发生时能够及时通知运维人员。
- # Prometheus告警规则示例
- apiVersion: monitoring.coreos.com/v1
- kind: PrometheusRule
- metadata:
- name: app-alert-rules
- spec:
- groups:
- - name: app.rules
- rules:
- - alert: HighPodMemoryUsage
- expr: sum(container_memory_usage_bytes{container!="POD", pod=~"app-.*"}) by (pod) / sum(container_spec_memory_limit_bytes{container!="POD", pod=~"app-.*"}) by (pod) > 0.9
- for: 5m
- labels:
- severity: warning
- annotations:
- summary: "High memory usage of pod {{ $labels.pod }}"
- description: "Pod {{ $labels.pod }} memory usage is above 90% for 5 minutes."
-
- - alert: PodCrashLooping
- expr: rate(kube_pod_container_status_restarts_total{pod=~"app-.*"}[15m]) * 60 * 15 > 0
- for: 5m
- labels:
- severity: critical
- annotations:
- summary: "Pod {{ $labels.pod }} is crash looping."
- description: "Pod {{ $labels.pod }} ({{ $labels.container }}) is restarting {{ $value }} times per 15 minutes."
复制代码
结论
Kubernetes调度器作为容器编排系统的核心组件,其工作原理和优化技巧对于构建高效、稳定的云原生环境至关重要。通过深入理解调度器的工作流程、资源感知机制和智能调度策略,我们可以更好地优化集群资源利用率,提高应用性能和系统稳定性。
本文从调度器的基本工作原理出发,详细介绍了过滤、打分和绑定等关键步骤,探讨了资源感知机制和智能调度策略,并提供了一系列实用的优化技巧和实际案例分析。最后,我们总结了构建稳定云原生环境的最佳实践,涵盖资源管理、调度策略、高可用性、安全性以及监控和日志等方面。
随着云原生技术的不断发展,Kubernetes调度器也在持续演进。未来,我们可以期待更多智能化、自动化的调度功能,如基于机器学习的资源预测、更灵活的调度策略和更高效的调度算法。通过不断学习和实践,我们可以更好地掌握Kubernetes调度器的核心原理和优化技巧,为构建高效、稳定的云原生环境贡献力量。 |
|