深入分析Kubernetes Critical Pod(三)

简介: 本文介绍了Kubelet在Predicate Admit准入检查时对CriticalPod的资源抢占的原理,以及Priority Admission Controller对CriticalPod的PriorityClassName特殊处理。

本文介绍了Kubelet在Predicate Admit准入检查时对CriticalPod的资源抢占的原理,以及Priority Admission Controller对CriticalPod的PriorityClassName特殊处理。

深入分析Kubernetes Critical Pod系列:
深入分析Kubernetes Critical Pod(一)
深入分析Kubernetes Critical Pod(二)
深入分析Kubernetes Critical Pod(三)
深入分析Kubernetes Critical Pod(四)

Kubelet Predicate Admit时对Critical的资源抢占处理

kubelet 在Predicate Admit流程中,会对Pods进行各种Predicate准入检查,包括GeneralPredicates检查本节点是否有足够的cpu,mem,gpu资源。如果GeneralPredicates准入检测失败,对于nonCriticalPod则直接Admit失败,但如果是CriticalPod则会触发kubelet preemption进行资源抢占,按照一定规则杀死一些Pods释放资源,抢占成功,则Admit成功。

流程的源头应该从kubelet初始化的流程开始。

pkg/kubelet/kubelet.go:315

// NewMainKubelet instantiates a new Kubelet object along with all the required internal modules.
// No initialization of Kubelet and its modules should happen here.
func NewMainKubelet(...) (*Kubelet, error) {
    ...
   criticalPodAdmissionHandler := preemption.NewCriticalPodAdmissionHandler(klet.GetActivePods, killPodNow(klet.podWorkers, kubeDeps.Recorder), kubeDeps.Recorder)
    klet.admitHandlers.AddPodAdmitHandler(lifecycle.NewPredicateAdmitHandler(klet.getNodeAnyWay, criticalPodAdmissionHandler, klet.containerManager.UpdatePluginResources))
    // apply functional Option's
    for _, opt := range kubeDeps.Options {
        opt(klet)
    }

    ...
    return klet, nil
}

在NewMainKubelet对kubelet进行初始化时,通过AddPodAdmitHandler注册了criticalPodAdmissionHandler,CriticalPod的Admit的特殊之处就体现在criticalPodAdmissionHandler。

然后,我们进入kubelet的predicateAdmitHandler流程中,看看GeneralPredicates失败后的处理逻辑。

pkg/kubelet/lifecycle/predicate.go:58

func (w *predicateAdmitHandler) Admit(attrs *PodAdmitAttributes) PodAdmitResult {
    ...

    fit, reasons, err := predicates.GeneralPredicates(podWithoutMissingExtendedResources, nil, nodeInfo)
    if err != nil {
        message := fmt.Sprintf("GeneralPredicates failed due to %v, which is unexpected.", err)
        glog.Warningf("Failed to admit pod %v - %s", format.Pod(pod), message)
        return PodAdmitResult{
            Admit:   fit,
            Reason:  "UnexpectedAdmissionError",
            Message: message,
        }
    }
    if !fit {
        fit, reasons, err = w.admissionFailureHandler.HandleAdmissionFailure(pod, reasons)
        if err != nil {
            message := fmt.Sprintf("Unexpected error while attempting to recover from admission failure: %v", err)
            glog.Warningf("Failed to admit pod %v - %s", format.Pod(pod), message)
            return PodAdmitResult{
                Admit:   fit,
                Reason:  "UnexpectedAdmissionError",
                Message: message,
            }
        }
    }
    ...
    return PodAdmitResult{
        Admit: true,
    }
}

在kubelet predicateAdmitHandler中对Pod进行GeneralPredicates检查cpu,mem,gpu资源时,如果发现资源不足导致Admit失败,则接着调用HandleAdmissionFailure进行额外处理。前提提到,kubelet初始化时注册了criticalPodAdmissionHandler为HandleAdmissionFailure。

CriticalPodAdmissionHandler struct定义如下:

pkg/kubelet/preemption/preemption.go:41

type CriticalPodAdmissionHandler struct {
    getPodsFunc eviction.ActivePodsFunc
    killPodFunc eviction.KillPodFunc
    recorder    record.EventRecorder
}

CriticalPodAdmissionHandler的HandleAdmissionFailure方法就是处理CriticalPod特殊的逻辑所在。

pkg/kubelet/preemption/preemption.go:66

// HandleAdmissionFailure gracefully handles admission rejection, and, in some cases,
// to allow admission of the pod despite its previous failure.
func (c *CriticalPodAdmissionHandler) HandleAdmissionFailure(pod *v1.Pod, failureReasons []algorithm.PredicateFailureReason) (bool, []algorithm.PredicateFailureReason, error) {
    if !kubetypes.IsCriticalPod(pod) || !utilfeature.DefaultFeatureGate.Enabled(features.ExperimentalCriticalPodAnnotation) {
        return false, failureReasons, nil
    }
    // InsufficientResourceError is not a reason to reject a critical pod.
    // Instead of rejecting, we free up resources to admit it, if no other reasons for rejection exist.
    nonResourceReasons := []algorithm.PredicateFailureReason{}
    resourceReasons := []*admissionRequirement{}
    for _, reason := range failureReasons {
        if r, ok := reason.(*predicates.InsufficientResourceError); ok {
            resourceReasons = append(resourceReasons, &admissionRequirement{
                resourceName: r.ResourceName,
                quantity:     r.GetInsufficientAmount(),
            })
        } else {
            nonResourceReasons = append(nonResourceReasons, reason)
        }
    }
    if len(nonResourceReasons) > 0 {
        // Return only reasons that are not resource related, since critical pods cannot fail admission for resource reasons.
        return false, nonResourceReasons, nil
    }
    err := c.evictPodsToFreeRequests(admissionRequirementList(resourceReasons))
    // if no error is returned, preemption succeeded and the pod is safe to admit.
    return err == nil, nil, err
}
  • 如果Pod不是CriticalPod,或者ExperimentalCriticalPodAnnotation Feature Gate是关闭的,则直接返回false,表示Admit失败。
  • 判断Admit的failureReasons是否包含predicate.InsufficientResourceError,如果包含,则调用evictPodsToFreeRequests触发kubelet preemption。注意这里的抢占不同于scheduler preemtion,不要混淆了。

evictPodsToFreeRequests就是kubelet preemption进行资源抢占的逻辑实现,其核心就是调用getPodsToPreempt挑选合适的待杀死的Pods(podsToPreempt)。

pkg/kubelet/preemption/preemption.go:121

// getPodsToPreempt returns a list of pods that could be preempted to free requests >= requirements
func getPodsToPreempt(pods []*v1.Pod, requirements admissionRequirementList) ([]*v1.Pod, error) {
    bestEffortPods, burstablePods, guaranteedPods := sortPodsByQOS(pods)

    // make sure that pods exist to reclaim the requirements
    unableToMeetRequirements := requirements.subtract(append(append(bestEffortPods, burstablePods...), guaranteedPods...)...)
    if len(unableToMeetRequirements) > 0 {
        return nil, fmt.Errorf("no set of running pods found to reclaim resources: %v", unableToMeetRequirements.toString())
    }
    // find the guaranteed pods we would need to evict if we already evicted ALL burstable and besteffort pods.
    guarateedToEvict, err := getPodsToPreemptByDistance(guaranteedPods, requirements.subtract(append(bestEffortPods, burstablePods...)...))
    if err != nil {
        return nil, err
    }
    // Find the burstable pods we would need to evict if we already evicted ALL besteffort pods, and the required guaranteed pods.
    burstableToEvict, err := getPodsToPreemptByDistance(burstablePods, requirements.subtract(append(bestEffortPods, guarateedToEvict...)...))
    if err != nil {
        return nil, err
    }
    // Find the besteffort pods we would need to evict if we already evicted the required guaranteed and burstable pods.
    bestEffortToEvict, err := getPodsToPreemptByDistance(bestEffortPods, requirements.subtract(append(burstableToEvict, guarateedToEvict...)...))
    if err != nil {
        return nil, err
    }
    return append(append(bestEffortToEvict, burstableToEvict...), guarateedToEvict...), nil
}

kubelet preemtion时候挑选待杀死Pods的逻辑如下:

  • 如果该Pod的某个Resource request quantity超过了现在的所有的bestEffortPods, burstablePods, guaranteedPods的该Resource request quantity,则podsToPreempt为nil,意味着无合适Pods以释放。
  • 如果释放所有bestEffortPods, burstablePods的资源都不足够,则再挑选guaranteedPods(guarateedToEvict)。挑选的规则是:

    • 规则一:越少的Pods被释放越好;
    • 规则二:释放的资源越少越好;
    • 规则一的优先级比规则二高;
  • 如果释放所有bestEffortPods及guarateedToEvict的资源都不足够,则再挑选burstablePods(burstableToEvict)。挑选的规则同上。
  • 如果释放所有burstableToEvict及guarateedToEvict的资源都不足够,则再挑选bestEffortPods(bestEffortToEvict)。挑选的规则同上。

也就是说:Pod Resource QoS优先级越低的越先被抢占,同一个QoS Level内挑选Pods按照如下规则:

  • 规则一:越少的Pods被释放越好;
  • 规则二:释放的资源越少越好;
  • 规则一的优先级比规则二高;

Priority Admission Controller对CriticalPod的特殊处理

我们先看看几类特殊的、系统预留的CriticalPod:

  • ClusterCriticalPod: PriorityClass Name是system-cluster-critical的Pod。
  • NodeCriticalPod:PriorityClass Name是system-node-critical的Pod。

如果AdmissionController中启动了Priority Admission Controller,那么在创建Pod时对Priority的检查也存在CriticalPod的特殊处理。

Priority Admission Controller主要作用是根据Pod中指定的PriorityClassName替换成对应的Spec.Pritory数值。

plugin/pkg/admission/priority/admission.go:138

// admitPod makes sure a new pod does not set spec.Priority field. It also makes sure that the PriorityClassName exists if it is provided and resolves the pod priority from the PriorityClassName.
func (p *priorityPlugin) admitPod(a admission.Attributes) error {
    operation := a.GetOperation()
    pod, ok := a.GetObject().(*api.Pod)
    if !ok {
        return errors.NewBadRequest("resource was marked with kind Pod but was unable to be converted")
    }

    // Make sure that the client has not set `priority` at the time of pod creation.
    if operation == admission.Create && pod.Spec.Priority != nil {
        return admission.NewForbidden(a, fmt.Errorf("the integer value of priority must not be provided in pod spec. Priority admission controller populates the value from the given PriorityClass name"))
    }
    if utilfeature.DefaultFeatureGate.Enabled(features.PodPriority) {
        var priority int32
        // TODO: @ravig - This is for backwards compatibility to ensure that critical pods with annotations just work fine.
        // Remove when no longer needed.
        if len(pod.Spec.PriorityClassName) == 0 &&
            utilfeature.DefaultFeatureGate.Enabled(features.ExperimentalCriticalPodAnnotation) &&
            kubelettypes.IsCritical(a.GetNamespace(), pod.Annotations) {
            pod.Spec.PriorityClassName = scheduling.SystemClusterCritical
        }
        if len(pod.Spec.PriorityClassName) == 0 {
            var err error
            priority, err = p.getDefaultPriority()
            if err != nil {
                return fmt.Errorf("failed to get default priority class: %v", err)
            }
        } else {
            // Try resolving the priority class name.
            pc, err := p.lister.Get(pod.Spec.PriorityClassName)
            if err != nil {
                if errors.IsNotFound(err) {
                    return admission.NewForbidden(a, fmt.Errorf("no PriorityClass with name %v was found", pod.Spec.PriorityClassName))
                }

                return fmt.Errorf("failed to get PriorityClass with name %s: %v", pod.Spec.PriorityClassName, err)
            }

            priority = pc.Value
        }
        pod.Spec.Priority = &priority
    }
    return nil
}

同时满足以下所有条件时,给Pod的Spec.PriorityClassName赋值为system-cluster-critical,即认为是ClusterCriticalPod。

  • 如果Enable了ExperimentalCriticalPodAnnotationPodPriority Feature Gate;
  • 该Pod没有指定PriorityClassName;
  • 该Pod属于kube-system namespace;
  • 该Pod打了scheduler.alpha.kubernetes.io/critical-pod="" Annotation;

总结

本文介绍了Kubelet在Predicate Admit准入检查时对CriticalPod的资源抢占的原理,以及Priority Admission Controller对CriticalPod的PriorityClassName特殊处理。下一篇是最后一处关于Kubernetes对CriticalPod进行特殊待遇的地方——DaemonSet Controller。

相关实践学习
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。     相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
目录
相关文章
|
9月前
|
Kubernetes Docker 容器
Kubernetes与Docker参数对照:理解Pod中的command、args与Dockerfile中的CMD、ENTRYPOINT。
需要明确的是,理解这些都需要对Docker和Kubernetes有一定深度的理解,才能把握二者的区别和联系。虽然它们都是容器技术的二个重要组成部分,但各有其特性和适用场景,理解它们的本质和工作方式,才能更好的使用这些工具,将各自的优点整合到生产环境中,实现软件的快速开发和部署。
355 25
|
Prometheus Kubernetes 监控
深入探索Kubernetes中的Pod自动扩展(Horizontal Pod Autoscaler, HPA)
深入探索Kubernetes中的Pod自动扩展(Horizontal Pod Autoscaler, HPA)
|
9月前
|
Kubernetes Shell Windows
【Azure K8S | AKS】在AKS的节点中抓取目标POD的网络包方法分享
在AKS中遇到复杂网络问题时,可通过以下步骤进入特定POD抓取网络包进行分析:1. 使用`kubectl get pods`确认Pod所在Node;2. 通过`kubectl node-shell`登录Node;3. 使用`crictl ps`找到Pod的Container ID;4. 获取PID并使用`nsenter`进入Pod的网络空间;5. 在`/var/tmp`目录下使用`tcpdump`抓包。完成后按Ctrl+C停止抓包。
341 12
|
存储 Kubernetes Docker
【赵渝强老师】Kubernetes中Pod的基础容器
Pod 是 Kubernetes 中的基本单位,代表集群上运行的一个进程。它由一个或多个容器组成,包括业务容器、基础容器、初始化容器和临时容器。基础容器负责维护 Pod 的网络空间,对用户透明。文中附有图片和视频讲解,详细介绍了 Pod 的组成结构及其在网络配置中的作用。
240 1
【赵渝强老师】Kubernetes中Pod的基础容器
|
运维 Kubernetes Shell
【赵渝强老师】K8s中Pod的临时容器
Pod 是 Kubernetes 中的基本调度单位,由一个或多个容器组成,包括业务容器、基础容器、初始化容器和临时容器。临时容器用于故障排查和性能诊断,不适用于构建应用程序。当 Pod 中的容器异常退出或容器镜像不包含调试工具时,临时容器非常有用。文中通过示例展示了如何使用 `kubectl debug` 命令创建临时容器进行调试。
240 1
|
Kubernetes 调度 容器
【赵渝强老师】K8s中Pod中的业务容器
Pod 是 Kubernetes 中的基本调度单元,由一个或多个容器组成。除了业务容器,Pod 还包括基础容器、初始化容器和临时容器。本文通过示例介绍如何创建包含业务容器的 Pod,并提供了一个视频讲解。示例中创建了一个名为 "busybox-container" 的业务容器,并使用 `kubectl create -f firstpod.yaml` 命令部署 Pod。
188 1
|
Kubernetes 容器 Perl
【赵渝强老师】K8s中Pod中的初始化容器
Kubernetes的Pod包含业务容器、基础容器、初始化容器和临时容器。初始化容器在业务容器前运行,用于执行必要的初始化任务。本文介绍了初始化容器的作用、配置方法及优势,并提供了一个示例。
253 1
|
2月前
|
人工智能 算法 调度
阿里云ACK托管集群Pro版共享GPU调度操作指南
本文介绍在阿里云ACK托管集群Pro版中,如何通过共享GPU调度实现显存与算力的精细化分配,涵盖前提条件、使用限制、节点池配置及任务部署全流程,提升GPU资源利用率,适用于AI训练与推理场景。
289 1
|
2月前
|
弹性计算 监控 调度
ACK One 注册集群云端节点池升级:IDC 集群一键接入云端 GPU 算力,接入效率提升 80%
ACK One注册集群节点池实现“一键接入”,免去手动编写脚本与GPU驱动安装,支持自动扩缩容与多场景调度,大幅提升K8s集群管理效率。
259 89
|
7月前
|
资源调度 Kubernetes 调度
从单集群到多集群的快速无损转型:ACK One 多集群应用分发
ACK One 的多集群应用分发,可以最小成本地结合您已有的单集群 CD 系统,无需对原先应用资源 YAML 进行修改,即可快速构建成多集群的 CD 系统,并同时获得强大的多集群资源调度和分发的能力。
294 9

相关产品

  • 容器服务Kubernetes版
  • 推荐镜像

    更多