Skip to main content

Size a cluster for cost

Inspect 1.36

Running your own nodes on Hetzner is already far cheaper than a hyperscaler (a large cloud like AWS, Azure, or Google Cloud). Sizing them well captures the rest of the saving. The method is one idea: match each part of your load to the cheapest machine that fits it.

run the same Syself Linux node image and join a cluster the same way. The cost decision comes down to how each backend bills, not any difference in what the node runs.

The cost model: bare metal vs cloud#

The two differ in what you get and how you pay. A bare-metal server gives you dedicated CPU cores, local NVMe storage, and a dedicated network link with unlimited traffic, all for a flat monthly price. On cloud you rent by the hour, dedicated vCPU costs extra, and both bandwidth and traffic are capped. For steady load, bare metal usually lands around 60% cheaper than the equivalent cloud servers.

Bare metal Cloud
Billing shape Flat monthly per server you own Per VM, per hour it runs
CPU Dedicated cores, included Shared by default; dedicated vCPU costs extra
Best for Steady, always-on load Elastic, bursty, unpredictable load
Storage Local NVMe included, survives reprovision Network volumes, billed per GB
Network Dedicated 1G, upgradeable to 10G, unlimited traffic Around 300 to 500 Mbps, 20 TB included, then per TB
Getting one Minutes to add from your pool, hours for a new server to be delivered Ready in minutes, scale to zero when idle

Bare metal gives you the most compute per euro for load that runs all the time, plus the storage and network to run databases on it. Cloud gives you servers in minutes and the freedom to pay nothing for capacity you are not using. The main catch with bare metal: a new server takes a few hours to be delivered, so plan a little headroom instead of buying reactively.

Fixed baseline plus elastic burst#

The most cost-effective layout for most teams is a bare-metal baseline for the load that never goes away, plus a cloud pool on the autoscaler for the peaks. The autoscaler adds cloud nodes when pods need them and removes them when idle.

yaml
		workers:
  machineDeployments:
    - class: workeramd64baremetal
      name: md-base
      replicas: 3
    - class: workeramd64hcloud
      name: md-burst
      metadata:
        annotations:
          cluster.x-k8s.io/cluster-api-autoscaler-node-group-min-size: "0"
          cluster.x-k8s.io/cluster-api-autoscaler-node-group-max-size: "10"
	

You pay a flat rate for the base and pay for cloud only during spikes. See for how to steer workloads across the two.

Local NVMe vs network storage#

Bare-metal local NVMe (fast disks attached straight to the server) carries no per-gigabyte network charge and no network hop, and it survives a reprovision. That makes it the cheap, fast home for databases and other stateful services.

Use cloud network volumes only when you need storage that detaches from one node and reattaches to another. Put stateful workloads on the bare-metal base, for both cost and performance.

Match the machine line to the load#

Hetzner Cloud has three server lines, and each fits a different load:

  • CPX (regular, shared vCPU): the balanced default. cpx32 (4 vCPU, 8 GB RAM) is the starting worker type. For memory-heavy workloads, step up to cpx41 (8 vCPU, 16 GB) or cpx51 (16 vCPU, 32 GB) before you add more, smaller nodes.
  • CCX (dedicated vCPU): for steady, latency-sensitive load. Use CCX, or bare metal, for control-plane nodes (the nodes running the Kubernetes API and etcd) and any service that needs consistent CPU.
  • CX (cost-optimized, shared vCPU): the cheapest line, on older hardware with variable performance and limited availability. Good for a spiky, tolerant burst pool on the autoscaler, but never for control planes or latency-sensitive services.

Own the bare-metal servers for reserved, always-on load: a machine you keep busy is cheaper owned than rented.

GPU workloads run on bare metal only, on Hetzner's GEX line or an auction server with a supported NVIDIA card. There is no HCloud GPU type, so a GPU pool cannot burst to cloud the way a CPU pool can.

For the machine types you can pick from, see .

Right-size against allocatable, not the spec#

A node's schedulable capacity is less than its raw size. kubectl describe node shows two numbers: Capacity (the raw hardware) and Allocatable (what the scheduler may actually hand to pods). Allocatable is always lower, because:

text
		Allocatable = Capacity - systemReserved - evictionHard
	

The kubelet (the Kubernetes agent on each node) sets aside systemReserved for the OS and system daemons, and reserves further headroom (evictionHard) so a pod burst cannot starve the node. Size pools against allocatable, not the spec sheet. Otherwise you pay for headroom you cannot schedule into, or you pack pods until the kubelet starts evicting them.

The reserved slice grows with node size, but shrinks as a percentage of total RAM. See for the reservation numbers by node size.

A cluster-wide pod cap also bounds how many workloads fit per node. Syself sets maxPods: 220, and the kubelet refuses the 221st pod. On a small node, you run out of memory long before you hit that cap. For right-sizing, allocatable memory is almost always the binding constraint, not the pod count.

Tip

Plan workload capacity a little under 220 pods per node. A few slots go to per-node system pods that Syself runs on every node.

See the for the eviction thresholds too.

Feed the allocatable numbers, not the spec-sheet numbers, into the comparison below.

Work the comparison#

Hetzner prices change, so do the arithmetic with current numbers, not a figure baked into a doc.

Price the steady load as owned bare metal #

Take your steady CPU and memory demand. Price it as owned bare-metal servers, at their flat monthly rate.

Price the burst as hourly cloud #

Take your peak-minus-steady demand. Price it as cloud VMs that run only for the hours you need them.

Compare against all-cloud #

Add the two. Compare the total against the single elastic-cloud bill you would pay to serve the whole peak on rented VMs all month.

Pull the per-machine prices from the Hetzner Cloud pricing page and the Robot server list. The saving comes from two places: a flat, low rate for the base instead of a metered hyperscaler rate, and nothing paid for burst capacity between spikes.

Once the layout is set, wire the burst pool to the so it adds and removes cloud nodes for you. The same method applies pool by pool if you , and the business case for the switch is in .