Skip to main content

Kubernetes 1.36's Pod-Level Resource Managers: The Sidecar Bin-Packing Math, After Hetzner's Price Hikes

9 min readDora NodaDora Noda
Share

Hetzner raised dedicated-vCPU prices twice in 2026. The second hike, on June 15, nearly tripled the CCX line: CCX23 went from €31.49 to €85.99 a month, CCX33 from €62.49 to €138.49 — increases of 173% and 122% respectively. If you're running a self-hosted PaaS on owned Hetzner boxes, every CPU core your scheduler leaves stranded now costs meaningfully more than it did in January.

Which makes it an inconvenient time to learn that, industry-wide, Kubernetes clusters are barely using the cores they've already reserved. Cast.ai's 2026 State of Kubernetes Optimization report puts average CPU utilization at 8% — down from 10% the year before — while CPU over-provisioning jumped from 40% to 69% year over year. A meaningful slice of that gap comes from a pattern every multi-container Pod runs into: sidecars that have to reserve a worst-case CPU slice individually, whether or not they ever use it, because Kubernetes has never let a Pod share one resource budget across its containers. As of Kubernetes 1.36, that changes — in alpha, and only partway. Here's what it's actually worth, worked through in real numbers on a real (now pricier) node.

The Headline Number

On a Hetzner CCX43 (16 vCPU / 64GB, €275.99/month post-hike), a tenant Pod shaped like "app + proxy sidecar + log-shipper sidecar" fits 2 to a node today and 3 to a node once its sidecars stop needing their own hard-pinned CPU reservations — a 50% density gain that turns into roughly $29,800/month in avoided Hetzner spend for a platform running 600 such Pods (fewer nodes bought outright, not a discount). The rest of this post is that math, shown, plus where it breaks down.

What Actually Shipped in v1.36

Kubernetes has let you set CPU and memory requests/limits on individual containers since the beginning. PodLevelResources — alpha in 1.32, beta and on-by-default since 1.34 — added a .spec.resources field so a Pod can also declare one overall CPU/memory/hugepages budget for itself, instead of that budget being purely the sum of its containers' requests.

PodLevelResourceManagers, new and alpha in 1.36, is what makes that budget do something at the node level: it extends the kubelet's CPU Manager, Memory Manager, and Topology Manager — previously strictly per-container — to understand a Pod-wide envelope. To turn it on, the kubelet needs:

  • PodLevelResources and PodLevelResourceManagers feature gates enabled
  • CPU Manager set to the static policy
  • Memory Manager set to the Static policy
  • Topology Manager set to pod or container scope (not none)

With all four in place, a Pod like this becomes possible:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: tenant-app
spec:
  resources:
    requests: { cpu: "5", memory: "8Gi" }
    limits: { cpu: "5", memory: "8Gi" }
  containers:
    - name: app
      resources:
        requests: { cpu: "3", memory: "6Gi" }
        limits: { cpu: "3", memory: "6Gi" }
    - name: envoy-sidecar
      # no individual resources set — draws from the pod's shared pool
    - name: fluent-bit-sidecar
      # same — shares the remaining ~2 CPU / 2Gi with the proxy

The kubelet carves the Pod's 5-CPU budget into an exclusive, NUMA-aligned 3-core slice for app (a Guaranteed, integer-CPU container still gets pinned cores exactly as before) and a shared pool — here, 2 CPU and 2Gi — that both sidecars draw from dynamically instead of each needing its own fixed, pre-negotiated ceiling. Pod-level requests/limits take precedence over container-level ones when both are set, and they now determine the Pod's QoS class and OOM score adjustment directly. A new resource_manager_allocations_total kubelet metric, tagged with a source label, distinguishes allocations pulled from a node's general pool versus a Pod's own shared pool — useful if you're the one operating the node and want to see the sharing actually happening.

The Worked Example, in Full

Here's why the "no individual resources set" line above matters, using real sidecar numbers instead of invented ones.

Kubernetes' CPU Manager static policy only hands out exclusive, pinned cores to containers that are both in a Guaranteed-QoS Pod and request whole CPUs — and Guaranteed QoS requires every container in the Pod to set requests == limits, sidecars included. Leave a sidecar Burstable and the whole Pod loses Guaranteed status, which also strips the main container of its exclusive, NUMA-aligned cores. So today, if you want your app container pinned, its sidecars have to commit to a fixed number too — and operators tend to pick that number defensively, not leanly:

  • Istio's own default for its Envoy sidecar is a 100m CPU request but a 2-core (2000m) limit — and Envoy genuinely burns around 0.5 vCPU per 1,000 req/s under load. To keep the Pod Guaranteed without throttling the proxy during a traffic spike, operators commonly pin requests == limits near that 2-core ceiling, not the lean 100m default.
  • Fluent Bit, in a documented sidecar configuration, ships with a 50m CPU limit — fine at low volume. But real production issue reports show Fluent Bit's CPU usage plateauing around 990m (essentially a full core) once event rates cross 5,000–7,000/sec, with logs backing up past that point. To avoid dropped or backlogged logs during a burst, operators size the shipper's Guaranteed reservation near that ~1-core ceiling too.

Put a 3-CPU illustrative app container (a NUMA-sensitive workload — the specific number is a stand-in, the pattern isn't) next to those two sidecars, and today's per-container Guaranteed model reserves:

ContainerReserved today (requests = limits, Guaranteed)
App (exclusive, NUMA-pinned)3 CPU
Envoy-style proxy sidecar2 CPU
Fluent Bit-style log shipper1 CPU
Pod total requested6 CPU

On a 16-vCPU CCX43, floor(16 / 6) = 2 such Pods fit per node — 12 of 16 cores claimed, 4 stranded (not quite enough for a third Pod, and unusable by anything else on the node because they're carved out of the exclusive-allocation pool).

With PodLevelResourceManagers, the app keeps its exclusive 3-core slice, but the two sidecars stop needing individually pinned ceilings — they share one pool sized for their typical simultaneous need with burst headroom, not the sum of both sidecars' individual worst cases:

Pod-level modelShared pool sizePod totalPods/node (16 vCPU)$/pod/month (€275.99, ~1.08 USD)
Today (container-level)6 CPU2$149.04
Pod-level, conservative pool2 CPU5 CPU3$99.36
Pod-level, tight pool1 CPU4 CPU4$74.52

At the conservative 2-CPU pool, a fleet running 600 of these tenant Pods needs 200 CCX43 nodes instead of 300 — 100 fewer nodes, about €27,600 (~$29,800) a month back in the infrastructure line, before anyone touches the tighter 1-CPU scenario.

Sensitivity: When This Math Stops Helping

That savings range isn't free money — it's a bet that both sidecars won't peak at the same instant for longer than the pool can absorb. The table above already shows the sensitivity: shrink the shared pool from 2 CPU to 1 and density jumps from 3 to 4 Pods/node; grow it back to 3 CPU (matching the old sidecars' summed worst-case reservations) and the pod-level total is 6 CPU again — the same density as today, with zero gain. That's the real crossover point: pod-level sharing only pays off when your sidecars' combined, simultaneous peak is genuinely smaller than the sum of their individual, defensive ceilings. Workloads where every sidecar in the Pod tends to spike together — a burst that hits the proxy and the log shipper at once — get little to nothing from this feature no matter how it's tuned.

The other variable is Topology Manager scope. pod scope evaluates NUMA alignment once for the Pod's whole declared budget; container scope still evaluates each container independently within that budget. Kubernetes' own docs describe this trade-off but — because the feature is brand-new alpha — there's no public benchmark yet quantifying how often pod scope's coarser alignment actually costs cross-NUMA latency versus container scope's finer one. Treat "preserves NUMA alignment" as mechanically true and empirically unproven at this stage, and re-test before trusting it on your own latency-sensitive workloads.

Where the Alpha Still Falls Short

A few gaps keep this from being the pod-wide resource envelope the KEP is aiming at:

  • CPU, memory, and hugepages only. No support for GPUs or other device/extended resources — a real limitation if your tenant Pods are AI-agent sandboxes bin-packed alongside a GPU allocation, since the device slice still has to be reasoned about entirely outside this mechanism.
  • No Windows Pods.
  • The manager policies are node-wide, not per-Pod. CPU Manager static and Memory Manager Static apply to the whole kubelet — every Guaranteed Pod scheduled on that node inherits pinned-core behavior, whether or not you wanted pod-level sharing for it specifically. You're opting a node in, not a workload.
  • It's alpha. No upgrade guarantees, no announced beta timeline, and a separate InPlacePodLevelResourcesVerticalScaling gate (also alpha in 1.36) is required if you want to resize that Pod-level budget without a restart.

What This Means for a Self-Hosted PaaS

None of this is a reason to skip it — it's a reason to pilot it narrowly. For a platform bin-packing tenant workloads onto owned hardware rather than autoscaling a hyperscaler fleet, every core that used to sit reserved-but-idle behind a defensive sidecar limit is a core that isn't buying anything, on hardware whose price just moved against you. Turning that on for one worker-node pool, on a representative tenant Pod shape, and watching the new resource_manager_allocations_total metric split between node-level and pod-level allocation, is a cheap way to find out whether your own sidecars behave more like the "2-CPU pool" row or the "3-CPU pool, no gain" row above — before deciding how far to lean on it.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, with the same Kubernetes primitives underneath that a feature like this is built for. Star the repo on GitHub or deploy your first app today.


Sources:

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