- 云原生
- 运维
【免费下载链接】descheduler
Descheduler for Kubernetes
导读
PodLifeTime 是 Kubernetes descheduler 框架中一个高度灵活的 Deschedule 插件,它不依赖节点资源利用率,而是完全以 Pod 自身的"年龄 + 状态 + 状态迁移时间"为判断依据,决定哪些 Pod 该被驱逐。本文以插件官方文档(pkg/framework/plugins/podlifetime/README.md)为主体,结合仓库源码、示例配置与单元/E2E 测试,完整讲解其过滤链组合逻辑、全部参数语义、经典使用场景与可直接复用的 DeschedulerPolicy 配置,帮助你在集群中实现"到期轮换、残留清理、故障回收"三类最常见的 Pod 生命周期治理。
插件定位:在 Descheduler 框架中的角色
PodLifeTime 被注册为Deschedule 扩展点插件。在 pkg/descheduler/setupplugins.go 中,插件以podlifetime.PluginName(即"PodLifeTime")注册进默认插件注册表,并绑定了参数类型PodLifeTimeArgs、校验函数ValidatePodLifeTimeArgs与默认值函数SetDefaults_PodLifeTimeArgs:
pluginregistry.Register(podlifetime.PluginName, podlifetime.New, &podlifetime.PodLifeTime{}, &podlifetime.PodLifeTimeArgs{}, podlifetime.ValidatePodLifeTimeArgs, podlifetime.SetDefaults_PodLifeTimeArgs, registry)因此在使用时,PodLifeTime 必须出现在策略文件的plugins.deschedule.enabled列表中,而不是balance或filter等其他扩展点。其核心实现位于 pkg/framework/plugins/podlifetime/pod_lifetime.go,参数类型定义在 pkg/framework/plugins/podlifetime/types.go。
工作原理:过滤链的 AND/OR 组合语义
PodLifeTime 的判定逻辑可以概括为一句话:所有非空的过滤类别之间是 AND 关系(Pod 必须满足每一个已配置的过滤条件才会被驱逐);每个过滤类别内部是 OR 关系(命中其中任意一项即可满足该类别)。
源码中 New() 通过podutil.WrapFilterFuncs把各个条件逐一串成一条过滤链:
- 命名空间过滤(include/exclude)与
labelSelector是插件级的通用前置过滤,与驱逐器的Filter/PreEvictionFilter一并组合; maxPodLifeTimeSeconds:Pod 年龄大于该秒数才通过;states:任一匹配即通过(OR);ownerKinds:按 OwnerReference 的 Kind 做 include/exclude;conditions:任一条件过滤器命中即通过(OR);exitCodes:任一容器终止退出码匹配即通过(OR)。
最终所有过滤器叠加后,Pod 必须全部通过才能进入驱逐候选列表。该语义与项目根文档 README.md 中"All non-empty filter categories are ANDed…Within each category, items are ORed"的描述完全一致。
排序与驱逐:最老优先、限额即止
候选 Pod 确定后,插件调用 SortPodsBasedOnAge(pkg/descheduler/pod/pods.go)按CreationTimestamp升序原地排序,保证最老的 Pod 先被驱逐;随后在 Deschedule() 中逐个调用handle.Evictor().Evict()执行驱逐。
驱逐循环对两种限额错误做了专门处理(pkg/framework/plugins/podlifetime/pod_lifetime.go#L179-L193):
EvictionNodeLimitError:单节点驱逐上限已满,continue跳过当前节点继续尝试其他节点上的 Pod;EvictionTotalLimitError:全局总驱逐上限已满,直接停止整个插件执行。
这些限额(maxPodsToEvictPerNode、maxPodsToEvictPerNamespace、maxPodsToEvictTotal)由驱逐器在 pkg/descheduler/evictions/evictions.go 中统一强制(total 与 per-node 上限在EvictPod内检查并返回对应错误类型),PodLifeTime 只需消费它们即可,无需自己实现限额逻辑。
注:Pod 的 PDB(Pod Disruption Budget)约束同样由驱逐器层负责,PodLifeTime 不绕过 PDB,驱逐请求会正常受 PDB 保护。
参数总览
下表完整列出了插件支持的配置参数(字段名、语义与 types.go 一致):
| 参数 | 说明 | 类型 | 必填 | 默认 |
|---|---|---|---|---|
maxPodLifeTimeSeconds | 年龄超过该秒数的 Pod 才会被驱逐 | uint | 否* | nil |
states | 按 Pod phase、Pod status reason、容器 waiting/terminated reason 过滤,命中任一即匹配 | []string | 否 | nil |
conditions | 仅驱逐满足状态条件(见 PodConditionFilter)的 Pod | []PodConditionFilter | 否 | nil |
exitCodes | 仅驱逐存在匹配容器终止退出码的 Pod | []int32 | 否 | nil |
ownerKinds | 按 OwnerReference 的 Kind 做 include/exclude | OwnerKinds | 否 | nil |
namespaces | 限定驱逐的命名空间(include 或 exclude) | Namespaces | 否 | nil |
labelSelector | 仅驱逐匹配这些标签的 Pod | metav1.LabelSelector | 否 | nil |
includingInitContainers | 将 state/exitCode 过滤扩展到 init 容器 | bool | 否 | false |
includingEphemeralContainers | 将 state 过滤扩展到 ephemeral 容器 | bool | 否 | false |
* 至少需要指定一个过滤条件(maxPodLifeTimeSeconds、states、conditions或exitCodes之一),否则参数校验失败。
这条"至少一个过滤条件"的硬性校验在 validation.go 中实现,同时校验还包括:namespaces 与 ownerKinds 的 include/exclude 互斥、labelSelector 合法性、states 取值白名单、conditions 每条至少设置一个字段。defaults.go(pkg/framework/plugins/podlifetime/defaults.go)目前保持各字段默认 nil/false 不变,即所有过滤维度默认关闭,未配置即不参与判定。
states 字段详解:四类状态的 OR 匹配
states是 PodLifeTime 最核心的过滤维度。它按OR语义同时匹配四类状态,任一命中即通过(见 pod_lifetime.go):
| 类别 | 取值示例 |
|---|---|
| Pod phase | Running、Pending、Succeeded、Failed、Unknown |
| Pod status reason | NodeAffinity、NodeLost、Shutdown、UnexpectedAdmissionError |
| 容器 waiting reason | CrashLoopBackOff、ImagePullBackOff、ErrImagePull、CreateContainerConfigError、CreateContainerError、InvalidImageName、PodInitializing、ContainerCreating |
| 容器 terminated reason | OOMKilled、Error、Completed、DeadlineExceeded、Evicted、ContainerCannotRun、StartError |
上述全部取值由 validation.go 中的podLifeTimeAllowedStates白名单强制约束,states 中不允许出现白名单之外的值,否则校验直接报错。
底层匹配由 pkg/descheduler/pod/pods.go 中的HasMatchingContainerWaitingState/HasMatchingContainerTerminatedState辅助函数完成:逐条检查容器的State.Waiting.Reason与State.Terminated.Reason是否命中集合。
当includingInitContainers为true时,InitContainerStatuses的 waiting/terminated reason 也会参与匹配;当includingEphemeralContainers为true时,EphemeralContainerStatuses同样参与。单元测试 pod_lifetime_test.go 验证了:未开启这两个开关时,init/ephemeral 容器的CreateContainerError不会被匹配(预期驱逐数为 0),开启后则能被驱逐(预期驱逐数为 1)。
conditions:按状态条件与迁移时间精细过滤
conditions允许你针对pod.status.conditions[]做精确匹配,非常适合"清理已 Succeeded 但残留过久"的场景。
PodConditionFilter 字段
单个条件过滤器内的字段级匹配是AND(所有已设置字段必须同时命中),未设置的字段不参与检查;多个条件过滤器之间是OR——Pod 的任一 condition 满足任意一个过滤器即被驱逐(实现见 pod_lifetime.go 的matchesAnyPodConditionFilter与matchesConditionFields):
| 字段 | 说明 |
|---|---|
type | 条件类型(如Ready、Initialized、ContainersReady) |
status | 条件状态(True、False、Unknown) |
reason | 条件原因(如PodCompleted) |
minTimeSinceLastTransitionSeconds | 要求匹配条件的lastTransitionTime距今至少该秒数 |
校验规则(validation.go):每条过滤器至少设置type/status/reason/minTimeSinceLastTransitionSeconds之一,否则校验失败。
迁移时间语义(重点)
当设置了minTimeSinceLastTransitionSeconds时,Pod 的条件必须同时满足 type/status/reason 字段匹配,且其lastTransitionTime必须足够久远;若该 condition 没有lastTransitionTime(零值),则视为不匹配。源码逻辑(pod_lifetime.go):
if f.MinTimeSinceLastTransitionSeconds != nil { if cond.LastTransitionTime.IsZero() { continue } idle := metav1.Now().Sub(cond.LastTransitionTime.Time) if idle < 0 || uint(idle.Seconds()) < *f.MinTimeSinceLastTransitionSeconds { continue } } return true测试 TestTransitionTimeFiltering 覆盖了三个关键行为:2 小时前的旧迁移时间可驱逐、1 分钟前的新迁移时间不可驱逐、迁移时间检查只作用于匹配字段命中的那条 condition(另一条 reason 不匹配的 condition 即便迁移时间很新也不影响判定)。
ownerKinds:按控制器类型定向驱逐
ownerKinds通过 Pod 的 OwnerReference 的Kind字段做 include/exclude(见 pod_lifetime.go):
| 字段 | 说明 |
|---|---|
include | 仅驱逐由这些 Kind 拥有的 Pod |
exclude | 不驱逐由这些 Kind 拥有的 Pod |
include与exclude最多只能设置一个(validation.go 强制)。测试 TestOwnerKindsFiltering 验证:exclude: [Job]时 Job 拥有的 Failed Pod 不被驱逐、非 Job Pod 正常驱逐;include: [Job]时只有 Job Pod 被驱逐。
典型价值:Job 控制器自身会清理已结束的 Pod,因此驱逐器通常应该排除 Job 拥有的 Pod,避免与 Job 的清理逻辑重复;而 ReplicaSet/DaemonSet 等长期运行的控制器则希望被驱逐后由控制器重建。
典型使用场景(可直接复用)
以下场景配置全部来自官方 README,字段语义与前述参数一一对应。
场景一:清理闲置过久的 Succeeded Pod
args: states: [Succeeded] conditions: - reason: PodCompleted status: "True" minTimeSinceLastTransitionSeconds: 14400 # 4 hours仅当 Pod 处于Succeeded阶段,且存在reason=PodCompleted、status=True的 condition,且该 condition 的迁移时间距今超过 4 小时,才会被驱逐。
场景二:驱逐 Failed Pod 但排除 Job 拥有的
args: states: [Failed] exitCodes: [1] ownerKinds: exclude: [Job] maxPodLifeTimeSeconds: 3600 includingInitContainers: trueFailed Pod 同时满足"存在退出码为 1 的已终止容器(含 init 容器)"且"年龄超过 1 小时"且"不属于 Job"才被驱逐。exitCodes匹配实现在 pod_lifetime.go,测试 TestExitCodesFiltering 验证了命中与未命中两种情形。
场景三:资源泄漏缓解——定期重启长驻 Pod
args: maxPodLifeTimeSeconds: 604800 # 7 days states: [Running]长期运行的 Pod 可能积累内存泄漏,本配置让运行超过 7 天的 Running Pod 被驱逐并由控制器重建,实现"到期轮换"。
场景四:清理 CrashLoopBackOff / ImagePullBackOff 卡死 Pod
args: states: [CrashLoopBackOff, ImagePullBackOff]states列表内部是 OR 语义,命中任一 waiting reason 即可被驱逐,用于快速回收陷入镜像拉取/启动循环的故障 Pod。
场景五:仅驱逐特定控制器拥有的 Pod
args: states: [Succeeded, Failed] ownerKinds: include: [Job] maxPodLifeTimeSeconds: 600只对 Job 拥有的 Succeeded/Failed Pod 生效,且要求其存活超过 600 秒,适合对"已完成但未及时清理"的 Job Pod 做兜底回收。
完整 DeschedulerPolicy 配置示例
示例 A:按年龄 + 状态过滤驱逐(1 天轮换)
apiVersion: descheduler/v1alpha2 kind: DeschedulerPolicy profiles: - name: default plugins: deschedule: enabled: - name: PodLifeTime pluginConfig: - name: PodLifeTime args: maxPodLifeTimeSeconds: 86400 # 1 day states: - Running namespaces: include: - default示例 B:基于状态迁移时间的精细驱逐(Succeeded 残留 4 小时)
apiVersion: descheduler/v1alpha2 kind: DeschedulerPolicy profiles: - name: default plugins: deschedule: enabled: - name: PodLifeTime pluginConfig: - name: PodLifeTime args: states: - Succeeded conditions: - reason: PodCompleted status: "True" minTimeSinceLastTransitionSeconds: 14400 namespaces: include: - default该配置的效果:default命名空间中,处于Succeeded阶段、带有PodCompleted=True条件、且该条件最后迁移时间距今超过 4 小时的 Pod 会被驱逐。
仓库还提供了两个可直接套用的独立示例文件:examples/pod-life-time.yml(7 天年龄 + Pending/PodInitializing 状态)与 examples/pod-life-time-transition.yml(Succeeded + PodCompleted + 4 小时迁移阈值),均以descheduler/v1alpha2API 版本编写,可作为--policy-config-file的输入。
命令行运行与验证
按 docs/user-guide.md 的"Balance Cluster By Pod Age"用例,可通过 CLI 直接加载策略文件运行:
descheduler -v=3 --evict-local-storage-pods --policy-config-file=pod-life-time.yml该命令以-v=3开启详细日志,--evict-local-storage-pods允许驱逐挂载本地存储的 Pod,并加载包含maxPodLifeTimeSeconds: 604800的PodLifeTime策略文件(即上述"7 天轮换"配置)。文档同时建议:为每个业务应用配置 Pod Disruption Budget(PDB),以保证驱逐不会导致应用可用性受损。
测试与可靠性依据
- 单元测试:pkg/framework/plugins/podlifetime/pod_lifetime_test.go(约 1367 行)覆盖年龄阈值边界(605 秒驱逐 / 595 秒不驱逐,见
TestPodLifeTime_AgeThreshold)、各 phase 状态、全部 waiting reason、Pod status reason、init/ephemeral 容器开关、条件过滤、迁移时间过滤、ownerKinds、exitCodes、组合过滤以及驱逐限额(per-node / per-namespace / total)下的"最老优先"行为。 - 参数校验测试:validation_test.go 验证了"无任何过滤条件时报错""非法 state 报错并列出完整白名单""include/exclude 互斥""空 condition 过滤器报错"等规则。
- 默认值测试:defaults_test.go 确认空参数时各字段保持 nil。
- E2E 测试:test/e2e/e2e_podlifetime_test.go 在真实集群中用 Job 制造 Failed/Succeeded Pod,验证
States: [Failed]可驱逐、OwnerKinds.Exclude: [Job]不驱逐 Job Pod、Conditions命中与不命中两种结果。
这些测试共同锁定了本文所述的所有参数语义与边界行为,可作为你自行验证配置时的参照。
小结
PodLifeTime 的价值在于把"时间"这一维度引入了驱逐决策:从简单的按年龄驱逐,到精确到"某个状态条件已迁移超过 N 秒"的细粒度回收,再到按控制器类型、命名空间、标签、退出码的组合筛选,它都能在不触碰运行良好 Pod 的前提下完成集群的"新陈代谢"。配置时请牢记三条准则:至少指定一个过滤条件、类别间 AND / 类别内 OR、为关键应用配置 PDB。
- 云原生
- 运维
【免费下载链接】descheduler
Descheduler for Kubernetes
相关推荐
iTerm2-Color-Schemes 完整指南:605 款终端配色一键导入与切换教程
iTerm2 Color Schemes 完整指南:605 款终端配色一键导入与切换教程 刚拿到一台 Mac、装好 iTerm2 的开发者,最常见的动作就是去搜
开发工具Android Activity 生命周期全解析:基于 android-training-course-in-chinese 的回调机制、状态迁移与实例状态保存实战
Android Activity 生命周期全解析:基于 android training course in chinese 的回调机制、状态迁移与实例状态保存
文档教程移动开发Kubernetes Pod 状态与生命周期管理:从构成、相位到探针与重启策略的完整实战指南
Kubernetes Pod 状态与生命周期管理:从构成、相位到探针与重启策略的完整实战指南 Pod 是 Kubernetes 中最小的调度与部署单元,理解它的
教程云原生容器编排
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考