Skip to content

Webhooks

Your controllers can serve admission webhooks (validating, mutating) and CRD conversion webhooks. Your control plane calls them like a Kubernetes cluster would, through a Service with a certificate it already trusts.

In the controller’s settings, set Webhook port to the port your controller serves webhooks on. controller-runtime uses 9443. In a Tenant, that’s:

controllers:
- name: guestbook
image: ghcr.io/you/guestbook-operator:v1.0.0
webhook: {port: 9443} # webhook: {} means 9443 too

The controller then gets:

Service ctrl-<controller>-webhook, port 443 In your control plane’s namespace. Your control plane forwards it to the webhook port.
/tmp/k8s-webhook-server/serving-certs/tls.crt, tls.key A serving certificate for that Service, where controller-runtime looks by default. It’s renewed before it expires, and the controller restarts with the new one.

Use a service reference with your control plane’s namespace (the one in WATCH_NAMESPACE), and leave caBundle empty: your control plane already trusts the certificate. You don’t need cert-manager.

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: guestbook
webhooks:
- name: vguestbook.webapp.example.com
clientConfig:
service:
namespace: acme # your control plane's namespace
name: ctrl-guestbook-webhook # ctrl-<controller>-webhook
path: /validate-webapp-example-com-v1-guestbook
rules:
- apiGroups: [webapp.example.com]
apiVersions: [v1]
operations: [CREATE, UPDATE]
resources: [guestbooks]
admissionReviewVersions: [v1]
sideEffects: None

Conversion webhooks work the same way, in the CRD:

spec:
conversion:
strategy: Webhook
webhook:
conversionReviewVersions: [v1]
clientConfig:
service: {namespace: acme, name: ctrl-guestbook-webhook, path: /convert}

Services in other namespaces don’t resolve, and url webhooks aren’t given the certificate.

A kubebuilder project’s webhooks work unchanged in the code. In config/:

  • Drop cert-manager (the certmanager directory and the cert-manager.io/inject-ca-from annotations) and the webhook-service: ctrlplane provides both.
  • In the generated webhook configurations and CRD patches, set the service name to ctrl-<controller>-webhook and the namespace to your control plane’s.
  • Don’t set ENABLE_WEBHOOKS=false.

Apply the webhook configurations and CRDs with your control plane’s kubeconfig.

  • Webhooks may only match your own API groups, not built-in ones: "", * and groups ending in k8s.io (which includes x-k8s.io) or kubernetes.io are refused. So they can’t intercept namespaces, RBAC or Secrets.
  • With failurePolicy: Fail (the default), requests your webhook matches fail while your controller isn’t running, for example while a new version can’t start.