Skip to main content

Expose an app with a LoadBalancer Service

Inspect 1.36

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 .

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.

service.yamlyaml
		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 .

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. 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 , not on the firewall, to protect it.

Next#