✓Kubernetes cluster (v1.25+), kubectl, Go (1.21+), basic understanding of Kubernetes API objects (Pod, Deployment, Service), TLS certificates, and HTTP servers.
✦ Definition~90s read
What is Kubernetes Admission Controllers?
Kubernetes Admission Controllers are plugins that intercept requests to the API server after authentication and authorization but before object persistence, enabling policy enforcement, mutation, and validation of resources. They matter because they are the last line of defense against misconfigurations and security violations in a cluster.
★
Think of Kubernetes Admission Controllers like a tool in your DevOps toolkit — once you understand what it does and when to reach for it, managing Kubernetes clusters becomes second nature.
Use them when you need to enforce organization-wide policies like resource limits, security contexts, or custom validations that cannot be expressed via RBAC or network policies alone.
Plain-English First
Think of Kubernetes Admission Controllers like a tool in your DevOps toolkit — once you understand what it does and when to reach for it, managing Kubernetes clusters becomes second nature.
A single misconfigured Pod can bring down a production cluster. In 2023, a major outage at a financial services firm was traced back to a developer deploying a container with privileged: true and hostNetwork: true, bypassing all existing security controls. The fix? Admission controllers. These plugins sit between the API server and etcd, intercepting every create, update, or delete request. They are the gatekeepers that can mutate, validate, or reject resources before they become reality. Unlike RBAC or network policies, admission controllers operate at the object level, giving you surgical control over what enters your cluster. If you're not using them, you're running a cluster with no safety net.
What Are Admission Controllers?
Admission controllers are compiled-in or webhook-based plugins that intercept Kubernetes API requests after authentication and authorization, but before the object is persisted in etcd. They can modify (mutating) or reject (validating) the request. Kubernetes ships with a set of built-in admission controllers (e.g., NamespaceLifecycle, LimitRanger, PodSecurityPolicy), but the real power comes from dynamic admission control via MutatingAdmissionWebhook and ValidatingAdmissionWebhook. These allow you to run custom logic in external HTTP servers. Every request to the API server—kubectl apply, a Deployment controller reconciling, a ServiceAccount token mount—passes through the configured chain of admission controllers. Understanding this pipeline is critical for debugging why a resource was rejected or modified unexpectedly.
check-admission-controllers.shBASH
1
kubectl api-resources --verbs=list -o name | xargs -I {} sh -c 'kubectl get --raw /api/v1/namespaces/default/{} 2>/dev/null | head -c 100'
Output
Error from server (Forbidden): admission webhook "deny-privileged.example.com" denied the request: Privileged containers are not allowed
🔥Admission vs Authorization
Admission controllers are not a replacement for RBAC. RBAC decides who can perform an action; admission controllers decide what can be created. They work together: RBAC gates the user, admission gates the object.
📊 Production Insight
In production, always enable the built-in 'NamespaceLifecycle' and 'LimitRanger' controllers to prevent resource starvation and ensure proper namespace isolation.
🎯 Key Takeaway
Admission controllers are the last checkpoint before an object is persisted—use them to enforce policies that cannot be expressed in RBAC or network policies.
Mutating vs Validating: The Two Flavors
Mutating admission controllers modify the incoming object before it is stored. They run first, in a chain, and can inject sidecars, set default resource limits, add labels, or patch security contexts. Validating admission controllers run after all mutating controllers, and can reject the request based on the final state of the object. The separation is crucial: mutating controllers can fix common omissions (e.g., adding a default PodSecurityContext), while validating controllers enforce hard rules (e.g., no privileged containers). Both are implemented as webhooks, but mutating webhooks must be idempotent—they may be called multiple times for the same request if the object is modified by another mutating webhook. A common pitfall is writing a mutating webhook that is not idempotent, leading to infinite loops or corrupted objects.
Mutating webhooks may be called multiple times for the same request. Ensure your mutation logic checks if the modification has already been applied (e.g., check for an annotation) to avoid duplicate sidecars or infinite loops.
📊 Production Insight
Set 'reinvocationPolicy: IfNeeded' on mutating webhooks to allow re-invocation after other mutating webhooks have run, ensuring your defaults are applied last.
🎯 Key Takeaway
Mutating webhooks modify objects before validation; validating webhooks reject non-compliant objects. Always make mutating webhooks idempotent.
thecodeforge.io
Kubernetes Admission Controllers
Building a Validating Webhook: Deny Privileged Containers
Let's build a simple validating webhook that rejects any Pod with 'securityContext.privileged: true'. The webhook is an HTTP server that receives AdmissionReview requests and returns AdmissionReview responses. We'll use Go with the 'k8s.io/api/admission/v1' package. The server listens on /validate, decodes the request, checks the Pod spec, and returns 'allowed: false' with a message if privileged is set. Deploy it as a Service and register it via ValidatingWebhookConfiguration. This is a common pattern for enforcing security policies without modifying the cluster's built-in controllers.
Kubernetes requires all webhook servers to use TLS. Use cert-manager to automate certificate provisioning, or generate self-signed certs and include the CA bundle in the webhook configuration.
📊 Production Insight
In production, always set 'timeoutSeconds' to a low value (e.g., 5) and implement a circuit breaker to avoid the webhook becoming a single point of failure.
🎯 Key Takeaway
A validating webhook is an HTTP server that returns 'allowed: false' to reject non-compliant resources. Always validate the request object thoroughly.
Building a Mutating Webhook: Inject a Sidecar Proxy
Mutating webhooks can automatically inject sidecar containers, such as a logging agent or service mesh proxy. The webhook patches the Pod spec to add a container, volume mounts, and annotations. The key is to use JSON Patch (RFC 6902) operations. For example, to inject an Envoy sidecar, you add a container to 'spec.containers' and a volume mount for the Envoy config. The webhook must be idempotent: check for an annotation like 'sidecar-injected: true' to avoid double injection. This pattern is used by Istio, Linkerd, and many internal platforms.
JSON Patch operations are applied sequentially. If you add a container and then modify an existing container, the indices may shift. Use 'add' with '-' to append, or use strategic merge patch if available.
📊 Production Insight
In production, use a 'reinvocationPolicy: IfNeeded' and ensure your webhook can handle being called multiple times without side effects.
🎯 Key Takeaway
Mutating webhooks use JSON Patch to modify objects. Always check for idempotency annotations to avoid double mutations.
Webhook Configuration: Rules, Failure Policy, and Timeouts
A webhook configuration defines which resources trigger the webhook via 'rules' (operations, apiGroups, apiVersions, resources, scope). You can also set 'failurePolicy' to 'Fail' (default) or 'Ignore'. 'Fail' means if the webhook is unreachable, the request is rejected—use for security-critical webhooks. 'Ignore' allows the request to proceed if the webhook is down—use for non-critical mutations like sidecar injection. 'timeoutSeconds' should be set low (e.g., 5-10) to avoid blocking the API server. 'sideEffects' must be 'None' or 'NoneOnDryRun' to indicate the webhook has no side effects beyond admission. 'reinvocationPolicy' controls whether the webhook is called again after other mutating webhooks. Proper configuration prevents cluster-wide outages.
Use 'Equivalent' to match requests for resources that are equivalent across API versions (e.g., apps/v1 and extensions/v1beta1). 'Exact' only matches the exact version specified.
📊 Production Insight
Always set 'timeoutSeconds' to 5 or less. Long timeouts can cause API server congestion and cascading failures.
🎯 Key Takeaway
Configure failurePolicy, timeoutSeconds, and sideEffects carefully to balance security and availability. Use 'Fail' for critical validations, 'Ignore' for best-effort mutations.
Debugging Admission Webhooks: Common Pitfalls
Admission webhooks can be tricky to debug. Common issues include: TLS certificate mismatches (CA bundle not matching server cert), webhook service unreachable (wrong namespace or port), infinite loops (mutating webhook modifies an object that triggers itself), and timeouts. Use 'kubectl describe validatingwebhookconfiguration' to see the configuration. Check the API server logs (often in /var/log/kube-apiserver.log) for admission errors. Enable verbose logging on your webhook server. A common mistake is forgetting to set 'sideEffects: None'—without it, the API server will not call the webhook on dry-run requests. Also, ensure your webhook handles both CREATE and UPDATE operations if needed.
debug-webhook.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
# Check webhook configuration
kubectl get validatingwebhookconfiguration -o yaml
# CheckAPI server logs for admission errors
kubectl logs -n kube-system kube-apiserver-controlplane | grep -i admission
# Test webhook with dry-run
kubectl apply -f pod.yaml --dry-run=server
# Simulate an admission request with curl
curl -k -X POST https://webhook-service:443/validate \
-H "Content-Type: application/json" \
-d @admission-review.json
Output
{"response":{"uid":"...","allowed":false,"status":{"metadata":{},"message":"Privileged containers are not allowed","code":403}}}
⚠ Don't Forget Dry-Run
If your webhook has side effects (e.g., writing to a database), set 'sideEffects: NoneOnDryRun' and skip the side effect when the request is a dry-run. Otherwise, dry-run requests will fail.
📊 Production Insight
In production, implement health checks and metrics on your webhook server. Use Prometheus to monitor request latency and error rates, and set up alerts for timeouts.
🎯 Key Takeaway
Debug admission webhooks by checking API server logs, using dry-run, and verifying TLS configuration. Always test with a dry-run first.
Built-in Admission Controllers You Should Know
Kubernetes ships with several built-in admission controllers that are enabled by default in most distributions. 'NamespaceLifecycle' prevents creation of resources in terminating namespaces. 'LimitRanger' enforces default and maximum resource limits per namespace. 'ResourceQuota' enforces namespace-level resource quotas. 'PodSecurity' (replaces PodSecurityPolicy) enforces Pod Security Standards (privileged, baseline, restricted). 'NodeRestriction' limits kubelet's self-modification capabilities. 'AlwaysPullImages' ensures images are always pulled, preventing use of stale or malicious images. Understanding these built-in controllers helps you avoid reinventing the wheel. For example, instead of writing a custom webhook to enforce resource limits, use LimitRanger and ResourceQuota.
list-admission-controllers.shBASH
1
2
3
4
5
# List enabled admission controllers (kube-apiserver flags)
kubectl get pods -n kube-system -l component=kube-apiserver -o jsonpath="{.items[0].spec.containers[0].command}" | tr ' ''\n' | grep enable-admission-plugins
# Example output:
# --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,NodeRestriction,PodSecurity,DefaultStorageClass,DefaultTolerationSeconds,ResourceQuota
Built-in controllers like NamespaceLifecycle and NodeRestriction are essential for cluster stability. Only disable them if you have a very specific reason and understand the consequences.
📊 Production Insight
In production, always enable 'PodSecurity' with the 'restricted' profile as a baseline, and use 'baseline' for namespaces that require relaxed policies.
🎯 Key Takeaway
Leverage built-in admission controllers before writing custom webhooks. They cover common use cases like resource limits, pod security, and namespace lifecycle.
thecodeforge.io
Kubernetes Admission Controllers
Admission Controllers and Pod Security Standards
Pod Security Standards (PSS) define three policies: privileged, baseline, and restricted. The 'PodSecurity' admission controller (GA since v1.25) enforces these policies at the namespace level via labels. You can set 'pod-security.kubernetes.io/enforce', 'pod-security.kubernetes.io/audit', and 'pod-security.kubernetes.io/warn' labels on namespaces. This replaces the deprecated PodSecurityPolicy (PSP). PSS is simpler and more maintainable. However, for fine-grained control, you may still need custom webhooks. For example, PSS does not allow you to block specific image registries or enforce specific seccomp profiles. In those cases, combine PSS with a validating webhook.
Use PSS for standard pod security. Use custom webhooks for policies that PSS cannot express, such as 'containers must not use the latest tag' or 'must have resource limits'.
📊 Production Insight
In production, start with 'baseline' policy and gradually move to 'restricted' as teams adapt. Use 'audit' and 'warn' modes to detect violations without breaking existing workloads.
🎯 Key Takeaway
Pod Security Standards provide a built-in way to enforce pod security. Use them as a baseline and supplement with custom webhooks for advanced policies.
Performance Considerations and Best Practices
Admission webhooks add latency to every API request. A slow webhook can cripple the API server. Best practices: keep webhooks stateless and fast (sub-millisecond response times). Use caching for expensive operations (e.g., fetching data from external APIs). Set 'timeoutSeconds' low (5s) and implement a circuit breaker to fail fast. Use 'failurePolicy: Ignore' for non-critical webhooks. Deploy multiple replicas behind a Service for high availability. Monitor webhook latency and error rates with Prometheus. Avoid making network calls to external services inside the webhook—they can fail and block the cluster. If you must, use a local cache or a sidecar that pre-fetches data.
A webhook that calls an external API (e.g., to check an image registry) can become a bottleneck or single point of failure. Cache data locally or use a sidecar to pre-fetch.
📊 Production Insight
In production, set up a dedicated ServiceAccount for the webhook with minimal RBAC permissions, and use NetworkPolicy to restrict egress traffic.
🎯 Key Takeaway
Admission webhooks must be fast and reliable. Use caching, low timeouts, and multiple replicas to minimize impact on API server performance.
Advanced Patterns: Chaining and Dynamic Policies
Multiple admission webhooks can be chained. The order of execution is determined by the webhook name (alphabetical) for mutating webhooks, and then validating webhooks run in parallel. You can use 'reinvocationPolicy' to re-run mutating webhooks after others have run. This allows patterns like: first webhook adds default labels, second webhook injects sidecar, third webhook validates final state. Dynamic policies can be implemented by having the webhook fetch policy from a ConfigMap or an external policy engine (e.g., OPA/Gatekeeper). Gatekeeper is a popular project that uses admission webhooks with a declarative policy language (Rego). It provides a framework for creating, managing, and auditing policies.
Instead of writing custom webhooks for every policy, use Gatekeeper (OPA) to manage policies declaratively. It handles webhook lifecycle, auditing, and dry-run.
📊 Production Insight
In production, use Gatekeeper's audit feature to detect violations without blocking, and gradually enforce policies after fixing existing violations.
🎯 Key Takeaway
Chain mutating webhooks in a specific order and use Gatekeeper for complex, dynamic policies. ReinvocationPolicy allows fine-grained control over mutation order.
Testing Admission Webhooks Locally
Before deploying to a cluster, test your webhook locally. Use 'kubectl create --dry-run=server' to simulate an admission request. You can also use the 'admission-webhook-bootstrapper' tool or write unit tests that send AdmissionReview JSON to your server. For integration testing, set up a local kind cluster and deploy the webhook. Use 'curl' to send test requests directly to the webhook endpoint. Ensure your webhook handles edge cases: empty objects, missing fields, and multiple containers. Test both allowed and denied scenarios. Automate these tests in CI/CD to prevent regressions.
{"response":{"uid":"test-uid","allowed":false,"status":{"metadata":{},"message":"Privileged containers are not allowed","code":403}}}
💡Use Dry-Run for Safe Testing
Always test with '--dry-run=server' before enforcing a new webhook. This shows you what would be rejected without actually blocking anything.
📊 Production Insight
In production, run a canary deployment of new webhooks with 'failurePolicy: Ignore' and monitor for errors before switching to 'Fail'.
🎯 Key Takeaway
Test webhooks locally with curl and AdmissionReview JSON. Use dry-run in the cluster to validate behavior before enforcing.
Mutating vs Validating Admission ControllersKey differences in behavior and use casesMutatingValidatingPrimary functionModify or inject resourcesReject or allow requestsExecution orderRuns first in admission chainRuns after mutating controllersCommon use caseInject sidecar proxies, set defaultsEnforce security policies, deny privilegIdempotency requiredYes, to avoid duplicate modificationsNot required, but recommendedResponse typeReturns modified object or patchReturns allowed or denied decisionTHECODEFORGE.IO
thecodeforge.io
Kubernetes Admission Controllers
Production Deployment: Certificates, Monitoring, and Rollback
Deploying admission webhooks in production requires careful planning. Use cert-manager to automate TLS certificate provisioning and rotation. Monitor webhook latency, error rates, and request volume with Prometheus. Set up alerts for high latency or error spikes. Implement a circuit breaker: if the webhook fails consecutively, stop calling it and fall back to 'failurePolicy: Ignore' temporarily. Have a rollback plan: if a webhook causes issues, delete the webhook configuration to disable it. Use feature flags or toggles to enable/disable webhooks without redeploying. Document the webhook's purpose, failure mode, and rollback procedure.
Certificates expire. Use cert-manager to automate renewal. Update the CA bundle in the webhook configuration when the CA certificate changes.
📊 Production Insight
In production, use a dedicated namespace for admission webhooks with NetworkPolicy to restrict access, and run multiple replicas across availability zones.
🎯 Key Takeaway
Productionize webhooks with automated TLS, monitoring, circuit breakers, and rollback plans. Always have a way to disable a webhook quickly.
⚙ Quick Reference
12 commands from this guide
File
Command / Code
Purpose
check-admission-controllers.sh
kubectl api-resources --verbs=list -o name | xargs -I {} sh -c 'kubectl get --ra...
What Are Admission Controllers?
mutating-webhook.yaml
apiVersion: admissionregistration.k8s.io/v1
Mutating vs Validating
main.go
"encoding/json"
Building a Validating Webhook
mutate.go
func mutate(w http.ResponseWriter, r *http.Request) {
Building a Mutating Webhook
validating-webhook-config.yaml
apiVersion: admissionregistration.k8s.io/v1
Webhook Configuration
debug-webhook.sh
kubectl get validatingwebhookconfiguration -o yaml
Debugging Admission Webhooks
list-admission-controllers.sh
kubectl get pods -n kube-system -l component=kube-apiserver -o jsonpath="{.items...
Built-in Admission Controllers You Should Know
namespace-pss.yaml
apiVersion: v1
Admission Controllers and Pod Security Standards
webhook-deployment.yaml
apiVersion: apps/v1
Performance Considerations and Best Practices
deny-privileged.rego
violation[{"msg": msg}] {
Advanced Patterns
test-webhook.sh
cat > test-request.json <
Testing Admission Webhooks Locally
cert-manager-issuer.yaml
apiVersion: cert-manager.io/v1
Production Deployment
Key takeaways
1
Admission Controllers are the Last Line of Defense
They intercept API requests after authn/authz and before persistence, enabling policy enforcement that cannot be achieved with RBAC or network policies alone.
2
Mutating vs Validating
Mutating webhooks modify objects (e.g., inject sidecars, set defaults) and must be idempotent. Validating webhooks reject non-compliant objects. Both are essential for a secure cluster.
3
Performance Matters
Admission webhooks add latency to every API request. Keep them fast, stateless, and use caching. Set low timeouts and use 'failurePolicy: Ignore' for non-critical webhooks.
4
Use Built-in Controllers First
Kubernetes ships with powerful built-in admission controllers like LimitRanger, ResourceQuota, and PodSecurity. Leverage them before writing custom webhooks.
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01JUNIOR
What is the difference between an admission controller and a webhook?
Q02JUNIOR
Can admission controllers modify existing resources?
Q03JUNIOR
How do I debug a webhook that is silently failing?
Q01 of 03JUNIOR
What is the difference between an admission controller and a webhook?
ANSWER
An admission controller is a general concept: a plugin that intercepts API requests. A webhook is a specific type of admission controller that calls an external HTTP server. Built-in admission control
Q02 of 03JUNIOR
Can admission controllers modify existing resources?
ANSWER
Yes, mutating admission controllers can modify existing resources on UPDATE operations. However, they cannot modify resources that already exist without an update request. Validating controllers can r
Q03 of 03JUNIOR
How do I debug a webhook that is silently failing?
ANSWER
Check the API server logs for admission errors. Use 'kubectl get events' to see if the webhook is unreachable. Enable verbose logging on your webhook server. Test with '--dry-run=server' to see if the
01
What is the difference between an admission controller and a webhook?
JUNIOR
02
Can admission controllers modify existing resources?
JUNIOR
03
How do I debug a webhook that is silently failing?
JUNIOR
FAQ · 6 QUESTIONS
Frequently Asked Questions
01
What is the difference between an admission controller and a webhook?
An admission controller is a general concept: a plugin that intercepts API requests. A webhook is a specific type of admission controller that calls an external HTTP server. Built-in admission controllers are compiled into the API server, while webhooks are dynamic and can be added without restarting the API server.
Was this helpful?
02
Can admission controllers modify existing resources?
Yes, mutating admission controllers can modify existing resources on UPDATE operations. However, they cannot modify resources that already exist without an update request. Validating controllers can reject updates that violate policies.
Was this helpful?
03
How do I debug a webhook that is silently failing?
Check the API server logs for admission errors. Use 'kubectl get events' to see if the webhook is unreachable. Enable verbose logging on your webhook server. Test with '--dry-run=server' to see if the webhook is called. Also verify TLS certificates and CA bundle.
Was this helpful?
04
What happens if my webhook is down?
It depends on the 'failurePolicy'. If set to 'Fail', the API server will reject all matching requests. If set to 'Ignore', the request will proceed without the webhook. For critical webhooks, use 'Fail' and ensure high availability.
Was this helpful?
05
Can I use admission controllers to enforce policies on custom resources?
Yes, you can write webhooks that match custom resources by specifying the appropriate apiGroups and resources in the rules. For example, to validate a 'Backup' custom resource, set 'apiGroups: ["backup.example.com"]' and 'resources: ["backups"]'.
Was this helpful?
06
How do I ensure idempotency in a mutating webhook?
Check for an annotation or label that indicates the mutation has already been applied. For example, if you inject a sidecar, check for an annotation like 'sidecar-injected: true'. If present, return an empty patch. Also, use 'reinvocationPolicy: IfNeeded' to control re-invocation.