使用 Kubernetes Scheduler Framework 插件机制实现 Filter 插件的最小可运行 Demo
使用 Kubernetes Scheduler Framework 插件机制实现 Filter 插件的最小可运行 Demo
本文介绍如何基于 Kubernetes v1.27.0 的 Go SDK(k8s.io/kubernetes
),使用 Kubernetes Scheduler Framework 插件机制实现一个最小的 Filter 插件 Demo。该 Demo 展示了一个简单的 Filter 插件,用于过滤不符合特定条件的节点,并将其集成到 Kubernetes 调度器中。文章还包括解决依赖问题的详细步骤,以确保项目在 v1.27.0 环境下正常运行。
前提条件
- Go 环境(建议 1.18 或以上)
- Kubernetes 集群(可使用 Minikube 或 Kind 搭建 v1.27.0 环境)
k8s.io/kubernetes
v1.27.0 SDK- 熟悉 Kubernetes 调度器和 Scheduler Framework 基本概念
背景知识
Kubernetes Scheduler Framework 是一个可扩展的框架,允许开发者通过插件机制自定义调度逻辑。Filter 插件是调度过程中的核心组件,用于过滤不满足 Pod 调度需求的节点。本 Demo 实现一个简单的 Filter 插件,检查节点是否包含标签 scheduler.custom/enabled: true
,只有满足条件的节点才会被保留。
实现步骤
以下是实现最小可运行 Filter 插件 Demo 的详细步骤,包括代码实现、依赖管理和部署。
1. 项目结构
创建以下项目结构:
custom-scheduler/
├── main.go
├── pkg/
│ └── plugins/
│ └── filter/
│ └── custom_filter.go
├── go.mod
├── scheduler-config.yaml
└── Dockerfile
2. 初始化 Go 模块
在项目根目录下初始化 Go 模块并添加依赖:
go mod init custom-scheduler
go get k8s.io/kubernetes@v1.27.0
go get k8s.io/component-base@v0.27.0
go get k8s.io/klog/v2@v2.100.1
解决依赖问题
在 v1.27.0 中,运行 go mod tidy
可能会遇到模块解析错误,例如 k8s.io/cloud-provider
、k8s.io/csi-translation-lib
和 k8s.io/mount-utils
被解析为无效的 v0.0.0
版本。为解决此问题,编辑 go.mod
文件,明确指定所有相关模块的版本,并使用 replace
指令确保一致性:
module custom-schedulergo 1.18require (k8s.io/api v0.27.0k8s.io/apimachinery v0.27.0k8s.io/client-go v0.27.0k8s.io/component-base v0.27.0k8s.io/klog/v2 v2.100.1k8s.io/kubernetes v1.27.0
)replace (k8s.io/api => k8s.io/api v0.27.0k8s.io/apimachinery => k8s.io/apimachinery v0.27.0k8s.io/client-go => k8s.io/client-go v0.27.0k8s.io/cloud-provider => k8s.io/cloud-provider v0.27.0k8s.io/component-base => k8s.io/component-base v0.27.0k8s.io/component-helpers => k8s.io/component-helpers v0.27.0k8s.io/controller-manager => k8s.io/controller-manager v0.27.0k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.27.0k8s.io/dynamic-resource-allocation => k8s.io/dynamic-resource-allocation v0.27.0k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.27.0k8s.io/kubelet => k8s.io/kubelet v0.27.0k8s.io/mount-utils => k8s.io/mount-utils v0.27.0
)
运行以下命令整理依赖并生成 vendor
目录:
go clean -modcache
go mod tidy
go mod vendor
如果仍然报错,尝试切换模块代理:
export GOPROXY=https://proxy.golang.org,direct
go mod tidy
go mod vendor
使用 vendor
模式构建以绕过在线模块解析:
go build -mod=vendor -o custom-scheduler .
禁用默认插件(可选)
某些默认插件(如 NodeVolumeLimits
和 VolumeZone
)可能引入不必要的依赖(如 k8s.io/cloud-provider
)。为最小化依赖,可以在调度器配置文件中禁用所有默认插件,仅启用自定义插件(见步骤 5)。
3. 实现 Filter 插件
在 pkg/plugins/filter/custom_filter.go
中实现自定义 Filter 插件,检查节点标签:
package filterimport ("context""k8s.io/apimachinery/pkg/runtime"v1 "k8s.io/api/core/v1"framework "k8s.io/kubernetes/pkg/scheduler/framework""k8s.io/klog/v2"
)type CustomFilterPlugin struct{}var _ framework.FilterPlugin = &CustomFilterPlugin{}func (pl *CustomFilterPlugin) Name() string {return "CustomFilter"
}func (pl *CustomFilterPlugin) Filter(ctx context.Context,state *framework.CycleState,pod *v1.Pod,nodeInfo *framework.NodeInfo,
) *framework.Status {labels := nodeInfo.Node().GetLabels()if labels["scheduler.custom/enabled"] != "true" {klog.Infof("Node %s filtered out: missing label scheduler.custom/enabled=true", nodeInfo.Node().Name)return framework.NewStatus(framework.Unschedulable, "missing required label")}klog.Infof("Node %s passed custom filter", nodeInfo.Node().Name)return nil
}func New(_ runtime.Object, handle framework.Handle) (framework.Plugin, error) {return &CustomFilterPlugin{}, nil
}
4. 实现主程序
在 main.go
中注册插件并启动调度器:
package mainimport ("context""custom-scheduler/pkg/plugins/filter""k8s.io/component-base/logs""k8s.io/kubernetes/cmd/kube-scheduler/app""k8s.io/klog/v2"
)func main() {logs.InitLogs()defer logs.FlushLogs()command := app.NewSchedulerCommand(app.WithPlugin("CustomFilter", filter.New),)if err := command.ExecuteContext(context.Background()); err != nil {klog.Fatal(err)}
}
5. 配置调度器
创建调度器配置文件 scheduler-config.yaml
,启用自定义插件并禁用默认插件以最小化依赖:
apiVersion: kubescheduler.config.k8s.io/v1beta2
kind: KubeSchedulerConfiguration
profiles:- schedulerName: custom-schedulerplugins:filter:enabled:- name: CustomFilterdisabled:- name: "*"preFilter:disabled:- name: "*"score:disabled:- name: "*"bind:disabled:- name: "*"
注意:禁用所有默认插件可减少依赖,但可能影响完整调度流程(如 Pod 绑定)。如需完整功能,可启用必要的默认插件(如 DefaultBinder
),并检查是否引入新依赖。
6. 编译与部署
编译程序
使用 vendor
模式编译:
go build -mod=vendor -o custom-scheduler .
创建 Docker 镜像
创建 Dockerfile
:
FROM golang:1.18
WORKDIR /app
COPY . .
RUN go build -mod=vendor -o custom-scheduler .
CMD ["/app/custom-scheduler"]
构建镜像:
docker build -t custom-scheduler:latest .
部署调度器
创建 scheduler-deployment.yaml
:
apiVersion: v1
kind: ConfigMap
metadata:name: custom-scheduler-confignamespace: kube-system
data:scheduler-config.yaml: |apiVersion: kubescheduler.config.k8s.io/v1beta2kind: KubeSchedulerConfigurationprofiles:- schedulerName: custom-schedulerplugins:filter:enabled:- name: CustomFilterdisabled:- name: "*"preFilter:disabled:- name: "*"score:disabled:- name: "*"bind:disabled:- name: "*"
---
apiVersion: apps/v1
kind: Deployment
metadata:name: custom-schedulernamespace: kube-systemlabels:app: custom-scheduler
spec:replicas: 1selector:matchLabels:app: custom-schedulertemplate:metadata:labels:app: custom-schedulerspec:containers:- name: custom-schedulerimage: custom-scheduler:latestargs:- --config=/etc/scheduler/scheduler-config.yamlvolumeMounts:- name: configmountPath: /etc/schedulervolumes:- name: configconfigMap:name: custom-scheduler-config
应用部署:
kubectl apply -f scheduler-deployment.yaml
为节点添加标签
为测试节点添加标签:
kubectl label nodes <node-name> scheduler.custom/enabled=true
创建测试 Pod
创建 test-pod.yaml
:
apiVersion: v1
kind: Pod
metadata:name: test-pod
spec:schedulerName: custom-schedulercontainers:- name: nginximage: nginx
应用 Pod:
kubectl apply -f test-pod.yaml
7. 验证
检查 Pod 是否调度到带有 scheduler.custom/enabled=true
标签的节点:
kubectl get pods -o wide
查看调度器日志:
kubectl logs -n kube-system -l app=custom-scheduler
如果节点没有所需标签,Pod 将保持 Pending 状态,日志会显示过滤原因。
8. 常见问题与解决方案
依赖解析错误
如果运行 go mod tidy
报错(如 404 Not Found
),尝试以下解决方案:
- 切换模块代理:
export GOPROXY=https://proxy.golang.org,direct
go mod tidy
go mod vendor
- 使用 v1.27.3:如果 v1.27.0 模块发布有问题,修改
go.mod
使用k8s.io/kubernetes@v1.27.3
和对应的v0.27.3
版本。 - 克隆 Kubernetes 源码:作为最后手段,克隆 Kubernetes v1.27.0 源码并在源码树中开发:
git clone -b v1.27.0 https://github.com/kubernetes/kubernetes
调度器功能不完整
禁用所有默认插件可能导致 Pod 无法绑定到节点。如需完整调度流程,在 scheduler-config.yaml
中启用必要插件(如 DefaultBinder
):
plugins:bind:enabled:- name: DefaultBinder
总结
通过以上步骤,我们基于 Kubernetes v1.27.0 SDK 实现并部署了一个简单的 Filter 插件 Demo。该插件通过检查节点标签实现自定义过滤逻辑,并成功集成到 Kubernetes 调度器中。依赖管理是实现过程中的关键挑战,通过明确指定版本、使用 vendor
模式和禁用默认插件,我们成功解决了模块解析问题。