Understanding Kubernetes Admission Controllers

What’s the Deal with Admission Controllers?

Alright, let’s talk about Admission Controllers in Kubernetes. If you’re new to this, don’t sweat it—they’re not as scary as they sound. Imagine them as the cool security guards at a concert, checking your ticket and maybe adding a VIP wristband before letting you in. In Kubernetes, they’re plugins that jump in to check or tweak API requests right after you’ve been verified but before your stuff gets saved in the cluster’s storage (etcd). Let’s break it down, keep it real, and throw in a beefy example to make it crystal clear.

What Are Admission Controllers?

These bad boys are part of the Kubernetes API server, stepping in after authentication (who are you?) and authorization (can you do this?). They either validate your request to make sure it’s legit or modify it to fit the vibe of your cluster. They’ve got two main jobs:

  1. Validation: Checking if your request plays by the rules—like, “Does this pod have the right labels or image?”

  2. Mutation: Sprucing up the request, like adding default resource limits or tossing in a sidecar container for logging.

They work in two phases:

  • Mutating: Tweaks the resource to make it better or match your setup.

  • Validating: Gives a thumbs-up or shuts it down. If any controller says “nope,” your request is toast.

How Do They Work?

Here’s the flow when you fire off a kubectl apply:

  1. Authentication: Kubernetes checks you’re legit.

  2. Authorization: Makes sure you’ve got the green light for the action.

  3. Admission Control: Your request hits a chain of controllers that can tweak it, check it, or reject it.

  4. Persistence: If it passes, it’s saved in etcd, and you’re golden.

The cluster admin sets the order of these controllers in the API server config. If one controller throws a fit, the whole request gets bounced with an error.

Types of Admission Controllers

Kubernetes comes with a bunch of built-in controllers, split into mutating (the tweakers) and validating (the rule-enforcers). Here’s a quick look at some heavy hitters:

Mutating Controllers

These guys change your resource before it’s saved.

  • DefaultStorageClass: Slaps a default storage class on PersistentVolumeClaims if you didn’t pick one.

  • PodPreset: Injects stuff like env vars or volumes into pods based on preset rules.

  • LimitRanger: Sets default CPU/memory limits to keep pods from going rogue.

  • ServiceAccount: Hooks up a default service account and mounts its token.

  • MutatingAdmissionWebhook: Lets you run custom code to tweak resources (we’ll dig into webhooks soon).

Validating Controllers

These are the strict ones that approve or deny.

  • ValidatingAdmissionWebhook: Runs your custom validation logic.

  • ResourceQuota: Keeps resource usage in check within a namespace.

  • PodSecurity: Blocks pods with sketchy settings, like running as root.

  • DenyServiceExternalIPs: Rejects services using unapproved external IPs.

  • NamespaceLifecycle: Stops you from creating stuff in namespaces that are being deleted.

Turning Them On or Off

You control these in the API server config with flags like:

--enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount
--disable-admission-plugins=PodSecurity

Kubernetes enables a default set, but you can tweak it to match your needs.

Getting Spicy with Webhooks

For the real power moves, you can use dynamic admission controllers like MutatingAdmissionWebhook and ValidatingAdmissionWebhook. These let you write custom logic in a separate server (a webhook) that the API server calls to check or modify requests.

How Webhooks Roll

  1. You send a request to the API server.

  2. The server pings your webhook over HTTPS with the request details.

  3. Your webhook either tweaks the resource or says “yay” or “nay.”

  4. The API server applies the webhook’s response and moves on.

Why Webhooks Are Awesome

  • Custom Rules: Block pods that don’t follow your org’s security standards.

  • Auto-Tweaks: Add a monitoring sidecar to every pod.

  • Compliance: Ensure resources meet legal or company policies.

A Beefy Example: Validating Webhook to Enforce Pod Security

Let’s level up with a stronger example. Say your company has a strict policy: no pods can run containers as root, and they must have a specific label (security=hardened). Here’s how you’d set up a validating webhook to enforce this.

Step 1: Write the Webhook Server

We’ll use Python with Flask to create a webhook server that checks pods. It’ll reject any pod where a container runs as root (i.e., securityContext.runAsUser is 0 or unset) or lacks the security=hardened label.

from flask import Flask, request, jsonify
import json

app = Flask(__name__)

@app.route('/validate', methods=['POST'])
def validate_pod():
    admission_review = request.get_json()
    pod = admission_review['request']['object']
    uid = admission_review['request']['uid']

    # Check for required label
    labels = pod.get('metadata', {}).get('labels', {})
    if labels.get('security') != 'hardened':
        return jsonify({
            'apiVersion': 'admission.k8s.io/v1',
            'kind': 'AdmissionReview',
            'response': {
                'uid': uid,
                'allowed': False,
                'status': {'message': 'Pod must have label security=hardened'}
            }
        })

    # Check for root user
    for container in pod.get('spec', {}).get('containers', []):
        security_context = container.get('securityContext', {})
        run_as_user = security_context.get('runAsUser', None)
        run_as_non_root = security_context.get('runAsNonRoot', False)
        if run_as_user == 0 or (run_as_user is None and not run_as_non_root):
            return jsonify({
                'apiVersion': 'admission.k8s.io/v1',
                'kind': 'AdmissionReview',
                'response': {
                    'uid': uid,
                    'allowed': False,
                    'status': {'message': 'Containers cannot run as root'}
                }
            })

    # All good
    return jsonify({
        'apiVersion': 'admission.k8s.io/v1',
        'kind': 'AdmissionReview',
        'response': {
            'uid': uid,
            'allowed': True
        }
    })

if __name__ == '__main__':
    app.run(ssl_context=('cert.pem', 'key.pem'), host='0.0.0.0', port=443)

Step 2: Deploy the Webhook Server

  • Package the Python app in a Docker container.

  • Deploy it as a Kubernetes Deployment and expose it via a Service (e.g., webhook-service in the default namespace).

  • Generate a TLS certificate and key (e.g., cert.pem and key.pem) and store them in a Kubernetes Secret.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: webhook-deployment
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: webhook
  template:
    metadata:
      labels:
        app: webhook
    spec:
      containers:
      - name: webhook
        image: your-webhook-image:latest
        ports:
        - containerPort: 443
        volumeMounts:
        - name: tls
          mountPath: /app/tls
          readOnly: true
      volumes:
      - name: tls
        secret:
          secretName: webhook-tls
---
apiVersion: v1
kind: Service
metadata:
  name: webhook-service
  namespace: default
spec:
  selector:
    app: webhook
  ports:
  - port: 443
    targetPort: 443

Step 3: Register the Webhook

Create a ValidatingWebhookConfiguration to tell Kubernetes to call your webhook for pod CREATE and UPDATE operations.

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: pod-security-webhook
webhooks:
  - name: pod-security-webhook.example.com
    rules:
      - apiGroups: [""]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["pods"]
        scope: "*"
    clientConfig:
      service:
        name: webhook-service
        namespace: default
        path: /validate
      caBundle: <base64-encoded-ca-cert>
    admissionReviewVersions: ["v1"]
    sideEffects: None
    failurePolicy: Fail
  • Replace <base64-encoded-ca-cert> with the base64-encoded CA certificate used to sign your webhook’s TLS cert.

  • The failurePolicy: Fail means if the webhook is down, requests are rejected (safer for security policies).

Step 4: Test It Out

Try creating a pod that breaks the rules:

apiVersion: v1
kind: Pod
metadata:
  name: bad-pod
spec:
  containers:
  - name: nginx
    image: nginx
    securityContext:
      runAsUser: 0  # Running as root

You’ll get an error like: Pod must have label security=hardened or Containers cannot run as root.

Now try a good pod:

apiVersion: v1
kind: Pod
metadata:
  name: good-pod
  labels:
    security: hardened
spec:
  containers:
  - name: nginx
    image: nginx
    securityContext:
      runAsNonRoot: true

This should go through without a hitch.

Cool Stuff You Can Do with Admission Controllers

  1. Tighten Security:

    • Use PodSecurity to block privileged or root-running pods.

    • Write webhooks to enforce trusted image registries.

  2. Manage Resources:

    • LimitRanger sets default CPU/memory limits to keep things tidy.

    • ResourceQuota caps resource usage per namespace.

  3. Keep Things Consistent:

    • DefaultStorageClass ensures uniform storage setups.

    • Webhooks can inject sidecars for logging or monitoring.

  4. Stay Compliant:

    • Use webhooks to add annotations for cost tracking or regulatory compliance.

Tips to Keep It Chill

  1. Don’t Go Overboard: Only enable the controllers you need—too many can slow your cluster down.

  2. Webhook Reliability: Make sure your webhook is always up and snappy, or it’ll mess with API calls.

  3. Test Like Crazy: Bad webhook code can block good requests or cause security gaps.

  4. Monitor and Log: Track webhook performance and log decisions for debugging.

  5. Secure Your Webhooks: Use TLS and RBAC to keep things locked down.

Watch Out for These Gotchas

  • Performance Drag: More controllers mean slower API requests.

  • Webhook Fails: If your webhook crashes, it can block requests unless you set failurePolicy: Ignore.

  • Order Matters: The sequence of controllers can change how mutations stack up.

  • Debugging Sucks: Controller errors can be tricky to pin down, especially with custom webhooks.

Wrapping It Up

Admission controllers are your Kubernetes wingman, helping you enforce rules, tweak resources, and keep your cluster in check. From built-in ones like PodSecurity to custom webhooks like our pod security example, they give you mad control. Just be smart—test thoroughly, keep it lean, and secure your webhooks. Wanna play around? Spin up a test cluster and try the built-in controllers or build a webhook like the one above. The Kubernetes docs are your friend for diving deeper. Have fun running your cluster like a boss!

2
Subscribe to my newsletter

Read articles from Animesh Srivastava directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Animesh Srivastava
Animesh Srivastava

I’m a DevOps engineer with over 4 years of experience in platform development and cloud-native technologies. I specialize in building and automating cloud infrastructure, designing efficient CI/CD pipelines, and writing code in Python and Golang. My work often focuses on Kubernetes, ensuring scalability, reliability, and fault-tolerance in high-availability systems. I’m passionate about tackling technical challenges, optimizing workflows, and creating robust platforms that drive operational efficiency.