Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
This article shows you how to configure Application Gateway for Containers for multiple application teams. A platform administrator authorizes two application namespaces on a centrally managed Gateway. Each application team then creates its own Gateway API ListenerSet and HTTPRoute without modifying the parent Gateway.
For the operating model, ownership boundaries, attachment controls, conflict behavior, and isolation boundaries, see Multi-team shared gateway.
Important
This multi-team model requires Gateway API. Application Gateway for Containers doesn't provide an equivalent ListenerSet-based delegation model through the Ingress API. If the applicable workloads currently use Ingress API, migrate their ingress configuration to Gateway API before following this article.
Prerequisites
Before you begin, ensure you have:
- If following the BYO deployment strategy, ensure you set up your Application Gateway for Containers resources and ALB Controller (Add-on or Helm).
- If following the ALB managed deployment strategy, ensure you provision your ALB Controller (Add-on or Helm) and provision the Application Gateway for Containers resources via the ApplicationLoadBalancer custom resource.
The following table shows the resources used in the examples:
| Resource | Value |
|---|---|
| Platform namespace | test-infra |
| Application namespaces | team-a, team-b |
| Gateway | gateway-01 |
| ListenerSets | team-a-listeners, team-b-listeners |
| Listeners | team-a-http, team-b-http |
| HTTPRoutes | team-a-route, team-b-route |
| Hostnames | team-a.contoso.com, team-b.contoso.com |
If your resources use different values, update the manifests and commands.
Create and authorize the application namespaces
Create the platform and application namespaces, and label both application namespaces so that the Gateway can select them:
kubectl apply -f - <<EOF
apiVersion: v1
kind: Namespace
metadata:
name: test-infra
---
apiVersion: v1
kind: Namespace
metadata:
name: team-a
labels:
gateway-access: shared
---
apiVersion: v1
kind: Namespace
metadata:
name: team-b
labels:
gateway-access: shared
EOF
The Gateway configuration in the next section evaluates the gateway-access=shared label. This label doesn't grant Kubernetes permissions.
Note
Use Kubernetes role-based access control (RBAC) to restrict each application team to its namespace and reserve write access to the parent Gateway for the platform team.
Deploy the required Gateway API resources
Create a Gateway that uses the ALB managed Application Gateway for Containers resource and permits ListenerSets from labeled namespaces:
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway-01
namespace: test-infra
annotations:
alb.networking.azure.io/alb-namespace: alb-test-infra
alb.networking.azure.io/alb-name: alb-test
spec:
gatewayClassName: azure-alb-external
allowedListeners:
namespaces:
from: Selector
selector:
matchLabels:
gateway-access: shared
listeners:
- name: platform-http
hostname: platform.contoso.com
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: Same
EOF
The Gateway's allowedListeners field permits ListenerSets only from namespaces with the gateway-access=shared label. The listener's allowedRoutes field independently controls which routes can attach to the platform-owned listener.
Verify the Gateway status
Wait for the Gateway to be accepted and programmed:
kubectl wait gateway/gateway-01 \
-n test-infra \
--for=condition=Accepted \
--timeout=5m
kubectl wait gateway/gateway-01 \
-n test-infra \
--for=condition=Programmed \
--timeout=5m
Inspect the Gateway status:
kubectl get gateway gateway-01 -n test-infra -o yaml
Confirm that the Gateway reports Accepted=True and Programmed=True, the platform-http listener reports Accepted=True and Programmed=True, and an address is assigned to the Gateway. Before the application teams create their ListenerSets, status.attachedListenerSets is 0 or omitted.
Deploy sample backends
Create a sample application and Service in its authorized namespace for each application team:
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: team-a-backend
namespace: team-a
spec:
replicas: 1
selector:
matchLabels:
app: team-a-backend
template:
metadata:
labels:
app: team-a-backend
spec:
containers:
- name: backend
image: registry.k8s.io/echoserver:1.10
ports:
- name: http
containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: team-a-backend
namespace: team-a
spec:
selector:
app: team-a-backend
ports:
- name: http
port: 80
targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: team-b-backend
namespace: team-b
spec:
replicas: 1
selector:
matchLabels:
app: team-b-backend
template:
metadata:
labels:
app: team-b-backend
spec:
containers:
- name: backend
image: registry.k8s.io/echoserver:1.10
ports:
- name: http
containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: team-b-backend
namespace: team-b
spec:
selector:
app: team-b-backend
ports:
- name: http
port: 80
targetPort: 8080
EOF
Wait for both backends to become available:
kubectl rollout status deployment/team-a-backend \
-n team-a \
--timeout=5m
kubectl rollout status deployment/team-b-backend \
-n team-b \
--timeout=5m
Create ListenerSets
Create a ListenerSet in its authorized namespace for each application team. Each parentRef selects the same platform-owned Gateway:
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: ListenerSet
metadata:
name: team-a-listeners
namespace: team-a
spec:
parentRef:
group: gateway.networking.k8s.io
kind: Gateway
name: gateway-01
namespace: test-infra
listeners:
- name: team-a-http
hostname: team-a.contoso.com
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Same
---
apiVersion: gateway.networking.k8s.io/v1
kind: ListenerSet
metadata:
name: team-b-listeners
namespace: team-b
spec:
parentRef:
group: gateway.networking.k8s.io
kind: Gateway
name: gateway-01
namespace: test-infra
listeners:
- name: team-b-http
hostname: team-b.contoso.com
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Same
EOF
The Gateway accepts both ListenerSets because their namespaces have the label selected by allowedListeners. Each ListenerSet listener permits routes from only its own namespace.
Wait for both ListenerSets to be accepted and programmed:
kubectl wait listenerset/team-a-listeners \
-n team-a \
--for=condition=Accepted \
--timeout=5m
kubectl wait listenerset/team-a-listeners \
-n team-a \
--for=condition=Programmed \
--timeout=5m
kubectl wait listenerset/team-b-listeners \
-n team-b \
--for=condition=Accepted \
--timeout=5m
kubectl wait listenerset/team-b-listeners \
-n team-b \
--for=condition=Programmed \
--timeout=5m
Inspect the ListenerSet statuses:
kubectl get listenerset team-a-listeners -n team-a -o yaml
kubectl get listenerset team-b-listeners -n team-b -o yaml
Confirm that both ListenerSets report Accepted=True and Programmed=True and that the team-a-http and team-b-http listeners report Accepted=True, ResolvedRefs=True, and Programmed=True. A top-level accepted condition doesn't guarantee that every listener is valid.
The parent Gateway now reports two attached ListenerSets:
kubectl get gateway gateway-01 \
-n test-infra \
-o jsonpath='{.status.attachedListenerSets}{"\n"}'
The command returns:
2
Attach HTTPRoutes
As each application team, create an HTTPRoute whose parentRefs entry selects its ListenerSet and listener:
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: team-a-route
namespace: team-a
spec:
parentRefs:
- group: gateway.networking.k8s.io
kind: ListenerSet
name: team-a-listeners
sectionName: team-a-http
hostnames:
- team-a.contoso.com
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: team-a-backend
port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: team-b-route
namespace: team-b
spec:
parentRefs:
- group: gateway.networking.k8s.io
kind: ListenerSet
name: team-b-listeners
sectionName: team-b-http
hostnames:
- team-b.contoso.com
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: team-b-backend
port: 80
EOF
Inspect the route statuses:
kubectl get httproute team-a-route -n team-a -o yaml
kubectl get httproute team-b-route -n team-b -o yaml
Confirm that each route's parent status identifies its team's ListenerSet and reports Accepted=True and ResolvedRefs=True.
Test the application listeners
Get the parent Gateway address:
GATEWAY_ADDRESS=$(kubectl get gateway gateway-01 \
-n test-infra \
-o jsonpath='{.status.addresses[0].value}')
Send a request with each application listener's hostname:
curl -i \
-H 'Host: team-a.contoso.com' \
"http://${GATEWAY_ADDRESS}/"
curl -i \
-H 'Host: team-b.contoso.com' \
"http://${GATEWAY_ADDRESS}/"
Each request returns 200 OK from its team's sample backend. After you create DNS records that map team-a.contoso.com and team-b.contoso.com to the Gateway address, you can send requests directly to the hostnames.
Verify listener conflict isolation
Introduce a conflict by changing Team B's listener to use Team A's hostname:
kubectl patch listenerset team-b-listeners \
-n team-b \
--type=json \
-p='[{"op":"replace","path":"/spec/listeners/0/hostname","value":"team-a.contoso.com"}]'
Inspect Team B's ListenerSet:
kubectl get listenerset team-b-listeners -n team-b -o yaml
Confirm that the team-b-http listener reports Accepted=False with a conflict reason. The conflicting listener isn't programmed because Team A's existing listener has precedence.
Confirm that the parent Gateway remains programmed:
kubectl wait gateway/gateway-01 \
-n test-infra \
--for=condition=Programmed \
--timeout=5m
Send another request to Team A:
curl -i \
-H 'Host: team-a.contoso.com' \
"http://${GATEWAY_ADDRESS}/"
The request continues to return 200 OK, demonstrating that Team B's listener conflict doesn't interrupt Team A.
Restore Team B's hostname:
kubectl patch listenerset team-b-listeners \
-n team-b \
--type=json \
-p='[{"op":"replace","path":"/spec/listeners/0/hostname","value":"team-b.contoso.com"}]'
Inspect the ListenerSet until the team-b-http listener reports Accepted=True, ResolvedRefs=True, and Programmed=True again:
kubectl get listenerset team-b-listeners -n team-b -o yaml
Send another request to Team B and confirm that it returns 200 OK:
curl -i \
-H 'Host: team-b.contoso.com' \
"http://${GATEWAY_ADDRESS}/"
This test demonstrates configuration-failure isolation between listeners. The application teams still share the underlying Application Gateway for Containers infrastructure and its capacity.
Congratulations, you configured a multi-team shared gateway, routed traffic for two application teams through independently managed ListenerSets, and verified that a listener conflict in one team doesn't interrupt the other team.
Troubleshoot attachment
Inspect the Gateway, both ListenerSets, and both HTTPRoutes when a request doesn't reach a backend:
kubectl get gateway gateway-01 -n test-infra -o yaml
kubectl get listenerset team-a-listeners -n team-a -o yaml
kubectl get listenerset team-b-listeners -n team-b -o yaml
kubectl get httproute team-a-route -n team-a -o yaml
kubectl get httproute team-b-route -n team-b -o yaml
Common conditions include:
| Condition or symptom | Cause | Resolution |
|---|---|---|
ListenerSet Accepted=False with reason NotAllowed |
The Gateway doesn't allow ListenerSets from the application namespace | Correct spec.allowedListeners or the namespace labels |
Listener Accepted=False with a conflict reason |
The hostname, port, or protocol conflicts with a higher-precedence Gateway or ListenerSet listener | Choose a nonconflicting listener configuration |
Listener ResolvedRefs=False |
A referenced object, such as a TLS Secret, is missing or not authorized | Create the object and any required ReferenceGrant |
HTTPRoute Accepted=False with reason NotAllowedByListeners |
The listener's allowedRoutes rules don't allow the route |
Correct allowedRoutes or move the route to an allowed namespace |
HTTPRoute ResolvedRefs=False |
A backend reference is missing or not authorized | Correct the backend reference and any required ReferenceGrant |
If some listeners are valid and others aren't, the ListenerSet can remain accepted. Always inspect the status entry for the specific listener selected by the route.
Clean up resources
Delete both teams' routes, ListenerSets, backends, and the Gateway:
kubectl delete httproute team-a-route -n team-a
kubectl delete httproute team-b-route -n team-b
kubectl delete listenerset team-a-listeners -n team-a
kubectl delete listenerset team-b-listeners -n team-b
kubectl delete service team-a-backend -n team-a
kubectl delete service team-b-backend -n team-b
kubectl delete deployment team-a-backend -n team-a
kubectl delete deployment team-b-backend -n team-b
kubectl delete gateway gateway-01 -n test-infra
Delete the example namespaces:
kubectl delete namespace team-a
kubectl delete namespace team-b
kubectl delete namespace test-infra
Deleting the Gateway doesn't delete a BYO Application Gateway for Containers frontend or the ALB managed ApplicationLoadBalancer resource used by the Gateway.