✅ BenefitsInfrastructureIntermediate✨ AI-assisted

Benefits of a Kubernetes Security Context for AI Pods

WittyTech Daily··4 min read
#kubernetes#security-context#seccomp#pod-security

Your inference pod pulls weights from outside, loads them through a stack of native libraries, and unless someone said otherwise, runs as whatever user the image picked, often root. A Kubernetes security context is the part of the pod spec that narrows what that process can do on the node. Below are five benefits, each with the YAML and the attack it blunts, then the cases where it bites. The reference is the Kubernetes security context docs.

Five benefits of a Kubernetes security context on model pods

The realistic threats are dull: a poisoned model file (pickle-based checkpoints can run code when loaded), a vulnerable dependency in the server, or an agent talked into a tool call it shouldn't make. A security context doesn't stop the break-in. It decides how much the intruder gets.

Run as non root so a hijacked process isn't root

spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 10001
    runAsGroup: 10001
    fsGroup: 10001

Code execution now lands as UID 10001, so it can't modify root-owned files baked into the image. With runAsNonRoot: true the kubelet refuses to start a container that would run as UID 0, so a lazy base image fails loudly. Use a numeric runAsUser, because a username in the image's USER line can't be verified as non-root. fsGroup makes mounted volumes group-owned by that GID, so the model cache stays writable.

readOnlyRootFilesystem keeps payloads off the disk

containers:
  - name: inference
    securityContext:
      readOnlyRootFilesystem: true
    volumeMounts:
      - name: tmp
        mountPath: /tmp
      - name: cache
        mountPath: /cache
      - name: dshm
        mountPath: /dev/shm
volumes:
  - name: tmp
    emptyDir: {}
  - name: cache
    emptyDir:
      sizeLimit: 50Gi
  - name: dshm
    emptyDir:
      medium: Memory

A malicious checkpoint or injected tool call can't drop a script into the image or patch your installed Python packages. Anything that legitimately writes gets a small, explicit emptyDir. Point HF_HOME, the Hugging Face cache location, at /cache/hf. An emptyDir cache is wiped when the pod reschedules, so big models want a persistent volume or weights baked into the image.

allowPrivilegeEscalation false closes the setuid door

containers:
  - name: inference
    securityContext:
      allowPrivilegeEscalation: false

This sets the Linux no_new_privs flag, so a setuid binary in the image can't hand the process more privileges than its parent had. Kubernetes defaults the field to true when you omit it, so you have to say it.

Drop capabilities on the pod to shed NET_RAW and friends

containers:
  - name: inference
    securityContext:
      capabilities:
        drop:
          - ALL

Container runtimes grant a default set of Linux capabilities, and that set includes NET_RAW, the ability to craft raw packets. A model server that listens on port 8000 needs none of them. If something breaks, add back only the named one, for example add: [NET_BIND_SERVICE] for ports below 1024. Omit the CAP_ prefix in the manifest.

Seccomp RuntimeDefault trims the kernel attack surface

spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault

Pods run Unconfined, with no syscall filtering, unless the node's kubelet runs with --seccomp-default (stable since v1.27, enabled per node). Setting RuntimeDefault yourself applies the container runtime's default filter, so a container escape has fewer kernel entry points to try.

Combine them in one Deployment and enforce restricted

apiVersion: apps/v1
kind: Deployment
metadata:
  name: inference
spec:
  replicas: 1
  selector:
    matchLabels:
      app: inference
  template:
    metadata:
      labels:
        app: inference
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 10001
        runAsGroup: 10001
        fsGroup: 10001
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: inference
          image: <your-registry>/inference-server:<tag>
          env:
            - name: HF_HOME
              value: /cache/hf
          resources:
            limits:
              nvidia.com/gpu: 1
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop:
                - ALL
          volumeMounts:
            - name: tmp
              mountPath: /tmp
            - name: cache
              mountPath: /cache
            - name: dshm
              mountPath: /dev/shm
      volumes:
        - name: tmp
          emptyDir: {}
        - name: cache
          emptyDir:
            sizeLimit: 50Gi
        - name: dshm
          emptyDir:
            medium: Memory

The same fields (minus readOnlyRootFilesystem, which is extra) are what the Restricted Pod Security Standard asks for. Turn on enforcement for the namespace and check the result:

kubectl label namespace <your-namespace> pod-security.kubernetes.io/enforce=restricted
kubectl exec deploy/inference -- id
kubectl exec deploy/inference -- touch /usr/local/bin/probe

id should report UID 10001, and touch should fail with Read-only file system.

When a Kubernetes security context is not worth it

  • Writable paths you didn't know about. A read-only root breaks servers that write compiled kernels or temp files in odd places. Run realistic traffic in staging, watch for Read-only file system errors, and mount an emptyDir per path. vLLM's Kubernetes deployment docs (v0.7.3) mount a memory-backed /dev/shm, so expect that one.
  • GPUs. The NVIDIA device plugin README requests GPUs through resources.limits with no securityContext. It mentions elevated privileges only for deploying the plugin itself in certain modes. Don't add privileged: true to your serving pod to make a GPU appear. I found no authoritative word on every driver and runtime combination, so test non-root on your GPU nodes first.
  • Workloads that need privilege. allowPrivilegeEscalation: false can't coexist with privileged or CAP_SYS_ADMIN. Coding-agent sandboxes that mount filesystems or nest containers won't fit, and running untrusted code deserves a sandboxed runtime, not only a security context.
  • It limits damage on the node, not data leaving it. Pair it with network policies.

A few lines in the pod spec turn a compromised model server from root on your node into a locked-down process with nowhere to write, which is the gap that matters when the weights come from outside your walls.

← More in Infrastructure