Recursive Read-Only Mounts

A volume is mounted readOnly, so why can a container still write under it? On Linux readOnly makes only that one mount read-only, so a tmpfs mounted read-write below a hostPath directory stays writable. Setting recursiveReadOnly: Enabled makes the whole tree read-only, and the API server requires readOnly true and mountPropagation None or unset. Without kernel and runtime support the container does not start, while IfPossible falls back to Disabled.

Play the animation 5 steps, animated, best on a wide screen

Recursive Read-Only Mounts, Kubernetes diagram

Recursive Read-Only Mounts, step by step

  1. A volume is mounted readOnly, so why can a container still write under it? Pod rro mounts the Node directory /mnt at /data with readOnly: true. The Node also has a tmpfs mounted read-write at /mnt/tmpfs, and the container sees it at /data/tmpfs. The recursiveReadOnly field is not set, so it is Disabled, the default, and none of the five checks apply.

    Play from step 1
  2. The app writes /data/a. That path is on the read-only mount itself, so the write is refused as a read-only file system and nothing reaches the Node.

    Play from step 2
  3. The app writes /data/tmpfs/x and it succeeds. On Linux a read-only mount is not recursively read-only by default, so the tmpfs mounted read-write below it stays writable, and x lands in the tmpfs on the Node.

    Play from step 3
  4. A Pod cannot change its volumeMounts, so the Pod is recreated with recursiveReadOnly: Enabled and mountPropagation left unset. The API server accepts that field only with readOnly: true and mountPropagation None or unset. All five checks hold on this Node, so the whole tree under /data is read-only. The same write to /data/tmpfs/x is refused, and the status reports Enabled.

    Play from step 4
  5. Enabled is strict: if instead Node-1 had a kernel older than 5.12, or a CRI or OCI runtime without support, the container would not start. IfPossible falls back to Disabled there. Checks 1 and 2 hold but not all of 3 to 5, so the write to /data/tmpfs/x is accepted and lands in the Node tmpfs again, and the status, Disabled, is the only sign on the Pod.

    Play from step 5

Sources