项目作者: schwarzeni

项目描述 :
参照官方样例项目 https://github.com/kubernetes/sample-controller 中的部分内容编写,部分文件中 `import` 部分的相关依赖根据项目不用的命名情况需要作出相应的修改。
高级语言: Go
项目地址: git://github.com/schwarzeni/k8s-sample-controller-doc-demo.git


Sample Controller for K8S

参考项目: kubernetes/sample-controller

编程环境:

  • Go v1.13.8
  • MacOS 10.15.8
  • Kubernetes v1.17.0

Write a CRD

Project Setup

注意,由于后期使用 code-generator 时,code-generator 不支持 go module 模式,所以项目虽然依然可以使用 go module ,但是目录需要建成 GOPATH 的样式,例如如下

  1. go mod init github.com/schwarzeni/k8s-sample-controller-doc-demo

此时项目 k8s-sample-controller-doc-demo 父文件夹路径应该为 github.com/schwarzeni/

安装相关的依赖,对于 1.17 版本的 Kubernetes ,下载 0.17.0 版本的相关客户端,下载 code-generator 时可能会报错,这个不用管

  1. go get -u k8s.io/code-generator@v0.17.0
  2. go get -u k8s.io/client-go@v0.17.0
  3. go get -u k8s.io/apimachinery@v0.17.0
  4. go get -u k8s.io/api@v0.17.0

建立 hack/tools.go 并写入如下内容,便于之后使用 go mod vendor

  1. // +build tools
  2. // This package imports things required by build scripts, to force `go mod` to see them as dependencies
  3. package tools
  4. import _ "k8s.io/code-generator"

之后执行命令:

  1. go mod vendor

此时项目结构如下:

  1. .
  2. ├── go.mod
  3. ├── go.sum
  4. ├── vendor
  5. └── ...
  6. ├── hack
  7. └── tools.go

文件 vendor/k8s.io/code-generator/generate-groups.sh 就是用来生成代码的脚本


Code Generation

首先先定义相关的 K8S 资源,然后使用 K8S 提供的工具生成相关的访问代码。这里的代码全部参照 kubernetes/sample-controller 中的相关代码。

在根目录下新建文件夹 pkg/apis/samplecontroller 作为定义资源的目录,在此目录下新建四个文件并填入相应的内容,IDE 可能会报错,这个不用管

  1. pkg/apis/samplecontroller
  2. ├── register.go
  3. └── v1alpha1
  4. ├── doc.go
  5. ├── register.go
  6. └── types.go

register.go

  1. package samplecontroller
  2. // GroupName is the group name used in this package
  3. const GroupName = "samplecontroller.k8s.io"

v1alpha1/doc.go

  1. // +k8s:deepcopy-gen=package
  2. // +groupName=samplecontroller.k8s.io
  3. // Package v1alpha1 is the v1alpha1 version of the API.
  4. package v1alpha1

v1alpha1/types.go

  1. package v1alpha1
  2. import (
  3. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  4. )
  5. // +genclient
  6. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
  7. // Foo is a specification for a Foo resource
  8. type Foo struct {
  9. metav1.TypeMeta `json:",inline"`
  10. metav1.ObjectMeta `json:"metadata,omitempty"`
  11. Spec FooSpec `json:"spec"`
  12. Status FooStatus `json:"status"`
  13. }
  14. // FooSpec is the spec for a Foo resource
  15. type FooSpec struct {
  16. DeploymentName string `json:"deploymentName"`
  17. Replicas *int32 `json:"replicas"`
  18. }
  19. // FooStatus is the status for a Foo resource
  20. type FooStatus struct {
  21. AvailableReplicas int32 `json:"availableReplicas"`
  22. }
  23. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
  24. // FooList is a list of Foo resources
  25. type FooList struct {
  26. metav1.TypeMeta `json:",inline"`
  27. metav1.ListMeta `json:"metadata"`
  28. Items []Foo `json:"items"`
  29. }

v1alpha1/register.go

  1. package v1alpha1
  2. import (
  3. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  4. "k8s.io/apimachinery/pkg/runtime"
  5. "k8s.io/apimachinery/pkg/runtime/schema"
  6. samplecontroller "github.com/schwarzeni/k8s-sample-controller-doc-demo/pkg/apis/samplecontroller"
  7. )
  8. // SchemeGroupVersion is group version used to register these objects
  9. var SchemeGroupVersion = schema.GroupVersion{Group: samplecontroller.GroupName, Version: "v1alpha1"}
  10. // Kind takes an unqualified kind and returns back a Group qualified GroupKind
  11. func Kind(kind string) schema.GroupKind {
  12. return SchemeGroupVersion.WithKind(kind).GroupKind()
  13. }
  14. // Resource takes an unqualified resource and returns a Group qualified GroupResource
  15. func Resource(resource string) schema.GroupResource {
  16. return SchemeGroupVersion.WithResource(resource).GroupResource()
  17. }
  18. var (
  19. // SchemeBuilder initializes a scheme builder
  20. SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
  21. // AddToScheme is a global function that registers this API group & version to a scheme
  22. AddToScheme = SchemeBuilder.AddToScheme
  23. )
  24. // Adds the list of known types to Scheme.
  25. func addKnownTypes(scheme *runtime.Scheme) error {
  26. scheme.AddKnownTypes(SchemeGroupVersion,
  27. &Foo{},
  28. &FooList{},
  29. )
  30. metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
  31. return nil
  32. }

再执行一次 go mod vendor

为自动生成的文件准备一个 header

hack/boilerplate.go.txt

  1. /*
  2. this is a header file
  3. */

执行如下命令生成代码:(我在 Goland 的终端中执行此命令会报错,但是在普通终端中执行就没问题了,目前还不太清楚原因)

  1. bash vendor/k8s.io/code-generator/generate-groups.sh all \
  2. github.com/schwarzeni/k8s-sample-controller-doc-demo/pkg/generated github.com/schwarzeni/k8s-sample-controller-doc-demo/pkg/apis \
  3. samplecontroller:v1alpha1 \
  4. --go-header-file hack/boilerplate.go.txt \
  5. --output-base /Users/nizhenyang/my-project/cloud/src

对于 --output-base ,假如你的项目绝对路径为 /Users/nizhenyang/my-project/cloud/src/github.com/schwarzeni/k8s-sample-controller-doc-demo ,将后半段 github.com/schwarzeni/k8s-sample-controller-doc-demo 视为 GOPATH,则这个参数的值就是前半段路径。

执行完代码之后,在 pkg/ 下会出现新生成的 generated 文件夹, 大致的结构如下

  1. pkg/generated
  2. ├── clientset
  3. └── versioned
  4. ├── clientset.go
  5. ├── doc.go
  6. ├── fake
  7. ├── scheme
  8. └── typed
  9. ├── informers
  10. └── externalversions
  11. ├── factory.go
  12. ├── generic.go
  13. ├── internalinterfaces
  14. └── samplecontroller
  15. └── listers
  16. └── samplecontroller
  17. └── v1alpha1

pkg/apis/samplecontroller/v1alpha1/ 下新生成了 zz_generated.deepcopy.go 文件。

生成代码任务完成,再执行一次 go mod vendor


Testing

这里写一个测试程序来验证我们生产代码的可用性,首先,根据自定义资源的格式准备两份 yaml 配置文件:

crd.yaml

  1. apiVersion: apiextensions.k8s.io/v1beta1
  2. kind: CustomResourceDefinition
  3. metadata:
  4. name: foos.samplecontroller.k8s.io
  5. spec:
  6. group: samplecontroller.k8s.io
  7. version: v1alpha1
  8. names:
  9. kind: Foo
  10. plural: foos
  11. scope: Namespaced

example-foo.yaml

  1. apiVersion: samplecontroller.k8s.io/v1alpha1
  2. kind: Foo
  3. metadata:
  4. name: example-foo
  5. spec:
  6. deploymentName: example-foo
  7. replicas: 1

执行命令 kubectl apply -f crd.yaml 创建资源定义

删除 vendor 目录,在项目根目录新建 main.go ,内容如下

  1. package main
  2. import (
  3. "log"
  4. "time"
  5. "github.com/schwarzeni/k8s-sample-controller-doc-demo/pkg/apis/samplecontroller/v1alpha1"
  6. "k8s.io/client-go/tools/cache"
  7. clientset "github.com/schwarzeni/k8s-sample-controller-doc-demo/pkg/generated/clientset/versioned"
  8. informers "github.com/schwarzeni/k8s-sample-controller-doc-demo/pkg/generated/informers/externalversions"
  9. "k8s.io/client-go/tools/clientcmd"
  10. )
  11. func main() {
  12. // 你的Kubernetes配置文件路径,一般为 ~/.kube/config
  13. config, err := clientcmd.BuildConfigFromFlags("", "config/config")
  14. if err != nil {
  15. panic(err)
  16. }
  17. exampleClient, err := clientset.NewForConfig(config)
  18. sharedInformers := informers.NewSharedInformerFactory(exampleClient, time.Second*2)
  19. informer := sharedInformers.Samplecontroller().V1alpha1().Foos().Informer()
  20. informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
  21. AddFunc: func(obj interface{}) {
  22. mObj := obj.(*v1alpha1.Foo)
  23. log.Printf("New Foo Added: %s", mObj.GetName())
  24. },
  25. DeleteFunc: func(obj interface{}) {
  26. mObj := obj.(*v1alpha1.Foo)
  27. log.Printf("Delete Foo : %s", mObj.GetName())
  28. },
  29. })
  30. stopCh := make(chan struct{})
  31. defer close(stopCh)
  32. informer.Run(stopCh)
  33. }

执行该程序

执行 kubectl apply -f foo-example.yaml ,发现程序输出了 “New Foo Added: example-foo”

执行 kubectl delete -f foo-example.yaml ,发现程序输出了 “Delete Foo : example-foo”