Skip to content

Expose services with a load balancer

View as Markdown

Give an app a public address by attaching reserved IPs to your cluster’s LB IP pool. Each Service of type LoadBalancer in the cluster then takes one of those addresses. To serve many sites from a single address instead, put an ingress controller in front of them. It routes each request to the right site by its hostname; see Set up an ingress controller with HTTPS.

  • A cluster in the Worker Joining, Worker Joined, or Ready state, and its kubeconfig set up for kubectl. See Connect to a Kubernetes cluster.
  • At least one public reserved IP in the same project and region, with Status Available. See Reserve an IP. A reserved IP is billed every month from when you reserve it until you delete it, whether it is attached or detached. For the rate, see E2E’s pricing page.
  • Permission to update Kubernetes and to create reserved IPs in the project. To detach an address later, you need permission to update Kubernetes and to delete reserved IPs.

Kubernetes has three Service types, and each one is reachable from a different place:

Type Reachable from Use it for
ClusterIP Inside the cluster only Internal APIs, databases, and anything other pods call. This is the default.
NodePort Networks that can route to the nodes’ addresses in your VPC Quick tests. It is rarely the right entry point for production.
LoadBalancer A public reserved IP that you attached to the cluster Public entry points, alone or behind an ingress controller

A ClusterIP Service gets a stable internal address and a DNS name. It is reachable from inside the cluster only.

apiVersion: v1
kind: Service
metadata:
name: api
spec:
type: ClusterIP
selector:
app: api
ports:
- port: 80
targetPort: 8080

Other pods reach it as api in the same namespace, and as api.<namespace>.svc.cluster.local from other namespaces. To test it from inside the cluster, and to reach it from your computer while it stays private, run:

kubectl get svc api
kubectl run -it --rm curl --image=curlimages/curl --restart=Never -- curl -s http://api
kubectl port-forward svc/api 8080:80

A NodePort Service opens the same port, from 30000 to 32767, on every node and forwards it to the Service.

apiVersion: v1
kind: Service
metadata:
name: api
spec:
type: NodePort
selector:
app: api
ports:
- port: 80
targetPort: 8080
nodePort: 30080

It answers at <node-ip>:30080, where <node-ip> is a node’s address in your VPC. Only networks that can route to the nodes’ VPC addresses can reach it. For production, use a LoadBalancer Service instead. The NodePort port is high and unusual, and each client sends its traffic to the one node it addresses. A node can also be removed when its pool is resized.

A new cluster’s LB IP pool is empty, and a LoadBalancer Service waits until it has an address to take. The pool takes public reserved IPs only.

  1. In Kubernetes, select the cluster’s name to open Kubernetes Cluster Details, and select the LB IP Pool tab. The addresses already in the pool are under Attached IPs.
  2. Select Add Reserve IP. You can do this once the cluster reaches Worker Joining.
  3. Under Reserve IPs, choose one or more of the project’s Available reserved IPs. If there is none, select Click here to reserve a new one.
  4. Select Add Reserve IP.

The addresses join the pool under Attached IPs.

You can also attach an address from the Reserve IP page: choose Kubernetes Cluster in Attach IP. See Attach a reserved IP.

With at least one free address in the pool, create a Service of type LoadBalancer. Save this as service.yaml, with your own app’s label under selector and the port your app listens on as the target port:

apiVersion: v1
kind: Service
metadata:
name: web
spec:
type: LoadBalancer
selector:
app: web
ports:
- port: 80
targetPort: 8080

Apply it and watch it take an address:

kubectl apply -f service.yaml
kubectl get svc web -w

EXTERNAL-IP changes from <pending> to one of the addresses you attached. Check that the app answers on it, where <external-ip> is that address:

curl http://<external-ip>

Each LoadBalancer Service takes its own address from the pool. When every attached address is already taken, a new Service stays at <pending> until you attach another IP or delete a Service that holds one.

Deleting a LoadBalancer Service frees its address for another Service in the same cluster. To give the address back to the project, detach it from the pool:

  1. Find any Service that still uses the address, where <ip-address> is the address:

    kubectl get svc --all-namespaces -o wide | grep <ip-address>
  2. Delete that Service. TIR refuses to detach an address while a Service still uses it, and the refusal names the Service.

  3. On the LB IP Pool tab, select Detach IP next to the address, and confirm in Detach Reserve IP.

The address goes back to the project’s reserved IPs, ready for another cluster or resource, and keeps its monthly charge until you delete it. See Delete a reserved IP.

Your app now answers on a public address of its own. Once you run more than one or two public sites, set up an ingress controller so that they share one address and get HTTPS. For an address that stays at <pending> or will not detach, see Troubleshooting TIR Kubernetes.