This is not an officially supported Google product
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.
apiVersion: databases.k8s.io/v1alpha1
kind: MongoDB
metadata:
name: mongo-instance
spec:
replicas: 3
storage: 100Gi
- Provision a kubernetes cluster
- Install go
- Install kubectl
- Install kubebuilder
$ go mod init github.com/pwittrock/kubebuilder-workshop
$ kubebuilder init --domain example.com --license apache2 --owner "The Kubernetes authors"
$ 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
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"`
}
// 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"`
}
These tell kubectl how to print the object.
// +kubebuilder:printcolumn:name="storage",type="string",JSONPath=".spec.storage",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"
This allows status to be updated independently from the spec.
// +kubebuilder:subresource:status
This allows kubectl scale
work.
// +kubebuilder:subresource:scale:specpath=.spec.replicas,statuspath=.status.statefulSetStatus.replicas
This is necessary to read / write the objects from the client
func init() {
databasesv1alpha1.AddToScheme(scheme)
appsv1.AddToScheme(scheme)
corev1.AddToScheme(scheme)
// +kubebuilder:scaffold:scheme
}
This will be needed later for setting owners references
main.go
err = (&controllers.MongoDBReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("MongoDB"),
Recorder: mgr.GetEventRecorderFor("mongodb"),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr)
if err != nil {
setupLog.Error(err, "unable to create controller", "controller", "MongoDB")
os.Exit(1)
}
mongodb_controller.go
type MongoDBReconciler struct {
client.Client
Log logr.Logger
Recorder record.EventRecorder
Scheme *runtime.Scheme
}
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
Update the SetupWithManager
function to configure MondoDB to own StatefulSets and Services.
func (r *MongoDBReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&v1alpha1.MongoDB{}).
Owns(&appsv1.StatefulSet{}). // Generates StatefulSets
Owns(&corev1.Service{}). // Generates Services
Complete(r)
}
// 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
}
// 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
}
// 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
}
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
}
$ make manifests
$ kubectl apply -k config/crd
$ make run
Edit config/samples/databases_v1alpha1_mongodb.yaml
apiVersion: databases.k8s.io/v1alpha1
kind: MongoDB
metadata:
name: mongo-instance
spec:
replicas: 1
storage: 100Gi
$ kubectl apply -f config/samples/databases_v1alpha1_mongodb.yaml
$ kubectl get mongodbs,statefulsets,services,pods
$ kubectl logs mongodb-sample-mongodb-statefulset-0 mongo
$ kubectl run mongo-test -t -i --rm --image mongo bash
$ mongo <cluster ip address of mongodb service>:27017
$ rm -rf ~/.kube/cache/ # clear the discovery cache
$ kubectl scale mongodbs/mongodb-sample --replicas=3
$ kubectl get mongodbs
- 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