暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

mac 上学习k8s系列(20)CRD (part II)

        Kubernetes目前常使用CRD+Controller的方式扩展API,官方提供了CRD代码的自动生成器code-generator。

        最新的工具kubebuilder ,已经非常方便我们完成 CRD/Controller,甚至 Operator 的开发(当然 Operator 的开发也有专用的 operator-sdk开源框架)

和k8s.io/code-generator类似,是一个码生成工具,用于为你的CRD生成kubernetes-style API实现。区别在于:

  • Kubebuilder不会生成informers、listers、clientsets,而code-generator会。

  • Kubebuilder会生成Controller、Admission Webhooks,而code-generator不会。

  • Kubebuilder会生成manifests yaml,而code-generator不会。

  • Kubebuilder还带有一些其他便利性设施。

使用步骤如下:

    go mod init my.domain

    1, 初始化

      kubebuilder init --domain example.com --license apache2 --owner "The Kubernetes authors"

      2, 创建CRD api

        kubebuilder create api --group webapp --version v1 --kind Frigate

        3, 安装CRD

          make install

          4, 启动controller(本地)

            make run

            思路基本一致,本文还是从古老的官方code-generator进行介绍,官方工具地址:

              k8s.io/code-generator/generate-groups.sh

              code-generator提供了以下工具为kubernetes中的资源生成代码:

              1,deepcopy-gen: 生成深度拷贝方法,避免性能开销

              deepcopy-gen是用于自动生成DeepCopy函数的工具,使用方法:

                在文件中添加注释 // +k8s:deepcopy-gen=package
                为单个类型添加自动生成 // +k8s:deepcopy-gen=true
                为单个类型关闭自动生成 // +k8s:deepcopy-gen=false

                2,client-gen:为资源生成标准的操作方法

                (get,list,create,update,patch,delete,deleteCollection,watch),在 pkg/apis/GROUP/{VERSION}/types.go中使用,使用

                // +genclient标记对应类型生成的客户端, 如果与该类型相关联的资源不是命名空间范围的(例如PersistentVolume), 则还需要附加 

                // + genclient:nonNamespaced标记,

                  // +genclient - 生成默认的客户端动作函数(create, update, delete, get, list, update, patch, watch以及 是否生成updateStatus取决于.Status字段是否存在)。
                  // +genclient:nonNamespaced - 所有动作函数都是在没有名称空间的情况下生成
                  // +genclient:onlyVerbs=create,get - 指定的动作函数被生成.
                  // +genclient:skipVerbs=watch - 生成watch以外所有的动作函数.
                  // +genclient:noStatus - 即使 .Status字段存在也不生成updateStatus动作函数

                  3,informer-gen: 生成informer,提供事件机制来相应kubernetes的event

                  4,lister-gen: 为get和list方法提供只读缓存层

                  5,conversion-gen是用于自动生成在内部和外部类型之间转换的函数的工具。一般的转换代码生成任务涉及三套程序包:

                  一套包含内部类型的程序包,

                  一套包含外部类型的程序包,

                  单个目标程序包(即,生成的转换函数所在的位置,以及开发人员授权的转换功能所在的位置)。包含内部类型的包在Kubernetes的常规代码生成框架中扮演着称为 peerpackage的角色。

                  使用方法

                    标记转换内部软件包 // +k8s:conversion-gen=<import-path-of-internal-package>
                    标记转换外部软件包 // +k8s:conversion-gen-external-types=<import-path-of-external-package>
                    标记不转换对应注释或结构 // +k8s:conversion-gen=false

                    6,defaulter-gen:用于生成Defaulter函数

                      为包含字段的所有类型创建defaulters, // +k8s:defaulter-gen=<field-name-to-flag>
                      所有都生成 // +k8s:defaulter-gen=true|false

                      7,go-to-protobuf:通过go struct生成pb idl

                      8,import-boss:在给定存储库中强制执行导入限制

                      9,openapi-gen:生成openAPI定义,使用方法:

                        +k8s:openapi-gen=true 为指定包或方法开启
                        +k8s:openapi-gen=false 指定包关闭

                        10,register-gen:生成register

                        11,set-gen

                        其中informer和listers是构建controller的基础,kubebuilder也是基于informer的机制生成的代码。code-generator还专门整合了这些gen,形成了generate-groups.sh和generate-internal-groups.sh这两个脚本.

                                默认的生成脚本在code-generator下的generate-groups.sh,如果想生成自定义的crd,运行下面的命令:

                          ./generate-groups.sh all github.com/nevermore/project/pkg/client github.com/nevermore/project/pkg/apis "foo:v1 bar:v1beta1

                          在我们的源代码中出现了很多,类似go generator的编译tag:

                            doc.go
                            // +k8s:deepcopy-gen=package,register
                            // +groupName=samplecontroller.k8s.io
                              types.go
                              // +genclient
                              // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

                              出现了这样的tag,这些tag到底是什么意思呢,有什么作用呢?

                              1,分类,其实code-generator将tag分为了两种

                              Global tags: 全局的tag,放在具体版本的doc.go文件中

                              Local tags: 本地的tag,放在types.go文件中的具体的struct上.

                              tag的使用语法为:

                                // +tag-name

                                // +tag-name=value

                                并且 这些注释块必须分开,这也是源代码中 注释存在分割的原因.

                                A,Global

                                全局的tag是写在doc.go中的,典型的内容如下:

                                  // +k8s:deepcopy-gen=package


                                  // Package v1 is the v1 version of the API.
                                  // +groupName=example.com
                                  package v1

                                  注意: 空行不能省

                                  +k8s:deepcopy-gen=: 它告诉deepcopy默认为该包中的每一个类型创建deepcopy方法,如果不需要深度复制,可以选择关闭此功能

                                    // +k8s:deepcopy-gen=false

                                    如果不启用包级别的深度复制,那么就需要在每个类型上加入深度复制

                                      // +k8s:deepcopy-gen=true

                                      +groupName: 定义group的名称,注意别弄错了.

                                      注意 这里是 +k8s:deepcopy-gen=,最后是 = ,和local中的区别开来.


                                      B,local

                                      本地的tag直接写在类型上,典型的内容如下:

                                        // +genclient
                                        // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
                                        // Foo is a specification for a Foo resource
                                        type Foo struct {
                                        metav1.TypeMeta `json:",inline"`
                                        metav1.ObjectMeta `json:"metadata,omitempty"`


                                        Spec FooSpec `json:"spec"`
                                        Status FooStatus `json:"status"`
                                        }

                                        可以看到local支持两种tag

                                        +genclient: 此标签是告诉client-gen,为此类型创建clientset,但也有以下几种用法.

                                        1,对于集群范围内的资源(没有namespace限制的),需要使用

                                          // +genclient:nonNamespaced

                                          ,生成的clientset中的namespace()方法就不再需要传入参数

                                          2,使用子资源分离的,例如/status分离的,则需要使用+genclient:noStatus,来避免更新到status资源(当然代码的struct中也没有status)

                                          3,对于其他的值,这里不做过多的解释,请参照

                                            // +genclient:noVerbs
                                            // +genclient:onlyVerbs=create,delete
                                            // +genclient:skipVerbs=get,list,create,update,patch,delete,deleteCollection,watch
                                            // +genclient:method=Create,verb=create,result=k8s.io/apimachinery/pkg/apis/meta/v1.Status

                                            +k8s:deepcopy-gen:interfaces=: 为struct生成实现 tag值的DeepCopyXxx方法,例如:

                                              // +k8s:deepcopy-gen:interfaces=example.com/pkg/apis/example.SomeInterface

                                              将生成 DeepCopySomeInterface() SomeInterface方法


                                              代码生成实践

                                              1,在$GOPATH/src/新建好相应的路径(go mod 同理)

                                                mkdir -p $GOPATH/src/github.com/nevermore/project/pkg/client
                                                mkdir -p $GOPATH/src/github.com/nevermore/project/pkg/apis/foo/v1

                                                进入到v1路径下,新建三个文件

                                                  touch doc.go types.go regsiter.go
                                                    pkg/apis/ip/v1/types.go

                                                    该文件包含了资源的数据结构,对应yamltypes不能有interface{} ,否则自动生成的时候会生成出错,因此其实也不建议通过代码自动生成,还是手动去编写会更好。

                                                    修改每个文件开头为package v1;同理配置apis/bar/v1beta1相应的文件。

                                                    最终生成相应的

                                                      clientset、listers、informers

                                                      准备好上述3个文件后,我们就可以用generator进行代码生成,生成完毕后,我们分析下源码结构:

                                                      生成的deepcopy.文件,里面定义每个结构体的深拷贝函数:

                                                        pkg/apis/ip/v1/zz_generated.deepcopy.go 
                                                        func (in *Ip) DeepCopyInto(out *Ip)
                                                        func (in *Ip) DeepCopy() *Ip
                                                        func (in *Ip) DeepCopyObject() runtime.Object

                                                         生成的client文件,目录层级如下:

                                                          pkg/client/
                                                          clientset/versioned
                                                                  clientset.go
                                                                doc.go
                                                                fake
                                                                   clientset_generated.go
                                                                   doc.go
                                                                   register.go
                                                                scheme
                                                          doc.go
                                                          register.go
                                                                typed/ip/v1
                                                          doc.go
                                                          fake
                                                          doc.go
                                                          fake_ip.go
                                                          fake_ip_client.go
                                                                    generated_expansion.go
                                                          ip.go
                                                          ip_client.go
                                                                informers/externalversions
                                                                            factory.go
                                                                            generic.go
                                                                            internalinterfaces                    
                                                                           factory_interfaces.go 
                                                                            ip
                                                          interface.go
                                                                              v1/                 
                                                                            interface.go
                                                                            ip.go
                                                                 listers/ip/v1 
                                                                  expansion_generated.go
                                                                  ip.go

                                                          在cllientset.go中定义了clientset结构体

                                                            func New(c rest.Interface) *Clientset 


                                                            type Clientset struct {
                                                            *discovery.DiscoveryClient
                                                            rocduV1 *rocduv1.RocduV1Client
                                                            }

                                                             clientset_generated.go定义了测试用的clientset

                                                              type Clientset struct {
                                                              testing.Fake
                                                              discovery *fakediscovery.FakeDiscovery
                                                              tracker testing.ObjectTracker
                                                              }

                                                              register.go对schema进行了注册:

                                                                var scheme = runtime.NewScheme()
                                                                var codecs = serializer.NewCodecFactory(scheme)


                                                                var localSchemeBuilder = runtime.SchemeBuilder{
                                                                rocduv1.AddToScheme,
                                                                }

                                                                scheme/register.go是真正完成schema注册的地方

                                                                  var Scheme = runtime.NewScheme()
                                                                  var Codecs = serializer.NewCodecFactory(Scheme)
                                                                  var ParameterCodec = runtime.NewParameterCodec(Scheme)
                                                                  var localSchemeBuilder = runtime.SchemeBuilder{
                                                                  rocduv1.AddToScheme,
                                                                  }

                                                                  Scheme定义了序列化和反序列化API对象的方法,用于将group、版本和类型信息转换为Go模式和从Go模式转换为Go模式的类型注册表,以及不同版本的Go模式之间的映射。当和API Server通信的时候能够处理新的types类型的话就需要先让client能够知道有这个新的types类型存在。

                                                                          AddToScheme 会利用到反射,因此新定义的types类型的结构体的命名必须要和自定义的Kind的命名(如VirtualService)保持一致,否则会找不到对应的kind,

                                                                  接着看下typed/ip/v1/fake/fake_ip.go

                                                                    type FakeIps struct {
                                                                    Fake *FakeRocduV1
                                                                    ns string
                                                                    }

                                                                    typed/ip/v1/fake/fake_ip_client.go

                                                                      type FakeRocduV1 struct {
                                                                      *testing.Fake
                                                                      }

                                                                      generated_expansion.go

                                                                        type IpExpansion interface{}

                                                                        ip.go

                                                                          type ips struct {
                                                                          client rest.Interface
                                                                          ns string
                                                                          }
                                                                            type IpInterface interface {
                                                                            Create(ctx context.Context, ip *v1.Ip, opts metav1.CreateOptions) (*v1.Ip, error)
                                                                            Update(ctx context.Context, ip *v1.Ip, opts metav1.UpdateOptions) (*v1.Ip, error)
                                                                            Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error
                                                                            DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error
                                                                            Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.Ip, error)
                                                                            List(ctx context.Context, opts metav1.ListOptions) (*v1.IpList, error)
                                                                            Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error)
                                                                            Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.Ip, err error)
                                                                            IpExpansion
                                                                            }

                                                                            ip_client.go

                                                                              type RocduV1Client struct {
                                                                              restClient rest.Interface
                                                                              }
                                                                              func (c *RocduV1Client) Ips(namespace string) IpInterface

                                                                              informers/externalversions/factory.go

                                                                                type sharedInformerFactory struct {
                                                                                client versioned.Interface
                                                                                namespace string
                                                                                tweakListOptions internalinterfaces.TweakListOptionsFunc
                                                                                lock sync.Mutex
                                                                                defaultResync time.Duration
                                                                                customResync map[reflect.Type]time.Duration




                                                                                informers map[reflect.Type]cache.SharedIndexInformer
                                                                                // startedInformers is used for tracking which informers have been started.
                                                                                // This allows Start() to be called multiple times safely.
                                                                                startedInformers map[reflect.Type]bool
                                                                                }

                                                                                informers/externalversions/generic.go

                                                                                  type genericInformer struct {
                                                                                  informer cache.SharedIndexInformer
                                                                                  resource schema.GroupResource
                                                                                  }

                                                                                  informers/externalversions/ip/interface.go

                                                                                    type Interface interface {
                                                                                    // V1 provides access to shared informers for resources in V1.
                                                                                    V1() v1.Interface
                                                                                    }


                                                                                    type group struct {
                                                                                    factory internalinterfaces.SharedInformerFactory
                                                                                    namespace string
                                                                                    tweakListOptions internalinterfaces.TweakListOptionsFunc
                                                                                    }

                                                                                    v1/ interface.go

                                                                                      type version struct {
                                                                                      factory internalinterfaces.SharedInformerFactory
                                                                                      namespace string
                                                                                      tweakListOptions internalinterfaces.TweakListOptionsFunc
                                                                                      }

                                                                                      以上就是informers的源码内容,下面看看listener

                                                                                      listers/ip/v1 expansion_generated.go

                                                                                        type IpListerExpansion interface{}

                                                                                        ip.go

                                                                                          type ipLister struct {
                                                                                          indexer cache.Indexer
                                                                                          }
                                                                                            type ipNamespaceLister struct {


                                                                                            indexer cache.Indexer
                                                                                            namespace string
                                                                                            }

                                                                                            生成完上述代码,我们就可以编写对应controller来管理上述资源,并通过kubectl操作对应的yaml文件。


                                                                                            文章转载自golang算法架构leetcode技术php,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                                                                            评论