This guide is based on the KubeBuilder version 3.13.0 and Kubernetes veriosn 1.27.1.
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 thecore/v1
API group.- The objective is to monitor all
Pod
objects and take appropriate actions when necessary.
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:
SetupWebhookWithManager
function.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.