Dedicated proxy pools¶
A dedicated proxy (per Gateway) is a proxy Deployment the controller provisions for a single named Gateway, serving only that Gateway's routes in isolation from the shared pool. It is a pool in its own right — a Deployment scaled by CoxswainGatewayParameters.spec.replicas (default 1), not a single pod — with its own Service and ServiceAccount.
This is a Gateway API feature. A Gateway opts in through spec.infrastructure.parametersRef (GEP-1762), or inherits the choice from its GatewayClass's spec.parametersRef, pointing at a CoxswainGatewayParameters object. Classic Ingress has no equivalent of parametersRef and is always served by the shared pool — as is every Gateway that doesn't opt in.
Opt a Gateway into a dedicated pool¶
Create a CoxswainGatewayParameters object and point the Gateway at it via spec.infrastructure.parametersRef:
apiVersion: gateway.coxswain-labs.dev/v1alpha1
kind: CoxswainGatewayParameters
metadata:
name: tenant-a-defaults
namespace: tenant-a
spec:
replicas: 2 # scale the dedicated pool; defaults to 1
serviceType: ClusterIP
# image: defaults to the controller's own image when omitted
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: tenant-a-gw
namespace: tenant-a
spec:
gatewayClassName: coxswain
infrastructure:
parametersRef:
group: gateway.coxswain-labs.dev
kind: CoxswainGatewayParameters
name: tenant-a-defaults # in the Gateway's own namespace
listeners:
- name: http
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: Same
A Gateway can only reference a CoxswainGatewayParameters in its own namespace — the reference carries no namespace field. To set defaults for every dedicated Gateway cluster-wide, attach the parametersRef to the GatewayClass instead; a Gateway-level reference overlays the class-level one field by field.
Tunable fields on CoxswainGatewayParameters:
| Field | Effect | Default |
|---|---|---|
replicas |
Static replica count (ignored when autoscaling.enabled) |
1 |
resources |
Resource requests/limits on the proxy container | controller default |
image |
Override the proxy image | controller's own image |
serviceType |
LoadBalancer, NodePort, or ClusterIP for the proxy Service |
LoadBalancer |
podTemplate |
Partial PodTemplateSpec merged over the rendered template (nodeSelector, tolerations, env, sidecars, …) |
— |
autoscaling.enabled |
Provision an HPA for the dedicated proxy Deployment | false |
autoscaling.minReplicas |
HPA lower bound; must be ≥ 2 for the PDB to be provisioned |
— |
autoscaling.maxReplicas |
HPA upper bound | — |
autoscaling.targetCPUUtilizationPercentage |
HPA CPU utilization target | — |
When autoscaling.enabled: true, the controller provisions an HorizontalPodAutoscaler and, when minReplicas ≥ 2, a PodDisruptionBudget alongside the Deployment. The Deployment's spec.replicas is left unset so the HPA is the sole replica authority. All three objects carry the same GEP-1762 name (<gateway-name>-<gateway-class-name>), the same owner reference, and the coxswain-controller field manager.
spec:
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
Automatic provisioning by the controller¶
serve controller runs a provisioning operator that watches every Gateway. For any Gateway whose parametersRef (or whose GatewayClass's parametersRef) resolves to a CoxswainGatewayParameters object, the operator applies a dedicated proxy Deployment / Service / ServiceAccount to the cluster via server-side-apply under field manager coxswain-controller, owner-referenced to the parent Gateway so deletion cascades.
Apply the dev fixture set and verify the resources land:
kubectl apply -f deploy/dev/dedicated-gateway/
kubectl get deploy,svc,sa -n tenant-a \
-l gateway.networking.k8s.io/gateway-name=tenant-a-gw
# Three resources named <gateway-name>-coxswain land in tenant-a.
Field-manager assertion:
kubectl get deployment tenant-a-gw-coxswain -n tenant-a -o json | \
jq '.metadata.managedFields[].manager'
# "coxswain-controller"
Garbage collection on Gateway deletion (owner-ref cascade):
kubectl delete gateway tenant-a-gw -n tenant-a
# All three resources disappear within ~30s.
If parametersRef targets a missing CoxswainGatewayParameters object, the operator publishes an Accepted=False, reason=InvalidParameters condition on the Gateway via the shared override channel.
Cross-namespace route attachment (from: All / from: Selector)¶
When a listener declares allowedRoutes.namespaces.from: All or from: Selector, no additional
operator action is needed. The controller's cluster-wide reflector already watches routes across all
namespaces; cross-namespace HTTPRoutes are resolved at reconcile time and compiled into the dedicated
snapshot before it is pushed to the dedicated proxy. The dedicated proxy receives the complete,
pre-scoped routing world from the controller — it has no cluster-wide reflector and no K8s RBAC of
its own.
RBAC¶
The dedicated proxy holds zero Kubernetes API credentials. The provisioned ServiceAccount exists
only as a pod identity — the controller stamps its name ({gateway-name}-{gatewayclass-name}, per
GEP-1762) into the Gateway's discovery registry entry, and the discovery server uses it to verify the
proxy's SVID before delivering any snapshot.
The Gateway carries a gateway.coxswain-labs.dev/dedicated-cleanup finalizer so the provisioned
Deployment, Service, and ServiceAccount are removed before Kubernetes finalizes the Gateway
deletion.
Run a dedicated proxy manually¶
For debugging or for parity testing against the shared pool, you can run a dedicated proxy directly
instead of having the controller provision it. The proxy subscribes to the controller with
Scope::Gateway and receives only its Gateway's compiled snapshot — no parametersRef is required
for the manual path.
Start the controller first (in a separate terminal), then start the dedicated proxy alongside it:
cargo run --bin coxswain -- serve proxy --dedicated \
--gateway-name coxswain-test \
--gateway-namespace default \
--discovery-endpoint https://localhost:50051 \
--discovery-bootstrap-endpoint https://localhost:50052 \
--log-format console
Verify only that Gateway's routes are loaded — via the controller's admin port, naming this dedicated proxy's pod (the proxy itself carries no routes query surface):
curl -s http://<controller-admin-address>:8082/api/v1/fleet/proxies/<pod-name>/routes | jq .
The output lists exactly the hosts the target Gateway's HTTPRoutes serve; Ingress routes and routes attached to other Gateways do not appear. Cross-namespace routes are included automatically — the controller compiles them before pushing.