Trendyol Tech
Published in

Trendyol Tech

Photo by LOGAN WEAVER on Unsplash

Getting Started to Write Your First Kubernetes Admission Webhook Part 1✨

Demo

In this demo, we are going to develop“MutatingAdmissionWebhook” that checks the size of our Memcached Custom Resource, then, if the size equal to zero, we will set the size as 3 by default. But before creating our webhook, we should create our Memcached Operator first, so, in this demo, we are going to create an operator, then, we will create a webhook for the custom resource managed by the operator.

Prerequisites

  • Minikube v1.18.1
  • Operator SDK v1.5.0
  • kubectl v1.20.5
  • Go v1.16.3

Memcached Operator

A Kubernetes Operator is an application-specific controller that extends the functionality of the Kubernetes API to create, configure, and manage instances of complex applications on behalf of a Kubernetes user.

$ mkdir -p memcached-operator
$ cd memcached-operator
$ operator-sdk init --repo=github.com/developer-guy/memcached-operator
Writing scaffold for you to edit…
Get controller runtime:
$ go get sigs.k8s.io/controller-runtime@v0.7.2
Update dependencies:
$ go mod tidy
Next: define a resource with:
$ operator-sdk create api
# Let's look at the folder structure after the project initialized, you should see similar output like the following:
$ tree -L 2 .
.
├── Dockerfile
├── Makefile
├── PROJECT
├── config
│ ├── certmanager
│ ├── default
│ ├── manager
│ ├── prometheus
│ ├── rbac
│ └── scorecard
├── go.mod
├── go.sum
├── hack
│ └── boilerplate.go.txt
└── main.go
# Let's watch the filesystem changes by using fswatch command utility before running "operator-sdk create api" command.
$ fswatch . | xargs -n 1 -I {} echo {}
...
...
...
# Open a second terminal and run this command below, and watch changes on the filesystem through first terminal
$ operator-sdk create api --group cache --version v1 --kind Memcached --resource=true --controller=true
Writing scaffold for you to edit...
api/v1/memcached_types.go
controllers/memcached_controller.go
Update dependencies:
$ go mod tidy
Running make:
$ make generate
go: creating new go.mod: module tmp
Downloading sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.1
go get: added sigs.k8s.io/controller-tools v0.4.1
/Users/batuhan.apaydin/workspace/projects/personal/poc/operator-sdk-examples/memcached-operator/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
# don't forget to add IMG variable, it should specify your DockerHub user id, mine is devopps.
$ make docker-build docker-push IMG=devopps/memcached-operator:v1
...
...
...
The push refers to repository [docker.io/devopps/memcached-operator]
7d8ae67ed609: Pushed
1a5ede0c966b: Layer already exists
v1: digest: sha256:9f017578347d1f861bce531e852d2d11293ee961bc072ee00f4c1f019a4bd858 size: 739
# let's check if image successfully pushed
$ crane ls devopps/memcached-operator
v1
$ crane digest devopps/memcached-operator:v1
sha256:9f017578347d1f861bce531e852d2d11293ee961bc072ee00f4c1f019a4bd858 <-- it should be same hash with the hash of docker push command output.
$ minikube start
😄 minikube v1.18.1 on Darwin 10.15.7
✨ Using the virtualbox driver based on user configuration
👍 Starting control plane node minikube in cluster minikube
🔥 Creating virtualbox VM (CPUs=3, Memory=8192MB, Disk=20000MB) ...
🐳 Preparing Kubernetes v1.20.2 on Docker 20.10.3 ...
▪ Generating certificates and keys ...
▪ Booting up control plane ...
▪ Configuring RBAC rules ...
🔎 Verifying Kubernetes components...
▪ Using image gcr.io/k8s-minikube/storage-provisioner:v4
🌟 Enabled addons: storage-provisioner, default-storageclass
🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
$ make install
...
customresourcedefinition.apiextensions.k8s.io/memcacheds.cache.my.domain created
# let's check if our custom resource definition created
$ kubectl get customresourcedefinitions.apiextensions.k8s.io
NAME CREATED AT
memcacheds.cache.my.domain 2021-04-05T07:23:44Z
$ make deploy IMG=devopps/memcached-operator:v1
...
namespace/memcached-operator-system created
customresourcedefinition.apiextensions.k8s.io/memcacheds.cache.my.domain configured
serviceaccount/memcached-operator-controller-manager created
role.rbac.authorization.k8s.io/memcached-operator-leader-election-role created
clusterrole.rbac.authorization.k8s.io/memcached-operator-manager-role created
clusterrole.rbac.authorization.k8s.io/memcached-operator-metrics-reader created
clusterrole.rbac.authorization.k8s.io/memcached-operator-proxy-role created
rolebinding.rbac.authorization.k8s.io/memcached-operator-leader-election-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/memcached-operator-manager-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/memcached-operator-proxy-rolebinding created
configmap/memcached-operator-manager-config created
service/memcached-operator-controller-manager-metrics-service created
deployment.apps/memcached-operator-controller-manager created
# check if controller are working on namespace memcached-operator-system
$ kubectl get pods --namespace memcached-operator-system
NAME READY STATUS RESTARTS AGE
memcached-operator-controller-manager-6c9655978-7smfl 2/2 Running 0 72s
# this is our custom resource
$ cat config/samples/cache_v1_memcached.yaml
apiVersion: cache.my.domain/v1
kind: Memcached
metadata:
name: memcached-sample
spec:
# Add fields here
foo: bar
size: 0
$ kubectl apply -f config/samples/cache_v1_memcached.yaml
memcached.cache.my.domain/memcached-sample created
# now, we can get our CR like any other Kubernetes resource such as Deployment, Pod etc.
$ kubectl get memcacheds.cache.my.domain
NAME AGE
memcached-sample 63s
$ kubectl get memcacheds.cache.my.domain memcached-sample -oyaml
apiVersion: cache.my.domain/v1
kind: Memcached
metadata:
name: memcached-sample
spec:
# Add fields here
foo: bar
size: 0

MutatingAdmissionWebhook for Memcached Custom Resource

In this section we are going to generate a code template for our webhook, then, we will add a new field called “size” to our custom resource, then, in the webhook logic, we will check the value of the size field, if it is equal to zero, we will update the size as three.

# --defaulting: This flag will scaffold the resources required for a mutating webhook# --programmatic-validation: This flag will scaffold the resources required for a validating webhook$ operator-sdk create webhook --group cache --version v1 --kind Memcached --defaulting --programmatic-validation
Writing scaffold for you to edit...
api/v1/memcached_webhook.go
// Default implements webhook.Defaulter so a webhook will be registered for the type
func (r *Memcached) Default() {
memcachedlog.Info("default", "name", r.Name)

// TODO(user): fill in your defaulting logic.
}
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *Memcached) ValidateCreate() error {
memcachedlog.Info("validate create", "name", r.Name)

// TODO(user): fill in your validation logic upon object creation.
return nil
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (r *Memcached) ValidateUpdate(old runtime.Object) error {
memcachedlog.Info("validate update", "name", r.Name)

// TODO(user): fill in your validation logic upon object update.
return nil
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (r *Memcached) ValidateDelete() error {
memcachedlog.Info("validate delete", "name", r.Name)

// TODO(user): fill in your validation logic upon object deletion.
return nil
}
if r.Spec.Size == 0 {
r.Spec.Size = 3
}
$ make manifests
.PHONY: install-cert-manager ## Deploy cert-manager to the cluster
install-cert-manager:
helm install \
cert-manager jetstack/cert-manager \
--namespace cert-manager \
--version v1.2.0 \
--create-namespace \
--set installCRDs=true
.PHONY: install-cert-manager ## Deploy cert-manager to the cluster
install-cert-manager:
helm install \
cert-manager jetstack/cert-manager \
--namespace cert-manager \
--version v1.2.0 \
--create-namespace \
--set installCRDs=true
$ make install-cert-manager
$ kubectl get pods --namespace cert-manager
NAME READY STATUS RESTARTS AGE
cert-manager-85f9bbcd97-fq8wn 1/1 Running 0 26s
cert-manager-cainjector-74459fcc56-z2h8b 1/1 Running 0 26s
cert-manager-webhook-57d97ccc67-466mq 1/1 Running 0 26s
$ make docker-build docker-push IMG=devopps/memcached-operator:v2
...
The push refers to repository [docker.io/devopps/memcached-operator]
b09d21af0c50: Pushed
1a5ede0c966b: Layer already exists
v2: digest: sha256:69fbcaaf3bc3bb00c542b47e5eca1e96586c5e22123d2edc0067c4904a73ba8d size: 739
$ make deploy IMG=devopps/memcached-operator:v2
...
namespace/memcached-operator-system unchanged
customresourcedefinition.apiextensions.k8s.io/memcacheds.cache.my.domain configured
serviceaccount/memcached-operator-controller-manager unchanged
role.rbac.authorization.k8s.io/memcached-operator-leader-election-role unchanged
clusterrole.rbac.authorization.k8s.io/memcached-operator-manager-role configured
clusterrole.rbac.authorization.k8s.io/memcached-operator-metrics-reader unchanged
clusterrole.rbac.authorization.k8s.io/memcached-operator-proxy-role unchanged
rolebinding.rbac.authorization.k8s.io/memcached-operator-leader-election-rolebinding unchanged
clusterrolebinding.rbac.authorization.k8s.io/memcached-operator-manager-rolebinding unchanged
clusterrolebinding.rbac.authorization.k8s.io/memcached-operator-proxy-rolebinding unchanged
configmap/memcached-operator-manager-config unchanged
service/memcached-operator-controller-manager-metrics-service unchanged
service/memcached-operator-webhook-service created
deployment.apps/memcached-operator-controller-manager configured
certificate.cert-manager.io/memcached-operator-serving-cert created
issuer.cert-manager.io/memcached-operator-selfsigned-issuer created
mutatingwebhookconfiguration.admissionregistration.k8s.io/memcached-operator-mutating-webhook-configuration created
validatingwebhookconfiguration.admissionregistration.k8s.io/memcached-operator-validating-webhook-configuration created
$ cat << EOF | k apply -f -
apiVersion: cache.my.domain/v1
kind: Memcached
metadata:
name: memcached-sample
spec:
# Add fields here
foo: bar
size: 0
EOF
memcached.cache.my.domain/memcached-sample created
$ kubectl get memcacheds.cache.my.domain memcached-sample -oyaml | kubectl neat
apiVersion: cache.my.domain/v1
kind: Memcached
metadata:
name: memcached-sample
namespace: default
spec:
foo: bar
size: 3 <-- you should see the size as 3.

Conclusion

Without using these scaffolding tools, our life could get much harder. By using them, we can create Kubernetes Admission Webhooks easily and quickly.

References

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
developer-guy

I do mostly Go, Kubernetes, and cloud-native stuff ⛵️🐰🐳