从0到1使用kubebuilder创建crd

简介

从0到1,手把手教会如何使用kubebuilder创建crd, 并且定制自己的控制器。

代码:https://github.com/zoux86/operator-example

0. 下载kubebuilder

# download kubebuilder and install locally.
curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)
chmod +x kubebuilder && mv kubebuilder /usr/local/bin/

1. 创建目录

~/go/src 是我的go src目录

github.com/zoux86/operator-example是想自定义的crd项目

// 可以看出来go mod init 指定的字符串就是mod文件里面的module目录
~/go/src/github.com/zoux86/operator-example#  go mod init github.com/zoux86/operator-example
go: creating new go.mod: module github.com/zoux86/operator-example

 ~/go/src/github.com/zoux86/operator-example # ls
go.mod

 ~/go/src/github.com/zoux86/operator-example # cat go.mod
module github.com/zoux86/operator-example   

go 1.18

2. 初始化项目

执行kubebuilder init这一条命令就行了

 ~/go/src/github.com/zoux86/operator-example # ~/kubebuilder init --domain github.com --license apache2 --owner "zoux86"
Writing kustomize manifests for you to edit...
Writing scaffold for you to edit...
Get controller runtime:
$ go get sigs.k8s.io/controller-runtime@v0.11.2
go: downloading sigs.k8s.io/controller-runtime v0.11.2
go: downloading k8s.io/apimachinery v0.23.5
go: downloading k8s.io/client-go v0.23.5
go: downloading k8s.io/utils v0.0.0-20211116205334-6203023598ed
go: downloading k8s.io/component-base v0.23.5
go: downloading k8s.io/api v0.23.5
go: downloading k8s.io/apiextensions-apiserver v0.23.5
go: downloading sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6
go: downloading golang.org/x/net v0.0.0-20211209124913-491a49abca63
go: downloading golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f
Update dependencies:
$ go mod tidy
go: downloading github.com/Azure/go-autorest/autorest v0.11.18
go: downloading github.com/Azure/go-autorest/autorest/adal v0.9.13
go: downloading github.com/Azure/go-autorest/tracing v0.6.0
go: downloading github.com/Azure/go-autorest/autorest/mocks v0.4.1
go: downloading github.com/Azure/go-autorest/autorest/date v0.3.0
go: downloading github.com/Azure/go-autorest/logger v0.2.1
go: downloading golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
Next: define a resource with:
$ kubebuilder create api


查看文件目录

k8s apis通常有三个组件Resource, Controller, Manager,它们分别定义/实现在以下的三个package当中:

  • cmd/...:主流程程序Manager入口,负责初始化依赖包、启停Controller。用户通常不需要编辑此包,可以依赖脚手架。通过kubebuilder init自动创建生成。
  • pkg/apis/...:包含API资源的定义。编辑*_types.go文件来修改资源定义。每个资源的定义文件存在于pkg/apis/<api-group-name>/<api-version-name>/<api-kind-name>_types.go中。通过kubebuilder create api自动创建生成。
  • pkg/controller/...:包含Controller的实现。编辑*_controller.go实现Controller。通过kubebuilder create api自动创建生成。
 ~/go/src/github.com/zoux86/operator-example  tree
.
├── Dockerfile
├── Makefile
├── PROJECT
├── README.md
├── config
│   ├── default
│   │   ├── kustomization.yaml
│   │   ├── manager_auth_proxy_patch.yaml
│   │   └── manager_config_patch.yaml
│   ├── manager
│   │   ├── controller_manager_config.yaml
│   │   ├── kustomization.yaml
│   │   └── manager.yaml
│   ├── prometheus
│   │   ├── kustomization.yaml
│   │   └── monitor.yaml
│   └── rbac
│       ├── auth_proxy_client_clusterrole.yaml
│       ├── auth_proxy_role.yaml
│       ├── auth_proxy_role_binding.yaml
│       ├── auth_proxy_service.yaml
│       ├── kustomization.yaml
│       ├── leader_election_role.yaml
│       ├── leader_election_role_binding.yaml
│       ├── role_binding.yaml
│       └── service_account.yaml
├── go.mod
├── go.sum
├── hack
│   └── boilerplate.go.txt
└── main.go


3. 创建api和controller

其实从create api 后的输出我们可以看出来:我们修改逻辑后就可以部署了

 ~/go/src/github.com/zoux86/operator-example # ~/kubebuilder create api --group zouxapp --kind PodCount --version v1
Create Resource [y/n]
y
Create Controller [y/n]
y
Writing kustomize manifests for you to edit...     // 先修改这2个文件
Writing scaffold for you to edit...
api/v1/podcount_types.go
controllers/podcount_controller.go
Update dependencies:
$ go mod tidy                                   
Running make:
$ make generate                                     
mkdir -p /Users/game-netease/go/src/github.com/zoux86/operator-example/bin
GOBIN=/Users/game-netease/go/src/github.com/zoux86/operator-example/bin go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.8.0
go: downloading sigs.k8s.io/controller-tools v0.8.0
go: downloading github.com/spf13/cobra v1.2.1
go: downloading golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff
go: downloading github.com/fatih/color v1.12.0
go: downloading k8s.io/api v0.23.0
go: downloading k8s.io/apimachinery v0.23.0
go: downloading github.com/gobuffalo/flect v0.2.3
go: downloading k8s.io/apiextensions-apiserver v0.23.0
go: downloading github.com/mattn/go-colorable v0.1.8
go: downloading github.com/mattn/go-isatty v0.0.12
go: downloading golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e
go: downloading golang.org/x/mod v0.4.2
/Users/game-netease/go/src/github.com/zoux86/operator-example/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
Next: implement your new API and generate the manifests (e.g. CRDs,CRs) with:
$ make manifests


执行create api后,生成以下文件:

api/v1/groupversion_info.go
api/v1/podcount_types.go                      // 需要修改这个文件中crd的定义
api/v1/zz_generated.deepcopy.go
config/crd/kustomization.yaml
config/crd/kustomizeconfig.yaml
config/crd/patches/cainjection_in_podcounts.yaml
config/crd/patches/webhook_in_podcounts.yaml
config/rbac/podcount_editor_role.yaml
config/rbac/podcount_viewer_role.yaml
config/samples/zouxapp_v1_podcount.yaml
controllers/podcount_controller.go            // 需要修改这个文件的controller运行逻辑
controllers/suite_test.go
go.mod
main.go

4. 实现自己的crd和控制器逻辑

根据实际情况而定,这里的控制器逻辑很简单,就是创建同步podCount的spec.count到status里面。

func (r *PodCountReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    rlog := log.FromContext(ctx)
    rlog.Info("start to reconciling podCount %s", req.Name)
    podCount := &zouxappv1.PodCount{}
    err := r.Client.Get(ctx, req.NamespacedName, podCount)
    if err != nil {
        rlog.Error(err, fmt.Sprintf("get podcount %s/%s err during reconcile.", req.Namespace, req.Name))
        return ctrl.Result{}, nil
    }
    podCountCopy := podCount.DeepCopy()
    if podCount.Spec.Count <= 0 {
        podCountCopy.Status.Count = 0
    } else {
        podCountCopy.Status.Count = podCount.Spec.Count
    }

    err = r.Client.Status().Update(ctx, podCountCopy)
    if err != nil {
        rlog.Error(err, fmt.Sprintf("update crd podcount status error %s/%s  during reconcile.", req.Namespace, req.Name))
    }
    //r.Status().Update(ctx, podCountCopy, metav1.UpdateOptions{})
    // TODO(user): your logic here

    return ctrl.Result{}, err
}

5. make manifests, 创建crd的相关yaml

 ~/go/src/github.com/zoux86/operator-example # make manifests
/Users/game-netease/go/src/github.com/zoux86/operator-example/bin/controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases

执行make manifests之后,我们会得到2个文件。

config/crd/bases/
config/rbac/role.yaml


# cat config/rbac/role.yaml
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  creationTimestamp: null
  name: manager-role
rules:
- apiGroups:
  - zouxapp.github.com
  resources:
  - podcounts
  verbs:
  - create
  - delete
  - get
  - list
  - patch
  - update
  - watch
- apiGroups:
  - zouxapp.github.com
  resources:
  - podcounts/finalizers
  verbs:
  - update
- apiGroups:
  - zouxapp.github.com
  resources:
  - podcounts/status
  verbs:
  - get
  - patch
  - update

# cat config/crd/bases/zouxapp.github.com_podcounts.yaml
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  annotations:
    controller-gen.kubebuilder.io/version: v0.8.0
  creationTimestamp: null
  name: podcounts.zouxapp.github.com
spec:
  group: zouxapp.github.com
  names:
    kind: PodCount
    listKind: PodCountList
    plural: podcounts
    singular: podcount
  scope: Namespaced
  versions:
  - name: v1
    schema:
      openAPIV3Schema:
        description: PodCount is the Schema for the podcounts API
        properties:
          apiVersion:
            description: 'APIVersion defines the versioned schema of this representation
              of an object. Servers should convert recognized schemas to the latest
              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
            type: string
          kind:
            description: 'Kind is a string value representing the REST resource this
              object represents. Servers may infer this from the endpoint the client
              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
            type: string
          metadata:
            type: object
          spec:
            description: PodCountSpec defines the desired state of PodCount
            properties:
              count:
                description: Foo is an example field of PodCount. Edit podcount_types.go
                  to remove/update
                type: integer
            type: object
          status:
            description: PodCountStatus defines the observed state of PodCount
            properties:
              count:
                description: 'INSERT ADDITIONAL STATUS FIELD - define observed state
                  of cluster Important: Run "make" to regenerate code after modifying
                  this file'
                type: integer
            type: object
        type: object
    served: true
    storage: true
    subresources:
      status: {}
status:
  acceptedNames:
    kind: ""
    plural: ""
  conditions: []
  storedVersions: []

6. 在集群中部署crd

 ~/go/src/github.com/zoux86/operator-example #kubectl  --kubeconfig=kubeconfig get node create -f config/crd/bases
customresourcedefinition.apiextensions.k8s.io/podcounts.zouxapp.github.com created

 ~/go/src/github.com/zoux86/operator-example # kubectl --kubeconfig=kubeconfig create -f config/samples/zouxapp_v1_podcount.yaml 
podcount.zouxapp.github.com/podcount-sample created


上集群验证,可以看到创建成功了,但是可以看出来没有status.count,这个因为集群还没部署控制器

root# kubectl get crd   | grep podc
podcounts.zouxapp.github.com                            2022-08-25T06:57:09Z


root # kubectl get podcounts.zouxapp.github.com
NAME              AGE
podcount-sample   11s

root # kubectl get podcounts.zouxapp.github.com -oyaml
apiVersion: v1
items:
- apiVersion: zouxapp.github.com/v1
  kind: PodCount
  metadata:
    creationTimestamp: "2022-08-25T07:01:16Z"
    generation: 1
    name: podcount-sample
    namespace: default
    resourceVersion: "467368378"
    selfLink: /apis/zouxapp.github.com/v1/namespaces/default/podcounts/podcount-sample
    uid: a8b42a4c-1ebd-430a-890f-b0238f4ad125
  spec:
    count: 3
kind: List
metadata:
  resourceVersion: ""
  selfLink: ""

7. 部署controller

之前 CRD 并不会完成任何工作,只是在 ETCD 中创建了一条记录。所以我们需要部署写的controller。

运行CRD controller

 ~/go/src/github.com/zoux86/operator-example ## go run ./main.go
I0825 15:35:12.827589   63628 request.go:665] Waited for 1.000074041s due to client-side throttling, not priority and fairness, request: GET:https://xxx/apis/apiextensions.k8s.io/v1?timeout=32s
1.6614129137223601e+09  INFO    controller-runtime.metrics      Metrics server is starting to listen    {"addr": ":8080"}
1.6614129137230568e+09  INFO    setup   starting manager
1.661412913723448e+09   INFO    Starting server {"path": "/metrics", "kind": "metrics", "addr": "[::]:8080"}
1.661412913723448e+09   INFO    Starting server {"kind": "health probe", "addr": "[::]:8081"}
1.661412913723506e+09   INFO    controller.podcount     Starting EventSource    {"reconciler group": "zouxapp.github.com", "reconciler kind": "PodCount", "source": "kind source: *v1.PodCount"}
1.661412913723542e+09   INFO    controller.podcount     Starting Controller     {"reconciler group": "zouxapp.github.com", "reconciler kind": "PodCount"}
1.661412913825421e+09   INFO    controller.podcount     Starting workers        {"reconciler group": "zouxapp.github.com", "reconciler kind": "PodCount", "worker count": 1}
I0825 15:35:13.825542   63628 podcount_controller.go:50] start to reconciling podCount podcount-sample
I0825 15:35:13.868618   63628 podcount_controller.go:50] start to reconciling podCount podcount-sample

查看发现生效了

root# kubectl get podcounts.zouxapp.github.com -oyaml
apiVersion: v1
items:
- apiVersion: zouxapp.github.com/v1
  kind: PodCount
  metadata:
    creationTimestamp: "2022-08-25T07:01:16Z"
    generation: 1
    name: podcount-sample
    namespace: default
    resourceVersion: "467385745"
    selfLink: /apis/zouxapp.github.com/v1/namespaces/default/podcounts/podcount-sample
    uid: a8b42a4c-1ebd-430a-890f-b0238f4ad125
  spec:
    count: 3
  status:
    count: 3
kind: List
metadata:
  resourceVersion: ""
  selfLink: ""
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容