Create a directory, and then run the init command inside of it to initialize a new project. Follows an example.
[blazehu@MacBook ~]$ mkdir ~/Project/workspace-go/example [blazehu@MacBook ~]$ cd ~/Project/workspace-go/example [blazehu@MacBook ~]$ kubebuilder init --domain blazehu.com --owner "blazehu" --repo blazehu.com/example Writing kustomize manifests for you to edit... Writing scaffold for you to edit... Get controller runtime: $ go get sigs.k8s.io/controller-runtime@v0.10.0 Update dependencies: $ go mod tidy Next: define a resource with: $ kubebuilder create api
If your project is initialized within GOPATH, the implicitly called go mod init will interpolate the module path for you. Otherwise –repo=must be set.
Adding a new API
[blazehu@MacBook ~]$ kubebuilder create api --group cos --version v1 --kind Bucket Create Resource [y/n] y Create Controller [y/n] y Writing kustomize manifests for you to edit... Writing scaffold for you to edit... api/v1/bucket_types.go controllers/bucket_controller.go Update dependencies: $ go mod tidy Running make: $ make generate go: creating new go.mod: module tmp Downloading sigs.k8s.io/controller-tools/cmd/controller-gen@v0.7.0 go get: added sigs.k8s.io/controller-tools v0.7.0 /Users/huyuhan/Project/workspace-go/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
Designing an API
api/v1/bucket_types.go
// BucketSpec defines the desired state of Bucket type BucketSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file
// Foo is an example field of Bucket. Edit bucket_types.go to remove/update Name string`json:"name,omitempty"` Region string`json:"region,omitempty"` ACL string`json:"acl,omitempty"` }
tips: Finalizers allow controllers to implement asynchronous pre-delete hooks. Let’s say you create an external resource (such as a storage bucket) for each object of your API type, and you want to delete the associated external resource on object’s deletion from Kubernetes, you can use a finalizer to do that.
/* Copyright 2022 blazehu. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */
// Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. // TODO(user): Modify the Reconcile function to compare the state specified by // the Bucket object against the actual cluster state, and then // perform operations to make the cluster state reflect the state specified by // the user. // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile func(r *BucketReconciler)Reconcile(ctx context.Context, req ctrl.Request)(ctrl.Result, error) { logger := log.FromContext(ctx)
// examine DeletionTimestamp to determine if object is under deletion if bucket.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 // registering our finalizer. if !controllerutil.ContainsFinalizer(bucket, bucketFinalizerName) { controllerutil.AddFinalizer(bucket, bucketFinalizerName) if err := r.Update(ctx, bucket); err != nil { return ctrl.Result{}, err } } else { if err := r.updateExternalResources(bucket); err != nil { logger.Error(err, "unable to create Bucket") return ctrl.Result{}, err } logger.Info("create Bucket succeed") } } else { // The object is being deleted if controllerutil.ContainsFinalizer(bucket, bucketFinalizerName) { // our finalizer is present, so lets handle any external dependency if err := r.deleteExternalResources(bucket); err != nil { // if fail to delete the external dependency here, return with error // so that it can be retried logger.Error(err, "unable to delete Bucket") return ctrl.Result{}, err }
// remove our finalizer from the list and update it. controllerutil.RemoveFinalizer(bucket, bucketFinalizerName) if err := r.Update(ctx, bucket); err != nil { return ctrl.Result{}, err } logger.Info("delete Bucket succeed") }
// Stop reconciliation as the item is being deleted return ctrl.Result{}, nil }
// SetupWithManager sets up the controller with the Manager. func(r *BucketReconciler)SetupWithManager(mgr ctrl.Manager)error { return ctrl.NewControllerManagedBy(mgr). For(&cosv1.Bucket{}). Complete(r) }
Test It Out
Install the CRDs into the cluster (make install)
[blazehu@MacBook ~]$ make install /Users/huyuhan/Project/workspace-go/example/bin/controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases /Users/huyuhan/Project/workspace-go/example/bin/kustomize build config/crd | kubectl apply -f - customresourcedefinition.apiextensions.k8s.io/buckets.cos.blazehu.com created
Run your controller (this will run in the foreground, so switch to a new terminal if you want to leave it running) (make run)
[blazehu@MacBook ~]$ make run /Users/huyuhan/Project/workspace-go/example/bin/controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases /Users/huyuhan/Project/workspace-go/example/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..." go fmt ./... go vet ./... go run ./main.go 2022-01-27T22:05:30.207+0800 INFO controller-runtime.metrics metrics server is starting to listen {"addr": ":8080"} 2022-01-27T22:05:30.207+0800 INFO setup starting manager 2022-01-27T22:05:30.208+0800 INFO starting metrics server {"path": "/metrics"} 2022-01-27T22:05:30.208+0800 INFO controller.bucket Starting EventSource {"reconciler group": "cos.blazehu.com", "reconciler kind": "Bucket", "source": "kind source: /, Kind="} 2022-01-27T22:05:30.208+0800 INFO controller.bucket Starting Controller {"reconciler group": "cos.blazehu.com", "reconciler kind": "Bucket"} 2022-01-27T22:05:30.309+0800 INFO controller.bucket Starting workers {"reconciler group": "cos.blazehu.com", "reconciler kind": "Bucket", "worker count": 1}
[blazehu@MacBook ~]$ kubectl apply -f cos_v1_bucket.yaml bucket.cos.blazehu.com/bucket-sample created [blazehu@MacBook ~]$ kubectl get bucket.cos.blazehu.com -n blazehu NAME AGE bucket-sample 17s
Tencent cloud console view found that the bucket was created normally.
Delete Instances of Custom Resources (delete bucket.cos.blazehu.com/bucket-sample)