--- title: "Expose a service with a load balancer" description: "Attach public service IPs to an E2E Kubernetes cluster's LB IP Pool, and expose an application on one with a LoadBalancer service." content_type: "how-to" product: "kubernetes" platform: "MyAccount" url: "/myaccount/kubernetes/how-to/expose-a-service-with-a-load-balancer/" topic: ["networking-and-storage"] relationships: {"requires":["/myaccount/kubernetes/how-to/connect-with-kubectl/","/myaccount/reserved-ip/how-to/reserve-an-ip/"]} --- # Expose a service with a load balancer 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. ## Before you begin - Your cluster reads **Running**. - kubectl is connected to the cluster. See [Connect to a cluster with kubectl](/myaccount/kubernetes/how-to/connect-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](/myaccount/reserved-ip/how-to/reserve-an-ip/). ## Attach a public service IP to the pool 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](#optional-give-a-service-a-private-lb-ip). ## Create the LoadBalancer service 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: ```yaml 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: ```bash kubectl apply -f hello-service.yaml kubectl get service hello --watch ``` kubectl prints a new line each time the service changes. It shows `` first, then the IP from the pool: ```text NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE hello LoadBalancer 80:/TCP 2s hello LoadBalancer 80:/TCP 5s ``` ## Check the external IP When `EXTERNAL-IP` changes from `` 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: ```bash curl http:// ``` Here `` is the address in `EXTERNAL-IP`. If the address stays on ``, see [A LoadBalancer service stays on pending](/myaccount/kubernetes/troubleshooting/kubernetes/#a-loadbalancer-service-stays-on-pending). ## Stop using a public service IP 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](/myaccount/reserved-ip/how-to/delete-a-reserved-ip/). For current prices, see [E2E's pricing page](https://www.e2enetworks.com/pricing). ## (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: ```yaml 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. ## (Optional) Give a service a private LB IP 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](/myaccount/kubernetes/how-to/manage-security-groups/#know-the-groups-a-cluster-carries). 2. To give a service that address, name it in the service's MetalLB annotation, as in this example. Replace `` with the address you reserved: ```yaml apiVersion: v1 kind: Service metadata: name: hello-private annotations: metallb.universe.tf/loadBalancerIPs: 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: ```bash 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://`: it returns the NGINX welcome page. To make a node there, see [Create a node](/myaccount/node/how-to/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 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: ```bash 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](/myaccount/kubernetes/how-to/install-marketplace-apps/). 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: ```bash kubectl annotate service ingress-nginx-controller --namespace ingress-nginx metallb.universe.tf/loadBalancerIPs= ``` 3. Find the controller's address, the `EXTERNAL-IP` of `ingress-nginx-controller`: ```bash 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: ```yaml 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: ```bash kubectl apply -f hello-ingress.yaml curl -H "Host: hello.example.com" http:// ``` Here `` 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 ``. For TLS certificates on those host names, install **Cert-Manager** from the same tab; see [cert-manager](https://cert-manager.io/docs/) for how to request certificates. The [Gateway API](https://gateway-api.sigs.k8s.io/) 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](https://gateway-api.sigs.k8s.io/guides/) 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](/myaccount/kubernetes/how-to/manage-security-groups/).