Kubernetes provides ways to extend its built-in functionality, probably most commonly with custom resource types and custom controllers, but in addition, Kubernetes has some other very interesting features. For example, admission webhooks
can be used to extend the API for modifying the basic behaviour of certain Kubernetes resources.
Admission Controllers are snippets of code used to intercept requests to the Kubernetes API Server before the object is persisted, and to let them through after they have been authenticated and authorized. Mutating
controllers may modify the resource objects they process, Validating
controllers do not. If any controller in either phase rejects the request, the entire request is immediately rejected and the error is returned to the end user.
This means that there are special controllers that can intercept Kubernetes API requests and modify or reject them according to custom logic.
Kubernetes has a list of controllers that it has implemented itself.
Of course you can also write your own controllers, although these sound more powerful, they need to be compiled into kube-apiserver and can only be started when apiserver is started.
You can also use the kube-apiserver startup parameters directly to see what controllers are supported built-in.
|
|
Due to the limitations of the controller above, we need to use the concept of dynamic rather than coupling it with the apiserver, and Admission webhooks
solves this limitation with a dynamic configuration method.
What is an admission webhook?
There are two special Admission Controllers in the Kubernetes apiserver: MutatingAdmissionWebhook
and ValidatingAdmissionWebhook
, which will send an admission request to an external HTTP callback service and receive an admission response. If these two Admission Controllers are enabled, the Kubernetes administrator can create and configure an admission webhook in the cluster.
The overall steps are shown below.
- Check that the admission webhook controller is enabled in the cluster and configure it as required.
- Write an HTTP callback for handling the admission request, which can be a simple HTTP service deployed in the cluster, or even a
serverless
function. - Configure the admission webhook with the
MutatingWebhookConfiguration
andValidatingWebhookConfiguration
resources.
The difference between these two types of admission webhooks is very clear: validating webhooks
can reject requests, but they cannot modify the objects fetched in an admission request, whereas mutating webhooks
can modify objects before returning an admission response by creating patch to modify the object before returning the admission response, and if the webhook rejects a request, an error will be returned to the end user.
The very hot Service Mesh application istio
, which automatically injects the sidecar container Envoy
into the Pod by mutating webhooks.
https://istio.io/latest/docs/setup/additional-setup/sidecar-injection/
Creating and Configuring an Admission Webhook
We’ve covered the theory of the Admission Webhook, so let’s test it out in a real Kubernetes cluster. We will create a webhook webserver, deploy it to the cluster, and then create a webhook configuration to see if it works.
First make sure that MutatingAdmissionWebhook
and ValidatingAdmissionWebhook
are enabled in apiserver with the parameter --enable-admission-plugins
, which is already enabled by default in v1.25+. If not, you need to add these two parameters and restart apiserver.
Then check if the Admission registration API is enabled in the cluster by running the following command.
Once these prerequisites are met, we can write the admission webhook server, which is actually a webserver developed to handle an AdmissionReview
request sent by the APIServer, in the following format.
|
|
Then just construct an AdmissionReview
object and write it back.
|
|
The determining fields are .response.uid
, which uniquely identifies the request, and .response.allowed
, which indicates whether the request has passed or failed, and the status field, which is mainly for error messages.
We can refer directly to the example implementation code in the Kubernetes e2e test.
https://github.com/kubernetes/kubernetes/blob/release-1.25/test/images/agnhost/webhook/main.go
Writing a webhook
Once the previous prerequisites have been met, let’s implement a webhook example that performs validating
and mutating webhook
authentication by listening to two different HTTP endpoints (validate and mutate).
The full code for this webhook is available on Github: https://github.com/cnych/admission-webhook-example(train4 branch). The webhook is a simple HTTP service with TLS authentication, deployed in our cluster using Deployment.
The main logic in the code is in two files: main.go
and webhook.go
. main.go contains the code to create the HTTP service, while webhook.go contains the logic for both the validates
and mutates
webhooks, most of the code is relatively simple. file to see how to use the standard golang package to start the HTTP service and how to read the TLS configured certificate from the command line flags.
Then there is the more important serve function, which handles incoming HTTP requests for the mutate
and validating
functions. This function deserializes the AdmissionReview
object from the request, performs some basic content validation, calls the appropriate mutate
and validating
functions based on the URL path, and then serializes the AdmissionReview
object.
|
|
The main admission logic is the validate
and mutate
functions. The validate
function checks if the resource object needs to be validated: resources in the kube-system
namespace are not validated, and if you want to display a declaration that a resource is not validated, you can declare it by adding an admission-webhook-example.qikqiak.com/validate=false
to the annotation
. If validation is required, the resource type is compared to its counterpart based on the kind of the resource type and the tag. The service or deployment resource is deserialized from the request. If some label is missing, Allowed is set to false in the response. if validation fails, the reason for the failure is written in the response and the end user receives a failure message when trying to create the resource. the validate function is implemented as shown below.
|
|
The method for determining whether checks are required is as follows, either by ignoring them through namespace or by configuring them through annotations settings.
|
|
The code for the mutate
function is very similar, but instead of just comparing tags and setting Allowed in the response, it creates a patch that adds the missing tag to the resource and sets not_available
to the value of the tag.
|
|
Build
We’ve actually packaged the code into a docker image that you can use directly, the image repository address is: cnych/admission-webhook-example:v4
. Of course, if you want to change some of the code, you’ll need to rebuild the project. Since this project is developed in go, the package management tool has been changed to go mod
, so we need to make sure that the go environment is installed in the build environment, and of course docker is essential, since all we need is to package it as a docker image.
Clone Project.
We can see that there is a build script under the root of the code, so we just need to provide our own docker image username and build it.
Deployment
Registering our webhook with the apiserver, how the apiserver knows the service exists and how to invoke the interface requires the use of the ValidatingWebhookConfiguration
and MutatingWebhookConfiguration
objects. By creating this resource object, apiserver will register our webhook in its ValidatingAdmissionWebhook
controller module, with a few caveats when creating the object.
- apiserver only supports HTTPS webhook, so you must prepare a TLS certificate, which you can usually get using Kubernetes
CertificateSigningRequest
orcert-manager
. clientConfig.caBundle
is used to specify the CA certificate for issuing TLS certificates, if you use KubernetesCertificateSigningRequest
to issue certificates, you can get the cluster CA from thekube-public namespace clusterinfo
, base64 formatted and written toclientConfig.caBundle
; if you use cert-manager to issue the certificate, the component will automatically inject the certificate.- To prevent yourself from intercepting yourself, you can use
objectSelector
to exclude yourself. - For in-cluster deployments, use service ref to specify the service
- For out-of-cluster deployments, use url to specify the HTTPS interface
Here we create a ValidatingWebhookConfiguration
object as shown below.
|
|
In this object we register a validating webhook and specify the address of the webhook via clientConfig.service
, normally we would also specify the contents of the caBundle
, but here we have configured a cert-manager.io/inject-ca-from: default/admission-example-tls-secret
annotations, so that we can automatically inject CA content with cert-manager
, and we also configure a namespaceSelector
to indicate that we have The webhook will only be applied to namespaces with the admission-webhook-example: enabled
tag.
So here we also need to deploy the cert-manager.
|
|
The following Pods will be run in the cert-manager
namespace when the installation is complete.
The cert-manager
has a component called CA injector
which is responsible for injecting the CA bundle into the Mutating | ValidatingWebhookConfiguration
. This means that we need to use an annotation with key certmanager.k8s.io/inject-ca-from
in the Mutating | ValidatingWebhookConfiguration
object, and the value of the annotation should start with <certificate-namespace>/<certificate-name>
to point to an existing certificate CR instance.
Here we create the Certificate
object as shown below, requiring only the Issuer
of selfSigned
.
|
|
Note that the Certificate
object name corresponds to the annotations above, and then we write the final issued certificate to the Secret object called admission-webhook-example-certs
.
Next we can deploy our webhook server. Deployment is very simple, we just need to configure the TLS configuration of the service. We can look at the configuration statement for the certificate in the deployment.yaml
file under the deployment folder in the root of the code and see that the certificate and private key files read from the command line parameters are mounted via a secret object.
The admission-webhook-example-certs
object is automatically generated from the Certificate
object created by the Cert-manager above, and once the secret object is created, we can create the deployment and service objects directly.
We then add the following tag to the default namespace.
Finally, just create the validatingwebhookconfigurations
object above, and once it is created, it will intercept the request and call our webhook service.
|
|
Testing
Now let’s create a deployment resource to verify that it works. There is a sleep.yaml
resource manifest file under the code repository, just create it.
Normally it would be created with the error message above because we didn’t bring any of the required tags and then deploy another sleep-with-labels.yaml
list of resources.
We can see that it deploys fine, then we remove the above deployment and deploy another sleep-no-validation.yaml
resource manifest, which does not have the required tags, but is configured with an annotation like admission-webhook-example.qikqiak.com/validate=false
, so that it can be created normally.
Deploying mutating webhook
In the same way we can create a MutatingWebhookConfiguration
object. First, we remove the validating webhook
from above to prevent interference with mutating, and then deploy the new configuration. The mutating webhook
configuration is basically the same as the validating webhook
configuration, but the path to the webook server is /mutate
.
|
|
Now we can deploy the sleep application above again and see if the label tags are correctly added.
|
|
Finally, we recreate the validating webhook
to test it together. Now, try to create the sleep application again. It works as expected.
Admission control is carried out in two stages, the first stage running the mutating admission
controller and the second stage running the validating admission
controller.
So mutating webhook
adds the missing labels in the first stage, and then validating webhook
doesn’t reject the deployment in the second stage, because the labels already exist, and sets their value with not_available
.
|
|
However, if we have such a need, it would be very cumbersome and inflexible to develop a separate webhook for the admission controller. To solve this problem we can use some policy management engines provided by Kubernetes to implement our needs without writing code, such as Kyverno
, Gatekeeper
, etc. Policy management is also officially supported in Kubernetes v1.26.