Writing Custom Kubernetes Controller and Webhooks | by Karthik K N | Mar, 2022

Create a Kubernetes API, controller, validate webhooks, and test.

Karthik KN
Photo by Benjamin Davies on Unsplash
$ mkdir custom-k8-controller

$ cd custom-k8-controller

$ go mod init github.com/Karthik-K-N/custom-k8-controller

$ kubebuilder init --domain sample.domain --repo github.com/Karthik-K-N/custom-k8-controller

$ kubebuilder create api --group calculator --version v1 --kind Sum
Create Resource [y/n]
y
Create Controller [y/n]
y

// SumSpec defines the desired state of Sum
type SumSpec struct {
NumberOne int `json:"numberOne,omitempty"`
NumberTwo int `json:"numberTwo,omitempty"`
}

// SumStatus defines the observed state of Sum
type SumStatus struct {
Result int `json:"result,omitempty"`
}

func (r *SumReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {

var sum calculatorv1.Sum
if err := r.Get(ctx, req.NamespacedName, &sum); err != nil {
klog.Error(err, "unable to fetch sum")
return ctrl.Result{}, client.IgnoreNotFound(err)
}
klog.Infof("Found the sum object %v", sum)

klog.Infof("Calculating the sum of %d and %d", sum.Spec.NumberOne, sum.Spec.NumberTwo)
result := sum.Spec.NumberOne + sum.Spec.NumberTwo
sum.Status.Result = result

klog.Info("Updating the result of calculation")
if err := r.Status().Update(ctx, &sum); err != nil {
klog.Error(err, "Unable to update sum status")
return ctrl.Result{}, err
}

klog.Infof("Successfully updated the sum status with result %d", result)
return ctrl.Result{}, nil
}

$ kind create cluster --name custom-controller
$ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.7.1/cert-manager.yaml
$ make manifests
$ make install
$ make run
$ cat custom-k8-controller/config/samples/calculator_v1_sum.yaml
apiVersion: calculator.sample.domain/v1
kind: Sum
metadata:
name: sum-sample
spec:
numberOne: 10
numberTwo: 20
$ kubectl create -f custom-k8-controller/config/samples/calculator_v1_sum.yaml$ kubectl get sum
NAME AGE
sum-sample 5m8s
$ kubectl get sum sum-sample -o yaml
apiVersion: calculator.sample.domain/v1
kind: Sum
metadata:
creationTimestamp: "2022-02-27T17:17:03Z"
generation: 1
name: sum-sample
namespace: default
resourceVersion: "4562"
uid: 9d25777c-d695-485f-9ba0-9c65526a8737
spec:
numberOne: 10
numberTwo: 20
status:
result: 30
//+kubebuilder:printcolumn:name="Number One",type="integer",JSONPath=".spec.numberOne",description="Input number one"
//+kubebuilder:printcolumn:name="Number Two",type="integer",JSONPath=".spec.numberTwo",description="Input number two"
//+kubebuilder:printcolumn:name="Result",type="integer",JSONPath=".status.result",description="Sum of two numbers"

// Sum is the Schema for the sums API
type Sum struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec SumSpec `json:"spec,omitempty"`
Status SumStatus `json:"status,omitempty"`
}

$ make manifests
$ make install
$ make run
$ kubectl get sum
NAME NUMBER ONE NUMBER TWO RESULT
sum-sample 10 20 30
$ kubebuilder create webhook --group calculator --version v1 --kind Sum  --programmatic-validation
func (r *Sum) ValidateCreate() error {
klog.Infof("In validate create of %s", r.Name)
if r.Spec.NumberOne < 0 || r.Spec.NumberTwo < 0 {
return fmt.Errorf("The input values Number One: %d Number Two: %d cannot be negative ", r.Spec.NumberOne, r.Spec.NumberTwo)
}
return nil
}
1. config/crd/kustomization.yaml
2. config/default/kustomization.yaml
$ cat config/default/webhookcainjection_patch.yamlapiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: validating-webhook-configuration
annotations:
cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
$ make docker-build IMG=karthik/custom-controller:v1
kind load docker-image karthik/custom-controller:v1 --name custom-controller
make deploy IMG=karthik/custom-controller:v1
$ kubectl -n custom-k8-controller-system  get pods
NAME READY STATUS RESTARTS AGE
custom-k8-controller-controller-manager-784f74cdf6-45hpq 2/2 Running 0 66s
$ kubectl create -f config/samples/calculator_v1_sum.yamlError from server (The input values Number One: -10 Number Two: 20 cannot be negative ): error when creating "config/samples/calculator_v1_sum.yaml": admission webhook "vsum.kb.io" denied the request: The input values Number One: -10 Number Two: 20 cannot be negative
$ kubectl -n custom-k8-controller-system logs -f custom-k8-controller-controller-manager-784f74cdf6-45hpqI0304 15:45:49.386571       1 sum_webhook.go:39] In validate create of sum-sample
1.646408749386699e+09 DEBUG controller-runtime.webhook.webhooks wrote response {"webhook": "/validate-calculator-sample-domain-v1-sum", "code": 403, "reason": "The input values Number One: -10 Number Two: 20 cannot be negative ", "UID": "2343b586-dc3f-45b5-9c75-df28b1c30b40", "allowed": false}
type SumWebhook struct {
}
func (r *SumWebhook) SetupWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(&v1.Sum{}).
WithValidator(r).
Complete()
}
// var _ webhook.Validator = &Sum{}
var _ webhook.CustomValidator = &SumWebhook{}
//if err = (&calculatorv1.Sum{}).SetupWebhookWithManager(mgr); err != nil {
// setupLog.Error(err, "unable to create webhook", "webhook", "Sum")
// os.Exit(1)
//}

if err = (&webhook.SumWebhook{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "Sum")
os.Exit(1)
}

Leave a Comment