Topic 8.2
Admission Webhooks for Custom Resources
In one line
Phase 5.4 covered admission control for built-in resources using ready-made policy engines — this topic covers the other real use case: validating and defaulting your OWN custom resources, exactly the piece a real Operator usually ships alongside its CRD.
Key ideas
- 01
Phase 5.4 covered admission control largely through policy engines (Kyverno, OPA/Gatekeeper) enforcing rules on BUILT-IN resource types — a genuinely different, equally common real use case is a CUSTOM RESOURCE (Topic 8.1) needing its OWN validation and defaulting logic, which is exactly what a real Operator typically ships its own dedicated admission webhook for.
- 02
A VALIDATING webhook for a custom resource enforces rules the CRD's own schema alone can't express — a CRD's OpenAPI schema can require a field to be a string or an integer, but genuinely cross-field logic ('replicas must be an odd number if this is a quorum-based cluster type') requires a real webhook actually evaluating the full object.
- 03
A MUTATING webhook for a custom resource can apply DEFAULTS or NORMALIZE values before they're ever stored — filling in a sensible default
versionif the user didn't specify one, or normalizing a user-provided value into a canonical internal form the Operator's own reconciliation logic expects to work with consistently. - 04
This is precisely why a real, mature Operator is usually MORE than just a controller watching a CRD — it typically ships its own webhook server too, registered specifically against its own custom resource type, providing the exact same validate-and-default safety net for its custom objects that Kubernetes' own built-in admission mechanisms (Phase 5.4) provide for built-in ones.
- 05
The full request lifecycle from Phase 5.4 (authenticate → authorize/RBAC → admission control) applies completely identically here — a request to create your custom resource still passes through all three stages; the only difference is that the admission webhook being invoked is one specifically written for YOUR resource type, rather than a general-purpose policy engine's generic rule evaluation.
- 06
Genuinely knowing this distinction — general policy engines (Kyverno/OPA) for built-in resources and organization-wide policies, versus a resource-specific webhook shipped alongside a CRD for that resource's own particular validation/defaulting needs — is what completes the full picture of admission control this course has built up across both Phase 5.4 and this topic.
Code & diagrams
The exact same three-stage request lifecycle from Phase 5.4, now with a webhook specific to YOUR resource type.
Registering a webhook specifically for a custom resource type, mirroring the built-in admission mechanism from Phase 5.4.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: postgrescluster-validator
webhooks:
- name: validate.postgrescluster.example.com
rules:
- apiGroups: ["postgresql.example.com"]
apiVersions: ["v1"]
resources: ["postgresclusters"] # ONLY this custom resource type
operations: ["CREATE", "UPDATE"]
clientConfig:
service:
name: postgres-operator-webhook
namespace: postgres-operator-system
path: /validate-postgresclusterConfirm defaulting and validation both actually run for a custom resource.
# Create a custom resource WITHOUT specifying "version" at all
kubectl apply -f - << 'EOF'
apiVersion: postgresql.example.com/v1
kind: PostgresCluster
metadata:
name: no-version-specified
spec:
replicas: 3
EOF
# Confirm the mutating webhook filled in a sensible default
kubectl get postgrescluster no-version-specified -o jsonpath='{.spec.version}'
# "16" <- defaulted, even though you never specified it
# Try something the validating webhook should reject
kubectl apply -f - << 'EOF'
apiVersion: postgresql.example.com/v1
kind: PostgresCluster
metadata:
name: bad-replica-count
spec:
replicas: 4 # even number — invalid for this quorum-based cluster type
version: "16"
EOF
# Error: admission webhook denied the request: replicas must be an odd numberExplain it without notes
Why can't a CRD's own schema alone enforce a rule like 'replicas must be an odd number,' and what's actually needed instead?
How does admission control for a custom resource relate to the general policy-engine-based admission control from Phase 5.4 — are they the same mechanism or different ones?
Practice
If you have access to a cluster with a real Operator installed (cert-manager, again, is a genuinely accessible example), check kubectl get validatingwebhookconfigurations and mutatingwebhookconfigurations for one registered specifically against that operator's own custom resource type.
Think through, for a hypothetical custom resource of your own design, one validation rule that genuinely couldn't be expressed by a CRD's schema alone and would require a real webhook.
Trade-offs
- ↔
Writing a genuinely correct custom admission webhook is real engineering effort (a webhook server that must be reliable and fast, since it sits directly in the request path of every affected operation) — for many custom resources, relying on the CRD's own schema validation plus a general policy engine (Phase 5.4) for anything beyond that is a simpler, lower-effort starting point than writing and hosting a fully custom webhook, reserved for genuinely mature, widely-used operators where the investment clearly pays off.
Done when you can
I understand why a CRD's schema alone can't express every validation rule a custom resource might need.
I can explain the difference between a general-purpose policy engine (Phase 5.4) and a resource-specific webhook shipped with an Operator.
I know a mature Operator typically ships its own admission webhook alongside its CRD and controller.