LoadBalancer Services each provisioning their own cloud load balancer
What does ZopNight detect here?
Every Service of type LoadBalancer provisions a separate cloud load balancer, each billing hourly plus its own public IP. Ten such Services means 10 load balancers. An Ingress controller fronts many services behind 1, which is usually the cheaper architecture.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | RC-1721 · RC-1821 · RC-1921 |
| Category | security |
| Severity | low |
| Metric | none — pure configuration read |
| Source | internal/rules/k8s/service_loadbalancer.go |
Where it applies
One Service, one load balancer, one bill
type: LoadBalancer asks the cloud provider for a dedicated load balancer. Not a route on a
shared one, but a distinct resource with its own hourly charge and its own public IPv4 address,
which is separately billable on every major cloud.
The arithmetic is linear and unforgiving: ten services exposed this way produce ten load balancers and ten addresses, none of which are visible from inside the cluster.
Why Ingress is usually the answer
An Ingress controller provisions one load balancer and routes to many backend services by host and path. Adding the eleventh service adds an Ingress rule, not another cloud resource.
The exceptions are real, and worth naming so this finding is not applied blindly:
- Non-HTTP traffic. Ingress is HTTP/HTTPS. A gRPC stream, a database protocol or raw TCP usually needs a LoadBalancer Service or a Gateway API implementation.
- Deliberate isolation. A tenant or environment that must not share an ingress path.
- Protocol features the ingress controller does not support: certain proxy protocols, some health-check semantics.
The security dimension
Each LoadBalancer Service is a public entry point. Ten of them is ten attack surfaces to configure, monitor and keep patched, versus one ingress with a single TLS configuration and a single place to apply rate limiting or WAF rules.
That consolidation is often worth more than the cost saving.
LoadBalancer Services and their ingress IPs
kubectl get svc -A -o json | jq -r ' .items[] | select(.spec.type=="LoadBalancer") | "\(.metadata.namespace)/\(.metadata.name)\t\(.status.loadBalancer.ingress[0].ip // "pending")"'Count the rows, then compare against your cloud console’s load balancer list. They should match, and a mismatch means orphaned balancers from deleted Services.
Before consolidating
Check whether anything external allowlisted the individual IPs. Moving behind an ingress changes the address clients connect to, and a partner firewall rule pointing at the old load balancer will break at cutover rather than at deploy.