Skip to main content

Kubernetes 1.36 'Haru': Three GA Graduations Your Small Fleet Actually Needs

9 min readDora NodaDora Noda
Share

Kubernetes 1.36 shipped 70 enhancements. Eighteen graduated to Stable. Your fleet needs three.

On April 22, 2026, Kubernetes released v1.36 "Haru" — 70 enhancements split into 18 Stable, 25 Beta, and 25 Alpha. For a maturing platform, that split is the news: most releases polish, few rewrite the map.

But "18 Stable" is a filter problem. If you run a self-hosted PaaS on a handful of Hetzner nodes via Cluster API Provider Hetzner (CAPH), eighteen new GA APIs is eighteen buy-or-skip decisions. Most coverage sells the hyperscale story — workload-aware scheduling, sharded list/watch, DRA for GPU packing. That story is real at 5,000 nodes. At 5 nodes, it is noise.

This post does the filter. Three of the 18 change operations for a small CAPH fleet. The rest can wait.

The three that matterWhat it solvesCost
User Namespaces (hostUsers: false)Container root ≠ host root — isolation without Kata/VMOne Pod field + kernel/runtime
MutatingAdmissionPolicy (CEL)Inject defaults without a webhook server or certsOne Policy + Binding
Volume Group SnapshotsCrash-consistent DB+WAL snapshot as one groupOne object — but needs CSI support you likely lack

Each section below gives you what it does, why it matters on Hetzner, a minimal YAML, and the gotcha. A final table adjudicates all 18 so the "three" is a judgment, not a cherry-pick.


1. User Namespaces: hostUsers: false is finally GA

What it does

Before 1.36, a container running as UID 0 was UID 0 on the host. Break the container boundary and you owned the node. User Namespaces adds a mapping: the container still sees itself as root, the host sees an unprivileged UID (typically remapped into the 65k–100k range).

The field is hostUsers, stable in 1.36 after years behind a feature gate:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: isolated-workload
spec:
  hostUsers: false
  containers:
  - name: app
    image: ghcr.io/example/app:1.4.2
    securityContext:
      allowPrivilegeEscalation: false

Verify the remapping with cat /proc/self/uid_map — you should see 0 100000 65536 rather than 0 0 4294967295. The official GA blog shows the same one-field shape.

Why it matters on a small fleet

Small fleets pack multiple tenants onto shared nodes to amortize Hetzner hardware. Classic hard isolation — Kata, gVisor, one VM per tenant — costs RAM and ops that erodes the "own the hardware" thesis. User Namespaces adds a real containment layer without a VM. It complements Pod Security restricted and seccomp, and for the class of CVEs where exploit requires host root, the remapped UID caps the blast radius.

Nine years to GA matters: this is a supportable default, not a beta gate that might vanish.

The gotcha

  • Kernel and runtime. Needs idmapped mounts (Linux 6.3+, modern containerd/CRI-O). Talos ships this; stock Ubuntu Hetzner images need a kernel check.
  • Incompatible fields. hostPath, hostNetwork: true, and hostPID: true cannot combine with hostUsers: false — the API server rejects the pod.
  • Volume backing. The path /var/lib/kubelet/pods must sit on a filesystem that supports idmapped mounts (ext4/xfs typically do). Test before rolling fleet-wide.

Rollout tip: do not annotate every pod. Use a MutatingAdmissionPolicy (next section) to default hostUsers: false for tenant namespaces.

bash
kubectl exec -n tenant-a isolated-workload -- cat /proc/self/uid_map
# expect: 0  100000  65536

2. MutatingAdmissionPolicy: CEL without the webhook

What it does

MutatingAdmissionPolicy + MutatingAdmissionPolicyBinding (GA in admissionregistration.k8s.io/v1) lets you declare mutations as CEL expressions evaluated inside the API server — no webhook Deployment, Service, or TLS cert.

Before, injecting a label or default meant running a webhook, issuing a cert, registering a MutatingWebhookConfiguration, and keeping it more available than the workloads it mutates. One unavailable webhook with failurePolicy: Fail blocks all creates.

CEL collapses that to two objects. This policy defaults tenant pods to hostUsers: false:

yaml
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicy
metadata:
  name: default-hostusers-false
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
    - apiGroups: [""]
      apiVersions: ["v1"]
      operations: ["CREATE"]
      resources: ["pods"]
  matchConditions:
  - name: is-tenant-workload
    expression: "'bex.io/tenant' in object.metadata.labels"
  mutations:
  - patchType: ApplyConfiguration
    applyConfiguration:
      expression: |
        Object{
          spec: Object.spec{
            hostUsers: false
          }
        }
yaml
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicyBinding
metadata:
  name: default-hostusers-false-binding
spec:
  policyName: default-hostusers-false
  validationActions: [Deny]
  matchResources:
    namespaceSelector:
      matchExpressions:
      - key: bex.io/managed
        operator: In
        values: ["true"]

No webhook image. No cert rotation. Delete the policy to roll back.

Why it matters

Platform-wide defaults — labels, cost-center annotations, hostUsers — are the highest-leverage automation on a small fleet. A webhook adds a service that must be more available than what it mutates, exactly backwards for a fleet minimizing moving parts. In-process CEL removes that service. The API server is already your most-monitored component.

The gotcha

  • Remove the beta gate. The MutatingAdmissionPolicy=true feature gate is GA and no longer needed. Remove it from API server extraArgs after upgrading.
  • RBAC. The API server's plugin now loads by default. Its informer list-watches MutatingAdmissionPolicy objects — if the aggregated API server ServiceAccount lacks RBAC to list/watch them, HasSynced() never returns and every mutating admission blocks.
  • CEL limits. Start with simple defaults and label injections. Complex deep-merge patches that a webhook does imperatively may not translate one-to-one.

Migrate by keeping the old MutatingWebhookConfiguration at failurePolicy: Ignore alongside the new policy, diff the mutated objects, then delete the webhook.


3. Volume Group Snapshots: one instant, not two races

What it does

VolumeGroupSnapshot (GA in groupsnapshot.storage.k8s.io/v1) captures multiple PVCs that share write ordering as one crash-consistent group. Before, you took two independent VolumeSnapshot objects and hoped the timing lined up. Under concurrent writes, one volume's snapshot at T and another at T+δ yields a torn restore — exactly what a WAL exists to prevent.

Group snapshots use a label selector:

yaml
apiVersion: groupsnapshot.storage.k8s.io/v1
kind: VolumeGroupSnapshot
metadata:
  name: tenant-db-consistent
  namespace: tenant-a
spec:
  volumeGroupSnapshotClassName: hcloud-group-snapshot-class
  source:
    selector:
      matchLabels:
        app.kubernetes.io/instance: tenant-a-db
        bex.io/volume-group: pg-data

All PVCs matching those labels — say, a 10 GiB data volume plus a 2 GiB WAL — are snapshotted together. Restore rehydrates the group to the same instant.

Why it matters

A PaaS with one PVC per tenant sidestepped this. The moment you do the better thing — separate data and WAL volumes — the single-PVC assumption breaks. For a platform promising per-tenant backups, "we snapshotted your volumes together" is the difference between a restorable backup and a plausible artifact that restores corrupt.

The gotcha — this is the most gated of the three

GA means the API types are stable, not that your storage stack supports them.

LayerSupports GROUP_SNAPSHOT today?
Snapshot controllerNeeds ~v8.x; most Hetzner bundles pin v5–v7
Hetzner CSI (hcloud-csi)No — per-volume snapshots only
Longhorn 1.11.xNo
Velero CSI integrationEarly/experimental

Practical stance:

  • Try it in staging: install group-snapshot CRDs and a VolumeGroupSnapshotClass, create a group snapshot against your real two-PVC topology, and see if it reaches Ready. If it stays Pending, the driver is the gate.
  • Keep WAL archiving (CloudNativePG + barman/pgBackRest) as the durable backup. Group snapshots will give a faster volume-consistent point; they do not replace point-in-time WAL recovery.
  • File the upstream driver request naming your two-volume topology — maintainers prioritize concrete use cases.

Expect "stage, don't ship" for Hetzner fleets for the next two quarters.


4. The other 15 — and why you can defer them

GA promotionOne lineVerdict for small fleet
DRA ResourceClaim v1Structured device API replacing nvidia.com/gpu: 1Watch — only with GPU nodes
DRA core APIs v1DRA control-plane typesWatch — prerequisite for DRA
DRA prioritized alternativesFallback alternatives per claimSkip — heterogeneous GPU pools
DRA admin accessRBAC split for DRASkip — with DRA
Fine-grained kubelet authzKubelet endpoints now need fine-grained RBACAdopt — audit NodeLog/Exec after upgrade
ImageVolume v1OCI image as volumeWatch — config/WASM distribution
SELinux volume labelingAutomatic SELinux labelsAdopt if on SELinux hosts
CSI token redactionCSI Secret no longer leaks driver tokenAdopt — verify on upgrade
HPA/scheduler hardeningReliability fixes, no YAML neededAdopt silently
Others (APF, pod certs)Control-plane hardeningSkip until high write QPS

Pattern: four DRA entries are GPU-only, several are "you already get it by upgrading" (fine-grained authz, CSI redaction), and the rest trigger at scale you do not have. The three gems are the only ones where GA changes what you write.


5. What to do Monday

  1. User Namespaces — adopt now. Validate kernel/containerd in staging, then default hostUsers: false via a MutatingAdmissionPolicy for tenant namespaces.
  2. MutatingAdmissionPolicy — adopt now. Replace one webhook-backed default with a Policy+Binding. Keep the webhook at Ignore for one cycle, then delete it.
  3. Volume Group Snapshots — stage. Install the CRDs, test against your two-PVC topology. On Hetzner CSI it will stay Pending — that is the driver speaking. Keep CNPG WAL backups durable.

Haru is a quiet release by design — no new primitive that renames your architecture, just eighteen graduations that say "you can depend on this now." For a rack-sized fleet, the payoff is unglamorous and real: the escape now hits an unprivileged UID from one field, the webhook you rotated certs for is gone, and the backup you test on Friday is no longer two snapshots you hoped were in sync.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. If this narrowed your 1.36 backlog from eighteen to three, the same holds for your platform: a smaller, sharper fleet is the one you can operate on a Tuesday. Star the repo on GitHub.

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