Configure a LoadBalancer Service
A Service of type LoadBalancer gets a platform load balancer on your workspace's public address (see Kubernetes, and Load balancers for where clients connect). You configure that load balancer from the Service itself, with the standard loadBalancerSourceRanges field and a few annotations under lb.sahabti.com/. The cluster is the source of truth: the console shows these settings on the load balancer's page, read-only, each with the field or annotation it comes from.
On the load balancer's Overview, the console shows where clients connect as an endpoint for your apps: its hostname with the public port, such as prod-default-db-e5f6a7b8-q2w7ze.lb.sahabti.app:40002, and the protocol, ready to copy. It is not a website address: websites use the cluster URL, https://<cluster>.sahabti.app, served by the cluster's managed gateway.
What you can set
| Setting | On the Service | Values | Default |
|---|---|---|---|
| Allowed sources | spec.loadBalancerSourceRanges | Up to 20 IPv4 addresses or ranges | Anyone |
| Denied sources | lb.sahabti.com/denied-sources | Up to 200 IPv4 addresses or ranges, separated by commas | None |
| Protection profile | lb.sahabti.com/protection-profile | standard or strict | standard |
| Idle timeout | lb.sahabti.com/idle-timeout | Whole seconds, from 10 to 3600 | 60 |
| Client IP | lb.sahabti.com/client-ip | off or proxy-protocol | off |
| Short-connection bans | lb.sahabti.com/short-session-bans | on or off | on |
Each setting applies to every port of the Service and takes effect within seconds. Remove a field or an annotation and its setting goes back to the default.
Allow only some sources
apiVersion: v1
kind: Service
metadata:
name: web
spec:
type: LoadBalancer
selector:
app: web
ports:
- port: 443
targetPort: 8443
loadBalancerSourceRanges:
- 203.0.113.0/24
- 198.51.100.7/32
Only these sources can connect to the Service's ports; everyone else is dropped. To change the list on a running Service:
kubectl patch service web -p '{"spec":{"loadBalancerSourceRanges":["203.0.113.0/24"]}}'
- A bare address counts as a
/32, and0.0.0.0/0anywhere in the list means anyone. - The load balancer has an IPv4 address only, so IPv6 ranges are ignored. A list of IPv6 ranges only cannot be applied.
- A range that another listed range already holds is dropped.
Deny sources
metadata:
annotations:
lb.sahabti.com/denied-sources: "192.0.2.15, 198.51.100.0/24"
kubectl annotate service web lb.sahabti.com/denied-sources="192.0.2.15,198.51.100.0/24" --overwrite
Denied sources are dropped before they reach any port of the load balancer, for as long as they are listed. Separate them with commas, spaces or new lines. Sources the load balancer bans on its own for tripping a protection limit are separate: they expire by themselves and show under Blocked sources on the load balancer's Security page.
Pick a protection profile
Every load balancer limits new and concurrent connections and floods per source, and bans a source that trips a limit. strict tightens the limits, which suits exposed databases and admin ports:
standard | strict | |
|---|---|---|
| New connections per second, per source | 50 | 20 |
| Concurrent connections, per source | 200 | 50 |
| Connections from a source that end within 4 seconds, per minute | 100 | 30 |
| Ban for a source that trips a limit | 10 minutes | 1 hour |
| SYN packets per second, per port | 5,000 | 2,000 |
| UDP packets per second, per port | 20,000 | 5,000 |
kubectl annotate service web lb.sahabti.com/protection-profile=strict --overwrite
For ports that legitimately see many short connections, such as monitoring probes or clients behind one office address, set lb.sahabti.com/short-session-bans: "off" to stop banning sources for them; every other limit still applies.
Set the idle timeout
How long a TCP connection may stay idle, with nothing sent either way, before the load balancer closes it. Raise it for long-lived connections such as WebSockets, message queues or database sessions that sit quiet between queries.
kubectl annotate service web lb.sahabti.com/idle-timeout=900 --overwrite
Pass the client's address
Your pods see connections coming from the load balancer, not from the client. With proxy-protocol, every TCP connection begins with a PROXY protocol v2 header that carries the client's address and port:
kubectl annotate service web lb.sahabti.com/client-ip=proxy-protocol --overwrite
Turn PROXY protocol on in the server that receives the connections, such as your ingress controller or web server, before you set the annotation: a server that does not expect the header treats it as a broken request. UDP ports never carry the header.
preserve, which keeps the client's address on the packets themselves, is not available for a cluster's load balancer: the cluster's nodes do not route their replies through it.
A complete example
apiVersion: v1
kind: Service
metadata:
name: shop
annotations:
lb.sahabti.com/protection-profile: strict
lb.sahabti.com/idle-timeout: "300"
lb.sahabti.com/denied-sources: "192.0.2.15"
lb.sahabti.com/client-ip: proxy-protocol
spec:
type: LoadBalancer
selector:
app: shop
ports:
- name: https
port: 443
targetPort: 8443
loadBalancerSourceRanges:
- 203.0.113.0/24
Quote numbers in annotations, as above: annotation values are strings.
Check what applied
The Service's events and its lb.sahabti.com/SettingsApplied condition say what the platform made of its settings:
kubectl describe service shop
kubectl get service shop -o jsonpath='{.status.conditions}'
| Status | Reason | What it means |
|---|---|---|
True | Applied | The load balancer follows every setting of the Service. |
False | InvalidSettings | At least one value cannot be applied. The message names each one; those settings keep their last valid value and every other setting is applied. |
False | NotSupported | The platform does not apply settings from the cluster yet. They take effect once it does, with no change on your side. |
A Warning event, LoadBalancerSettingsInvalid, is recorded when a value cannot be applied, and a Normal event, LoadBalancerSettingsApplied, once every setting applies again. A Service that sets none of these has no such condition.
On the load balancer's page in the console, Overview lists each setting with the field or annotation it comes from, the default when the Service does not set it, and any value that could not be applied.
When a value cannot be applied
An invalid value never stops the Service and never opens it wider: that setting keeps its last valid value, the other settings still apply, and the condition says why. A new load balancer is the one exception. If its allowed or denied sources cannot be applied when it is created, it stays closed, with no port answering and no external IP on the Service, until they are fixed.
| Cause | Fix |
|---|---|
loadBalancerSourceRanges lists only IPv6 ranges | List IPv4 ranges: the load balancer has an IPv4 address only. |
| More than 20 allowed or 200 denied sources | List fewer, or wider, ranges. |
idle-timeout with a unit, such as 300s, or outside 10 to 3600 | Write whole seconds: "300". |
protection-profile other than standard or strict | Use one of the two, in lowercase. |
client-ip: preserve | Use proxy-protocol. |
short-session-bans other than on or off | Use one of the two, in lowercase. |
A typo in an annotation name, such as lb.sahabti.com/idle-timout | The condition names the annotation it does not know; fix its name. |
Settings changes are limited to 30 a minute for each cluster. Past that, the platform applies the latest settings a few seconds later.