scheduler开发实战(二):带插件参数版
如果文章对你有用,可以点赞、好评、转发。
文章是在公司写的,代码都被加密了,也不能上传github,所以源代码就没上传了,不过按照文章来一步一步实现就行,如果有问题,评论区交流
建议全屏阅读,因为重要内容都写在代码里,代码行比较长
二:第一版的基础上,在配置文件中带插件参数。这样我们就可以把参数传递给NewDynamicScheduler函数,从而定制我们的调度器插件
这些参数是由k8s自动加载
X:修改NewDynamicScheduler函数,我们在配置文件中传递了一个插件参数给它
X:创建我们自定义的插件参数对象DynamicArgs,这个对象需要注册到scheduler自带的scheme中,否则k8s会无法从文件加载和转换我们自定义的对象
X:创建doc
X:创建types,必须,用来声明结构体
X:创建defaults,可选
X:创建register,必须,就是手动把pair(gvk,struct)注册到k8s内建的scheme中,以便k8s可以自动加载识别创建我们的对象(如DynamicArgs)
X:code-gen创建defaulter,可选,用来在初始化一个对象后对对象进行处理
X:code-gen创建deppcopy函数,必须,因为DynamicArgs必须实现runtime.Object接口
X:code-gen创建converter函数,必须,用于在不同的版本之间转换资源,可以用convertion-gen工具自动生成(如果有特殊需要可以删除对应的自动实现的函数,
并在同一个包下手动实现一份新的函数,推荐在另一个文件中实现,因为convertion-gen每次生成时都会清空旧文件
X:把所有版本的DynamicArgs的pair/defaulter/convert注册到k8s自带的scheme中
X:强制加载我们的文件,以便强制执行文件对应的init函数,强制注册到scheme中(否则有可能我们没有用到那些文件,从而导致没有加载,从而导致没有注册,从而导致启动失败)
下面我们就在第一版的基础上开始实现上面两个步骤
1:修改NewDynamicScheduler
import (
......
config "pkg/apis/plugins/config
......
)
func NewDynamicScheduler(plArgs runtime.Object, h framework.Handle) (framework.Plugin, error) { //插件工厂函数,用来创建插件对象
/*
1:这个config是我们项目自己创建的包,不是k8s自带的config包
只要我们的自定义对象DynamicArgs正确实现了types,正确实现了convert,正确regist到k8s内建的schme中,k8s系统就可以把kubeSchedulerconfiguration.yaml
中profiles.puginConfig.args转换成指定的对象,当plargs传递到这里时他已经被k8s自动转换成了内部版本,如果k8s无法正确识别或转换我们的DynamicArgs,那么这里指针强转就会失败
plArgs此时的实际类型会为runtime.Unknow,如果正确识别转换,那么plArgs此时的实际类型就是DynamicArgs。
2:这个DynamicArgs类型名字不是随便取的,如果我们的插件叫做Dynamic(profiles.puginConfig.name字段指定的),那么这个参数对象就必须叫做DynamicArgs,也就是说k8s他是这么做的:在读取
配置文件的时候,如果profiles.puginConfig.name叫做XXXX,那么k8s就会尝试把profiles.puginConfig.args解析成一个v1beta2版本的XXXXArgs对象,这个Args后缀是k8s代码里写死的,
如果kubescheduler.config.k8s.io组下面没有一个v1beta2版本的叫做DynamicArgs的struct,那么就会转换失败,那么plArgs就会是runtime.Unknow
3:k8s会把kubeSchedulerconfiguration.yaml文件解析成一个KubeSchedulerConfiguration对象,配置文件中的apiVersion字段是kubescheduler.config.k8s.io/v1beta2,
说明k8s会把配置文件解析成kubescheduler.config.k8s.io组下面v1beta2版本的KubeSchedulerConfiguration对象,但是因为K8s他是这么做的:在内部统一使用一个叫做__internal版本的对象,
只有在输入输出的时候才会使用到非__internal版本,我们叫做外部版本,如上面的v1beta2就是外部版本,因为我们读取配置文件时生成的KubeSchedulerConfiguration对象和DynamicArgs对象
都是外部版本(kubescheduler.config.k8s.io/v1beta2),所以我们需要一个转换函数,把它转换成内部版本(kubescheduler.config.k8s.io/__internal)
因为我们的profiles.puginConfig.args因为它是配置文件中的一个内嵌对象,所以profiles.puginConfig.args会被转换成kubescheduler.config.k8s.io/v1beta2版本的DynamicArgs,
即group和version(即gv)与配置文件的apiVersion字段保持一致,如果转换失败,那么plArgs的类型也会是runtime.Unknow
*/
args, ok := plArgs.(*config.DynamicArgs) //一个强转就行了,如果强转失败,说明出了问题,主要有三种可能:
//1:没有注册到k8s内建的scheme中
//2:没有实现kubescheduler.config.k8s.io/v1beta2和kubescheduler.config.k8s.io/__internal两个版本的DynamicArgs对象
//3:没有实现convert函数,导致DynamicArgs从v1beta2版本转换成__internal版本的时候转换失败
if !ok {
return nil, fmt.Errorf("error3")
}
fmt.Println("plArgs.PolicyConfigPath",args.PolicyConfigPath)
fmt.Println("plArgs.PromAddr",args.PromeAddr)
fmt.Println("plArgs.MetricsUpdatePeriodBySeconds",args.MetricsUpdatePeriodBySeconds)
fmt.Println("plArgs.ClusterName",args.ClusterName)
//下面是旧代码,没变
fmt.Println("I am NewDynamicScheduler")
state := &SchedulerState{
Value: 20240217,
}
go DoSomethingWithState(state) //一个线程去不断更新这个状态
return &DynamicScheduler{
Handle: h, //保存k8s传给我们的handler,我们可以用这个来获取nodes信息等信息
SchedulerState: state, //scheduler则通过这个指针去读取最新的状态
}, nil
}
2:创建插件参数对象DynamicArgs。
这个DynamicArgs必须放到kubescheduler.config.k8s.io group下面,而且我们必须至少实现两个版本v1beta2(因为配置文件的版本是v1beta2)和__internal(因为这是内部使用的版本)
两个版本实现流程一模一样,只是注册的版本不同,许多代码可以用code-gen生成,我们只要写好模板就行了,code-gen这里就不做详细介绍了,因为code-gen还没搞懂,目录问题非常多
路径一点点不对就无法生成,所以我后面会直接把code-gen生成的代码贴上来,然后大家复制粘贴一下就好,就不用自己去生成了(由于直接贴代码,所以注释中的路径看看就好,参考参考就行)
2.1 我们的项目叫做awesomeProject5,首先创建一个awesomeProject5/pkg/plugins/apis/config/v1beta2目录,然后在此目录下创建doc.go文件
// +k8s:deepcopy-gen=package
// +k8s:conversion-gen=awesomeProject5/pkg/plugins/apis/config //不用到v1beta2
// +groupName=kubescheduler.config.k8s.io
package v1beta2
2.2 创建types.go
package v1beta2
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object //提示code-gen生成这个接口
// DynamicArgs is the args struction of Dynamic scheduler plugin.
type DynamicArgs struct {
/*
DynamicArgs必须实现runtime.Object接口,此接口下面有两个方法,当我们在结构体内包含metav1.TypeMeta的时候我们就自动实现了第一个GetObjectKind() schema.ObjectKind方法
但是第二个方法需要我们手动实现,我们可以通过code-gen或者code-gen里面的deepcopy-gen来实现(code-gen脚本也是通过调用deepcopy-gen来生成代码)
*/
metav1.TypeMeta `json:",inline"`
// PolicyConfigPath specified the path of policy config.
PolicyConfigPath string `json:"policyConfigPath"` //因为是外部版本,是从配置文件解析,所以需要设置字段的json注释,如果不设置,那么就默认是字段名
PromeAddr string `json:"promeAddr"`
MetricsUpdatePeriodBySeconds metav1.Duration `json:"metricsUpdatePeriodBySeconds"`
ClusterName string `json:"clusterName"`
}
2.3 创建defaults.go
好像文件名也必须叫defaults.go
package v1beta2
func SetDefaults_DynamicArgs(obj *DynamicArgs) { //函数名必须叫做SetDefaults_XXX,XXX必须是结构体的名字,对于DynamicArgs,那么就是SetDefaults_DynamicArgs
if obj.PolicyConfigPath == "" {
obj.PolicyConfigPath = "/etc/kubernetes/abcdefg.yaml" //如果我们没有在参数里配置这个东西,那么就给一个默认路径,这是只是简单检查一下policyConfigPath,当然也可以有更多功能
}
return
}
2.4 创建register.go,主要实现注册到scheme的功能
文件名随便叫
package v1beta2
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/klog/v2"
kubeschedulerschemev1beta2 "k8s.io/kube-scheduler/config/v1beta2"
kubeschedulerconfig "k8s.io/kubernetes/pkg/scheduler/apis/config"
)
//必须和KubeSchedulerConfiguration的版本保持一致,即group=kubescheduler.config.k8s.io,version=v1beta2
var SchemeGroupVersion = schema.GroupVersion{Group: kubeschedulerconfig.GroupName, Version: "v1beta2"}
var (
localSchemeBuilder = &kubeschedulerschemev1beta2.SchemeBuilder //必须注册到k8s内建的scheme中,否则k8s就不知道我们的pair(gvk,struct)
//SchemeBuilder:如其名
AddToScheme = localSchemeBuilder.AddToScheme //AddToScheme类似于options机制,即AddToScheme内部保存了我们所登记的所有pair(gvk,struct),
//当我们传递一个scheme对象给AddToScheme的时候,AddToScheme函数就会把内部登记的pair注册到传进来的scheme对象中
//这个AddToScheme我们在外面调用
)
// addKnownTypes registers known types to the given scheme
func addKnownTypes(scheme *runtime.Scheme) error { //注册pair到scheme
scheme.AddKnownTypes(SchemeGroupVersion,
&DynamicArgs{},
)
return nil
}
/*
init函数负责把所有信息都登记到localSchemeBuilder里,当外部调用AddToScheme的时候就会调用我们在init里注册的函数,从而把pair、defaulter、convert注册到指定的scheme
因为要k8s知道我们的pair,那么后面我们肯定是注册到k8s内建的scheme中
!!只有这个文件被加载,init函数才会被执行,如果文件根本没有被加载,那么根本就不会执行,也就根本不会被注册,所以我们需要使用import语句强制导入,即强制加载此文件
*/
func init() {
// We only register manually written functions here. The registration of the
// generated functions takes place in the generated files. The separation
// makes the code compile even when the generated files are missing.
localSchemeBuilder.Register(addKnownTypes) //注册pair(gvk,struct)
localSchemeBuilder.Register(RegisterDefaults) //注册defaulter,这个RegisterDefaults函数是code-gen自动生成的
localSchemeBuilder.Register(RegisterConversions) //注册convert函数,这个函数也是code-gen自动生成的
fmt.Println("load DynamicArgs v1beta2")
}
2.5 工具code-gen工具生成defaulter函数,我们直接贴代码,就不贴
工具自动生成的文件叫zz_generated.defaulter.go,文件里的所有代码都是自动生成的,当重新生成的时候会清空文件中的所有内容,所以所有改动都应该在其他文件中实现
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
// Code generated by defaulter-gen. DO NOT EDIT.
package v1beta2
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// RegisterDefaults adds defaulters functions to the given scheme.
// Public to allow building arbitrary schemes.
// All generated defaulters are covering - they call all nested defaulters.
func RegisterDefaults(scheme *runtime.Scheme) error {
//这个函数类似于前面的addKnowTypes函数,就是实现登记功能
//就是实现登记defaulter到scheme,我们需要把这个函数保存到前面的localBuilder里面,
//以便调用AddToScheme的时候可以一同注册到scheme里
//因为defaulter函数、convert函数、pair(gvk,struct)都是通过scheme来获取的
scheme.AddTypeDefaultingFunc(&DynamicArgs{}, func(obj interface{}) { SetObjectDefaults_DynamicArgs(obj.(*DynamicArgs)) })
return nil
}
func SetObjectDefaults_DynamicArgs(in *DynamicArgs) {
SetDefaults_DynamicArgs(in) //这个函数就是我们在defaults.go里面手动实现的,也就是说code-gen只是生成一个默认函数a,这个函数调用了一个函数b,这个b需要我们手动实现
}
2.6 通过code-gen工具给DynamicArgs实现runtime.Object的DeepCopy函数
工具生成的文件名叫zz_generated.deepcopy.go
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1beta2
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DynamicArgs) DeepCopyInto(out *DynamicArgs) {
*out = *in
out.TypeMeta = in.TypeMeta
out.MetricsUpdatePeriodBySeconds = in.MetricsUpdatePeriodBySeconds
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamicArgs.
func (in *DynamicArgs) DeepCopy() *DynamicArgs {
if in == nil {
return nil
}
out := new(DynamicArgs)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *DynamicArgs) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
2.7 通过code-gen生成 convert函数
生成的文件名叫zz_generated.conversion.go
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
// Code generated by conversion-gen. DO NOT EDIT.
package v1beta2
import (
/*
注意下包的问题,如果有报错,自己修正路径,
这个config表示的是__internal版本,也就是说我们的__internal版本的DynamicArgs是放在config包下面且在config包里面注册到scheme的,这个包名可以随便叫
不过如果修改包名成XXX,下面的Convert_v1beta2_DynamicArgs_To_config_DynamicArgs也要改成Convert_v1beta2_DynamicArgs_To_XXX_DynamicArgs
因为他的这些名字的格式都是固定的,表示把v1beta2包下面的DynamicArgs转换成config包下面的DynamicArgs
*/
config "awesomeProject5/pkg/plugins/apis/config"
unsafe "unsafe"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
)
// RegisterConversions adds conversion functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterConversions(s *runtime.Scheme) error {
//同addKnowTypes和RegisterDefaulter,都是实现登记注册功能
//我们也需要把它保存到localBuilder里面,以便外部调用AddToScheme的时候注册到该scheme中
//外部v1beta2->内部__internal
if err := s.AddGeneratedConversionFunc((*DynamicArgs)(nil), (*config.DynamicArgs)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta2_DynamicArgs_To_config_DynamicArgs(a.(*DynamicArgs), b.(*config.DynamicArgs), scope)
}); err != nil {
return err
}
//内部__internal->外部v1beta2, 我们这里没用到,但还是写一下
if err := s.AddGeneratedConversionFunc((*config.DynamicArgs)(nil), (*DynamicArgs)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_DynamicArgs_To_v1beta2_DynamicArgs(a.(*config.DynamicArgs), b.(*DynamicArgs), scope)
}); err != nil {
return err
}
return nil
}
func autoConvert_v1beta2_DynamicArgs_To_config_DynamicArgs(in *DynamicArgs, out *config.DynamicArgs, s conversion.Scope) error {
out.PolicyConfigPath = in.PolicyConfigPath
out.PromeAddr = in.PromeAddr
out.MetricsUpdatePeriodBySeconds = in.MetricsUpdatePeriodBySeconds
out.ClusterName = in.ClusterName
return nil
}
// Convert_v1beta2_DynamicArgs_To_config_DynamicArgs is an autogenerated conversion function.
func Convert_v1beta2_DynamicArgs_To_config_DynamicArgs(in *DynamicArgs, out *config.DynamicArgs, s conversion.Scope) error {
return autoConvert_v1beta2_DynamicArgs_To_config_DynamicArgs(in, out, s)
}
func autoConvert_config_DynamicArgs_To_v1beta2_DynamicArgs(in *config.DynamicArgs, out *DynamicArgs, s conversion.Scope) error {
out.PolicyConfigPath = in.PolicyConfigPath
out.PromeAddr = in.PromeAddr
out.MetricsUpdatePeriodBySeconds = in.MetricsUpdatePeriodBySeconds
out.ClusterName = in.ClusterName
return nil
}
// Convert_config_DynamicArgs_To_v1beta2_DynamicArgs is an autogenerated conversion function.
func Convert_config_DynamicArgs_To_v1beta2_DynamicArgs(in *config.DynamicArgs, out *DynamicArgs, s conversion.Scope) error {
return autoConvert_config_DynamicArgs_To_v1beta2_DynamicArgs(in, out, s)
}
至此,v1beat2版本的DynamicArgs已经实现完成了,接下来再实现一版 __internal 版本的DynamicArgs就可以了,操作和v1beta2几乎一摸一样。
只有四个地方有区别:
1:所在的包名,即package v1beta2换成package config;
2:注册所使用的的SchemeBuilder不同;
3:注册时使用的版本不同
4:无需生成conversion函数,因为我们已经在v1beta2里登记和注册生成了v1beta2版本DynamicArgs和config版本DynamicArgs版本互相转换的转换函数
即config包的registry.go文件的init函数里无需调用localBuilder.Register(RegisterConversion)
下面我就仅仅点出不同,其他的就一个包名不同了
package config //包变成了config,不是v1beta2
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/klog/v2"
kubeschedulerconfig "k8s.io/kubernetes/pkg/scheduler/apis/config"
)
//注意:这里只有一个版本不同,group还是kubescheduler.config.k8s.io,只是version字段变成了__internal
var SchemeGroupVersion = schema.GroupVersion{Group: kubeschedulerconfig.GroupName, Version: runtime.APIVersionInternal}
var (
localSchemeBuilder = &kubeschedulerconfig.SchemeBuilder //这里我们使用的是config下面的SchemeBuilder,而v1beta2版本中我们使用的是k8s v1beta2下面的SchemeBuilder
// AddToScheme is a global function that registers this API group & version to a scheme
AddToScheme = localSchemeBuilder.AddToScheme
)
...其他一切都相同...
3:创建awesomeProject5/pkg/apis/plugins/config/scheme/scheme.go
文件名随便叫,目录名随便叫,我们就这里目录为awesomeProject5/pkg/apis/plugins/config/scheme,文件名为scheme.go
package scheme
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
kubeschedulerscheme "k8s.io/kubernetes/pkg/scheduler/apis/config/scheme"
myconfig "awesomeProject5/pkg/apis/plugins/config"
myv1beta2 "awesomeProject5/pkg/apis/plugins/config/v1beta2"
)
var (
// Re-use the in-tree Scheme.
Scheme = kubeschedulerscheme.Scheme //这是k8s自带的Scheme,我们就是注册到整个scheme里
// Codecs provides access to encoding and decoding for the scheme.
Codecs = serializer.NewCodecFactory(Scheme, serializer.EnableStrict) //编码解码器,是根据特定scheme创建的,可以用来从文件加载对象,但是我们第二版没有用到,可以不写
A=1
)
func init() {
AddToScheme(Scheme) //注册到k8s自带的scheme
}
// AddToScheme builds the kubescheduler scheme using all known versions of the kubescheduler api.
func AddToScheme(scheme *runtime.Scheme) {
utilruntime.Must(myconfig.AddToScheme(scheme)) //调用__internal版本的AddToScheme,并执行该文件里面的init
utilruntime.Must(myv1beta2.AddToScheme(scheme)) //调用v1beta2版本的AddToScheme,并执行该文件里面的init
fmt.Println("load myconfig and myv1beta2 ok")
}
4:强制加载
直接在main函数里访问config里面的变量就行
import (
myscheme "awesomeProject5/pkg/apis/plugins/config/scheme"
)
func main(){
......
myscheme.A=2 //这里会加载config,然后调用init,然后在AddToScheme中又触发myv1beta2.init......,然后执行AddToScheme,然后就完成了注册加载
//办法虽然土,但有用。。。。。。。
......
}
然后编译之后,我们运行程序,控制台打印出下面的信息就说明我们的DynamicScheduler成功启动了文章来源:https://www.toymoban.com/news/detail-833981.html
......
load DynamicArgs config
load DynamicArgs v1beta2
load myconfig and myv1beta2 ok
I0218 14:27:22.070130 20248 serving.go:348] Generated self-signed cert in-memory
......
W0218 14:02:14.221896 16008 authentication.go:340] No authentication-kubeconfig provided in order to lookup requestheader-client-ca-file in configmap/extension-api
server-authentication in kube-system, so request-header client certificate authentication won't work.
W0218 14:02:14.222412 16008 authorization.go:193] No authorization-kubeconfig provided, so SubjectAccessReview of authorization tokens won't work.
plArgs.PolicyConfigPath XXXXXX
plArgs.PromAddr XXXXXXX
plArgs.MetricsUpdatePeriodBySeconds XXXXXXX
plArgs.ClusterName XXXXXXX
I am NewDynamicScheduler
文章来源地址https://www.toymoban.com/news/detail-833981.html
到了这里,关于k8s scheduler开发实战(二):带插件参数版的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!