Kubebuilder Admission Webhook for Core Types


January 18, 2024

This guide is based on the KubeBuilder version 3.13.0 and Kubernetes veriosn 1.27.1.

Introduction

In this post, we will explore how to build an admission webhook server using Kubebuilder for core types, specifically focusing on Pods in Kubernetes. Let’s consider the following example:

  • We have a custom API type named MyPod.
  • We have defined a webhook server associated with this type that watches Pods from the core/v1 API group.
  • The objective is to monitor all Pod objects and take appropriate actions when necessary.

Getting Started

Let’s create the project:

mkdir myproject && cd myproject

DOMAIN=my.domain
REPO=my.domain/myproject

kubebuilder init --domain ${DOMAIN} --repo ${REPO}

kubebuilder create api --version v1 --kind MyPod
# Answer yes to create Resource and no to create Controller

# We create both mutating and validating webhook, 
# so we use both --conversion --defaulting flags
kubebuilder create webhook --version v1 --kind MyPod --conversion --defaulting

Now, let’s make some modifications. Modify the api/v1/mypod_webhook.go file to be similar to the code below. Please read the comments carefully. The important changes you must make include:

  • The SetupWebhookWithManager function.
  • The marker comments.
  • The implmenetation of Default and Validate functions.
package v1

import (
	"context"

	corev1 "k8s.io/api/core/v1"

	"k8s.io/apimachinery/pkg/runtime"
	ctrl "sigs.k8s.io/controller-runtime"
	logf "sigs.k8s.io/controller-runtime/pkg/log"
	"sigs.k8s.io/controller-runtime/pkg/webhook"
	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

var mypodlog = logf.Log.WithName("mypod-resource")

// SetupWebhookWithManager will setup the manager to manage the webhooks
func (r *MyPod) SetupWebhookWithManager(mgr ctrl.Manager) error {
	return ctrl.NewWebhookManagedBy(mgr).
		// For(r). // We are not interested in Mypod itself,
			         // We watch for a specific resource (i.e. Pod)
		For(&corev1.Pod{}).
		WithDefaulter(&MyPod{}).
		WithValidator(&MyPod{}).
		Complete()
}


// Carefully check out the following marker comments
//+kubebuilder:webhook:path=/mutate--v1-pod,mutating=true,failurePolicy=fail,sideEffects=None,groups="",resources=pods,verbs=create;update,versions=v1,name=mpod.kb.io,admissionReviewVersions=v1
//+kubebuilder:webhook:path=/validate--v1-pod,mutating=true,failurePolicy=fail,sideEffects=None,groups="",resources=pods,verbs=create;update;delete,versions=v1,name=vpod.kb.io,admissionReviewVersions=v1

// var _ webhook.Defaulter = &MyPod{} 
var _ webhook.CustomDefaulter = &MyPod{}
var _ webhook.CustomValidator = &MyPod{}

func (r *MyPod) Default(ctx context.Context, obj runtime.Object) error {
	// func (r *MyPod) Default() {
	mypodlog.Info("default", "name", r.Name)
	return nil
}

func (r *MyPod) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
	mypodlog.Info("validate CREATE", "name", r.Name)
	return nil, nil
}
func (r *MyPod) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
	mypodlog.Info("validate DELETE", "name", r.Name)
	return nil, nil
}
func (r *MyPod) ValidateUpdate(ctx context.Context, obj runtime.Object, newObj runtime.Object) (admission.Warnings, error) {
	mypodlog.Info("validate UPDATE", "name", r.Name)
	return nil, nil
}

The next step is to prepare the CRDs for your new custom API object and webhooks. Kubebuilder generates the required YAML files for you. However, you need to make a few changes to the webhook manifests to run the server locally.

# Generate yaml file based on the marker commands
make manifest

# Generate the CRD for your custom API type
cd config/crd
kubectl kustomize ./ > output.yaml

cat "---" >> output.yaml

# Adjust the path accordingly 
cat config/webhook/manifests.yaml >> output.yaml

Now output.yaml contains everything you need. You just need to modify all occurrences of webhooks.clientConfig fields in the output.yaml file. Following the instructions from another post on how to deploy a local webhook server, we should remove the service field and add caBundle and url fields for all webhooks:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  annotations:
    controller-gen.kubebuilder.io/version: v0.13.0
  name: mypods.my.domain
spec:
  conversion:
    strategy: Webhook
    webhook:
      clientConfig:
        # adjust the values based on your environment
        caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JS...
        url: https://172.17.0.1:9443/convert
        # service:
        #   name: webhook-service
        #   namespace: system
        #   path: /convert
      conversionReviewVersions:
      - v1
  # 
  # OMITTED FOR BREVITY
  # 
---
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: mutating-webhook-configuration
webhooks:
- admissionReviewVersions:
  - v1
  clientConfig:
    # adjust the values based on your environment
    caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JS...
    url: https://172.17.0.1:9443/mutate--v1-pod
    # service:
    #   name: webhook-service
    #   namespace: system
    #   path: /mutate--v1-pod
  failurePolicy: Fail
  name: mpod.kb.io
  rules:
  - apiGroups:
    - ""
    apiVersions:
    - v1
    operations:
    - CREATE
    - UPDATE
    resources:
    - pods
  sideEffects: None
- admissionReviewVersions:
  - v1
  clientConfig:
    # adjust the values based on your environment
    caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JS...
    url: https://172.17.0.1:9443/validate--v1-pod
    # service:
    #   name: webhook-service
    #   namespace: system
    #   path: /validate--v1-pod
  failurePolicy: Fail
  name: vpod.kb.io
  rules:
  - apiGroups:
    - ""
    apiVersions:
    - v1
    operations:
    - CREATE
    - UPDATE
    - DELETE
    resources:
    - pods
  sideEffects: None

Now apply the YAML file and run the server:

kubectl apply -f ./output.yaml

# go to the root direcotry and run
make run

Now, apply the following YAML file to create a Pod. Monitor the logs of the webhook server.

apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
spec:
  containers:
  - name: nginx-container
    image: nginx:latest
    ports:
    - containerPort: 80

That’s it! You may also check out this page and this page for more information.


Linux Kubernetes