Skip to content

Kubernetes LoadBalancer and Octavia

When you expose a Kubernetes application to the internet, you typically use a Service of type: LoadBalancer. On our platform, this automatically creates a real Octavia Load Balancer in your OpenStack project. Understanding this integration is key to troubleshooting networking issues and managing quota.

How it works

When you create a Service with type: LoadBalancer in your shoot cluster, the cloud-controller-manager — running in the shoot's control plane on the Seed — provisions an Octavia load balancer in OpenStack and wires it to your worker nodes.

%%{init: {'themeVariables': {'textColor': '#333333', 'edgeLabelBackground': '#ffffff', 'clusterBkg': 'transparent', 'clusterBorder': '#999999'}}}%%
flowchart LR
    Internet["Internet"]
    FIP["Floating IP<br>(public network)"]

    subgraph OS ["OpenStack"]
        LB["Octavia Load Balancer<br>Listener · Pool · Health Monitor"]
    end

    subgraph K8s ["Kubernetes Shoot Cluster"]
        W1["Worker Node 1"]
        W2["Worker Node 2"]
        Pod1["Pod: nginx"]
        Pod2["Pod: nginx"]
    end

    Internet --> FIP
    FIP --> LB
    LB --> W1
    LB --> W2
    W1 --> Pod1
    W2 --> Pod2

    classDef ext fill:#fce4ec,stroke:#c62828,color:#b71c1c;
    classDef fip fill:#e3f2fd,stroke:#1565c0,color:#0d47a1;
    classDef lb fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20;
    classDef worker fill:#fff3e0,stroke:#e65100,color:#bf360c;
    classDef pod fill:#f3e5f5,stroke:#6a1b9a,color:#4a148c;
    class Internet ext;
    class FIP fip;
    class LB lb;
    class W1,W2 worker;
    class Pod1,Pod2 pod;
    linkStyle 0,1 stroke:#c62828,stroke-width:2px;
    linkStyle 2,3 stroke:#2e7d32,stroke-width:2px;
    linkStyle 4,5 stroke:#e65100,stroke-width:2px;
%%{init: {'themeVariables': {'textColor': '#e0e0e0', 'edgeLabelBackground': '#2a2a2a', 'clusterBkg': 'transparent', 'clusterBorder': '#666666'}}}%%
flowchart LR
    Internet["Internet"]
    FIP["Floating IP<br>(public network)"]

    subgraph OS ["OpenStack"]
        LB["Octavia Load Balancer<br>Listener · Pool · Health Monitor"]
    end

    subgraph K8s ["Kubernetes Shoot Cluster"]
        W1["Worker Node 1"]
        W2["Worker Node 2"]
        Pod1["Pod: nginx"]
        Pod2["Pod: nginx"]
    end

    Internet --> FIP
    FIP --> LB
    LB --> W1
    LB --> W2
    W1 --> Pod1
    W2 --> Pod2

    classDef ext fill:#3e1e1e,stroke:#ef5350,color:#ffcdd2;
    classDef fip fill:#1a3a5c,stroke:#64b5f6,color:#e3f2fd;
    classDef lb fill:#1b3a2a,stroke:#66bb6a,color:#c8e6c9;
    classDef worker fill:#3e2723,stroke:#ff9800,color:#ffe0b2;
    classDef pod fill:#2a1a3a,stroke:#ab47bc,color:#e1bee7;
    class Internet ext;
    class FIP fip;
    class LB lb;
    class W1,W2 worker;
    class Pod1,Pod2 pod;
    linkStyle 0,1 stroke:#ef5350,stroke-width:2px;
    linkStyle 2,3 stroke:#66bb6a,stroke-width:2px;
    linkStyle 4,5 stroke:#ff9800,stroke-width:2px;

The provisioning flow

  1. You create a Service with type: LoadBalancer in your shoot cluster.
  2. The cloud-controller-manager (CCM) detects the new service.
  3. The CCM calls the OpenStack Octavia API to provision:
    • A load balancer with a VIP on the worker node network.
    • A listener for the port specified in the K8s Service.
    • A pool with the worker nodes as members.
    • A health monitor checking each worker node.
  4. A floating IP is created on the public network and associated with the load balancer VIP.
  5. The CCM writes the floating IP back to the Service status as the EXTERNAL-IP.
  6. kubectl get svc now shows the external IP — your application is reachable from the internet.

What appears in OpenStack

The Octavia load balancer is a real OpenStack resource in your project. You can see it in:

  • OpenStack Horizon → Network → Load Balancers
  • OpenStack CLI: openstack loadbalancer list

Each K8s LoadBalancer service creates:

Octavia resource Purpose
Load Balancer The top-level LB object with a VIP on the worker network
Listener Listens on the port specified in the K8s Service
Pool Contains the worker nodes as members
Members One per worker node, forwarding to the NodePort
Health Monitor Checks worker node health via the NodePort

Practical example

Deploy nginx with a LoadBalancer service:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.27
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: nginx

Apply and check the service:

kubectl apply -f nginx.yaml
kubectl get svc nginx

While Octavia is provisioning, the external IP shows as <pending>:

NAME    TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
nginx   LoadBalancer   10.97.246.12   <pending>     80:31234/TCP   5s

Once Octavia is ready (typically 30–60 seconds):

NAME    TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)        AGE
nginx   LoadBalancer   10.97.246.12   192.0.2.50     80:31234/TCP   45s

The EXTERNAL-IP is the floating IP assigned by Octavia. Your application is now reachable at http://192.0.2.50.

Verify in OpenStack

The load balancer and floating IP are visible in OpenStack:

openstack loadbalancer list
openstack floating ip list

NodePort vs LoadBalancer vs Ingress

NodePort LoadBalancer Ingress
Exposes A single service on a high port (30000–32767) A single service on a standard port Multiple services via HTTP(S) routing
External IP Worker node IP + node port Dedicated floating IP Depends on the Ingress controller's service type
Octavia LB No Yes — one per service Only if the Ingress controller uses LoadBalancer
TLS termination No Possible with annotations Yes (Ingress controller handles TLS)
Quota None 1 Octavia LB + 1 floating IP per service 1 Octavia LB (for the Ingress controller)
Best for Internal / debug access Single service exposed to internet Multiple HTTP(S) services on one IP

Recommendation

For multiple HTTP(S) services, use an Ingress controller (e.g., nginx-ingress) with a single LoadBalancer service. This consumes only one Octavia LB and gives you TLS termination and path-based routing.

Quota considerations

Each Service of type LoadBalancer creates an Octavia load balancer in your OpenStack project. This consumes:

  • Octavia load balancers — check your project quota.
  • Floating IPs — one per LoadBalancer service.
  • Ports — on the worker node network.

If you hit the quota limit, new LoadBalancer services will stay in <pending> state. Check your quota in the Cloud Services Portal or with:

openstack quota show

Cleanup

When you delete a Kubernetes Service of type LoadBalancer, the cloud-controller-manager automatically deletes the corresponding Octavia load balancer and releases the floating IP:

kubectl delete svc nginx

Warning

Always delete the K8s Service first. If you manually delete the Octavia load balancer in OpenStack, the CCM may not be able to clean up properly, leaving orphaned resources.

Manual Octavia Load Balancers

You can also create Octavia load balancers manually for non-Kubernetes workloads (e.g., OpenStack VMs). See the OpenStack Load Balancer guide for CLI examples including HTTP, HTTPS termination, and health monitors.

Next step

Learn how to create persistent service accounts or go back to cluster management.