Introduction
In a multi-tenant environment where many teams share one Kubernetes cluster, Ingress is a thorny topic. Because the external entry point is a shared resource, the risk that "one team's misconfiguration harms another team" is ever-present. At the same time, on the cost side there is the practical pressure that "giving each tenant its own load balancer multiplies the bill."
The platform team's challenge is to satisfy isolation (so one team's problem does not spread to others) and cost efficiency (sharing resources to lower the bill) at the same time. These two often conflict. Strong isolation demands resource separation, and resource separation drives up cost.
In this article we start with the trade-offs of a shared controller versus per-tenant controllers, then cover namespace isolation, the load balancer cost structure, resource quotas, noisy-neighbor prevention, security boundaries, chargeback/showback models, large-scale Ingress operations, and the platform team's self-service perspective — a comprehensive look at multi-tenant Ingress operations and cost optimization.
Shared Controller vs Per-Tenant Controllers
The most fundamental decision is "will all teams share one Ingress Controller, or will each team get its own?"
Shared controller model Per-tenant controller model
─────────────────── ───────────────────────────
External LB (1) team-a LB team-b LB
│ │ │
┌───────────────┐ ┌──────────┐ ┌──────────┐
│ Shared Ingress │ │ a ctrl │ │ b ctrl │
│ Controller │ └────┬─────┘ └────┬─────┘
└───────┬───────┘ │ │
┌─────┼─────┐ team-a team-b
team-a team-b team-c (strong iso) (strong iso)
(cost-efficient, weak iso)
The trade-offs of the two models:
| Item | Shared controller | Per-tenant controller |
|---|---|---|
| Load balancer cost | Low (1 shared) | High (one per tenant) |
| Isolation strength | Weak (shared config/failures) | Strong (independent processes) |
| Operational complexity | Low (central management) | High (many to manage) |
| Noisy-neighbor risk | High | Low |
| Blast radius | Large (everyone affected) | Small (tenant-limited) |
| Version independence | Low (bulk upgrade) | High (per-tenant upgrade) |
Generally a shared controller is favorable for many small teams, while per-tenant controllers favor a small number of tenants with strong regulatory needs or very high traffic. Many organizations mix the two, adopting a hybrid where ordinary teams use a shared controller but only teams with special requirements get a dedicated controller.
Namespace Isolation
The basic isolation unit in a shared cluster is the namespace. You can make the controller watch only a specific namespace, or separate IngressClasses per team.
To restrict the controller to watching only a specific namespace, set watch-namespace.
Helm values example (ingress-nginx)
controller:
scope:
enabled: true
namespace: "team-a"
ingressClassResource:
name: team-a-nginx
controllerValue: "k8s.io/team-a-nginx"
With this, the team-a controller handles only Ingresses in the team-a namespace, so another team's misconfiguration does not affect this controller. Combined with a per-team IngressClass, routing ownership becomes clear.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
namespace: team-a
spec:
ingressClassName: team-a-nginx
rules:
- host: team-a.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
To strengthen isolation while still using a shared controller, Contour's HTTPProxy delegation model is elegant. The platform team owns the root domain definition and delegates only the sub-path routing to each team's namespace.
Platform team: root definition + delegation
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: root
namespace: platform
spec:
virtualhost:
fqdn: example.com
includes:
- name: team-a-routes
namespace: team-a
conditions:
- prefix: /team-a
Load Balancer Cost
The biggest variable in multi-tenant cost is the cloud load balancer. A cloud LB is provisioned for each LoadBalancer-type service, with hourly charges and throughput charges.
Per-tenant LB (high cost) Shared LB (low cost)
───────────────────── ────────────────────
team-a -> LB1 (monthly fee) all teams -> LB1 (one monthly fee)
team-b -> LB2 (monthly fee) │
team-c -> LB3 (monthly fee) shared controller branches by host/path
= LB fee x 3 = LB fee x 1
The key to lowering cost is minimizing the number of LoadBalancer services. When one shared controller routes all tenants by host/path behind a single LB, the LB's fixed fee is shared among the tenants. Even when per-tenant controllers are truly needed, it is worth evaluating a configuration that places multiple controllers behind a shared LB where possible.
Resource Quotas
In a shared-controller environment, you must apply quotas so one tenant cannot monopolize resources and starve others. Use namespace-scoped ResourceQuota and LimitRange.
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-a-quota
namespace: team-a
spec:
hard:
requests.cpu: "4"
requests.memory: 8Gi
limits.cpu: "8"
limits.memory: 16Gi
count/ingresses.networking.k8s.io: "20"
Apply a quota on the Ingress count itself, too, to prevent one team from creating thousands of Ingresses and exploding the controller's configuration-generation burden. Set appropriate requests/limits separately on the controller Pods so they operate stably even during traffic spikes.
Noisy-Neighbor Prevention
The biggest risk of a shared controller is that one tenant's runaway traffic paralyzes the entire controller. The mechanisms to prevent this:
- **Rate limiting**: Per-tenant request-rate limits so one team cannot monopolize controller throughput.
- **Connection limits**: A cap on concurrent connections.
- **Timeouts**: Reasonable timeouts so a slow backend does not hog workers.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
namespace: team-a
annotations:
nginx.ingress.kubernetes.io/limit-rps: "100"
nginx.ingress.kubernetes.io/limit-connections: "50"
nginx.ingress.kubernetes.io/proxy-read-timeout: "30"
spec:
ingressClassName: shared-nginx
rules:
- host: team-a.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
That said, such annotation-based arbitrary configuration injection can carry security issues, so it is better to put a gate (policy engine) in place that allows only policies vetted by the platform team where possible.
Security Boundaries
In multitenancy, routing is itself a security boundary. If one team hijacks another team's host or path, traffic theft occurs. You must enforce the following.
- **Hostname ownership validation**: Use a policy engine (such as a validating webhook) to enforce that a team can only use its assigned domains in an Ingress.
- **IngressClass enforcement**: Restrict teams so they can only use their own IngressClass.
- **TLS Secret isolation**: Prevent certificate Secrets from crossing namespaces.
- **Annotation allowlist**: Block dangerous snippet-family annotations.
With a policy engine, enforcing a rule like "an Ingress in the team-a namespace must have a host starting with team-a" can prevent host collisions and hijacking at the source.
Chargeback/Showback Models
When the platform team operates shared infrastructure, how to allocate cost to tenants matters. There are two approaches.
- **Chargeback**: Bill actual cost to the tenant. Clear, but requires precise metering.
- **Showback**: Do not bill, but make each team's usage/cost visible. Encourages behavior improvement.
It is common to prorate the fixed cost of a shared LB and shared controller by each tenant's traffic share, while attributing per-tenant dedicated controllers or extra LBs directly to that team.
| Cost item | Allocation method |
|---|---|
| Shared LB fixed fee | Prorated by traffic share |
| Shared controller compute | Prorated by request count/bandwidth share |
| Dedicated controller | Fully attributed to the tenant |
| Extra LB | Fully attributed to the tenant |
| Data transfer (egress) | Attributed by per-tenant egress metering |
By aggregating request counts and bandwidth with tenant (namespace/host) labels from Prometheus metrics, you can automate showback reports.
Large-Scale Ingress Operations
When tenants and Ingresses grow to hundreds or thousands, the controller itself becomes a bottleneck. Key points for large-scale operation:
- **Configuration reload burden**: The nginx family regenerates/reloads configuration whenever an Ingress changes. With many Ingresses, reloads are frequent and heavy. The Envoy family (xDS) updates dynamically, so this burden is lower.
- **Controller sharding**: When one controller cannot handle too many Ingresses, shard controllers by namespace/IngressClass.
- **Memory/CPU scaling**: Controller resource demand grows in proportion to the Ingress count, so scale up appropriately after monitoring.
- **State visibility**: Observe internal controller metrics such as reload time, config-generation latency, and queue length.
At the scale of thousands, a dynamically updating (Envoy family) controller is often operationally more favorable than a reload-based one.
Platform Team Perspective: Self-Service
The platform team's goal is to enable "each team to safely create an Ingress without requesting the platform team every time." Patterns for this:
- **Provide golden paths**: Offer templates equipped with a vetted IngressClass, default TLS issuance (cert-manager), and default policies.
- **Guardrails via policy**: Automatically validate safety rules — host ownership, annotation allowlist, IngressClass enforcement — with a policy engine.
- **Accountability via showback**: Let each team see its own cost/usage to encourage autonomous optimization.
- **Abstracted interface**: Teams declare only the host and backend, and the platform hides the complex controller configuration. The Gateway API's role separation (GatewayClass/Gateway for the platform, HTTPRoute for app teams) fits this model well.
The Gateway API was designed with multitenancy and role separation in mind, so it fits naturally into the platform team's self-service model. With the Ingress-frozen trend, a multi-tenant platform is well worth evaluating Gateway API adoption for.
Example Scenario
Let's frame a hypothetical organization. Fifty teams share one cluster.
- **Ordinary teams (45)**: A single shared controller + a single shared LB. Apply namespace quotas, host-ownership policy, and rate-limit guardrails. Cost is showed back by traffic share.
- **High-traffic teams (3)**: Dedicated controllers to avoid noisy neighbors. However, placed behind the shared LB to avoid extra LB cost.
- **Regulated teams (2)**: Strong isolation requirements, so a dedicated controller + dedicated LB. Cost fully attributed to that team.
A hybrid like this maintains cost efficiency for the many teams while meeting special requirements, and makes cost attribution fair.
Conclusion
Operating multi-tenant Ingress is a balancing act between the conflicting goals of isolation and cost efficiency. To summarize the core principles: First, the baseline is a hybrid of a shared controller + shared LB to keep cost low, with only teams that have special requirements separated into dedicated setups. Second, prevent noisy-neighbor and security-boundary problems with guardrails — namespace isolation, quotas, rate limiting, and host-ownership policy. Third, use showback to give each team cost visibility and accountability, encouraging autonomous optimization.
And in the 2026 trend, if you are newly designing a multi-tenant platform, I strongly recommend basing self-service on the Gateway API, which has role separation built in. Now that Ingress is frozen, multitenancy is one of the areas where the Gateway API shines most.
References
- Kubernetes Ingress official docs: https://kubernetes.io/docs/concepts/services-networking/ingress/
- Kubernetes multi-tenancy docs: https://kubernetes.io/docs/concepts/security/multi-tenancy/
- Kubernetes ResourceQuota docs: https://kubernetes.io/docs/concepts/policy/resource-quotas/
- ingress-nginx multitenancy/scope docs: https://kubernetes.github.io/ingress-nginx/
- ingress-nginx rate-limit annotations: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/
- Project Contour multitenancy (delegation) docs: https://projectcontour.io/docs/main/config/inclusion-delegation/
- Gateway API official site: https://gateway-api.sigs.k8s.io/
- cert-manager official docs: https://cert-manager.io/docs/
- Traefik official docs: https://doc.traefik.io/traefik/
현재 단락 (1/162)
In a multi-tenant environment where many teams share one Kubernetes cluster, Ingress is a thorny top...