KubernetesLXIII · Linux Security Controls in KubernetesLinux security controls
AppArmor profiles — kernel-enforced workload confinement
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
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
runtime/defaulton every container. The minimum production default.- Custom profiles for high-value workloads. A workload that processes untrusted data has a custom profile that denies writes outside the data directory.
- Profiles via a DaemonSet. Every node has the same set of profiles.
- Audit profiles. A profile that allows
/etc/**write is a Critical finding.
Production failure modes
- Profile not on the node. The Pod fails to start. The fix is the DaemonSet.
- Profile is too permissive. The profile allows too many operations. The fix is to tighten the profile and re-test.
- No
runtime/default. The container runs without AppArmor confinement. The fix is to set the annotation. - 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
Q1. What is the AppArmor `runtime/default` profile?
Q2. AppArmor is the default Linux Security Module on Ubuntu, Debian, and most Debian-based distributions.
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.
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.