Skip to content
Switch branches/tags

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time

This is not an officially supported Google product

Kubebuilder Workshop

The Kubebuilder Workshop provides a hands-on experience creating Kubernetes APIs using kubebuilder.

Futher documentation on kubebuilder can be found here

Create a Kubernetes API for creating MongoDB instances similar to what is shown in this blog post

It should be possible to manage MongoDB instances by running kubectl apply -f on the yaml file declaring a MongoDB instance.

kind: MongoDB
  name: mongo-instance
  replicas: 3
  storage: 100Gi

Kubernetes API Deep Dive Slides

Posted Here


Setup the project

$ go mod init
$ kubebuilder init --domain --license apache2 --owner "The Kubernetes authors"

Create the MongoDB Resource and Controller Stubs

$ kubebuilder create api --group databases --version v1alpha1 --kind MongoDB
  • enter y to have it create the stub for the Resource
  • enter y to have it create the stub for the Controller

Implement the Resource

Update the MongoDBSpec

Modify the MongoDB API Schema (e.g. MongoDBSpec) in api/v1alpha1/mongodb_types.go.

// MongoDBSpec defines the desired state of MongoDB
type MongoDBSpec struct {
	// replicas is the number of MongoDB replicas
    // +kubebuilder:validation:Minimum=1
	// +optional
	Replicas *int32 `json:"replicas,omitempty"`

    // storage is the volume size for each instance
	// +optional
	Storage *string `json:"storage,omitempty"`

Update the MongoDBStatus

// MongoDBStatus defines the observed state of MongoDB
type MongoDBStatus struct {
	// statefulSetStatus contains the status of the StatefulSet managed by MongoDB
	StatefulSetStatus appsv1.StatefulSetStatus `json:"statefulSetStatus,omitempty"`

	// serviceStatus contains the status of the Service managed by MongoDB
	ServiceStatus corev1.ServiceStatus `json:"serviceStatus,omitempty"`

Update the print column markers

These tell kubectl how to print the object.

// +kubebuilder:printcolumn:name="storage",type="string",JSONPath="",format="byte"
// +kubebuilder:printcolumn:name="replicas",type="integer",JSONPath=".spec.replicas",format="int32"
// +kubebuilder:printcolumn:name="ready replicas",type="integer",JSONPath=".status.statefulSetStatus.readyReplicas",format="int32"
// +kubebuilder:printcolumn:name="current replicas",type="integer",JSONPath=".status.statefulSetStatus.currentReplicas",format="int32"

Add the status subresource

This allows status to be updated independently from the spec.

// +kubebuilder:subresource:status

Add the scale subresource

This allows kubectl scale work.

// +kubebuilder:subresource:scale:specpath=.spec.replicas,statuspath=.status.statefulSetStatus.replicas

Update main.go

Add StatefulSets and Services to the Scheme

This is necessary to read / write the objects from the client

func init() {

	// +kubebuilder:scaffold:scheme

Pass the Scheme and Recorder to the Controller

This will be needed later for setting owners references


	err = (&controllers.MongoDBReconciler{
		Client:   mgr.GetClient(),
		Log:      ctrl.Log.WithName("controllers").WithName("MongoDB"),
		Recorder: mgr.GetEventRecorderFor("mongodb"),
		Scheme:   mgr.GetScheme(),
	if err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "MongoDB")


type MongoDBReconciler struct {
	Log      logr.Logger
	Recorder record.EventRecorder
	Scheme   *runtime.Scheme

Implement the Controller

Add StatefulSet and Service RBAC markers

Add an RBAC rule so the Controller can read / write StatuefulSets and Services

// +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=get;list;watch;create;update;patch;delete

Watch StatefulSets and Services

Update the SetupWithManager function to configure MondoDB to own StatefulSets and Services.

func (r *MongoDBReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		Owns(&appsv1.StatefulSet{}). // Generates StatefulSets
		Owns(&corev1.Service{}).     // Generates Services

Read MongoDB from Reconcile

	// Fetch the MongoDB instance
	mongo := &v1alpha1.MongoDB{}
	if err := r.Get(ctx, req.NamespacedName, mongo); err != nil {
		log.Error(err, "unable to fetch MongoDB")
		if apierrs.IsNotFound(err) {
			return ctrl.Result{}, nil
		return ctrl.Result{}, err

Write the Service object

	// Generate Service
	service := &corev1.Service{
		ObjectMeta: ctrl.ObjectMeta{
			Name:      req.Name + "-mongodb-service",
			Namespace: req.Namespace,
	_, err := ctrl.CreateOrUpdate(ctx, r.Client, service, func() error {
		util.SetServiceFields(service, mongo)
		return controllerutil.SetControllerReference(mongo, service, r.Scheme)
	if err != nil {
		return ctrl.Result{}, err

Write the StatefulSet object

	// Generate StatefulSet
	ss := &appsv1.StatefulSet{
		ObjectMeta: ctrl.ObjectMeta{
			Name:      req.Name + "-mongodb-statefulset",
			Namespace: req.Namespace,
	_, err = ctrl.CreateOrUpdate(ctx, r.Client, ss, func() error {
		util.SetStatefulSetFields(ss, service, mongo, mongo.Spec.Replicas, mongo.Spec.Storage)
		return controllerutil.SetControllerReference(mongo, ss, r.Scheme)

	if err != nil {
		return ctrl.Result{}, err

Update the MongoDB status

	ssNN := req.NamespacedName
	ssNN.Name = ss.Name
	if err := r.Get(ctx, ssNN, ss); err != nil {
		log.Error(err, "unable to fetch StatefulSet", "namespaceName", ssNN)
		return ctrl.Result{}, err
	mongo.Status.StatefulSetStatus = ss.Status

	serviceNN := req.NamespacedName
	serviceNN.Name = service.Name
	if err := r.Get(ctx, serviceNN, service); err != nil {
		log.Error(err, "unable to fetch Service", "namespaceName", serviceNN)
		return ctrl.Result{}, err
	mongo.Status.ServiceStatus = service.Status

	err = r.Update(ctx, mongo)
	if err != nil {
		return ctrl.Result{}, err

Run the API

Install CRDs into a cluster

$ make manifests
$ kubectl apply -k config/crd

Run the Controller locally

$ make run

Edit the sample MongoDB file

Edit config/samples/databases_v1alpha1_mongodb.yaml

kind: MongoDB
  name: mongo-instance
  replicas: 1
  storage: 100Gi

Create the sample

$ kubectl apply -f config/samples/databases_v1alpha1_mongodb.yaml

Test the Application

Inspect cluster state

$ kubectl get mongodbs,statefulsets,services,pods
$ kubectl logs mongodb-sample-mongodb-statefulset-0 mongo

Connect to the running MongoDB instance from within the cluster using a Pod

$ kubectl run mongo-test -t -i --rm --image mongo bash
$ mongo <cluster ip address of mongodb service>:27017

Test scale

$ rm -rf ~/.kube/cache/ # clear the discovery cache
$ kubectl scale mongodbs/mongodb-sample --replicas=3
$ kubectl get mongodbs 

Test Garbage Collection

  • delete the mongodb instance
    • kubectl delete -f config/samples/databases_v1alpha1_mongodb.yaml
  • look for garbage collected resources (they should be gone)
    • kubectl get monogodbs
    • kubectl get statefulsets
    • kubectl get services
    • kubectl get pods
  • recreate the MongoDB instance
    • kubectl apply -f config/samples/databases_v1alpha1_mongodb.yaml


Workshop materials for the kubebuilder workshop



No releases published


No packages published