Expose an app with a LoadBalancer Service
A type: LoadBalancer Service places one application on the internet on any TCP-based protocol. The Hetzner cloud-controller-manager (CCM) watches for these Services, creates a Hetzner load balancer for each one, and writes its public address back to the Service.
Note
Hetzner load balancers carry TCP only (they also serve HTTP and HTTPS, which run on TCP). UDP is not supported: a Service with a UDP port receives no working load balancer. To expose a UDP service, use a NodePort on the nodes directly and manage the address and failover yourself. See UDP is not supported .
Prerequisites#
- kubectl access to the workload cluster.
- At least one workload pod running behind the Service.
Create the Service#
Set type: LoadBalancer and point the selector at your pods. The two annotations specify where the CCM places the load balancer.
apiVersion: v1
kind: Service
metadata:
name: my-app
annotations:
load-balancer.hetzner.cloud/location: fsn1
load-balancer.hetzner.cloud/name: my-app-lb
spec:
type: LoadBalancer
selector:
app: my-app
ports:
- name: http
port: 80
targetPort: 8080
Apply it and read the address the CCM assigns:
$ kubectl apply -f service.yaml
$ kubectl get svc my-app
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-app LoadBalancer 100.96.42.17 <pending> 80:31820/TCP 5s
EXTERNAL-IP shows <pending> until the CCM finishes creating the load balancer, usually within a minute. Read the assigned IPv4 address once it appears:
$ kubectl get svc my-app -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
The load balancer also receives a public IPv6 address. Both appear in status.loadBalancer.ingress. To serve IPv6 clients, publish an AAAA record for the IPv6 address. See Serve IPv6 clients .
The Service only routes to ready pods#
A type: LoadBalancer Service forwards to a pod only while that pod reports Ready, so configure a readiness probe for the pod. Without one, Kubernetes considers the pod ready as soon as its container starts, and the load balancer sends live traffic before the application can serve it. This matters more here than on a static fleet, because the platform drains and replaces nodes during upgrades and self-healing, so pods are created and removed more frequently. Run a production-ready workload covers the probe together with the replica count and PodDisruptionBudget the application needs.
Warning
The CCM reaches your pods through a NodePort, a port opened on every node. Cilium forwards NodePort traffic in eBPF before the host firewall processes it, so the assigned NodePort responds on every node's public IP from anywhere on the internet. Treat the NodePort as public: rely on the Service's own authentication or a network policy , not on the firewall, to protect it.
Next#
- Configure a Hetzner load balancer for the full annotation reference: type, algorithm, and health checks.
- Preserve the client source IP so the pod sees the caller's address instead of the load balancer's.
- Choose how to expose an application to weigh this against an ingress controller once you expose more than one application.
Choose how to expose an application
Decide between a LoadBalancer Service, an ingress controller, or a NodePort before you make a workload reachable from the internet.
Install Traefik for HTTP routing
Place many HTTP applications behind one Hetzner load balancer with Traefik. The platform includes no ingress controller, so you install the one you prefer.