This is the multi-page printable view of this section. Click here to print.
Usage
- 1: Overview
- 2: Scaffolder
1 - Overview
This framework is based on the controller-runtime project. Therefore one way of consuming it would be to bootstrap a kubebuilder project, such as
kubebuilder init \
--domain kyma-project.io \
--repo github.com/myorg/mycomponent-operator \
--project-name=mycomponent-operator
kubebuilder create api \
--group operator \
--version v1alpha1 \
--kind MyComponent \
--resource \
--controller \
--make
First you will enhance the custom resource type, as generated by kubebuilder in the api/v1alpha1
folder:
// MyComponentSpec defines the desired state of MyComponent
type MyComponentSpec struct {
// Add fields here ...
}
// MyComponentStatus defines the observed state of MyComponent
type MyComponentStatus struct {
component.Status `json:",inline"`
}
In many cases, it makes sense to embed one or more of the following structs into the spec:
component.PlacementSpec
if instances should be allowed to specify target namespace and name different from the component’s namespace and namecomponent.ClientSpec
if (remote) deployments via a specified kubeconfig shall be possiblecomponent.ImpersonationSpec
if you want to support impersonation of the deployment (e.g. via a service account).
Most likely you will add own attributes to the spec, allowing to parameterize the deployment of your component.
Including component.Status
into the status is mandatory, but you are free to add further fields if needed.
In order to make the custom resource type implement the Component
interface, add the following methods:
var _ component.Component = &MyComponent{}
func (s *MyComponentSpec) ToUnstructured() map[string]any {
result, err := runtime.DefaultUnstructuredConverter.ToUnstructured(s)
if err != nil {
panic(err)
}
return result
}
func (c *MyComponent) GetSpec() runtimetypes.Unstructurable {
return &c.Spec
}
func (c *MyComponent) GetStatus() *component.Status {
return &c.Status.Status
}
Now we are settled to replace the controller generated by kubebuilder with the component-operator-runtime reconciler in the scaffolded main.go
:
// Replace this by a real resource generator (e.g. HelmGenerator or KustomizeGenerator, or your own one).
resourceGenerator, err := manifests.NewDummyGenerator()
if err != nil {
setupLog.Error(err, "error initializing resource generator")
os.Exit(1)
}
if err := component.NewReconciler[*operatorv1alpha1.MyComponent](
"mycomponent-operator.kyma-project.io",
nil,
nil,
nil,
nil,
resourceGenerator,
).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "MyComponent")
os.Exit(1)
}
In addition, you have to add the apiextensions.k8s.io/v1
and apiregistration.k8s.io/v1
groups to the used scheme, such
that the kubebuilder generated init()
function looks like this:
func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(apiextensionsv1.AddToScheme(scheme))
utilruntime.Must(apiregistrationv1.AddToScheme(scheme))
utilruntime.Must(operatorv1alpha1.AddToScheme(scheme))
}
Furthermore, pay attention to bypass informer caching in the client at least for the following types:
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
// ...
Client: client.Options{
Cache: &client.CacheOptions{
DisableFor: []client.Object{
&operatorv1alpha1.MyComponent{},
&apiextensionsv1.CustomResourceDefinition{},
&apiregistrationv1.APIService{},
},
},
},
// ...
})
Now the actual work starts, which means that you will tailor the custom resource type’s spec according to
the needs of the managed component, and implement a meaningful resource generator, replacing manifests.NewDummyGenerator()
.
2 - Scaffolder
The recommended way to get started with the implementation of a new component operator is to use the included scaffolding tool, which can be downloaded from the releases page.
After installing the scaffolder, a new project can be created like this:
scaffold \
--group-name operator.kyma-project.io \
--kind MyComponent \
--operator-name mycomponent-operator.kyma-project.io \
--go-module github.com/myorg/mycomponent-operator \
--image mycomponent-operator:latest \
<output-directory>
In this example, some options were left out, using the according default values; the full option list is as follows:
Usage: scaffold [options] [output directory]
[output directory]: Target directory for the generated scaffold; must exist
[options]:
--version Show version
--owner string Owner of this project, as written to the license header (default "SAP SE")
--spdx-license-headers Whether to write license headers in SPDX format
--group-name string API group name
--group-version string API group version (default "v1alpha1")
--kind string API kind for the component
--resource string API resource (plural) for the component; if empty, it will be the pluralized kind
--operator-name string Unique name for this operator, used e.g. for leader election and labels; should be a valid DNS hostname
--with-validating-webhook Whether to scaffold validating webhook
--with-mutating-webhook Whether to scaffold mutating webhook
--go-version string Go version to be used (default "1.21")
--go-module string Name of the Go module, as written to the go.mod file
--kubernetes-version string Kubernetes go-client version to be used (default "v0.28.1")
--controller-runtime-version string Controller-runtime version to be used (default "v0.16.0")
--controller-tools-version string Controller-tools version to be used (default "v0.13.0")
--code-generator-version string Code-generator version to be used (default "v0.28.1")
--admission-webhook-runtime-version string Admission-webhook-runtime version to be used (default "v0.1.0")
--envtest-kubernetes-version string Kubernetes version to be used by envtest (default "1.27.1")
--image string Name of the Docker/OCI image produced by this project (default "controller:latest")
--skip-post-processing Skip post-processing
After generating the scaffold, the next steps are:
- Enhance the spec type of the generated custom resource type (in
api/<group-version>/types.go
) according to the needs of your component - Implement a meaningful resource generator and use it in
main.go
instead ofmanifests.NewDummyGenerator()
; to do so you can either implement your own generator, or reuse one of the generic generators shipped with this repository.