Skip to content

Expose a service with a load balancer

View as Markdown

To reach an application from the internet, expose it with a Kubernetes service of type LoadBalancer. The service gets its external IP from the cluster’s LB IP Pool: the public service IPs you attach to the cluster in MyAccount, and any private LB IPs you reserve for traffic inside the VPC. MetalLB, which runs in the cluster, gives each LoadBalancer service one IP of its own from the pool, so attach one IP for each LoadBalancer service you run.

  • Your cluster reads Running.
  • kubectl is connected to the cluster. See Connect to a cluster with kubectl.
  • You have an application running in the cluster. The examples expose a Deployment named hello that runs the NGINX web server. To create it, run kubectl create deployment hello --image=nginx --replicas=2.
  • For a public service IP, you have room under your account’s reserved-IP limit and enough infra credits. A public service IP counts toward the limit. See Reserve an IP.
  1. In MyAccount, under Compute, click Kubernetes. In the cluster’s row actions, click Add LB IP, which opens the cluster on its LB IP Pool tab. Or open the cluster and click the LB IP Pool tab.
  2. If the tab says Currently no reserved Service IP is available, you hold no public service IP yet. Click Click here to reserve one. With too few infra credits, Low Infra Credits opens with a link to buy more.
  3. If the tab says Currently no Service IP attached, click Click here. Attach Public IP opens.
  4. Under Select IP, choose a reserved IP that is not attached to anything else.
  5. Click Attach IP.

The IP is listed under Service IP on the LB IP Pool tab. For each further LoadBalancer service, attach another public service IP. A service without the MetalLB annotation takes any free IP in the pool, private LB IPs included; to be sure a service gets a public address, name the public service IP in its annotation, as in Give a service a private LB IP.

Write the service as a manifest. This example sends traffic on port 80 to port 80 of the pods of the hello Deployment, which carry the label app: hello. For your own application, change the name, the label, and the ports:

apiVersion: v1
kind: Service
metadata:
name: hello
spec:
type: LoadBalancer
selector:
app: hello
ports:
- protocol: TCP
port: 80
targetPort: 80

Save it as hello-service.yaml, apply it, and watch the service:

kubectl apply -f hello-service.yaml
kubectl get service hello --watch

kubectl prints a new line each time the service changes. It shows <pending> first, then the IP from the pool:

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello LoadBalancer <cluster-ip> <pending> 80:<port>/TCP 2s
hello LoadBalancer <cluster-ip> <external-ip> 80:<port>/TCP 5s

When EXTERNAL-IP changes from <pending> to one of the IPs on the LB IP Pool tab, press Ctrl+C. Your application answers on that address, on the service’s port:

curl http://<external-ip>

Here <external-ip> is the address in EXTERNAL-IP. If the address stays on <pending>, see A LoadBalancer service stays on pending.

On the LB IP Pool tab, click Detach IP next to the IP. Detach Public IP asks you to confirm; click Detach IP.

A detached IP stays reserved in your account. Public service IPs are billed from when you reserve them until you release them, including after the cluster that used them is deleted. To stop the charge, release it; see Delete a reserved IP. For current prices, see E2E’s pricing page.

(Optional) Limit who can reach the service

Section titled “(Optional) Limit who can reach the service”

Security groups control traffic to the nodes’ own IPs. A LoadBalancer service on a public service IP is reachable on every port it exposes, so to limit who reaches it, list the address ranges it accepts under the service’s source ranges, as in this example:

apiVersion: v1
kind: Service
metadata:
name: hello
spec:
type: LoadBalancer
loadBalancerSourceRanges:
- 203.0.113.0/24
selector:
app: hello
ports:
- protocol: TCP
port: 80
targetPort: 80

Replace 203.0.113.0/24 with your own ranges, one per line, and apply the file again. Requests from the listed ranges reach the service; requests from anywhere else get no answer.

For an application that only machines inside your VPC call, such as an internal API that other nodes in your VPC use, give its service a private LB IP. Private LB IPs are free, and reachable only inside the VPC.

  1. On the LB IP Pool tab, under Private LB IP Pool, click Reserve Private IP. It takes the next free address in the cluster’s VPC, or in its subnet, and adds it to the pool. E2E also adds a security group for that IP to the cluster; see Manage a cluster’s security groups.

  2. To give a service that address, name it in the service’s MetalLB annotation, as in this example. Replace <private-lb-ip> with the address you reserved:

    apiVersion: v1
    kind: Service
    metadata:
    name: hello-private
    annotations:
    metallb.universe.tf/loadBalancerIPs: <private-lb-ip>
    spec:
    type: LoadBalancer
    selector:
    app: hello
    ports:
    - protocol: TCP
    port: 80
    targetPort: 80
  3. Save it as hello-private.yaml, apply it, and check the service:

    kubectl apply -f hello-private.yaml
    kubectl get service hello-private

EXTERNAL-IP shows the private LB IP. From a node in the same VPC, run curl http://<private-lb-ip>: it returns the NGINX welcome page. To make a node there, see Create a node. The same annotation gives a service one particular public service IP from the pool.

A cluster holds up to 4 private LB IPs; a fifth is refused with Maximum of 4 private LB IPs reached. To give one back, click Release IP next to it. A cluster’s private LB IPs are released when the cluster is deleted.

(Optional) Route several applications through one IP

Section titled “(Optional) Route several applications through one IP”

Each LoadBalancer service takes one IP from the pool. To serve several applications from one address, run an ingress controller, which takes traffic coming into the cluster and routes it by host name. The controller has one LoadBalancer service, and your applications sit behind it as ClusterIP services, which are reachable only inside the cluster.

  1. Free an IP for the controller. If every IP in the pool is in use, attach another public service IP, or make hello a ClusterIP service first so its IP returns to the pool:

    kubectl delete service hello
    kubectl expose deployment hello --port=80
  2. Install the NGINX Ingress Controller from the cluster’s Marketplace tab; see Install marketplace apps on a cluster. Its service takes any free IP from the pool, private LB IPs included. If the pool holds a free private LB IP, name your public service IP in the controller’s MetalLB annotation:

    kubectl annotate service ingress-nginx-controller --namespace ingress-nginx metallb.universe.tf/loadBalancerIPs=<public-service-ip>
  3. Find the controller’s address, the EXTERNAL-IP of ingress-nginx-controller:

    kubectl get service ingress-nginx-controller --namespace ingress-nginx
  4. Write an Ingress that sends requests for one host name to the service. Replace hello.example.com with your own host name:

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
    name: hello
    spec:
    ingressClassName: nginx
    rules:
    - host: hello.example.com
    http:
    paths:
    - path: /
    pathType: Prefix
    backend:
    service:
    name: hello
    port:
    number: 80
  5. Save it as hello-ingress.yaml, apply it, and send a request with that host name:

    kubectl apply -f hello-ingress.yaml
    curl -H "Host: hello.example.com" http://<ingress-ip>

    Here <ingress-ip> is the controller’s EXTERNAL-IP. The NGINX welcome page is returned. Requests for any other host name get 404.

To add another application, add a rule with its host name and service. When the routing works, point your domain’s DNS A record for each host name at <ingress-ip>. For TLS certificates on those host names, install Cert-Manager from the same tab; see cert-manager for how to request certificates. The Gateway API is a newer way to route the same traffic. Its resources are not installed on a new cluster, so install its CRDs first; see Getting started in the Gateway API docs.

Your application is reachable on its IP from the cluster’s LB IP Pool. To control traffic to the nodes themselves, see Manage a cluster’s security groups.