Skip to main content

Rootless Kubelet Hits Beta in Kubernetes 1.37: What Dropping Host Root Changes for Multi-Tenant Nodes (and What Still Needs It)

11 min readDora NodaDora Noda
Share
On this page

Every container-breakout CVE of the last four years ends with the same half-sentence: resulting in arbitrary code execution as root on the host. On September 4, 2026, Kubernetes promoted the fix for that ending — KubeletInUserNamespace, aka rootless mode — to beta in v1.37. After eight years as an experiment and five as an alpha, running the kubelet, the CRI runtime, and the rest of the node components as a non-root host user is now a supported, conformance-tested configuration.

The verdict, up front, because beta-announcement posts bury it: nothing on your existing nodes changes. The feature gate is now on by default, but the gate only permits the kubelet to run inside a user namespace — it does not put it there. What beta actually gives a multi-tenant operator is three things: a stable API signal (runningInUserNamespace on every Node object) to schedule around, node-conformance CI running on rootless clusters, and a documented compatibility inventory to pilot against. The right move is a one-pool pilot for your untrusted workloads, not a fleet conversion. This post is the blast-radius accounting, the works-or-not table, and that pilot checklist.

Beta changes the gate default, not your nodes

KubeletInUserNamespace (KEP-2033, authored by Akihiro Suda of NTT) started as an experiment in 2018, merged as an alpha in v1.22 (2021), and rode along quietly ever since. The v1.37 graduation to beta — part of the "Garhwal" release that shipped August 26, 2026 — changes exactly three things, and the announcement is admirably honest that the gate itself is "quite boring":

  1. The gate is enabled by default. All it does is let the kubelet ignore permission errors when setting node-level sysctls (vm.overcommit_memory, kernel.panic, and friends) and when watching /dev/kmsg. A rootful kubelet never hits those errors, so existing clusters observe zero behavior change.
  2. kubectl get nodes -o yaml now reports runningInUserNamespace in NodeSystemInfo. This is the operationally important bit: you can label and taint on it, keeping workloads that need real root (the announcement names "some CNI plugin installers" explicitly) off rootless nodes through ordinary scheduling.
  3. Node-conformance end-to-end tests now run on a rootless cluster (ci-kubernetes-e2e-kind-rootless). Beta means the project's own CI proves the configuration on every release — the difference between "known to work" and "guaranteed to keep working."

Two related milestones made this possible: pod-level user namespaces (hostUsers: false) went GA in v1.36, and containerd 2.1 added writable cgroups. Note the layering, because the names invite confusion: pod user namespaces isolate workloads while the kubelet still runs as host root; rootless mode isolates the node components themselves. They compose — the announcement's headline composition is Kubernetes-in-Kubernetes without privileged: true — and for a multi-tenant fleet you eventually want both.

What rootless actually moves

A Linux user namespace maps a non-root host UID (say, 1000) to UID 0 inside the namespace. The kubelet, containerd or CRI-O, the CNI plugins, and kube-proxy all run as that fake root: enough privilege to mount volumes, manage cgroups, and configure pod network namespaces, but every capability dies at the namespace boundary. On the host, the whole node stack is just UID 1000's processes.

The before/after for a breakout that reaches the node layer:

Attacker reaches…Rootful node (today)Rootless node (beta)
kubelet / CRI / OCI runtimeHost root: read any secret, mount any path, load kernel modulesConfined to the unprivileged account; kernel, bootloader, and firmware untouched
A privileged: true podReal host rootFake root only — privilege does not cross the namespace boundary
Kernel vulnerability itselfHost rootStill host root — user namespaces mitigate node-component bugs, not kernel bugs

That last row is the one to internalize before you touch anything. The announcement states it plainly: user namespaces "are not effective for mitigating vulnerabilities in the kernel itself," and rootless mode belongs alongside seccomp and the rest of your hardening, not instead of it. Beta narrows the blast radius of the node stack; it does not shrink the kernel's.

The CVE receipts: what beta would have contained

The announcement names five historical breakouts as motivation, and they make a useful containment table because each one lands in a different node component:

CVEComponentWhat happenedUnder rootless mode
CVE-2022-0811 ("cr8escape")CRI-OTricked into setting arbitrary sysctls like kernel.core_pattern → host root RCEAttacker sets sysctls in a namespace they already own; host core_pattern unreachable
CVE-2023-27561runcVolume-mount race bypassed masked paths, exposing host procfsNo host procfs in the mount view to expose
CVE-2024-10220kubeletgitRepo volumes executed arbitrary commands as rootCommands run as the unprivileged node user
CVE-2025-31133runcSymlink trickery bind-mounted attacker paths, wrote host /proc/sysrq-trigger and core_patternWrites land on namespace-scoped procfs, not the host's
CVE-2026-53488containerdCrafted image labels executed arbitrary host commandsCommands execute without host privilege

The pattern across all five: the exploit's payload still runs, but its payoff — host root — is gone. For a PaaS running untrusted tenant code (AI-agent sandboxes, preview environments, CI runners building arbitrary Dockerfiles), that converts a cluster-takeover incident into a single-node-account compromise. Still bad. Structurally smaller.

The compatibility inventory: what still needs real root

Here is the honest table, assembled from the KEP-2033 caveats and the official setup docs. Treat any row marked broken as a pilot-pool exclusion rule, not a future fix to wait for.

FeatureStatus on rootless nodesDetail
CNI: Flannel (VXLAN)WorksThe only CNI the KEP names as known-working; Usernetes runs multi-node VXLAN on it
CNI: eBPF-based (Cilium et al.)BlockedLoading host-kernel BPF programs needs host privilege; Cilium's BPF-token support is an open CFP, and tokens need kernel 6.8+
Other CNI pluginsVaries"Some CNI plugins might not work" — verify yours before the pilot, and keep CNI installers off the pool via the runningInUserNamespace taint
emptyDir, hostPath, local, configMap, secret, downwardAPIWorksLocal and API volumes are fine
Block volumes, kernel-mode NFS, iSCSIBrokenUser namespaces only support tmpfs, bind, and FUSE; FUSE-based CSI is possible but explicitly not recommended
Hetzner Cloud Volumes (hcloud-volumes)Broken on the poolhcloud-csi provisions attachable block volumes through a privileged node DaemonSet — PVCs on this class cannot mount on rootless nodes, and the DaemonSet itself must be excluded from the pool
Device plugins / GPU poolsNot a pilot candidateThe device cgroup controller cannot be delegated to the namespace; plugins expecting real device access stay rootful
hostNetwork pods and host-assuming DaemonSetsAudit requiredThe "host" network is the intermediate RootlessKit namespace, not the true host netns — exporters, sniffers, and installers that assume true host networking need per-DaemonSet review
seccompWorksSupported; keep enforcing it — it covers the kernel-attack row rootless does not
AppArmorBrokenPod creation with an AppArmor profile fails; containerd rootless config sets disable_apparmor = true
SELinuxPartialApplying an existing profile works; creating new profiles does not; non-FUSE overlayfs also requires SELinux disabled
HugepagesBrokensystemd cannot delegate the hugetlb controller
runAsUserLimitedUID range bounded by /etc/subuid
NodePorts below 1024Broken by defaultDefault range (30000–32767) is fine; sub-1024 needs CAP_NET_BIND_SERVICE on the RootlessKit binary
kube-proxyWorks with configiptables or userspace mode plus zeroed conntrack sysctl settings (maxPerCore: 0, timeouts 0s)
cgroup drivercgroupfs, not systemdThe delegated tree is consumed via cgroupfs unless you run a second systemd inside the namespace; cgroup v1 unsupported entirely
Container image layersWorks with configfuse-overlayfs snapshotter (plain overlayfs needs kernel 5.11+ and SELinux disabled)
Network performanceDegraded by defaultslirp4netns userspace networking adds overhead; the lxc-user-nic setuid helper is the documented mitigation

Three exclusion rules fall out of the table: no GPU pools, no AppArmor-profiled workloads, no persistent hcloud-volumes on the pilot pool. That still leaves the workloads that need rootless most — stateless, untrusted, bursty tenant compute.

The one-pool pilot playbook on CAPH

Cluster API Provider Hetzner has no rootless toggle — its contract is OS images with container runtime, kubelet, and kubeadm pre-installed (via kubernetes-sigs/image-builder reference images) plus custom scripts through the kubeadm config. So the trial is hand-rolled at the image layer, fenced to one MachineDeployment. Concretely:

  1. Pick the pool's workload first. Stateless, untrusted, local-disk-only: AI-agent sandboxes, branch preview environments, CI build runners. No hcloud-volumes PVCs, no GPUs, no AppArmor annotations, no hostNetwork. If a workload needs any row marked broken above, it stays on the rootful pools.
  2. Build the image. Starting from the CAPH image-builder reference: add an unprivileged node user with a /etc/subuid + /etc/subgid range, enable lingering and a Delegate=yes systemd unit for the cgroup tree, install RootlessKit, and ship containerd preconfigured for rootless (disable_apparmor, restrict_oom_score_adj, disable_hugetlb_controller, fuse-overlayfs snapshotter, SystemdCgroup = false) plus a kubelet config with cgroupDriver: cgroupfs. Any 2026 kernel clears the 5.11 overlayfs floor; prefer 6.8+ so BPF tokens are available when eBPF CNIs catch up.
  3. Bootstrap inside the namespace. The user namespace must exist before the kubelet starts — RootlessKit (or unshare, or become-root) creates it, with writable /etc, /run, /var/lib/kubelet, /var/lib/cni, and /var/lib/containerd inside the namespace. Usernetes is the reference implementation of this shape (kubeadm joining from inside the namespace); rootless k3s is the shortcut if you want the trial running before you finish the image.
  4. Fence with the beta's own signal. Label and taint the pool from nodeInfo.runningInUserNamespace, and add affinity rules excluding privileged DaemonSets (hcloud-csi-node, monitoring agents awaiting their host-network audit, CNI installers) from ever scheduling there. Run Flannel VXLAN for the pilot unless your CNI is verified rootless-safe.
  5. Forward the ports deliberately. The kubelet's 10250 and every NodePort live in the intermediate netns; expose them to the host with RootlessKit's port driver, slirp4netns, or the k3s rootlessports controller — and verify kubectl logs/exec and health probes through the forwarder before admitting tenant traffic.
  6. Measure the tax, then decide. Compare pod-startup latency (fuse-overlayfs + slirp cost), sustained throughput, and failed-scheduling/volume-mount rates against a rootful pool. The KEP links iperf-based measurements of the slirp hop as a starting reference, but the number that matters is yours, on your traffic shape; your call is whether the containment is worth it for this pool's threat model.
  7. Rollback is a scale-to-zero. Cordon, drain, scale the pilot MachineDeployment to zero. The pool is cattle; the experiment must be reversible in one kubectl scale without touching the rootful fleet.

Expect the CNI and CSI rows — not the kubelet — to dominate your pilot effort. The gate being "boring" cuts both ways: the kubelet side is nearly free now, so every remaining cost is in the drivers around it.

What would make this fleet-default

Beta is a credible signal, not a migration order. Three things would change the calculus from "pilot pool" to "default for tenant compute": GA graduation (explicitly pending adoption feedback — SIG Node wants your pilot reports), BPF-token support landing in eBPF CNIs so Cilium-based fleets are not stuck on Flannel, and the Kubernetes-in-Kubernetes story maturing so a tenant's whole cluster can nest inside a user-namespaced pod. Until then, the shape of the win is clear: your most dangerous workloads stop sharing host root with the node agent, one pool at a time.

The scariest sentence in container security has always ended "as root on the host." Kubernetes 1.37 just gave you a supported way to delete that ending.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.

Sources

  • Kubernetes Blog, "Kubernetes v1.37: KubeletInUserNamespace (aka Rootless mode) Graduates to Beta" (Akihiro Suda, NTT — September 4, 2026): beta scope, CVE motivation list, use cases, alpha-to-beta changes, kind/minikube/Usernetes/k3s paths.
  • KEP-2033: "Kubelet in User Namespace (aka Rootless mode)" — authoritative constraints/caveats (cgroup, network, volumes, SecurityContext) and RootlessKit design.
  • Kubernetes Docs, "Running Kubernetes Node Components as a Non-root User" (Beta since v1.37) — prerequisites, manual node deploy, containerd/CRI-O/kubelet/kube-proxy configs, caveats.
  • Kubernetes Blog, "Kubernetes v1.37: Garhwal" (August 26, 2026) — release vehicle for the graduation.
  • cilium/cilium#43587 (CFP: BPF-token support) — why eBPF CNIs stay rootful for now; needs kernel 6.8+.
  • hetznercloud/csi-driver — hcloud-volumes as attachable block storage via a privileged node plugin.
  • syself/cluster-api-provider-hetzner — CAPH node-image contract (pre-installed runtime/kubelet/kubeadm, custom scripts via kubeadm config).
  • CNCF Blog, "runc container breakout vulnerabilities: A technical overview" (November 2025) — CVE-2025-52881 class context for the 2025 runc procfs-redirect family.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex