--repo github.com/lixd/i-operator 是仓库地址,也就是 go module 名称
完整输出如下:
1
2
3
4
5
6
7
8
9
❯ sudo kubebuilder init --domain crd.lixueduan.com --repo github.com/lixd/i-operator
INFO Writing kustomize manifests for you to edit...
INFO Writing scaffold for you to edit...
INFO Get controller runtime:
$ go get sigs.k8s.io/controller-runtime@v0.19.1
INFO Update dependencies:
$ go mod tidy
Next: define a resource with:
$ kubebuilder create api
ps:遇到一个问题,mac 下执行命令未添加 sudo 时会初始化失败:
1
Error: failed to initialize project: unable to scaffold with "base.go.kubebuilder.io/v4": exit status 1
可以看到在最后打印出了一个提示信息
1
2
Next: define a resource with:
$ kubebuilder create api
❯ sudo kubebuilder create api --group core --version v1 --kind Application
INFO Create Resource [y/n]y
INFO Create Controller [y/n]y
INFO Writing kustomize manifests for you to edit...
INFO Writing scaffold for you to edit...
INFO api/v1/application_types.go
...
INFO internal/controller/application_controller_test.go
INFO Update dependencies:
$ go mod tidy
INFO Running make:
$ make generate
mkdir -p /Users/lixueduan/17x/projects/i-operator/bin
Downloading sigs.k8s.io/controller-tools/cmd/controller-gen@v0.16.4
go: downloading sigs.k8s.io/controller-tools v0.16.4
...
go: downloading golang.org/x/text v0.19.0
/Users/lixueduan/17x/projects/i-operator/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
同样在最后给出了提示
1
2
Next: implement your new API and generate the manifests (e.g. CRDs,CRs) with:
$ make manifests
先实现 API,然后执行 make manifests 命令去生成相应的 manifest 文件,这样就可以 Apply 到集群里去了。
// ApplicationSpec defines the desired state of Application.typeApplicationSpecstruct{// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster// Important: Run "make" to regenerate code after modifying this fileImagestring`json:"image,omitempty"`Enabledbool`json:"enabled,omitempty"`}// ApplicationStatus defines the observed state of Application.typeApplicationStatusstruct{Readybool`json:"ready,omitempty"`}
func(r*ApplicationReconciler)Reconcile(ctxcontext.Context,reqctrl.Request)(ctrl.Result,error){logger:=log.FromContext(ctx)log:=logger.WithValues("application",req.NamespacedName)log.Info("start reconcile")// query appvarappv1.Applicationerr:=r.Get(ctx,req.NamespacedName,&app)iferr!=nil{log.Error(err,"unable to fetch application")// we'll ignore not-found errors, since they can't be fixed by an immediate// requeue (we'll need to wait for a new notification), and we can get them// on deleted requests.returnctrl.Result{},client.IgnoreNotFound(err)}// examine DeletionTimestamp to determine if object is under deletionifapp.ObjectMeta.DeletionTimestamp.IsZero(){// The object is not being deleted, so if it does not have our finalizer,// then lets add the finalizer and update the object. This is equivalent// to registering our finalizer.if!controllerutil.ContainsFinalizer(&app,AppFinalizer){controllerutil.AddFinalizer(&app,AppFinalizer)iferr=r.Update(ctx,&app);err!=nil{log.Error(err,"unable to add finalizer to application")returnctrl.Result{},err}}}else{// The object is being deletedifcontrollerutil.ContainsFinalizer(&app,AppFinalizer){// our finalizer is present, so lets handle any external dependencyiferr=r.deleteExternalResources(&app);err!=nil{log.Error(err,"unable to cleanup application")// if fail to delete the external dependency here, return with error// so that it can be retried.returnctrl.Result{},err}// remove our finalizer from the list and update it.controllerutil.RemoveFinalizer(&app,AppFinalizer)iferr=r.Update(ctx,&app);err!=nil{returnctrl.Result{},err}}// Stop reconciliation as the item is being deletedreturnctrl.Result{},nil}// Your reconcile logiclog.Info("run reconcile logic")iferr=r.syncApp(ctx,app);err!=nil{log.Error(err,"unable to sync application")returnctrl.Result{},err}// sync statusvardeployappsv1.DeploymentobjKey:=client.ObjectKey{Namespace:app.Namespace,Name:deploymentName(app.Name)}err=r.Get(ctx,objKey,&deploy)iferr!=nil{log.Error(err,"unable to fetch deployment","deployment",objKey.String())returnctrl.Result{},client.IgnoreNotFound(err)}copyApp:=app.DeepCopy()// now,if ready replicas is gt 1,set status to truecopyApp.Status.Ready=deploy.Status.ReadyReplicas>=1if!reflect.DeepEqual(app,copyApp){// update when changedlog.Info("app changed,update app status")iferr=r.Client.Status().Update(ctx,copyApp);err!=nil{log.Error(err,"unable to update application status")returnctrl.Result{},err}}returnctrl.Result{},nil}
❯ kubebuilder create webhook --group core --version v1 --kind Application --defaulting --programmatic-validation
INFO Writing kustomize manifests for you to edit... ilder create webhook --group core --version v1 --kind Application --defaulting --programmatic-validation
INFO Writing scaffold for you to edit...
INFO internal/webhook/v1/application_webhook.go
INFO internal/webhook/v1/application_webhook_test.go
INFO internal/webhook/v1/webhook_suite_test.go
INFO Update dependencies:
$ go mod tidy
INFO Running make:
$ make generate
/Users/lixueduan/17x/projects/i-operator/bin/controller-gen object:headerFile="hack/boilerplate.go.txt"paths="./..."Next: implement your new Webhook and generate the manifests with:
$ make manifests
// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Application.func(v*ApplicationCustomValidator)ValidateCreate(ctxcontext.Context,objruntime.Object)(admission.Warnings,error){application,ok:=obj.(*corev1.Application)if!ok{returnnil,fmt.Errorf("expected a Application object but got %T",obj)}applicationlog.Info("Validation for Application upon creation","name",application.GetName())ifisValidImageName(application.Spec.Image){returnnil,fmt.Errorf("invalid image name: %s",application.Spec.Image)}returnnil,nil}
执行 make manifests 命令,会根据我们定义的 CRD 生成对应的 yaml 文件,以及其他部署相关的 yaml 文件:
1
2
❯ make manifests
/Users/lixueduan/17x/projects/i-operator/bin/controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
生成的 crd 中我们定义的 Spec 和 Status 部分如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
spec:description:ApplicationSpec defines the desired state of Application.properties:enabled:type:booleanimage:type:stringtype:objectstatus:description:ApplicationStatus defines the observed state of Application.properties:ready:type:boolean
随后执行 make install 命令即可将 CRD 部署到集群,这也就是为什么需要在本地准备好 Kubeconfig 以及 kubectl 工具。
执行 make run 命令即可在本地运行 Controller,这也就是为什么需要在本地准备好 kubeconfig 文件。
ps:如果之前创建了 Webhook 这里会无法正常启动,具体见下一篇本地调试~
1
2
3
4
5
6
7
8
9
❯ make run
/Users/lixueduan/17x/projects/i-operator/bin/controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases10:00:30
/Users/lixueduan/17x/projects/i-operator/bin/controller-gen object:headerFile="hack/boilerplate.go.txt"paths="./..."go fmt ./...
go vet ./...
go run ./cmd/main.go
2024-12-19T10:03:35+08:00 INFO setup starting manager
2024-12-19T10:03:35+08:00 INFO starting server {"name": "health probe", "addr": "[::]:8081"}2024-12-19T10:03:35+08:00 INFO Starting EventSource {"controller": "application", "controllerGroup": "core.crd.lixueduan.com", "controllerKind": "Application", "source": "kind source: *v1.Application"}