What is MutatingWebhook in Kubernetes
A MutatingWebhook in Kubernetes is a type of webhook that allows for modifying resources before they are persisted in the Kubernetes API server. This can be used to enforce certain policies or configurations on resources as they are created or updated.
For example, a MutatingWebhook could be used to automatically add specific labels or annotations to a Kubernetes pod, or to inject environment variables into a container. The webhook is triggered by a specific event such as a create or update and it modifies the object before it is persisted.
MutatingWebhooks are defined in a Kubernetes MutatingWebhookConfiguration object, which specifies the types of resources the webhook applies to, the endpoint to call for the webhook, and the types of operations (create, update, delete) that trigger the webhook.
Once the webhook is defined and registered to the API server, it will be invoked automatically when a matching resource is created, updated or deleted. It can accept or reject the resource based on its validation process.
Here’s an example of a MutatingWebhook manifest file that adds a label to pods:
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: add-label-webhook
webhooks:
- name: add-label.example.com
clientConfig:
url: https://webhook.example.com/add-label
caBundle:
...
rules:
- operations: [ "CREATE", "UPDATE" ]
apiGroups: ["", "apps", "batch"]
apiVersions: ["v1", "v1beta1", "v1beta2"]
resources: ["pods"]
failurePolicy: Ignore
It’s worth noting that this is just an example and the exact configuration will depend on the specific use case and the requirements.
A Kubernetes manifest file for a MutatingWebhook typically includes the following fields:
apiVersion: The version of the Kubernetes API that the resource belongs to.
kind: The type of resource being defined in the manifest. For a MutatingWebhook, this will be "MutatingWebhookConfiguration".
metadata: Metadata about the resource, including its name and labels.
webhooks: An array of webhooks, each of which includes the following fields:
- name: A unique name for the webhook
- clientConfig: Configuration for the client that calls the webhook, including the url endpoint and the caBundle for verifying the webhook’s server certificate. caBundle is a A PEM encoded CA bundle which will be used to validate the webhook server’s certificate.
- rules: An array of rules for triggering the webhook, including the operations (create, update, delete) and resources that the webhook applies to.
- failurePolicy: A policy for handling failures, such as Ignore or Fail.
Some additional use cases for mutating webhooks in Kubernetes include:
- Automatic sidecar injection: A mutating webhook can be used to automatically inject a sidecar container into a pod, for example to provide logging or monitoring functionality.
- Automatic configuration injection: A mutating webhook can be used to automatically inject configuration files or environment variables into a container, for example to provide database connection details or API keys.
- Automatic security enhancements: A mutating webhook can be used to automatically add security-related configuration to a pod, such as adding a security context or configuring network policies.
- Automatic scaling: A mutating webhook can be used to automatically configure horizontal pod autoscaler(HPA) based on the resource usage of a pod.
- Automatic patching: A mutating webhook can be used to automatically patch containers that are running vulnerable versions of software.
- Automatic validations: A mutating webhook can be used to automatically validate the configuration of a pod or service before it is created, and reject the request if it does not meet certain criteria.
Here is an example of a mutating webhook manifest file in YAML that injects labels into pods in the my-namespace namespace:
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: inject-labels-webhook
webhooks:
- name: inject-labels.example.com
rules:
- apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
namespaceSelector:
matchExpressions:
- {key: "name", operator: "In", values: ["core-services"]}
failurePolicy: Fail
clientConfig:
service:
name: webhook-service
namespace: webhook-namespace
path: "/inject"
caBundle: <base64 encoded ca bundle>
This manifest creates a mutating webhook configuration named inject-labels-webhook, and a webhook named inject-labels.example.com. The webhook is configured to only mutate pods in the v1 apiGroup, and to fail if the mutation fails. The webhook is also configured to only mutate pods in the my-namespace namespace. The webhook is backed by a service named webhook-service in the webhook-namespace namespace, and calls the /inject path on that service. The clientConfig also includes a caBundle field, which should contain the base64 encoded CA bundle for the certificate used to secure the webhook service.
This manifest uses the namespaceSelector field to specify that the webhook should only mutate pods in the my-namespace namespace. The matchExpressions field is used to specify that the name label should match the value my-namespace using the In operator. This means that the webhook will only be invoked on pod resources in the my-namespace namespace.
Here is an example of a mutating webhook in Go that injects labels into pods:
package main
import (
"encoding/json"
"fmt"
"net/http"
admissionv1 "k8s.io/api/admission/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
)
var (
runtimeScheme = runtime.NewScheme()
codecs = serializer.NewCodecFactory(runtimeScheme)
deserializer = codecs.UniversalDeserializer()
)
func main() {
http.HandleFunc("/inject", inject)
http.ListenAndServe(":8080", nil)
}
func inject(w http.ResponseWriter, r *http.Request) {
var body []byte
if r.Body != nil {
if data, err := ioutil.ReadAll(r.Body); err == nil {
body = data
}
}
if len(body) == 0 {
http.Error(w, "no body found", http.StatusBadRequest)
return
}
contentType := r.Header.Get("Content-Type")
if contentType != "application/json" {
http.Error(w, "invalid Content-Type, want `application/json`", http.StatusUnsupportedMediaType)
return
}
var admissionResponse *admissionv1.AdmissionResponse
ar := admissionv1.AdmissionReview{}
if _, _, err := deserializer.Decode(body, nil, &ar); err != nil {
admissionResponse = &admissionv1.AdmissionResponse{
Result: &metav1.Status{
Message: err.Error(),
},
}
} else {
admissionResponse = mutatePods(ar)
}
admissionReview := admissionv1.AdmissionReview{}
if admissionResponse != nil {
admissionReview.Response = admissionResponse
if ar.Request != nil {
admissionReview.Response.UID = ar.Request.UID
}
}
resp, err := json.Marshal(admissionReview)
if err != nil {
http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError)
}
if _, err := w.Write(resp); err != nil {
http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
}
}
func mutatePods(ar admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {
req := ar.Request
var pod corev1.Pod