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 matter | What it solves | Cost |
|---|---|---|
User Namespaces (hostUsers: false) | Container root ≠ host root — isolation without Kata/VM | One Pod field + kernel/runtime |
| MutatingAdmissionPolicy (CEL) | Inject defaults without a webhook server or certs | One Policy + Binding |
| Volume Group Snapshots | Crash-consistent DB+WAL snapshot as one group | One 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:
apiVersion: v1
kind: Pod
metadata:
name: isolated-workload
spec:
hostUsers: false
containers:
- name: app
image: ghcr.io/example/app:1.4.2
securityContext:
allowPrivilegeEscalation: falseVerify 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, andhostPID: truecannot combine withhostUsers: false— the API server rejects the pod. - Volume backing. The path
/var/lib/kubelet/podsmust 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.
kubectl exec -n tenant-a isolated-workload -- cat /proc/self/uid_map
# expect: 0 100000 655362. 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:
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
}
}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=truefeature 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
MutatingAdmissionPolicyobjects — 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:
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-dataAll 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.
| Layer | Supports GROUP_SNAPSHOT today? |
|---|---|
| Snapshot controller | Needs ~v8.x; most Hetzner bundles pin v5–v7 |
Hetzner CSI (hcloud-csi) | No — per-volume snapshots only |
| Longhorn 1.11.x | No |
| Velero CSI integration | Early/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 reachesReady. If it staysPending, 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 promotion | One line | Verdict for small fleet |
|---|---|---|
DRA ResourceClaim v1 | Structured device API replacing nvidia.com/gpu: 1 | Watch — only with GPU nodes |
DRA core APIs v1 | DRA control-plane types | Watch — prerequisite for DRA |
| DRA prioritized alternatives | Fallback alternatives per claim | Skip — heterogeneous GPU pools |
| DRA admin access | RBAC split for DRA | Skip — with DRA |
| Fine-grained kubelet authz | Kubelet endpoints now need fine-grained RBAC | Adopt — audit NodeLog/Exec after upgrade |
ImageVolume v1 | OCI image as volume | Watch — config/WASM distribution |
| SELinux volume labeling | Automatic SELinux labels | Adopt if on SELinux hosts |
| CSI token redaction | CSI Secret no longer leaks driver token | Adopt — verify on upgrade |
| HPA/scheduler hardening | Reliability fixes, no YAML needed | Adopt silently |
| Others (APF, pod certs) | Control-plane hardening | Skip 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
- User Namespaces — adopt now. Validate kernel/containerd in staging, then default
hostUsers: falsevia aMutatingAdmissionPolicyfor tenant namespaces. - MutatingAdmissionPolicy — adopt now. Replace one webhook-backed default with a Policy+Binding. Keep the webhook at
Ignorefor one cycle, then delete it. - 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.