Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Admission Controllers: Mutating and Validating Webhooks

By Kokil Thapa | Last reviewed: September 2026

If you manage Kubernetes clusters, you eventually hit a wall where RBAC and static YAML validation aren't enough. You need to automatically inject sidecars, enforce company-specific labeling standards, or block insecure configurations before they ever reach etcd. This is exactly what Admission Controllers: Mutating and Validating Webhooks solve by intercepting API server requests dynamically. Understanding the distinction between these two webhook types is critical for maintaining cluster stability and preventing self-inflicted outages during deployment.

For teams managing complex infrastructure, treating these webhooks as an afterthought leads to fragile pipelines. I've seen production clusters locked out because a validating webhook was too strict, and deployments silently fail because a mutating webhook returned malformed JSON patches. If you're building platform engineering tooling or securing multi-tenant environments, getting this architecture right is non-negotiable. For broader context on securing your infrastructure stack, see my guide on Kubernetes secrets management done right, which complements admission control strategies.

How do Admission Controllers: Mutating and Validating Webhooks differ in execution order?

The most common source of bugs in admission control is misunderstanding the request lifecycle. The Kubernetes API server processes webhooks in a strict, non-negotiable sequence. Getting this wrong means your validating webhook might reject an object that hasn't been mutated yet, or your mutating webhook might overwrite changes made by another controller.

API Server Request LifecycleAuthN / AuthZMutatingWebhooksSchema ValidationValidatingWebhooksPersist to etcdMutating Phase Rules• Can modify the object (JSON Patch)• Runs in ordered stages• May be re-invoked if object changesValidating Phase Rules• Cannot modify the object• Runs in parallel within stage• Returns Allowed / Denied only
Execution order of Admission Controllers: Mutating and Validating Webhooks showing auth, mutation, schema validation, and persistence phases

The flow is always: Authentication → Authorization → Mutating Webhooks → Schema Validation → Validating Webhooks → Persistence. Note that schema validation happens between mutating and validating webhooks. This means your mutating webhook must produce a structurally valid object; otherwise, the API server rejects it before your validating webhook even runs.

Mutating webhook re-invocation

A subtle behavior catches many engineers off guard: if any mutating webhook modifies an object, the entire mutating chain restarts from the first webhook. This loop continues until no webhook makes changes or the maximum re-invocation limit (currently 5 in Kubernetes 1.32+) is reached. Design your mutating webhooks to be idempotent. If Webhook A adds a label and Webhook B removes it, you'll create an infinite loop that fails the request.

Failure policy matters

Every webhook configuration includes a failurePolicy. Setting this to Fail means the API request is rejected if the webhook is unreachable or returns an error. Setting it to Ignore allows the request through. In production legal-tech portals I've built, we use Fail for security-critical validations (blocking PII leaks) but Ignore for non-critical mutations like optional monitoring labels. Choose deliberately; the default varies by implementation method.

How do you implement a mutating webhook for sidecar injection?

Mutating webhooks return RFC 6902 JSON Patches. The most common use case is injecting sidecar containers into pods at creation time. Here's a practical pattern for implementing this safely.

<?php
// Example: PHP-based webhook handler (Laravel/Symfony)
// Endpoint: POST /webhooks/mutate-pods

public function mutatePod(Request $request): JsonResponse
{
    $review = json_decode($request->getContent(), true);
    $pod = $review['request']['object'];
    $patches = [];

    // Only inject if annotation requests it
    if (($pod['metadata']['annotations']['inject-sidecar'] ?? '') === 'true') {
        $sidecar = [
            'name' => 'log-agent',
            'image' => 'fluent/fluent-bit:3.2',
            'resources' => [
                'limits' => ['memory' => '128Mi', 'cpu' => '100m'],
                'requests' => ['memory' => '64Mi', 'cpu' => '50m'],
            ],
        ];

        // Handle missing containers array edge case
        if (!isset($pod['spec']['containers'])) {
            $patches[] = ['op' => 'add', 'path' => '/spec/containers', 'value' => [$sidecar]];
        } else {
            $patches[] = ['op' => 'add', 'path' => '/spec/containers/-', 'value' => $sidecar];
        }

        // Add volume mount for shared logs
        $patches[] = ['op' => 'add', 'path' => '/spec/volumes/-', 'value' => [
            'name' => 'shared-logs',
            'emptyDir' => new \stdClass(),
        ]];
    }

    return response()->json([
        'apiVersion' => 'admission.k8s.io/v1',
        'kind' => 'AdmissionReview',
        'response' => [
            'uid' => $review['request']['uid'],
            'allowed' => true,
            'patchType' => 'JSONPatch',
            'patch' => base64_encode(json_encode($patches)),
        ],
    ]);
}

Key implementation details that prevent production incidents:

  • Always check existing state: Before adding to arrays, verify they exist. Pods may have zero containers in certain init scenarios.
  • Use append operations: Use /spec/containers/- to append rather than targeting specific indices, which shift during re-invocation.
  • Set resource limits: Injected sidecars without limits cause node overcommit. Always define CPU/memory constraints in the patch.
  • Return proper AdmissionReview: The response must include the original request UID. Mismatched UIDs cause silent failures.

For teams deploying Laravel applications on Kubernetes, understanding this mutation pattern is essential when integrating with service meshes or observability stacks. See Kubernetes for Laravel getting started for foundational cluster setup before adding admission control complexity.

When should you use validating webhooks versus OPA/Gatekeeper?

Not every policy needs a custom webhook. The ecosystem has matured significantly by 2026, and choosing between custom validating webhooks and policy engines like OPA Gatekeeper, Kyverno, or Kubewarden depends on your specific constraints.

CriteriaCustom Validating WebhookOPA Gatekeeper / Kyverno
Best forComplex business logic, external API calls, database lookupsStandard compliance, label enforcement, image registry allowlists
Development effortHigh — write, test, deploy, monitor separate serviceLow — declarative Rego/YAML policies
Performance overheadVariable — depends on your code quality and external depsPredictable — optimized evaluation engine with caching
Audit trailBuild your own logging/metricsBuilt-in audit controller + constraint violations
Dry-run supportMust implement manuallyNative kubectl dry-run=server integration
External dataDirect HTTP/DB access in your languageRequires ExternalData provider (Gatekeeper) or API calls (Kyverno)
Maintenance burdenYou own upgrades, security patches, scalingCommunity-maintained, regular releases

In practice, I recommend starting with Kyverno or Gatekeeper for 80% of policies. Reserve custom validating webhooks for cases where policy decisions require querying external systems — checking license validity against a SaaS backend, verifying budget approval in an ERP system, or cross-referencing tenant quotas in a multi-tenant platform. These are scenarios where Rego becomes unwieldy and your existing PHP/Go/Python expertise delivers faster, more maintainable solutions.

Policy Enforcement Decision TreeNeed Policy Check?Requires External Data/API?YESNOCustom WebhookPHP/Go/Python serviceFull control, higher ops costPolicy EngineOPA Gatekeeper / KyvernoDeclarative, lower maintenanceHybrid Approach (Recommended for Most Teams)Use policy engine for standard compliance + custom webhook only for complex business logic
Decision framework for selecting Admission Controllers: Mutating and Validating Webhooks versus declarative policy engines

How do you configure webhook certificates and avoid TLS errors?

TLS certificate issues cause more webhook deployment failures than any other problem. The API server requires HTTPS with a trusted CA. Here's the reliable approach using cert-manager, which has become the de facto standard by 2026.

# Install cert-manager (v1.17+ for K8s 1.32)
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.0/cert-manager.yaml

# Create a self-signed issuer for webhooks
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: webhook-selfsigned
  namespace: admission-system
spec:
  selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: admission-webhook-cert
  namespace: admission-system
spec:
  secretName: admission-webhook-tls
  duration: 2160h # 90 days
  renewBefore: 360h # 15 days
  subject:
    organizations: ["platform-team"]
  isCA: false
  privateKey:
    algorithm: RSA
    size: 2048
  usages: ["server auth"]
  dnsNames:
    - admission-webhook.admission-system.svc
    - admission-webhook.admission-system.svc.cluster.local
  issuerRef:
    name: webhook-selfsigned
    kind: Issuer

Critical gotchas I've encountered repeatedly:

  1. DNS names must match exactly: Include both the short service name and the FQDN. The API server uses the FQDN; local testing often uses the short name. Missing either causes intermittent TLS handshake failures.
  2. Namespace matters: The certificate secret must live in the same namespace as your webhook deployment. Cross-namespace secret references don't work for webhook serving certs.
  3. CA bundle injection: Use cert-manager's caBundle annotation on your MutatingWebhookConfiguration or ValidatingWebhookConfiguration. Manually copying CA bundles breaks on certificate rotation.
  4. Reload on rotation: Your webhook pod must reload when the TLS secret updates. Use stakater/reloader or mount the secret with subPath disabled so kubelet propagates updates. Pods that cache old certs cause 502 errors during rotation windows.

What are the performance and reliability risks of admission webhooks?

Webhooks sit in the critical path of every matching API request. A slow or failing webhook degrades cluster-wide performance. Treat them with the same rigor as your primary application database.

High-Availability Webhook ArchitectureAPI ServerMultiple replicasService (ClusterIP)Load balances across podsSession affinity: NoneWebhook Pod 1Readiness probeResource limits setGraceful shutdownWebhook Pod 2Readiness probeResource limits setGraceful shutdownWebhook Pod 3Readiness probeResource limits setGraceful shutdownCritical SafeguardstimeoutSeconds ≤ 10 | failurePolicy: Fail (security) / Ignore (optional)matchExpressions to exclude kube-system | HorizontalPodAutoscaler enabled
Production-grade HA architecture for Admission Controllers: Mutating and Validating Webhooks with safeguards

Essential operational safeguards:

  • Set aggressive timeouts: Default timeout is 10 seconds in recent Kubernetes versions. Lower it to 3–5 seconds for simple validations. Long-running webhooks block API server threads and cascade into cluster-wide latency.
  • Exclude critical namespaces: Always add matchExpressions to exclude kube-system and your webhook's own namespace. A webhook that blocks its own redeployment creates an unrecoverable deadlock requiring manual CRD deletion.
  • Implement health checks: Readiness probes must verify the webhook can actually process requests, not just that the HTTP port is open. Check certificate validity and downstream dependencies in the readiness handler.
  • Monitor rejection rates: Expose Prometheus metrics for allowed/denied counts, latency histograms, and error rates. Alert on denial spikes — they usually indicate a buggy policy update, not legitimate security events.
  • Test with dry-run: Before deploying policy changes, run kubectl apply --dry-run=server -f manifest.yaml. This exercises the full admission chain without persisting anything. Make this mandatory in CI pipelines.

For teams running Laravel or PHP applications on Kubernetes, webhook latency directly impacts deployment velocity. Pair admission control tuning with broader performance work described in PHP-FPM tuning for high-traffic websites to ensure your application layer doesn't become the next bottleneck after fixing webhook overhead.

Practical Next Steps for Production Clusters

Start with policy engines for standard governance. Build custom Admission Controllers: Mutating and Validating Webhooks only when business logic demands external state or complex transformations. Always implement TLS via cert-manager, set conservative timeouts, exclude system namespaces, and establish monitoring before promoting webhooks to production. Test every change with server-side dry-run first. If you're designing Kubernetes admission policies for a Nepal-based legal-tech platform or global SaaS product and need hands-on implementation support, reach out to discuss your architecture.

Frequently Asked Questions

Mutating webhooks modify incoming API requests before persistence, such as injecting sidecars or adding labels. Validating webhooks only accept or reject requests based on policy without altering the object. Mutating controllers run first to allow modifications, followed by validating controllers which enforce final compliance checks on the potentially modified resource state.

The API server processes MutatingAdmissionWebhook controllers first in a defined sequence, allowing multiple passes until no changes occur. After mutations stabilize, ValidatingAdmissionWebhook controllers run sequentially to enforce policies. If any validating webhook rejects the request, the entire operation fails immediately. This strict ordering ensures validation always occurs against the final mutated object state.

Configure failurePolicy to Ignore in your WebhookConfiguration so API requests proceed if the webhook service is unreachable. For critical security enforcement, use Fail but implement high availability with multiple replicas and pod anti-affinity. Always test failure modes in non-production environments first. I have seen clusters become completely unmanageable when a single-point-of-failure webhook went down with a Fail policy.

Keep webhook processing under 200ms to avoid degrading API server performance. The default timeout is 10 seconds, but long waits block all matching API requests and can cascade into cluster instability. Optimize payload parsing, use efficient validation logic, and deploy webhooks close to the API server. On production systems I maintain, we alert if p99 latency exceeds 300ms.

Yes, if a webhook re-modifies fields it previously set, the API server will re-invoke it repeatedly until hitting the re-invocation limit. Design idempotent mutations that detect existing state before modifying. Test thoroughly with dry-run requests. In practice, most loops stem from conditional logic that does not account for already-applied defaults or annotations added by prior webhook passes.

Enforce mutual TLS using certificates signed by a trusted CA, typically via cert-manager. Validate the API server's client certificate in your webhook handler. Restrict network access with NetworkPolicies allowing only the API server CIDR. Never expose webhook services publicly. On legal-tech portals requiring strict compliance, I additionally log all admission decisions for audit trails and anomaly detection.

Common causes include incorrect label selectors matching unintended resources, stale cached configurations, or schema mismatches between expected and actual object versions. Check webhook logs for specific rejection reasons and verify the matched rule scope. Use kubectl auth can-i and dry-run flags to test. I have debugged this repeatedly where namespace selectors were too broad after a Helm upgrade.

Use OPA Gatekeeper or Kyverno for standardized, declarative policies with built-in auditing and constraint templates. Write custom webhooks only for complex procedural logic, external system integration, or performance-critical paths where Rego overhead is unacceptable. Custom code increases maintenance burden significantly. For most compliance and governance needs on client projects, I recommend Gatekeeper over bespoke implementations.

Register webhooks for specific apiVersions and handle conversion explicitly. Do not assume object structure across versions. Use the admission review's kind and version fields to dispatch appropriate logic. Test against all supported Kubernetes versions in CI. Breaking changes between API versions are a frequent source of silent failures in webhook deployments I have troubleshot during cluster upgrades.

Expose Prometheus metrics for request count, latency histograms, error rates, and rejection counts per rule. Log structured admission decisions including resource metadata, user identity, and decision reason at debug level. Add distributed tracing correlation IDs from admission reviews. Without these, diagnosing intermittent rejections or latency spikes becomes guesswork. I treat webhook observability as mandatory, not optional, on any production cluster.

Minimal compute cost; two small pods (0.25 vCPU, 512Mi RAM) suffice for moderate traffic, roughly Rs 2,000–4,000 monthly (~USD 15–30) on managed Kubernetes. Primary cost is engineering time for development, testing, and maintenance. Managed policy engines like Gatekeeper add negligible overhead. Budget for certificate management tooling and monitoring stack instead. Cost concerns rarely justify skipping proper admission control.

Yes, by configuring rules for core/v1 secrets and configmaps resources. However, this introduces significant risk since these objects are frequently accessed. Apply narrow namespace and label selectors to limit scope. Avoid logging secret data. Validate only when absolutely necessary. On sensitive projects, I separate secret validation into dedicated, highly restricted webhooks with stricter failure policies and audit logging.

Use kind or minikube with port-forwarding to expose local webhook servers. Generate test admission review JSON payloads matching expected schemas. Tools like kubeval and conftest validate policy logic offline. Write integration tests using envtest in Go or pytest-kubernetes in Python. Never rely solely on production testing. I validate every webhook change through automated suite runs before merging to main branches.

Each webhook sees the cumulative result of prior mutations in its configured order. Later webhooks may overwrite earlier changes unintentionally. Coordinate field ownership through conventions or annotations indicating which controller manages specific attributes. Document mutation contracts clearly. Conflicts here cause subtle, hard-to-debug issues. On shared infrastructure, I enforce naming prefixes for webhook-managed labels to prevent accidental collisions.

Yes, for technical controls like mandatory PAN/VAT fields, data residency annotations, or Bikram Sambat date format validation on custom resources. Encode IRD or sector-specific rules as validating webhook policies. Combine with application-layer validation for defense in depth. Admission controllers catch misconfigurations before persistence, reducing downstream compliance failures. I have implemented such checks on legal-tech and financial service platforms serving Nepali users.

Share this article

What I've Built

Products I Build & Run

Legal-tech and language-services platforms I designed, built and operate — each running in production for real clients across Nepal and Australia.

More in the making — I keep shipping tools for Nepal's legal, language and digital work. Got an idea worth building?

Quick Contact Options
Choose how you want to connect me: