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
- You create a
Servicewithtype: LoadBalancerin your shoot cluster. - The cloud-controller-manager (CCM) detects the new service.
- 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.
- A floating IP is created on the public network and associated with the load balancer VIP.
- The CCM writes the floating IP back to the Service status as the
EXTERNAL-IP. kubectl get svcnow 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.