-
Notifications
You must be signed in to change notification settings - Fork 115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Controller Manager Quickstart #267
Open
RolandMa1986
wants to merge
2
commits into
kubesphere:master
Choose a base branch
from
RolandMa1986:sig-developer
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
145 changes: 145 additions & 0 deletions
145
developer-guide/development/controller-mamager-quickstart.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
# Controller Manager Quickstart | ||
|
||
KubeSphere follows the [sample-controller](https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/sample-controller) pattern to implement controllers for the reconciliation process. It watches CustomResourceDefinition(CRD) desired state and actual state through informers and listers. Then, it sends instructions through clients to try and make the current state be more like the desired state. | ||
|
||
> We won't discuss the pros and cons of the sample-controller comparing with Kubebuilder here. Just show you how to implement a controller. | ||
|
||
**Note:** Read `sample-controller`'s [README.md](https://github.com/kubernetes/sample-controller/blob/master/README.md) and go through the `controller.go` before you begin. | ||
|
||
## Defining types | ||
|
||
To implement a controller, you should define your API Groups, Versions, and Kinds firstly. Please read more about the [Kubernetes API Conventions](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#websockets-and-spdy) if you want to understand those concepts deeper. | ||
|
||
For an example, the `Group` Kind that was defined under `iam.kubershpere.io` API Group as below: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
```golang | ||
// +genclient:nonNamespaced | ||
// +genclient | ||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object | ||
// +k8s:openapi-gen=true | ||
// +kubebuilder:printcolumn:name="Workspace",type="string",JSONPath=".metadata.labels.kubesphere\\.io/workspace" | ||
// +kubebuilder:resource:categories="group",scope="Cluster" | ||
|
||
// Group is the Schema for the groups API | ||
type Group struct { | ||
metav1.TypeMeta `json:",inline"` | ||
metav1.ObjectMeta `json:"metadata,omitempty"` | ||
Status GroupStatus `json:"status,omitempty"` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess you just missed here. Please provide a good indent code for all code lines. |
||
Spec GroupSpec `json:"spec,omitempty"` | ||
} | ||
|
||
// GroupSpec defines the desired state of Group | ||
type GroupSpec struct { | ||
//We don't have Spec for Group. | ||
} | ||
|
||
``` | ||
|
||
In the comment, both `+k8s` and `+kubebuilder` markers are used in KubeSphere. | ||
|
||
- `+k8s:deepcopy-gen` & `+genclient`: which is used by [k8s.io/code-generator](https://github.com/kubernetes/code-generator) to generate informers, listers, etc. | ||
- `+kubebuilder`: which telling [controller-tools](https://github.com/kubernetes-sigs/controller-tools) to generate CRD YAMLs. | ||
|
||
## Generate manifests and packages | ||
|
||
There are directives defined in the Makefile, which can help you generate codes automatically base on the markers we defined. | ||
|
||
1. Generate manifests e.g. CRD, RBAC, etc. | ||
|
||
```bash | ||
make manifests | ||
``` | ||
2. Generate typed client, informers, listers, and deep-copy packages | ||
|
||
```bash | ||
make deepcopy clientset | ||
``` | ||
|
||
## Implements a controller | ||
|
||
If you have already learned the `sample-controller` as we suggested, you may found there are three major parts in the `sample-controller`'s implementation: | ||
1. A `Controller` struct is defined to hold variables and a `NewController` function which use to create a `Controller` instance. | ||
2. An asynchronous background worker runtime uses to process the queued items with re-try logic. | ||
3. A `reconcile` function will contain your "do stuff" logic and some other helper functions. | ||
|
||
Since the asynchronous background worker runtime is reusable, so we abstracted those functions into the `BaseController` struct in KubeSphere. You can compose the struct to inherit functions from it. | ||
|
||
Now you are ready to write your controller. | ||
|
||
**First**, define a `Controller`, make sure the `controller.BaseController` is included. Declare necessary interfaces or types in the fields. | ||
```golang | ||
type Controller struct { | ||
controller.BaseController | ||
k8sClient kubernetes.Interface | ||
ksClient kubesphere.Interface | ||
groupInformer iamv1alpha2informers.GroupInformer | ||
groupLister iamv1alpha1listers.GroupLister | ||
} | ||
``` | ||
|
||
**Second**, Create a `NewController` function that use to create a `Controller` instance. In the function `NewController`, register a `Handler` that use to process reconcile logic. Meanwhile, you need to subscribe the informer's event by register the `Enqueue` function. | ||
|
||
You can only subscribe to one informer as your primary resource. That's to say, only one kind of CRD can be put in the queue. If you need to subscribe to another related resource change event, create another function to handle it. And if it is owned by a primary resource, it should enqueue that primary resource for processing. | ||
|
||
```golang | ||
// NewController creates Group Controller instance | ||
func NewController(k8sClient kubernetes.Interface, ksClient kubesphere.Interface, groupInformer iamv1alpha2informers.GroupInformer) *Controller { | ||
|
||
ctl := &Controller{ | ||
BaseController: controller.BaseController{ | ||
Workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Group"), | ||
// workers will be started when all informer are syned. | ||
Synced: []cache.InformerSynced{groupInformer.Informer().HasSynced}, | ||
Name: controllerName, | ||
}, | ||
k8sClient: k8sClient, | ||
ksClient: ksClient, | ||
groupInformer: groupInformer, | ||
groupLister: groupInformer.Lister(), | ||
} | ||
|
||
// Register a Handler to reconcile the spec and status | ||
ctl.Handler = ctl.reconcile | ||
|
||
// Set up Enqueue as event handler for when Group resources change | ||
groupInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ | ||
AddFunc: ctl.Enqueue, | ||
UpdateFunc: func(old, new interface{}) { | ||
ctl.Enqueue(new) | ||
}, | ||
DeleteFunc: ctl.Enqueue, | ||
}) | ||
return ctl | ||
} | ||
|
||
``` | ||
|
||
**Last**, implement the `Start` interface to run the background. | ||
|
||
```golang | ||
|
||
func (c *Controller) Start(stopCh <-chan struct{}) error { | ||
return c.Run(1, stopCh) | ||
} | ||
|
||
|
||
func (c *Controller) reconcile(key string) error { | ||
// "do stuff" logic | ||
} | ||
``` | ||
|
||
|
||
|
||
## Resources | ||
|
||
For further information about how to implement a controller: | ||
|
||
- The `BaseController` code sample can be found in the pull request [#3138](https://github.com/kubesphere/kubesphere/pull/3138/files) | ||
- The example [sample controller](https://github.com/kubernetes/sample-controller) shows a code example of a controller that uses the clients, listers, and informers generated by the`code-generator`. | ||
- The article [Kubernetes Deep Dive: Code Generation for CustomResources](https://www.openshift.com/blog/kubernetes-deep-dive-code-generation-customresources) gives a step by step instructions on how to use `code-generator`. | ||
- The Kubernetes official document gives a best practice about how to [Writing Controllers](https://github.com/kubernetes/community/blob/8cafef897a22026d42f5e5bb3f104febe7e29830/contributors/devel/controllers.md) | ||
- To understand why only the status part of the custom resource should be updated, please refer to the [Kubernetes API conventions](https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status). | ||
|
||
|
||
|
||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good if you can put the relevant links here. For example, put the official link of CustomResourceDefinition.