# Use Load Balancers and Ingress

Kubernetes spans an internal network across all nodes automatically. Your pods and containers are reachable through a private IP address inside the cluster. To route traffic from the outside, you need in most cases a load balancer and optionally an ingress.

> [!TIP]
> For new deployments, use [Gateway API with Traefik]({{< ref "/product/compute/containers/how-to/gateway-api/" >}}), which is the recommended, modern approach. This guide focuses on the classic LoadBalancer + Ingress setup.

## Prerequisites

As a prerequisite for the following documentation, you need:

- An Exoscale SKS cluster
- Access to your cluster via kubectl
- Basic Linux knowledge

If you don't have access to a SKS cluster, follow the [Quick Start Guide]({{< ref "/product/compute/containers/quick-start/">}}). 

> [!NOTE]
> For Network Load Balancers (or NLBs) to be created automatically, you need
> the [Exoscale Cloud Controller
> Manager](https://github.com/exoscale/exoscale-cloud-controller-manager) (or
> CCM) add-on installed in your cluster. By default, the CCM should come
> automatically installed. In the CCM documentation, you can also find the
> [reference on service
> annotations](https://github.com/exoscale/exoscale-cloud-controller-manager/blob/master/docs/service-loadbalancer.md#annotations)
> which you might need for your service or ingress configuration.


## Exposing a Single Service with an Exoscale Load Balancer

To expose a single pod or a deployment, you need to create a service of type `LoadBalancer`.

The following example creates a deployment named `hello`, which consists of 3 hello-world pods/containers. This could be your web application:

```bash
kubectl create deployment hello --image=nginxdemos/hello:plain-text --replicas=3
```

As there is no service yet defined, you can only access the webpage of the containers via the respective internal IP inside the cluster. Use `kubectl get pods -o wide`, if you want to take a look at the created pods.

To allow access from outside the cluster, you can expose the deployment with a load balancer:

```bash
kubectl expose deployment hello --port=80 --target-port=80 --name=hello-service --type=LoadBalancer
```

This will create a service named `hello-service`, exposing the deployment `hello`. `--port` specifies the port on where the LoadBalancer should listen, which should be port 80 in most cases. `--target-port` specifies the target port that the container exposes or provides.

As we specified the flag `--type=LoadBalancer`, Kubernetes will talk over the CCM to Exoscale, and create a Network Load Balancer (or NLB). To get its IP address, you can use `kubectl get svc`:

```bash
kubectl get svc -n default
```

Output:

```bash
NAME            TYPE           CLUSTER-IP       EXTERNAL-IP       PORT(S)        AGE
hello-service   LoadBalancer   10.102.248.197   194.182.170.158   80:30665/TCP   2m3s
kubernetes      ClusterIP      10.96.0.1        <none>            443/TCP        52m
```

You can also see your load balancer in the Portal under `Compute` in the main navigation under the Load Balancers section, or use the CLI with the `exo compute load-balancer` and `exo compute load-balancer show` commands.

In this case, the web application would be now reachable via `194.182.170.158`. The service `LoadBalancer` will automatically route and balance traffic to your pods.

> [!NOTE]
> Deleting a cluster will not always delete the Network Load Balancers created
> by a Kubernetes service. Make sure to check your load balancers in the Portal
> or CLI after deleting a SKS cluster, and delete the load balancers if
> necessary. When the annotation `exoscale-loadbalancer-external` is set to
> true (see the example below), the load balancer will never be automatically
> deleted.


You can also specify this setup as a manifest, where you can define additional annotations for the `LoadBalancer` service:

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: hello
  name: hello
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello
  template:
    metadata:
      labels:
        app: hello
    spec:
      containers:
      - image: nginxdemos/hello:plain-text
        name: hello
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: hello
  name: hello-service
  annotations:
    # Uncomment if you want to use an already existing Exoscale LoadBalancer
    #service.beta.kubernetes.io/exoscale-loadbalancer-id: "09191de9-513b-4270-a44c-5aad8354bb47"
    #service.beta.kubernetes.io/exoscale-loadbalancer-external: "true"
    # When having multiple Nodepools attached to your SKS Cluster,
    # you need to specify then the ID of the underlying Instance Pool the NLB should forward traffic to
    #service.beta.kubernetes.io/exoscale-loadbalancer-service-instancepool-id: "F0D7A23E-14B8-4A6E-A134-1BFD0DF9A068"
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: hello
  type: LoadBalancer
```

You can find all annotations for the `LoadBalancer` service in the [Exoscale CCM](https://github.com/exoscale/exoscale-cloud-controller-manager/blob/master/docs/service-loadbalancer.md#annotations) documentation.

> [!NOTE]
> A Network Load Balancer can only route to one instance pool. If you have
> multiple nodepools attached to an SKS cluster, define which instance pool
> (underlying one of the attached nodepools) to route to via an annotation.
> Also, when a Kubernetes service points to a scheduled pod outside of its
> attached instance pool, it can still be reached, as Kubernetes automatically
> handles internal routing. However, this extra hop might result in less
> efficient traffic balancing.

> [!IMPORTANT]
> NLB backends are currently only available through classic SKS
> Nodepool. Nodes provisioned by
> [Karpenter]({{< ref "/product/compute/containers/how-to/karpenter/" >}}) are
> **not** eligible and therefore **cannot serve as NLB
> backends**. If you use Karpenter, make sure your `LoadBalancer`-type Services
> and Ingress/Gateway controllers are scheduled on a classic SKS Nodepool (for
> example, using node selectors or affinities).


![](routing-1.png "Routing with a LoadBalancer Service")

When you have multiple services or websites, you can either create additional Network Load Balancers, or use an ingress in addition to the LB-service as explained in the next chapter.

## Using an Ingress to Expose Multiple Websites

A Network Load Balancer only provides layer 4 (TCP) routing and cannot distinguish between different hostnames or website paths. To distinguish between the two, we need to enable layer 7 routing, we can point the NLB to a reverse proxy (or ingress) which is deployed on an arbitrary amount of nodes.

For defining routes, you create ingress resources. However, they have no effect without an **ingress controller**, which is a deployed reverse proxy. The next section explains how to install the Traefik Ingress Controller.

> [!NOTE]
> There are multiple solutions for ingress controllers available. Check the
> [Kubernetes
> documentation](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/)
> for more information.


The next figure shows how the routing works with a combination of Network Load Balancer, Ingress Controller and deployments for different websites:

![](routing-2.png "Routing using an Ingress")

> [!NOTE]
> Are you looking for general information on ingress and ingress configuration?

Take a look at the __free__ [Certified Container Engineer Course](https://academy.exoscale.com/courses/course-v1:Exoscale+CCE300+R2025/about) in our online academy.


### Deploying Traefik Ingress Controller

[Traefik](https://traefik.io/traefik/) is a modern cloud-native ingress controller that supports both traditional Kubernetes Ingress resources and the newer Gateway API. Install it using Helm:

```bash
helm repo add traefik https://traefik.github.io/charts
helm repo update
helm install traefik traefik/traefik --namespace traefik --create-namespace
```

This creates a Deployment running the Traefik proxy and a `LoadBalancer` service that provisions an Exoscale Network Load Balancer through the CCM.

> [!NOTE]
> You can customize the installation with Helm values. For example, to run
> Traefik as a DaemonSet on all nodes, use `--set deployment.kind=DaemonSet`.
> See the [Traefik Helm chart documentation](https://github.com/traefik/traefik-helm-chart)
> for all available options.

Wait until the Traefik pods have started:

```bash
kubectl get pods -n traefik -l app.kubernetes.io/name=traefik --watch
```

It should show *Running* after a while:

```bash
traefik-7d9f4b8c5-xk2mn   1/1     Running   0          45s
```

Use ctrl+c on your keyboard to cancel the `--watch` command.

You are now ready to deploy ingress configurations. To get the IP of the `LoadBalancer`, you can use `kubectl get svc -n traefik traefik` or check your load balancer information using the Portal or CLI.

### Creating Ingress Rules

You can now use an ingress rule to connect your applications with the Load Balancer.
Below is an example ingress rule. Save it to a file and apply it with `kubectl apply -f rules.yaml`:

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-myapp
spec:
  ingressClassName: traefik
  rules:
  - http:
      paths:
      - path: /app1
        pathType: Prefix
        backend:
          service:
            name: hello
            port:
              number: 80
      - path: /app2
        pathType: Prefix
        backend:
          service:
            name: other-service
            port:
              number: 80
```

The example will route the path `/app1` and `/app2` to different backend services.

Note that the ingress must point to an internal ClusterIP service. For running this example, you can take the example deployment in the first section, and change `type: LoadBalancer` to `type: ClusterIP`. The address `http://YOURLOADBALANCERIP/app1` will then route to the `hello` deployment.

You can also specify different hosts, either in the same ingress ruleset or via different manifests as in the following example:

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-myapp
spec:
  ingressClassName: traefik
  rules:
  - host: "example.com"
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: hello
            port:
              number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-otherapp
spec:
  ingressClassName: traefik
  rules:
  - host: "example-otherapp.com"
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: other-service
            port:
              number: 80
```

Use `kubectl get ingress` to list the rulesets that have been created. This command will also list the external IP address of the `LoadBalancer`.

The next figure shows how the objects or services of the whole configuration are interrelated:

![](routing-2-config.png "Link of configurations using Ingress")

## See Also

* For modern Kubernetes-native routing, see [Gateway API with Traefik]({{< ref "/product/compute/containers/how-to/gateway-api/" >}})
* [Traefik documentation](https://doc.traefik.io/traefik/)
* [Traefik Kubernetes Ingress documentation](https://doc.traefik.io/traefik/providers/kubernetes-ingress/)
* The Exoscale CCM documentation on [integrating with Network Load Balancers](https://github.com/exoscale/exoscale-cloud-controller-manager/blob/master/docs/service-loadbalancer.md)
* A breakdown of [Exoscale Network Load Balancers]({{< ref "/product/networking/nlb/">}})
* The Kubernetes documentation for [LoadBalancer](https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer)
* The Kubernetes documentation for [Kubernetes Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/)

