How to Create Kubernetes CRDs Using Golang and Master Controller Programming ️
Kubernetes has revolutionized the way we manage containerized applications, but what happens when you need to extend its capabilities to fit your unique use case? Enter Custom Resource Definitions (CRDs) and Controllers! With these powerful tools, you can define your own resources and automate their management in Kubernetes. 🌟
In this article, we’ll explore how to create Kubernetes CRDs using Golang and dive into the world of controller programming with the Watcher-Informer model. Whether you’re building a custom operator or extending Kubernetes for your organization, this guide will help you get started. Let’s dive in! 💻
🤔 Why Create Custom Resources and Controllers?
Before we jump into the how, let’s talk about the why. Here are a few reasons you might want to create CRDs and controllers:
Custom Workflows : You need to manage resources that Kubernetes doesn’t natively support.
Automation : You want to automate complex tasks like provisioning, scaling, or healing.
Extending Kubernetes : You’re building a platform or tool that integrates deeply with Kubernetes.
If any of these resonate with you, it’s time to roll up your sleeves and start building! 🛠️
🧰 What You’ll Need
Before we begin, make sure you have the following tools installed:
Go (Golang): We’ll use Go to write our CRD and controller. Install it from golang.org.
Kubebuilder or Operator SDK : These frameworks simplify the process of building Kubernetes operators. Install one of them.
Kubernetes Cluster : You’ll need a cluster to test your CRD and controller. Minikube or Kind are great for local development.
kubectl : The Kubernetes CLI tool for interacting with your cluster.
🚀 Step 1: Define Your Custom Resource (CRD)
Let’s create a CRD for a “Widget” resource.
- Scaffold Your Project :
Use Kubebuilder to set up your project.
kubebuilder init --domain mydomain.com --repo github.com/yourusername/widget-operator
kubebuilder create api --group widgets --version v1 --kind Widget
This generates the files you need for your CRD and controller.
2. Define the Widget Schema :
Open api/v1/widget_types.go and define your Widget resource.
package v1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// WidgetSpec defines the desired state of Widget
type WidgetSpec struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Replicas int32 `json:"replicas"`
}
// WidgetStatus defines the observed state of Widget
type WidgetStatus struct {
AvailableReplicas int32 `json:"availableReplicas"`
}
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
// Widget is the Schema for the widgets API
type Widget struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec WidgetSpec `json:"spec,omitempty"`
Status WidgetStatus `json:"status,omitempty"`
}
//+kubebuilder:object:root=true
// WidgetList contains a list of Widget
type WidgetList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Widget `json:"items"`
}
func init() {
SchemeBuilder.Register(&Widget{}, &WidgetList{})
}
- Generate and Apply the CRD :
Run these commands to generate and apply your CRD.
make manifests
kubectl apply -f config/crd/bases/
Boom! Your CRD is now live in the cluster. 🎉
🛠️ Step 2: Build the Controller
Now, let’s build the controller to manage your Widgets.
- Understand the Watcher-Informer Model :
Watcher : Watches for changes to resources. 👀
Informer : Caches resource state and triggers events. 🤖
2. Implement the Reconcile Loop :
Open controllers/widget_controller.go and add your logic.
package controllers
import (
"context"
"fmt"
"github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
widgetv1 "github.com/yourusername/widget-operator/api/v1"
)
// WidgetReconciler reconciles a Widget object
type WidgetReconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
}
func (r *WidgetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := r.Log.WithValues("widget", req.NamespacedName)
// Fetch the Widget
var widget widgetv1.Widget
if err := r.Get(ctx, req.NamespacedName, &widget); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// Your logic here
log.Info("Reconciling Widget", "name", widget.Name)
fmt.Printf("Widget Spec: %+v\n", widget.Spec)
// Update status
widget.Status.AvailableReplicas = widget.Spec.Replicas
if err := r.Status().Update(ctx, &widget); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
func (r *WidgetReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&widgetv1.Widget{}).
Complete(r)
}
- Run the Controller :
Start your controller locally to test it.
make run
🧪 Step 3: Test Your CRD and Controller
- Create a Widget Resource :
Create a widget-example.yaml file:
apiVersion: widgets.mydomain.com/v1
kind: Widget
metadata:
name: example-widget
spec:
name: "example-widget"
description: "This is an example widget."
replicas: 3
- Apply the Resource :
Use kubectl to apply it.
kubectl apply -f widget-example.yaml
- Check the Logs :
Watch your controller logs to see the magic happen! ✨
🚀 Step 4: Deploy Your Controller
Once it works locally, deploy it to your cluster.
- Build and Push the Docker Image :
make docker-build docker-push IMG=<your-docker-image>
- Deploy the Controller
make deploy IMG=<your-docker-image>
🎉 You Did It!
You’ve just:
✅ Created a Custom Resource Definition (CRD)
✅ Built a Controller using the Watcher-Informer model
✅ Automated your custom resources like a pro! 🚀
Now go forth and extend Kubernetes to fit YOUR needs! 🌟