Skip to main content
RunBook Academy

KubernetesLXIII · Linux Security Controls in KubernetesLinux security controls

AppArmor profiles — kernel-enforced workload confinement

Advanced⏱ ~12 minkubectlapparmor_parser

What you'll learn

  • Explain what AppArmor is and how the kernel enforces it
  • Configure a built-in AppArmor profile in a Pod
  • Author a custom AppArmor profile and load it onto nodes
  • Recognise the production failure modes (unconfined, missing profile)

Prerequisites

Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16

Not yet marked complete on this device.

AppArmor is a Linux kernel security module that confines programs to a limited set of resources (files, capabilities, network, etc.). It is the Linux alternative to SELinux (which is more common on RHEL-based distributions). Kubernetes integrates AppArmor via annotations, and custom profiles are loaded onto nodes via a DaemonSet. This lesson covers the AppArmor model, the built-in profiles, the custom profiles, and the production patterns.

How AppArmor works

AppArmor attaches a profile to a program. The profile is a set of rules: which files the program can read, which files it can write, which capabilities it can use, which network operations it can perform. A program that violates the profile is logged (or killed, depending on the rule’s mode).

flowchart LR
    A[Program] --> B[Syscall/file op]
    B --> C{AppArmor profile}
    C -->|allowed| D[Kernel]
    C -->|denied| E{Log mode}
    E -->|complain| F[Log but allow]
    E -->|enforce| G[Deny]

Profiles are loaded into the kernel and attached to containers at startup. The kubelet uses apparmor_parser to load the profile and aa_change_profile to attach it.

The built-in profiles

Kubernetes supports two built-in AppArmor profiles via annotations:

  • runtime/default — the runtime’s default AppArmor profile (typically equivalent to no confinement).
  • localhost/<profile-name> — a custom profile loaded onto the node at /etc/apparmor.d/<profile>.

The annotation is per-container:

apiVersion: v1
kind: Pod
metadata:
  name: api
spec:
  containers:
  - name: api
    image: myapp:v1.0
    annotations:
      container.apparmor.security.beta.kubernetes.io/api: runtime/default

A Pod with no AppArmor annotation runs without confinement.

A custom AppArmor profile

A custom profile is loaded onto the node at /etc/apparmor.d/<profile-name>:

#include <tunables/global>

profile myapp-profile flags=(attach_disconnected) {
  #include <abstractions/base>

  # Allow network access
  network inet tcp,
  network inet6 tcp,

  # Allow reading configuration
  /etc/myapp/ r,
  /etc/myapp/** r,

  # Allow writing to data directory
  /var/lib/myapp/** rwk,

  # Deny everything else
  deny /** w,
  deny /proc/*/mem w,
}

The profile allows the workload to read its configuration, write to its data directory, and use TCP networking. Everything else is denied.

Shipping profiles via a DaemonSet

Custom profiles are shipped to every node via a DaemonSet:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: apparmor-profiles
  namespace: kube-system
spec:
  selector:
    matchLabels:
      name: apparmor-profiles
  template:
    metadata:
      labels:
        name: apparmor-profiles
    spec:
      initContainers:
      - name: install
        image: myorg/apparmor-profiles:v1
        command:
        - cp
        - -r
        - /profiles/.
        - /etc/apparmor.d/
        volumeMounts:
        - name: profiles
          mountPath: /etc/apparmor.d
      containers:
      - name: pause
        image: busybox
        command: ["sleep", "infinity"]
      volumes:
      - name: profiles
        hostPath:
          path: /etc/apparmor.d
          type: DirectoryOrCreate

The DaemonSet’s initContainer copies the profiles from the image to the node’s /etc/apparmor.d/. The kubelet picks them up.

A workload with a custom profile

apiVersion: v1
kind: Pod
metadata:
  name: api
spec:
  containers:
  - name: api
    image: myapp:v1.0
    annotations:
      container.apparmor.security.beta.kubernetes.io/api: localhost/myapp-profile

The Pod is admitted only if the profile is loaded on the node. If the profile is missing, the Pod fails to start with AppArmor: profile not found.

Production patterns

  1. runtime/default on every container. The minimum production default.
  2. Custom profiles for high-value workloads. A workload that processes untrusted data has a custom profile that denies writes outside the data directory.
  3. Profiles via a DaemonSet. Every node has the same set of profiles.
  4. Audit profiles. A profile that allows /etc/** write is a Critical finding.

Production failure modes

  1. Profile not on the node. The Pod fails to start. The fix is the DaemonSet.
  2. Profile is too permissive. The profile allows too many operations. The fix is to tighten the profile and re-test.
  3. No runtime/default. The container runs without AppArmor confinement. The fix is to set the annotation.
  4. Profile changed without re-deploy. The DaemonSet ships a new profile, but existing Pods still use the old one. The fix is to re-create the Pods.

Cross-course references

  • The Linux course covers AppArmor in detail, including profile syntax.
  • The Observability course covers the audit log entries for AppArmor violations.

Quiz

Knowledge check · 4 questions

  1. Q1. What is the AppArmor `runtime/default` profile?

  2. Q2. AppArmor is the default Linux Security Module on Ubuntu, Debian, and most Debian-based distributions.

  3. Q3. Your workload sets `container.apparmor.security.beta.kubernetes.io/api: localhost/myapp-profile`. The Pod fails to start with `AppArmor: profile not loaded`. Walk the response.

    The profile is referenced in the Pod spec but not loaded on the node where the Pod was scheduled. The cluster has a DaemonSet for AppArmor profiles, but the DaemonSet does not include the `myapp-profile` profile. The Pod is pending.

  4. Q4. What annotation configures AppArmor on a Kubernetes container, and what values does it accept?

Passing score: 75%. Answers are checked in this browser.

Production discipline

AppArmor is the right primitive for workload confinement on Debian-based distributions. A defensible AppArmor programme uses runtime/default as the minimum, ships custom profiles via a DaemonSet, and audits profiles for permissive rules. A cluster whose containers are all under AppArmor has a kernel-level confinement programme that is auditable; a cluster whose containers run without AppArmor has a programme that is not.