Kubernetes 调度框架从传统的 Predicates(预选)和 Priorities(优选)转变为现代的 Filter 和 Score 扩展点是在 Kubernetes 1.15 到 1.18 这个时期逐步完成的。
Kubernetes 1.15 (2019年6月发布)
Kubernetes 1.16 (2019年9月发布)
Kubernetes 1.17 (2019年12月发布)
Kubernetes 1.18 (2020年3月发布)
Kubernetes 1.19 (2020年8月发布)
架构变化:
代码组织:
pkg/scheduler/algorithm/predicates
和 pkg/scheduler/algorithm/priorities
pkg/scheduler/framework/plugins
,按照插件功能分类配置方式:
--policy-config-file
配置预选和优选函数ComponentConfig
配置插件及其顺序这次架构重构使得 Kubernetes 调度器更加灵活、可扩展,同时保持了向后兼容性,让旧版本的调度策略能够平滑迁移到新架构。
在 Kubernetes 调度器中,predictor(预选)和 priority(优选)功能在最新版本中已经被整合到统一的调度框架(Scheduling Framework)中。在这个架构下,它们主要通过以下文件组织:
主要文件位于 pkg/scheduler/framework
目录下:
framework.go
: 定义了调度框架的核心接口和结构interface.go
: 定义了插件接口,包括 Filter(前身是 predictor)和 Score(前身是 priority)接口runtime.go
: 实现了调度框架的运行时调度插件位于 pkg/scheduler/framework/plugins
目录下,每个插件负责特定的预选或优选功能:
nodeaffinity
: 节点亲和性tainttoleration
: 污点容忍noderesources
: 节点资源匹配nodeports
: 端口资源volumebinding
: 卷绑定pkg/scheduler/scheduler.go
: 创建调度器的主入口pkg/scheduler/profile/profile.go
: 管理调度配置文件pkg/scheduler/framework/plugins/registry.go
: 注册所有可用的插件在当前版本中:
调度器调用这些插件的顺序在 pkg/scheduler/framework/cycle.go
文件中定义,遵循以下流程:
现代 Kubernetes 调度框架将调度周期划分为多个扩展点,其中:
这种基于插件的架构使调度器更加灵活和可扩展,允许用户通过组合不同的插件来自定义调度行为。
如果您需要查看特定的预选或优选逻辑,应该查看 pkg/scheduler/framework/plugins
目录下相应的插件实现。
Filter 扩展点(前身是 Predicates)是调度过程中的"预选"阶段,负责筛选出满足 Pod 基本要求的节点。
// 一些重要的内置 Filter 插件
- NodeName: 检查 Pod 指定的节点名称
- NodeUnschedulable: 检查节点是否可调度
- NodeResourcesFit: 检查节点资源是否足够
- NodeAffinity: 实现节点亲和性规则
- TaintToleration: 检查 Pod 是否容忍节点污点
- PodTopologySpread: 实现 Pod 拓扑分布约束
- VolumeRestrictions: 检查卷限制
- NodeVolumeLimits: 检查节点卷数量限制
Score 扩展点(前身是 Priorities)是调度过程中的"优选"阶段,为通过预选的节点评分。
// 一些重要的内置 Score 插件
- NodeResourcesBalancedAllocation: 平衡节点资源使用
- NodeResourcesFit: 根据请求资源量评分
- NodeAffinity: 节点亲和性优先级评分
- ImageLocality: 基于节点上已有镜像评分
- InterPodAffinity: Pod 间亲和性评分
- TaintToleration: 基于污点容忍评分
- PodTopologySpread: 拓扑分布评分
要创建自定义调度插件,需实现相关接口:
// Filter 插件接口
type FilterPlugin interface {
Plugin
Filter(ctx context.Context, state *CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *Status
}
// Score 插件接口
type ScorePlugin interface {
Plugin
Score(ctx context.Context, state *CycleState, pod *v1.Pod, nodeName string) (int64, *Status)
// 可选实现归一化方法
ScoreExtensions() ScoreExtensions
}
// 通用 Plugin 接口
type Plugin interface {
Name() string
}
创建一个插件注册函数:
func New(args runtime.Object, h framework.Handle) (framework.Plugin, error) {
// 解析配置参数
// 初始化插件
return &YourPlugin{
handle: h,
// 其他字段
}, nil
}
有两种主要方式部署自定义调度插件:
import (
"k8s.io/kubernetes/cmd/kube-scheduler/app"
"yourpkg/yourplugin" // 你的插件包
)
func main() {
command := app.NewSchedulerCommand(
app.WithPlugin("YourPlugin", yourplugin.New),
)
// 启动调度器
}
apiVersion: v1
kind: ConfigMap
metadata:
name: scheduler-config
namespace: kube-system
data:
scheduler-config.yaml: |
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
plugins:
filter:
enabled:
- name: "YourFilterPlugin"
score:
enabled:
- name: "YourScorePlugin"
weight: 5
spec:
containers:
- command:
- kube-scheduler
- --config=/etc/kubernetes/scheduler-config.yaml
volumeMounts:
- name: scheduler-config
mountPath: /etc/kubernetes/
volumes:
- name: scheduler-config
configMap:
name: scheduler-config
可以在 Pod 定义中指定使用自定义调度器:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
schedulerName: your-custom-scheduler # 指定使用的调度器
containers:
- name: container
image: nginx
// NodeTypeFilter 基于节点标签筛选特定类型节点
type NodeTypeFilter struct{}
func (pl *NodeTypeFilter) Name() string {
return "NodeTypeFilter"
}
func (pl *NodeTypeFilter) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
// 获取节点
node := nodeInfo.Node()
if node == nil {
return framework.NewStatus(framework.Error, "node not found")
}
// 检查节点是否有特定标签
if value, ok := node.Labels["node-type"]; ok && value == "special" {
return framework.NewStatus(framework.Success, "")
}
return framework.NewStatus(framework.Unschedulable, "node does not have the required type")
}
// PodDensityScore 基于节点已有Pod数量评分
type PodDensityScore struct {
handle framework.Handle
}
func (pl *PodDensityScore) Name() string {
return "PodDensityScore"
}
func (pl *PodDensityScore) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {
nodeInfo, err := pl.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
if err != nil {
return 0, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v", nodeName, err))
}
// 计算节点上运行的Pod数量
podCount := len(nodeInfo.Pods)
// Pod数量越少得分越高
score := 100 - int64(podCount)
if score < 0 {
score = 0
}
return score, framework.NewStatus(framework.Success, "")
}
// 归一化分数
func (pl *PodDensityScore) ScoreExtensions() framework.ScoreExtensions {
return pl
}
func (pl *PodDensityScore) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, scores framework.NodeScoreList) *framework.Status {
// 找出最高和最低分
var highest int64 = 0
var lowest int64 = math.MaxInt64
for _, nodeScore := range scores {
if nodeScore.Score > highest {
highest = nodeScore.Score
}
if nodeScore.Score < lowest {
lowest = nodeScore.Score
}
}
// 避免除零错误
if highest == lowest {
return framework.NewStatus(framework.Success, "")
}
// 归一化到0-100范围
for i, nodeScore := range scores {
scores[i].Score = (nodeScore.Score - lowest) * 100 / (highest - lowest)
}
return framework.NewStatus(framework.Success, "")
}
通过以上方式,可以根据业务需求实现自定义的调度逻辑,使Kubernetes调度更好地适应特定场景。
关于init 函数的逻辑:之前只调度init container 中 request.cpu 最大的,但是现在会比较init container 中最大的request.cpu 和 正常运行的container 的综合request.cpu ,取两者中最大的进行pod 调度,这样更有效率。
在 Kubernetes 中,scheduler 对 Pod 所需资源的计算确实有特殊的规则,尤其是对于包含 init 容器的 Pod。根据您查看的 Kubernetes 代码,以下是现行的资源计算机制:
常规容器资源计算:
Init 容器资源计算:
假设一个 Pod 有两个 init 容器和两个常规容器:
Init 容器 1:
Init 容器 2:
常规容器 1 & 2 总和:
Pod 的有效资源请求将是:
这种计算方式在 Kubernetes 的调度器代码中实现,具体在 pkg/scheduler/framework/plugins/noderesources
包中。计算 Pod 请求资源的 GetPodResourceRequest
函数会分别计算 init 容器的资源最大值和常规容器的资源总和,然后取两者中的较大值。
总结来说,当前的 Kubernetes scheduler 不只是计算 CPU request 最多的 init 容器,而是:
这种机制确保了 scheduler 在调度时既考虑了 init 容器的最大资源需求,也考虑了常规容器的总体资源需求。