Configure Gateway API on AKS
Bu içerik henüz dilinizde mevcut değil.
This walkthrough shows how to use AddGateway to expose .NET Aspire services on Azure Kubernetes Service (AKS) with Application Gateway for Containers (AGC) and automatic TLS certificate provisioning via cert-manager HTTP-01 challenges.
Gateway API with HTTP-01 is the recommended approach for TLS on AKS because:
- No DNS API access required — cert-manager validates domain ownership via HTTP, not DNS TXT records.
- No Azure DNS zone or managed identity needed — significantly simpler infrastructure than DNS-01.
- Works with any domain — including the auto-assigned AGC FQDN (
*.alb.azure.com), your own custom domain, or even without a pre-existing DNS zone.
Set up the AKS environment
Section titled “Set up the AKS environment”The following steps create the Azure infrastructure needed for Gateway API with HTTP-01 TLS. If you already have an AKS cluster with ALB and cert-manager configured, skip to Configure the AppHost.
Register resource providers and features
Section titled “Register resource providers and features”Application Gateway for Containers requires several resource providers and preview features to be registered in your Azure subscription:
# Register required resource providersaz provider register --namespace Microsoft.ContainerServiceaz provider register --namespace Microsoft.Networkaz provider register --namespace Microsoft.NetworkFunctionaz provider register --namespace Microsoft.ServiceNetworking
# Register preview features for ALB controller and Gateway APIaz feature register --namespace Microsoft.ContainerService \ --name ManagedGatewayAPIPreviewaz feature register --namespace Microsoft.ContainerService \ --name ApplicationLoadBalancerPreview
# Wait for registration to complete (may take a few minutes)az feature show --namespace Microsoft.ContainerService \ --name ApplicationLoadBalancerPreview --query properties.state -o tsv
# Re-register the provider after feature registrationaz provider register --namespace Microsoft.ContainerService
# Install required CLI extensionsaz extension add --name albaz extension add --name aks-preview# Register required resource providersaz provider register --namespace Microsoft.ContainerServiceaz provider register --namespace Microsoft.Networkaz provider register --namespace Microsoft.NetworkFunctionaz provider register --namespace Microsoft.ServiceNetworking
# Register preview features for ALB controller and Gateway APIaz feature register --namespace Microsoft.ContainerService ` --name ManagedGatewayAPIPreviewaz feature register --namespace Microsoft.ContainerService ` --name ApplicationLoadBalancerPreview
# Wait for registration to complete (may take a few minutes)az feature show --namespace Microsoft.ContainerService ` --name ApplicationLoadBalancerPreview --query properties.state -o tsv
# Re-register the provider after feature registrationaz provider register --namespace Microsoft.ContainerService
# Install required CLI extensionsaz extension add --name albaz extension add --name aks-previewCreate the resource group and cluster
Section titled “Create the resource group and cluster”# Variables — customize these for your environmentRESOURCE_GROUP=my-aspire-aksLOCATION=westus3CLUSTER_NAME=my-aspire-aksACR_NAME=myaspireacr
# Create resource groupaz group create --name $RESOURCE_GROUP --location $LOCATION
# Create AKS cluster with ALB controller, OIDC issuer, and workload identityaz aks create \ --resource-group $RESOURCE_GROUP \ --name $CLUSTER_NAME \ --location $LOCATION \ --enable-oidc-issuer \ --enable-workload-identity \ --generate-ssh-keys
# Enable the ALB controller addonaz aks update \ --resource-group $RESOURCE_GROUP \ --name $CLUSTER_NAME \ --enable-alb
# Get cluster credentialsaz aks get-credentials \ --resource-group $RESOURCE_GROUP \ --name $CLUSTER_NAME# Variables — customize these for your environment$RESOURCE_GROUP = "my-aspire-aks"$LOCATION = "westus3"$CLUSTER_NAME = "my-aspire-aks"$ACR_NAME = "myaspireacr"
# Create resource groupaz group create --name $RESOURCE_GROUP --location $LOCATION
# Create AKS cluster with ALB controller, OIDC issuer, and workload identityaz aks create ` --resource-group $RESOURCE_GROUP ` --name $CLUSTER_NAME ` --location $LOCATION ` --enable-oidc-issuer ` --enable-workload-identity ` --generate-ssh-keys
# Enable the ALB controller addonaz aks update ` --resource-group $RESOURCE_GROUP ` --name $CLUSTER_NAME ` --enable-alb
# Get cluster credentialsaz aks get-credentials ` --resource-group $RESOURCE_GROUP ` --name $CLUSTER_NAMECreate and attach a container registry
Section titled “Create and attach a container registry”# Create Azure Container Registryaz acr create \ --resource-group $RESOURCE_GROUP \ --name $ACR_NAME \ --sku Basic
# Attach ACR to AKS (grants AcrPull access)az aks update \ --resource-group $RESOURCE_GROUP \ --name $CLUSTER_NAME \ --attach-acr $ACR_NAME# Create Azure Container Registryaz acr create ` --resource-group $RESOURCE_GROUP ` --name $ACR_NAME ` --sku Basic
# Attach ACR to AKS (grants AcrPull access)az aks update ` --resource-group $RESOURCE_GROUP ` --name $CLUSTER_NAME ` --attach-acr $ACR_NAMECreate the ApplicationLoadBalancer
Section titled “Create the ApplicationLoadBalancer”Create a dedicated subnet for AGC in the cluster’s VNet, then deploy the ApplicationLoadBalancer custom resource:
# Get the managed cluster resource group and VNetMC_RG=$(az aks show --resource-group $RESOURCE_GROUP --name $CLUSTER_NAME \ --query nodeResourceGroup -o tsv)VNET_NAME=$(az network vnet list --resource-group $MC_RG --query "[0].name" -o tsv)
# Create a subnet for the ALB (delegated to ServiceNetworking)az network vnet subnet create \ --resource-group $MC_RG \ --vnet-name $VNET_NAME \ --name subnet-alb \ --address-prefix 10.237.0.0/24 \ --delegations Microsoft.ServiceNetworking/trafficControllers
# Get the full subnet IDSUBNET_ID=$(az network vnet subnet show \ --resource-group $MC_RG \ --vnet-name $VNET_NAME \ --name subnet-alb \ --query id -o tsv)
# Deploy the ApplicationLoadBalancer CRDkubectl apply -f - <<EOFapiVersion: alb.networking.azure.io/v1kind: ApplicationLoadBalancermetadata: name: alb-aspire namespace: defaultspec: associations: - $SUBNET_IDEOF
# Wait for it to become readykubectl get applicationloadbalancer alb-aspire --watch# Get the managed cluster resource group and VNet$MC_RG = $(az aks show --resource-group $RESOURCE_GROUP --name $CLUSTER_NAME ` --query nodeResourceGroup -o tsv)$VNET_NAME = $(az network vnet list --resource-group $MC_RG --query "[0].name" -o tsv)
# Create a subnet for the ALB (delegated to ServiceNetworking)az network vnet subnet create ` --resource-group $MC_RG ` --vnet-name $VNET_NAME ` --name subnet-alb ` --address-prefix 10.237.0.0/24 ` --delegations Microsoft.ServiceNetworking/trafficControllers
# Get the full subnet ID$SUBNET_ID = $(az network vnet subnet show ` --resource-group $MC_RG ` --vnet-name $VNET_NAME ` --name subnet-alb ` --query id -o tsv)
# Deploy the ApplicationLoadBalancer CRD@"apiVersion: alb.networking.azure.io/v1kind: ApplicationLoadBalancermetadata: name: alb-aspire namespace: defaultspec: associations: - $SUBNET_ID"@ | kubectl apply -f -
# Wait for it to become readykubectl get applicationloadbalancer alb-aspire --watchInstall cert-manager
Section titled “Install cert-manager”Install cert-manager with Gateway API support enabled. Unlike DNS-01 validation, HTTP-01 does not require a managed identity, federated credentials, or workload identity configuration — cert-manager only needs to create a temporary HTTPRoute on the Gateway.
# Install cert-manager with Gateway API supporthelm upgrade --install cert-manager oci://quay.io/jetstack/charts/cert-manager \ --namespace cert-manager \ --create-namespace \ --set crds.enabled=true \ --set config.enableGatewayAPI=true \ --wait
# Verify cert-manager is runningkubectl get pods -n cert-manager# Install cert-manager with Gateway API supporthelm upgrade --install cert-manager oci://quay.io/jetstack/charts/cert-manager ` --namespace cert-manager ` --create-namespace ` --set crds.enabled=true ` --set config.enableGatewayAPI=true ` --wait
# Verify cert-manager is runningkubectl get pods -n cert-managerCreate the HTTP-01 ClusterIssuer
Section titled “Create the HTTP-01 ClusterIssuer”Create a ClusterIssuer that uses the gatewayHTTPRoute solver. This tells cert-manager to respond to ACME HTTP-01 challenges by creating a temporary HTTPRoute on the Gateway — the challenge is served from the same frontend and IP as your application.
kubectl apply -f - <<EOFapiVersion: cert-manager.io/v1kind: ClusterIssuermetadata: name: letsencrypt-http01spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: your-email@example.com privateKeySecretRef: name: letsencrypt-http01-account-key solvers: - http01: gatewayHTTPRoute: parentRefs: - kind: GatewayEOF
# Verify the issuer is readykubectl get clusterissuer letsencrypt-http01@"apiVersion: cert-manager.io/v1kind: ClusterIssuermetadata: name: letsencrypt-http01spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: your-email@example.com privateKeySecretRef: name: letsencrypt-http01-account-key solvers: - http01: gatewayHTTPRoute: parentRefs: - kind: Gateway"@ | kubectl apply -f -
# Verify the issuer is readykubectl get clusterissuer letsencrypt-http01Configure the AppHost
Section titled “Configure the AppHost”Add a Kubernetes Gateway to your AppHost project to expose your frontend service through AGC. The gateway configuration includes the gateway class, ALB annotations, routing, hostname, and TLS settings.
var builder = DistributedApplication.CreateBuilder(args);
// Parameters for deployment configurationvar registryEndpoint = builder.AddParameter("registryEndpoint");var gatewayClassName = builder.AddParameter("gatewayClassName");var loadBalancerName = builder.AddParameter("loadBalancerName");var loadBalancerNamespace = builder.AddParameter("loadBalancerNamespace");var clusterIssuer = builder.AddParameter("clusterIssuer");var externalFqdn = builder.AddParameter("externalFqdn");
// Kubernetes environmentvar k8s = builder.AddKubernetesEnvironment("k8s");
// Container registryvar acr = builder.AddContainerRegistry("acr", registryEndpoint);
// Application servicesvar api = builder.AddProject<Projects.ApiService>("api");
var frontend = builder.AddProject<Projects.Frontend>("frontend") .WithReference(api);
// Gateway with TLSvar gateway = k8s.AddGateway("ingress") .WithGatewayClass(gatewayClassName) .WithGatewayAnnotation( "alb.networking.azure.io/alb-name", loadBalancerName) .WithGatewayAnnotation( "alb.networking.azure.io/alb-namespace", loadBalancerNamespace) .WithRoute("/", frontend.GetEndpoint("http")) .WithHostname(externalFqdn) .WithTls();
builder.Build().Run();import { DistributedApplication } from "@aspire/apphost";
const builder = DistributedApplication.createBuilder();
// Parameters for deployment configurationconst registryEndpoint = builder.addParameter("registryEndpoint");const gatewayClassName = builder.addParameter("gatewayClassName");const loadBalancerName = builder.addParameter("loadBalancerName");const loadBalancerNamespace = builder.addParameter("loadBalancerNamespace");const clusterIssuer = builder.addParameter("clusterIssuer");const externalFqdn = builder.addParameter("externalFqdn");
// Kubernetes environmentconst k8s = builder.addKubernetesEnvironment("k8s");
// Container registryconst acr = builder.addContainerRegistry("acr", registryEndpoint);
// Application servicesconst api = builder.addProject("api");
const frontend = builder.addProject("frontend") .withReference(api);
// Gateway with TLSconst gateway = k8s.addGateway("ingress") .withGatewayClass(gatewayClassName) .withGatewayAnnotation( "alb.networking.azure.io/alb-name", loadBalancerName) .withGatewayAnnotation( "alb.networking.azure.io/alb-namespace", loadBalancerNamespace) .withRoute("/", frontend.getEndpoint("http")) .withHostname(externalFqdn) .withTls();
builder.build().run();The WithTls() call with no arguments auto-generates the TLS secret name (ingress-tls) and adds a cert-manager annotation to the Gateway referencing the ClusterIssuer specified via the clusterIssuer parameter.
Deploy
Section titled “Deploy”Set environment variables to provide parameter values, then run aspire deploy:
export Parameters__registryEndpoint=myaspireacr.azurecr.ioexport Parameters__gatewayClassName=azure-alb-externalexport Parameters__loadBalancerName=alb-aspireexport Parameters__loadBalancerNamespace=defaultexport Parameters__clusterIssuer=letsencrypt-http01export Parameters__externalFqdn=myapp.example.com
aspire deploy$env:Parameters__registryEndpoint = "myaspireacr.azurecr.io"$env:Parameters__gatewayClassName = "azure-alb-external"$env:Parameters__loadBalancerName = "alb-aspire"$env:Parameters__loadBalancerNamespace = "default"$env:Parameters__clusterIssuer = "letsencrypt-http01"$env:Parameters__externalFqdn = "myapp.example.com"
aspire deployPoint DNS to the Gateway
Section titled “Point DNS to the Gateway”After deployment, the Gateway is assigned a public FQDN by AGC. Create a DNS record pointing your hostname to this address.
-
Get the Gateway address:
Terminal window kubectl get gateway ingress -o jsonpath='{.status.addresses[0].value}'This returns the AGC frontend FQDN, for example:
abc123.fz50.alb.azure.com. -
Create a CNAME record at your DNS provider pointing your hostname (e.g.,
myapp.example.com) to the AGC FQDN.If using Azure DNS:
Terminal window AGC_FQDN=$(kubectl get gateway ingress \-o jsonpath='{.status.addresses[0].value}')az network dns record-set cname set-record \--resource-group $RESOURCE_GROUP \--zone-name example.com \--record-set-name myapp \--cname $AGC_FQDNTerminal window $AGC_FQDN = $(kubectl get gateway ingress `-o jsonpath='{.status.addresses[0].value}')az network dns record-set cname set-record `--resource-group $RESOURCE_GROUP `--zone-name example.com `--record-set-name myapp `--cname $AGC_FQDN -
Wait for cert-manager to complete the HTTP-01 challenge. cert-manager starts the challenge immediately on deployment and retries with exponential backoff until DNS resolves. This typically takes 5–10 minutes once the DNS record is active.
Verify
Section titled “Verify”After deployment and DNS propagation, verify that your Gateway is configured correctly and TLS is working.
-
Check the Gateway status:
Terminal window kubectl get gateway ingress -o wideThe
ADDRESScolumn should show the public FQDN assigned by AGC. If the address is empty, see the Troubleshooting section. -
Check the certificate status:
Terminal window kubectl get certificatekubectl describe certificate ingress-tlsThe certificate should show
Ready: Trueonce cert-manager has completed the HTTP-01 challenge. This typically takes 5–10 minutes after DNS is pointed to the Gateway. -
Test HTTPS access:
Terminal window curl https://myapp.example.comYou should receive a valid response with a trusted TLS certificate from Let’s Encrypt.
How it works
Section titled “How it works”When you deploy a Gateway with WithHostname() and WithTls(), the following sequence occurs:
-
Bootstrap TLS. On the first deploy, Aspire generates a self-signed placeholder certificate and creates the Kubernetes Secret referenced by the Gateway’s HTTPS listener. This makes the listener valid immediately so AGC can start programming the frontend.
-
Gateway programming. AGC detects the Gateway resource and provisions a frontend with a public FQDN. The HTTPS listener is valid (using the bootstrap certificate) so AGC begins routing traffic.
-
HTTP-01 challenge. cert-manager detects the Gateway’s
cert-manager.io/issuerannotation and creates aCertificateresource. The HTTP-01 solver creates a temporaryHTTPRouteon the same Gateway, responding to the ACME challenge at/.well-known/acme-challenge/<token>. Because theHTTPRouteshares the same frontend and IP, the ACME server’s validation request reaches the solver directly. -
Certificate issuance. Once the ACME server verifies the challenge response, Let’s Encrypt issues a trusted certificate. cert-manager stores it in the Kubernetes Secret, replacing the bootstrap certificate. AGC picks up the new certificate automatically.
-
Automatic renewal. cert-manager renews the certificate before expiry (default: 30 days before). The HTTP-01 flow repeats transparently.
Why HTTP-01 works with Gateway API but not Ingress
Section titled “Why HTTP-01 works with Gateway API but not Ingress”AGC creates a separate frontend (with its own FQDN and IP) for each Ingress resource. When cert-manager creates a solver Ingress for an HTTP-01 challenge, AGC assigns it a different frontend — the ACME server’s validation request goes to the wrong IP and fails.
With Gateway API, multiple HTTPRoute resources share a single Gateway and therefore a single frontend. When cert-manager creates a temporary solver HTTPRoute, it attaches to the same Gateway and is served from the same IP. The ACME validation request reaches the solver, and the challenge succeeds.
cert-manager comparison
Section titled “cert-manager comparison”| Approach | Works with Ingress? | Works with Gateway? | Needs DNS API? |
|---|---|---|---|
HTTP-01 (gatewayHTTPRoute) | ❌ (separate frontends per Ingress) | ✅ | No |
| DNS-01 (Azure DNS) | ✅ | ✅ | Yes |
Manual (acme.sh / openssl) | ✅ | ✅ | Manual DNS TXT |
Alternative: DNS-01 with Azure DNS
Section titled “Alternative: DNS-01 with Azure DNS”If you already have an Azure DNS zone and prefer DNS-based validation, you can use DNS-01 instead of HTTP-01. This requires additional infrastructure: a managed identity with the DNS Zone Contributor role, federated credentials for workload identity, and patching the cert-manager deployment.
For the full DNS-01 setup steps, see the cert-manager section of Configure Ingress on AKS. The AppHost configuration is the same — only the ClusterIssuer and clusterIssuer parameter value differ.
Deploy without TLS
Section titled “Deploy without TLS”Deploy without TLS (not recommended)
To deploy without TLS, omit WithTls() from your Gateway configuration:
var gateway = k8s.AddGateway("ingress") .WithGatewayClass(gatewayClassName) .WithGatewayAnnotation( "alb.networking.azure.io/alb-name", loadBalancerName) .WithGatewayAnnotation( "alb.networking.azure.io/alb-namespace", loadBalancerNamespace) .WithRoute("/", frontend.GetEndpoint("http")) .WithHostname(externalFqdn);const gateway = k8s.addGateway("ingress") .withGatewayClass(gatewayClassName) .withGatewayAnnotation( "alb.networking.azure.io/alb-name", loadBalancerName) .withGatewayAnnotation( "alb.networking.azure.io/alb-namespace", loadBalancerNamespace) .withRoute("/", frontend.getEndpoint("http")) .withHostname(externalFqdn);Deploy without hostname
Section titled “Deploy without hostname”Deploy without a hostname
To deploy without a hostname, omit WithHostname() and WithTls():
var gateway = k8s.AddGateway("ingress") .WithGatewayClass(gatewayClassName) .WithGatewayAnnotation( "alb.networking.azure.io/alb-name", loadBalancerName) .WithGatewayAnnotation( "alb.networking.azure.io/alb-namespace", loadBalancerNamespace) .WithRoute("/", frontend.GetEndpoint("http"));const gateway = k8s.addGateway("ingress") .withGatewayClass(gatewayClassName) .withGatewayAnnotation( "alb.networking.azure.io/alb-name", loadBalancerName) .withGatewayAnnotation( "alb.networking.azure.io/alb-namespace", loadBalancerNamespace) .withRoute("/", frontend.getEndpoint("http"));After deployment, get the Gateway’s assigned address:
kubectl get gateway ingress -o jsonpath='{.status.addresses[0].value}'Troubleshooting
Section titled “Troubleshooting”Gateway not getting an address
Section titled “Gateway not getting an address”If kubectl get gateway shows no address:
- Verify GatewayClassName: Ensure the
gatewayClassNameparameter matches a validGatewayClassin your cluster:Terminal window kubectl get gatewayclass - Check ALB annotations: The
alb.networking.azure.io/alb-nameandalb.networking.azure.io/alb-namespaceannotations must reference an existingApplicationLoadBalancerresource:Terminal window kubectl get applicationloadbalancer -A - Check ALB controller logs: Look for errors in the ALB controller pod:
Terminal window kubectl logs -n kube-system -l app=alb-controller --tail=50
cert-manager HTTP-01 challenge not completing
Section titled “cert-manager HTTP-01 challenge not completing”If the certificate stays in a Pending state:
-
Check the challenge status:
Terminal window kubectl get challengekubectl describe challenge -
Verify DNS resolves to the Gateway. The ACME server must be able to reach
http://<your-hostname>/.well-known/acme-challenge/<token>. If DNS doesn’t point to the Gateway’s AGC address yet, the challenge will fail and retry with exponential backoff (starting at ~1 minute, up to ~32 minutes). -
Ensure the Gateway has a valid HTTPS listener. AGC won’t program the frontend if any listener references a TLS secret that doesn’t exist. Aspire creates a bootstrap self-signed certificate automatically on first deploy. If the secret was manually deleted, recreate it:
Terminal window # Check if the secret existskubectl get secret ingress-tls# If missing, redeploy with aspire deploy to recreate the bootstrap cert -
Verify cert-manager has Gateway API support enabled:
Terminal window kubectl get deployment cert-manager -n cert-manager \-o jsonpath='{.spec.template.spec.containers[0].args}'Look for
--enable-gateway-api-routes=truein the args. If missing, upgrade the Helm release with--set config.enableGatewayAPI=true.
Certificate order stuck in “errored” state
Section titled “Certificate order stuck in “errored” state”If a cert-manager Order resource shows state: errored, delete the Certificate to trigger a fresh ACME flow:
# Check the order statuskubectl get orderkubectl describe order <order-name>
# Delete the certificate to trigger re-issuancekubectl delete certificate ingress-tls
# Redeploy to recreateaspire deploy- Kubernetes Gateway API documentation
- Application Gateway for Containers documentation
- Quickstart: Deploy ALB Controller (AKS add-on)
- cert-manager Gateway API support
- cert-manager with Let’s Encrypt on AKS (Gateway API)
- Register resource providers for AGC
- .NET Aspire deployment overview
- Deploy to AKS with Aspire