Skip to main content

Load balance to bare-metal nodes

Inspect 1.36

A Hetzner load balancer serves bare-metal (Robot) nodes exactly like Cloud nodes. The CCM handles both natively, so there is nothing extra to do for bare metal. Point a type: LoadBalancer Service at a pool that includes Robot servers and it works. The rest of this page explains how the CCM adds each kind and how the standard settings behave, so a mixed pool is easy to reason about.

Server targets and IP targets#

The CCM adds the two machine kinds as different target types:

  • Hetzner Cloud nodes join as server targets. The CCM references the cloud server by its ID, and Hetzner tracks it.
  • Bare-metal (Robot) nodes join as IP targets. The CCM adds the Robot server by its public IPv4 address.

Both back the same type: LoadBalancer Service, so a mixed pool of Cloud and Robot nodes works, and the load balancer spreads traffic across both.

Bare-metal targets stay IPv4#

By default the platform pins bare-metal targets to IPv4, with HCLOUD_LOAD_BALANCERS_ROBOT_TARGET_ADDRESS_FAMILY=ipv4 on the CCM. This is the same reason the rest of the cluster runs IPv4: nodes have no usable IPv6, so an IPv6 target would never pass its health check and would remain permanently unhealthy. On an IPv4-only cluster the default is the correct setting, and you leave it unchanged.

You can set the family per Service with the robot-target-address-family annotation:

yaml
		metadata:
  annotations:
    load-balancer.hetzner.cloud/robot-target-address-family: ipv4
	

externalTrafficPolicy on a bare-metal pool#

Prefer externalTrafficPolicy: Local. It removes a hop and keeps the load balancer's health check tied to a locally ready pod. It does not recover the client's source address: the Hetzner load balancer already replaced the source with its own IP before the packet reaches any node, so neither policy shows the pod the real client. Use PROXY protocol for that.

  • Local (preferred): the load balancer sends each request directly to a node that runs a ready pod, with no extra node-to-node hop and no in-cluster source-NAT. The load balancer health-checks each node and holds only the nodes that run a pod in the pool. For an ingress setup, those are the nodes running the reverse-proxy pod.
  • Cluster: any target node accepts the request and may forward it to a pod on another node, which adds a hop and source-NATs the packet inside the cluster. Either way the pod sees the load balancer's address, not the client's.

Local means a targeted node must run a ready pod, or it drops out of the pool. On a fixed set of Robot servers, place the workload so that every server that should take traffic runs a pod. See for how to recover the real client address, and for the pool churn.

For why you would run bare metal at all, and how to add it, see and .