Skip to content

LAB 06 – Application Gateway for Containers

Story: Security wants CloudCart behind a managed Azure layer-7 load balancer that lives outside the cluster, scales on its own and can later get a WAF. That's Application Gateway for Containers (AGC) – Azure's Gateway API implementation and the successor to the Application Gateway Ingress Controller (AGIC).

Duration ~45 min
Creates managed identity, delegated subnet, ALB Controller (Helm), AGC resource (created by the controller)
Files alb.yaml, gateway-agc.yaml, httproute-agc.yaml
flowchart LR
  C[client] --> AGC[Application Gateway for Containers<br/>Azure resource, own frontend FQDN]
  AGC -->|direct to pod IPs| P[CloudCart pods]
  CTRL[ALB Controller<br/>in AKS] -- workload identity --> ARM[Azure Resource Manager]
  ARM -. creates / configures .-> AGC

Managed deployment: you only create Kubernetes objects (ApplicationLoadBalancer, Gateway, HTTPRoute); the ALB Controller creates and configures the Azure resources.

Step 1 – Providers and variables

source env.sh
for ns in Microsoft.ContainerService Microsoft.Network Microsoft.NetworkFunction Microsoft.ServiceNetworking; do
  az provider register --namespace $ns
done
MC_RG=$(az aks show -g $RG -n $CLUSTER --query nodeResourceGroup -o tsv)
MC_RG_ID=$(az group show -n $MC_RG --query id -o tsv)
OIDC=$(az aks show -g $RG -n $CLUSTER --query oidcIssuerProfile.issuerUrl -o tsv)

Step 2 – Identity for the ALB Controller (workload identity)

az identity create -g $RG -n azure-alb-identity
ALB_PRINCIPAL=$(az identity show -g $RG -n azure-alb-identity --query principalId -o tsv)
ALB_CLIENT_ID=$(az identity show -g $RG -n azure-alb-identity --query clientId -o tsv)

# Reader on the node resource group
az role assignment create --assignee-object-id $ALB_PRINCIPAL --assignee-principal-type ServicePrincipal \
  --scope $MC_RG_ID --role "acdd72a7-3385-48ef-bd42-f606fba81ae7"

# trust tokens of the controller's service account (the name azure-alb-identity is required)
az identity federated-credential create -g $RG --identity-name azure-alb-identity \
  --name azure-alb-identity --issuer $OIDC \
  --subject system:serviceaccount:azure-alb-system:alb-controller-sa

Step 3 – Install the ALB Controller

helm upgrade --install alb-controller oci://mcr.microsoft.com/application-lb/charts/alb-controller \
  --namespace azure-alb-system --create-namespace \
  --version $ALB_CONTROLLER_VERSION \
  --set albController.namespace=azure-alb-system \
  --set albController.podIdentity.clientID=$ALB_CLIENT_ID \
  --set albController.installGatewayApiCRDs=false
kubectl -n azure-alb-system rollout status deploy/alb-controller --timeout=180s
kubectl get gatewayclass azure-alb-external

installGatewayApiCRDs=false: the Gateway API CRDs are already installed and managed by AKS since LAB04 (--enable-gateway-api). Two owners of the same CRDs don't mix – without this flag the controller's init container fails with CrashLoopBackOff. (On a cluster without managed Gateway API, leave the default true.)

✅ Checkpoint: controller pods Running, GatewayClass azure-alb-external Accepted=True.

Step 4 – A delegated subnet for AGC

AGC needs its own subnet (≥ /24) in the cluster's VNet, delegated to Microsoft.ServiceNetworking/trafficControllers:

CLUSTER_SUBNET_ID=$(az vmss list -g $MC_RG --query '[0].virtualMachineProfile.networkProfile.networkInterfaceConfigurations[0].ipConfigurations[0].subnet.id' -o tsv)
read -d '' VNET_NAME VNET_RG VNET_ID <<< "$(az network vnet show --ids $CLUSTER_SUBNET_ID --query '[name, resourceGroup, id]' -o tsv)"

az network vnet subnet create -g $VNET_RG --vnet-name $VNET_NAME -n subnet-alb \
  --address-prefixes 10.225.0.0/24 \
  --delegations Microsoft.ServiceNetworking/trafficControllers
export ALB_SUBNET_ID=$(az network vnet subnet show -g $VNET_RG --vnet-name $VNET_NAME -n subnet-alb --query id -o tsv)

# the controller manages AGC in the node RG and joins it to the subnet
az role assignment create --assignee-object-id $ALB_PRINCIPAL --assignee-principal-type ServicePrincipal \
  --scope $MC_RG_ID --role "fbc52c3f-28ad-4303-a892-8a056630b8f1"     # AppGw for Containers Configuration Manager
az role assignment create --assignee-object-id $ALB_PRINCIPAL --assignee-principal-type ServicePrincipal \
  --scope $ALB_SUBNET_ID --role "4d97b98b-1d4f-4787-a291-c67834d212e7" # Network Contributor

Step 5 – Let the controller create AGC

envsubst < labs/LAB06-app-gateway-for-containers/alb.yaml | kubectl apply -f -

# check every minute until Deployment=True (5-10 min)
kubectl -n alb-infra get applicationloadbalancer alb-cloudcart \
  -o jsonpath='{range .status.conditions[*]}{.type}={.status} {.message}{"\n"}{end}'
kubectl get overlayextensionconfigs -n azure-alb-system \
  -o custom-columns=NAME:.metadata.name,RANGE:.spec.extensionIPRange,STATE:.status.state

In the portal (node resource group) a resource of type Application Gateway for Containers appears.

With Azure CNI Overlay pods have no VNet IPs, so the controller also creates an OverlayExtensionConfig: AKS networking (azure-cns) then routes traffic from the AGC subnet into the pod overlay. It's normal to see Deployment=False … timed out waiting for overlay extension config to be ready for a few minutes – the controller retries until the config is Succeeded and the status returns to Deployment=True.

Step 6 – Gateway and route

kubectl apply -f labs/LAB06-app-gateway-for-containers/gateway-agc.yaml
kubectl apply -f labs/LAB06-app-gateway-for-containers/httproute-agc.yaml
kubectl -n cloudcart get gateway cloudcart-agc -w
export AGC_FQDN=$(kubectl -n cloudcart get gateway cloudcart-agc -o jsonpath='{.status.addresses[0].value}')
curl -s http://$AGC_FQDN/ | grep -E 'version|pod'
for i in $(seq 1 20); do curl -s http://$AGC_FQDN/ | grep -o '"version": "[0-9.]*"'; done | sort | uniq -c

✅ Checkpoint: AGC answers on its own FQDN (*.alb.azure.com) and splits traffic 80/20 between v1 and v2.

Compare with LAB04: same Gateway API objects, different gatewayClassName – the implementation is now an Azure service, not pods in your cluster.

Step 7 – Clean up (AGC is billed per hour)

kubectl -n cloudcart delete httproute cloudcart-agc
kubectl -n cloudcart delete gateway cloudcart-agc
kubectl -n alb-infra delete applicationloadbalancer alb-cloudcart     # controller deletes the Azure resource

Discussion

  • AGC vs. App Routing (LAB04) vs. AGIC: where does the data plane run, who pays, WAF, scaling, private frontends.
  • BYO deployment: the AGC resource is created by Terraform/Bicep and only referenced from the Gateway.

Troubleshooting

Symptom Likely cause
init container init-alb-controller-crds in BackOff Gateway API CRDs are AKS-managed – rerun the Step 3 command (helm upgrade --install updates the existing release)
controller pods CrashLoopBackOff, auth errors federated credential name/subject wrong, or workload identity not enabled on the cluster
Deployment=False … timed out waiting for overlay extension config usually transient – wait 5–10 min; if it stays, check kubectl get overlayextensionconfigs -A and the azure-cns pods in kube-system
ApplicationLoadBalancer never Deployment=True missing role assignment (Configuration Manager / Network Contributor), subnet not delegated or too small
Gateway without address annotations alb-namespace / alb-name don't match the ApplicationLoadBalancer
RegionNotSupported AGC isn't available in your region – check the product availability page

Files for this lab: Think-Cube/aks-labs/labs/LAB06-app-gateway-for-containers