跳转到内容
Docs Try Aspire
Docs Try

Configure Ingress on AKS

此内容尚不支持你的语言。

This walkthrough shows how to use AddIngress to expose .NET Aspire services on Azure Kubernetes Service (AKS) with Application Gateway for Containers (AGC) and automatic TLS certificate provisioning via cert-manager DNS-01 challenges.

The following steps create all the Azure infrastructure needed for this walkthrough. If you already have an AKS cluster with ALB and cert-manager configured, skip to Configure the AppHost.

Application Gateway for Containers requires several resource providers and preview features to be registered in your Azure subscription:

Terminal window
# Register required resource providers
az provider register --namespace Microsoft.ContainerService
az provider register --namespace Microsoft.Network
az provider register --namespace Microsoft.NetworkFunction
az provider register --namespace Microsoft.ServiceNetworking
# Register preview features for ALB controller and Gateway API
az feature register --namespace Microsoft.ContainerService \
--name ManagedGatewayAPIPreview
az 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 registration
az provider register --namespace Microsoft.ContainerService
# Install required CLI extensions
az extension add --name alb
az extension add --name aks-preview
Terminal window
# Variables — customize these for your environment
RESOURCE_GROUP=my-aspire-aks
LOCATION=westus3
CLUSTER_NAME=my-aspire-aks
ACR_NAME=myaspireacr
DNS_ZONE=myapp.example.com
# Create resource group
az group create --name $RESOURCE_GROUP --location $LOCATION
# Create AKS cluster with ALB controller, OIDC issuer, and workload identity
az aks create \
--resource-group $RESOURCE_GROUP \
--name $CLUSTER_NAME \
--location $LOCATION \
--enable-oidc-issuer \
--enable-workload-identity \
--generate-ssh-keys
# Enable the ALB controller addon
az aks update \
--resource-group $RESOURCE_GROUP \
--name $CLUSTER_NAME \
--enable-alb
# Get cluster credentials
az aks get-credentials \
--resource-group $RESOURCE_GROUP \
--name $CLUSTER_NAME
Terminal window
# Create Azure Container Registry
az 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 a dedicated subnet for AGC in the cluster’s VNet, then deploy the ApplicationLoadBalancer custom resource:

Terminal window
# 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
kubectl apply -f - <<EOF
apiVersion: alb.networking.azure.io/v1
kind: ApplicationLoadBalancer
metadata:
name: alb-aspire
namespace: default
spec:
associations:
- $SUBNET_ID
EOF
# Wait for it to become ready
kubectl get applicationloadbalancer alb-aspire --watch
Terminal window
# Create the DNS zone
az network dns zone create \
--resource-group $RESOURCE_GROUP \
--name $DNS_ZONE
# List the name servers — delegate these in your domain registrar
az network dns zone show \
--resource-group $RESOURCE_GROUP \
--name $DNS_ZONE \
--query nameServers -o tsv

Install cert-manager with Azure DNS support

Section titled “Install cert-manager with Azure DNS support”
Terminal window
# Install cert-manager with Gateway API support
helm 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
# Create a managed identity for cert-manager
az identity create \
--name cert-manager-identity \
--resource-group $RESOURCE_GROUP
IDENTITY_CLIENT_ID=$(az identity show \
--name cert-manager-identity \
--resource-group $RESOURCE_GROUP \
--query clientId -o tsv)
IDENTITY_PRINCIPAL_ID=$(az identity show \
--name cert-manager-identity \
--resource-group $RESOURCE_GROUP \
--query principalId -o tsv)
# Grant DNS Zone Contributor role on the DNS zone
DNS_ZONE_ID=$(az network dns zone show \
--resource-group $RESOURCE_GROUP \
--name $DNS_ZONE \
--query id -o tsv)
az role assignment create \
--assignee-object-id $IDENTITY_PRINCIPAL_ID \
--assignee-principal-type ServicePrincipal \
--role "DNS Zone Contributor" \
--scope $DNS_ZONE_ID
# Create a federated credential for cert-manager's service account
OIDC_ISSUER=$(az aks show \
--resource-group $RESOURCE_GROUP \
--name $CLUSTER_NAME \
--query oidcIssuerProfile.issuerUrl -o tsv)
az identity federated-credential create \
--name cert-manager-fedcred \
--identity-name cert-manager-identity \
--resource-group $RESOURCE_GROUP \
--issuer $OIDC_ISSUER \
--subject "system:serviceaccount:cert-manager:cert-manager" \
--audiences "api://AzureADTokenExchange"
# Annotate and label the cert-manager service account for workload identity
kubectl annotate serviceaccount cert-manager \
--namespace cert-manager \
azure.workload.identity/client-id=$IDENTITY_CLIENT_ID \
--overwrite
kubectl label serviceaccount cert-manager \
--namespace cert-manager \
azure.workload.identity/use=true \
--overwrite
# Patch the deployment to inject workload identity env vars
kubectl patch deployment cert-manager \
--namespace cert-manager \
--type=json \
-p='[{"op":"add","path":"/spec/template/metadata/labels/azure.workload.identity~1use","value":"true"}]'
# Wait for rollout
kubectl rollout status deployment cert-manager --namespace cert-manager
# Get the subscription ID for the ClusterIssuer
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
# Create a ClusterIssuer using Azure DNS for DNS-01 challenges
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: your-email@example.com
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- dns01:
azureDNS:
hostedZoneName: $DNS_ZONE
resourceGroupName: $RESOURCE_GROUP
subscriptionID: $SUBSCRIPTION_ID
environment: AzurePublicCloud
managedIdentity:
clientID: $IDENTITY_CLIENT_ID
EOF
# Verify the issuer is ready
kubectl get clusterissuer letsencrypt-prod

Define your Kubernetes environment, container registry, and ingress in the AppHost project. The example below uses parameters so that environment-specific values are supplied at deploy time.

var builder = DistributedApplication.CreateBuilder(args);
// Parameters
var registryEndpoint = builder.AddParameter("registryEndpoint");
var helmNamespace = builder.AddParameter("helmNamespace");
var helmChartVersion = builder.AddParameter("helmChartVersion");
var helmChartReleaseName = builder.AddParameter("helmChartReleaseName");
var ingressClassName = builder.AddParameter("ingressClassName");
var loadBalancerName = builder.AddParameter("loadBalancerName");
var loadBalancerNamespace = builder.AddParameter("loadBalancerNamespace");
var clusterIssuer = builder.AddParameter("clusterIssuer");
var externalFqdn = builder.AddParameter("externalFqdn");
// Kubernetes environment
var k8s = builder.AddKubernetesEnvironment("k8s")
.WithHelm(helm =>
{
helm.Namespace = helmNamespace;
helm.ChartVersion = helmChartVersion;
helm.ReleaseName = helmChartReleaseName;
});
// Container registry
var acr = builder.AddContainerRegistry("acr", registryEndpoint);
// Application services
var api = builder.AddProject<Projects.ApiService>("api");
var frontend = builder.AddProject<Projects.Frontend>("frontend")
.WithReference(api);
// Ingress
var ingress = k8s.AddIngress("ingress")
.WithIngressClass(ingressClassName)
.WithIngressAnnotation(
"alb.networking.azure.io/alb-name", loadBalancerName)
.WithIngressAnnotation(
"alb.networking.azure.io/alb-namespace", loadBalancerNamespace)
.WithIngressAnnotation(
"cert-manager.io/cluster-issuer", clusterIssuer)
.WithDefaultBackend(frontend.GetEndpoint("http"))
.WithHostname(externalFqdn)
.WithTls();
builder.Build().Run();

Set environment variables to provide parameter values, then run aspire deploy:

Terminal window
export Parameters__registryEndpoint=myregistry.azurecr.io
export Parameters__helmNamespace=my-app
export Parameters__helmChartVersion=0.1.0
export Parameters__helmChartReleaseName=my-app
export Parameters__ingressClassName=azure-alb-external
export Parameters__loadBalancerName=my-alb
export Parameters__loadBalancerNamespace=alb-infra
export Parameters__clusterIssuer=letsencrypt-dns
export Parameters__externalFqdn=myapp.example.com
aspire deploy

After the deployment completes, verify the ingress is working:

  1. Check the ingress address. Wait for AGC to assign an IP or FQDN to the ingress:

    Terminal window
    kubectl get ingress -n my-app

    The ADDRESS column should show the AGC frontend address.

  2. Check the certificate status. Verify that cert-manager has issued a certificate for your domain:

    Terminal window
    kubectl get certificate -n my-app

    The READY column should show True once the certificate is issued.

  3. Access the application. Open https://myapp.example.com in your browser and confirm you see a valid TLS certificate.

When you deploy an ingress with the cert-manager.io/cluster-issuer annotation and WithTls, the following sequence occurs:

  1. Self-signed bootstrap certificate. On the first deploy, cert-manager creates a temporary self-signed certificate so AGC can start serving traffic immediately.

  2. Annotation detection. cert-manager detects the cert-manager.io/cluster-issuer annotation on the ingress resource and creates a Certificate resource targeting the configured ClusterIssuer.

  3. DNS-01 challenge. The ClusterIssuer uses the DNS-01 solver to validate domain ownership. cert-manager creates a TXT record in your Azure DNS zone, the ACME server validates it, and a real certificate is issued.

  4. Certificate rotation. cert-manager stores the issued certificate in a Kubernetes secret referenced by the ingress TLS configuration and automatically renews it before expiry.

Deploy without TLS (not recommended for production)

Remove the WithTls call and the cert-manager.io/cluster-issuer annotation:

var ingress = k8s.AddIngress("ingress")
.WithIngressClass(ingressClassName)
.WithIngressAnnotation(
"alb.networking.azure.io/alb-name", loadBalancerName)
.WithIngressAnnotation(
"alb.networking.azure.io/alb-namespace", loadBalancerNamespace)
.WithDefaultBackend(frontend.GetEndpoint("http"))
.WithHostname(externalFqdn);
Deploy without a custom hostname

Remove the WithHostname, WithTls, and cert-manager annotation calls:

var ingress = k8s.AddIngress("ingress")
.WithIngressClass(ingressClassName)
.WithIngressAnnotation(
"alb.networking.azure.io/alb-name", loadBalancerName)
.WithIngressAnnotation(
"alb.networking.azure.io/alb-namespace", loadBalancerNamespace)
.WithDefaultBackend(frontend.GetEndpoint("http"));

AGC has a limit of 5 frontends per Application Gateway for Containers instance. Each Ingress resource creates its own frontend. If you exceed this limit, new ingress resources remain in a pending state.

To check your current frontend count:

Terminal window
kubectl get ingress -A -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.loadBalancer.ingress[0].hostname}{"\n"}{end}'

Consider consolidating multiple services behind a single ingress with path-based routing, or request a quota increase from Azure support.

If the certificate READY status remains False, check the following:

  1. cert-manager logs. Look for errors related to DNS-01 challenge creation:

    Terminal window
    kubectl logs -n cert-manager deploy/cert-manager -f
  2. Challenge status. Check if the ACME challenge is progressing:

    Terminal window
    kubectl get challenges -n my-app
  3. Workload identity. Verify that the cert-manager service account has the correct federated credential and that the managed identity has DNS Zone Contributor permissions on the Azure DNS zone:

    Terminal window
    kubectl describe serviceaccount cert-manager -n cert-manager
  4. ClusterIssuer status. Confirm the issuer is ready:

    Terminal window
    kubectl get clusterissuer letsencrypt-dns -o yaml

If your browser automatically redirects HTTP URLs to HTTPS even when you haven’t configured TLS, this is due to HTTP Strict Transport Security (HSTS). Modern browsers cache HSTS policies for domains, including subdomains of .com and other top-level domains.

To work around this during development:

  • Clear your browser’s HSTS cache for the domain.
  • Use a private/incognito browser window.
  • Deploy with TLS enabled to avoid the issue entirely.