Deploy to Azure Kubernetes Service (AKS)
Este conteúdo não está disponível em sua língua ainda.
Deploy your Aspire application to Azure Kubernetes Service (AKS). Aspire provisions the AKS cluster, Azure Container Registry (ACR), and any Azure resources your app depends on — then deploys your application in a single command.
Start with Deploy to Kubernetes for the shared Kubernetes deployment model and target selection. For AKS hosting integration details, see AKS integration.
Prerequisites
Section titled “Prerequisites”- Aspire prerequisites
- Aspire CLI installed
- kubectl installed and available on your
PATH - Helm v4.2.0 or later installed and available on your
PATH - Azure CLI installed and available on your
PATH - An active Azure account and subscription
By default, local deployment uses Azure CLI credentials. Authenticate with Azure CLI before deploying:
az loginConfigure your AppHost for AKS
Section titled “Configure your AppHost for AKS”Add the AKS hosting integration to your AppHost:
aspire add azure-kubernetesThe Aspire CLI adds the 📦 Aspire.Hosting.Azure.Kubernetes integration to your AppHost.
Then add the AKS environment in your AppHost:
var builder = DistributedApplication.CreateBuilder(args);
var aks = builder.AddAzureKubernetesEnvironment("aks");
var api = builder.AddProject<Projects.MyApi>("api");
builder.Build().Run();import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const aks = await builder.addAzureKubernetesEnvironment('aks');
const api = await builder .addNodeApp('api', './api', 'src/index.ts') .withHttpEndpoint({ env: 'PORT' }) .withExternalHttpEndpoints();
await builder.build().run();When an AKS environment is present, all compute resources are automatically deployed to AKS — no additional opt-in is required.
What Aspire provisions
Section titled “What Aspire provisions”When you run aspire deploy, Aspire provisions the following Azure resources:
- AKS cluster — a managed Kubernetes cluster
- Azure Container Registry (ACR) — to store container images for your application
- Managed identity — for secure, credential-free access between AKS and ACR
- Azure resources — any Azure resources referenced in your AppHost (databases, caches, messaging, etc.)
Aspire builds your container images, pushes them to ACR, generates Helm charts, and installs them to the AKS cluster.
Configure node pools
Section titled “Configure node pools”System node pool
Section titled “System node pool”Customize the system node pool VM size and scaling using WithSystemNodePool:
builder.AddAzureKubernetesEnvironment("aks") .WithSystemNodePool("Standard_D4s_v5", minCount: 1, maxCount: 5);const aks = await builder.addAzureKubernetesEnvironment('aks');await aks.withSystemNodePool('Standard_D4s_v5', 1, 5);Additional node pools
Section titled “Additional node pools”Add additional node pools for workload isolation or specialized hardware, then use WithNodePool to schedule workloads on them:
var aks = builder.AddAzureKubernetesEnvironment("aks");var gpuPool = aks.AddNodePool("gpupool", "Standard_NC6s_v3", minCount: 0, maxCount: 5);
builder.AddContainer("ml-worker", "my-ml-image") .WithNodePool(gpuPool);const aks = await builder.addAzureKubernetesEnvironment('aks');const gpuPool = await aks.addNodePool('gpupool', 'Standard_NC6s_v3', 0, 5);
const worker = await builder.addContainer('ml-worker', 'my-ml-image');await worker.withNodePool(gpuPool);Deploy to AKS
Section titled “Deploy to AKS”Deploy your application to AKS with a single command:
aspire deployAspire performs the following steps:
-
Provisions Azure infrastructure — creates the AKS cluster, ACR, managed identity, and any Azure resources defined in your AppHost.
-
Builds container images — builds Docker images for your project and container resources.
-
Pushes images to ACR — tags and pushes the built images to the provisioned Azure Container Registry.
-
Generates and installs Helm charts — creates Kubernetes manifests from your app model and installs them to the AKS cluster using Helm.
Expose your app to the internet
Section titled “Expose your app to the internet”By default, the services Aspire deploys to AKS are reachable only from inside the cluster. To accept traffic from the public internet — for example a web frontend or a public API — you add an Application Gateway for Containers (AGC) load balancer and a Gateway API Gateway to your AppHost, then opt into automatic HTTPS with cert-manager. Everything in this section is provisioned by aspire deploy alongside the rest of your infrastructure; you don’t run any az aks, az network alb, or helm install commands by hand.
This walkthrough uses the integrated AGC + cert-manager APIs that ship with 📦 Aspire.Hosting.Azure.Kubernetes and require no additional package. If you have a pre-existing AKS cluster that wasn’t provisioned by Aspire, see the bring-your-own-cluster walkthroughs for Gateway API on AKS and Ingress on AKS instead.
How AGC ingress fits together
Section titled “How AGC ingress fits together”When you opt in via AddLoadBalancer, Aspire flips the AKS cluster’s Bicep to enable the AKS-managed Gateway API and Application Load Balancer ingress profiles. After deploy, the cluster runs the Microsoft-managed AGC controller, which watches for Aspire’s generated Gateway and HTTPRoute resources and programs an Azure-hosted HTTPS frontend automatically.
The pieces fit together like this:
| Aspire AppHost call | What it produces in Azure / Kubernetes |
|---|---|
AddAzureKubernetesEnvironment | AKS cluster (with Gateway API + ALB ingress profile when an LB is added) and ACR |
AddAzureVirtualNetwork + AddSubnet | A VNet and subnets — one for AKS nodes, one (or more) delegated to AGC |
AddLoadBalancer(name, subnet) | An AGC ApplicationLoadBalancer CR plus role assignments on the subnet |
AddGateway(name).WithLoadBalancer(lb) | A Gateway API Gateway resource attached to the AGC frontend |
WithRoute(path, endpoint) | An HTTPRoute that points the path at your service |
AddCertManager(name).AddIssuer(...) | cert-manager Helm release plus a ClusterIssuer |
WithTls(issuer) | An HTTPS listener and a cert-manager-issued certificate per gateway hostname |
Prerequisites for AGC
Section titled “Prerequisites for AGC”Application Gateway for Containers ingress on AKS uses preview Azure features that need to be registered once per subscription. Run these commands and wait for both feature registrations to report Registered:
az provider register --namespace Microsoft.ContainerServiceaz provider register --namespace Microsoft.ServiceNetworking
az feature register --namespace Microsoft.ContainerService \ --name ManagedGatewayAPIPreviewaz feature register --namespace Microsoft.ContainerService \ --name ApplicationLoadBalancerPreview
# Wait until both features are Registered before deployingaz feature show --namespace Microsoft.ContainerService \ --name ApplicationLoadBalancerPreview --query properties.state -o tsv
az provider register --namespace Microsoft.ContainerServiceaz provider register --namespace Microsoft.ContainerServiceaz provider register --namespace Microsoft.ServiceNetworking
az feature register --namespace Microsoft.ContainerService ` --name ManagedGatewayAPIPreviewaz feature register --namespace Microsoft.ContainerService ` --name ApplicationLoadBalancerPreview
# Wait until both features are Registered before deployingaz feature show --namespace Microsoft.ContainerService ` --name ApplicationLoadBalancerPreview --query properties.state -o tsv
az provider register --namespace Microsoft.ContainerServiceAdd a virtual network and subnets
Section titled “Add a virtual network and subnets”AGC requires a dedicated subnet for its frontend that is delegated to Microsoft.ServiceNetworking/trafficControllers. The AKS node subnet and the AGC subnet must not overlap, and the AKS service CIDR defaults to 10.0.0.0/16 — pick a VNet range that avoids it.
#pragma warning disable ASPIREAZURE003 // AddSubnet is evaluation-only
var vnet = builder.AddAzureVirtualNetwork("vnet", "10.100.0.0/16");var aksSubnet = vnet.AddSubnet("aks-nodes", "10.100.0.0/22");var albSubnet = vnet.AddSubnet("alb-public", "10.100.4.0/24");
var aks = builder.AddAzureKubernetesEnvironment("aks") .WithSubnet(aksSubnet);const vnet = await builder.addAzureVirtualNetwork('vnet', '10.100.0.0/16');const aksSubnet = await vnet.addSubnet('aks-nodes', '10.100.0.0/22');const albSubnet = await vnet.addSubnet('alb-public', '10.100.4.0/24');
const aks = await builder.addAzureKubernetesEnvironment('aks');await aks.withSubnet(aksSubnet);Add an Application Gateway for Containers load balancer
Section titled “Add an Application Gateway for Containers load balancer”AddLoadBalancer returns a typed handle that gateways and ingresses attach to. Behind the scenes it delegates the supplied subnet to AGC, grants the controller’s managed identity Network Contributor on the subnet, and applies the ApplicationLoadBalancer custom resource once the azure-alb-external GatewayClass is ready in the cluster.
var publicLb = aks.AddLoadBalancer("public", albSubnet);const publicLb = await aks.addLoadBalancer('public', albSubnet);Add a Gateway with a route to your service
Section titled “Add a Gateway with a route to your service”AddGateway declares a Kubernetes Gateway API Gateway. WithLoadBalancer attaches it to the AGC ALB you just created and defaults the gatewayClassName to azure-alb-external. WithRoute adds an HTTPRoute that forwards a path to the named endpoint on one of your services.
var api = builder.AddProject<Projects.MyApi>("api") .WithExternalHttpEndpoints();
aks.AddGateway("storefront") .WithLoadBalancer(publicLb) .WithRoute("/", api.GetEndpoint("http"));const api = await builder .addNodeApp('api', './api', 'src/index.ts') .withHttpEndpoint({ env: 'PORT' }) .withExternalHttpEndpoints();
const storefront = await aks.addGateway('storefront');await storefront.withLoadBalancer(publicLb);await storefront.withRoute('/', api.getEndpoint('http'));Use the host overload of WithRoute when you need to send different hostnames to different backends — for example WithRoute("api.contoso.com", "/", api.GetEndpoint("http")).
First deploy: HTTP on the AGC FQDN
Section titled “First deploy: HTTP on the AGC FQDN”Run aspire deploy. After it finishes, AGC assigns the gateway a frontend FQDN of the form <random>.fz<n>.alb.azure.com. Read it from the deployed Gateway and curl your service to confirm the load balancer and route are wired up before adding TLS:
kubectl get gateway storefront -o jsonpath='{.status.addresses[0].value}'curl http://<the-fqdn-printed-above>/Add automatic HTTPS with cert-manager
Section titled “Add automatic HTTPS with cert-manager”AddCertManager installs the upstream cert-manager Helm chart with the CRDs and Gateway API watcher enabled and the --force-conflicts flag set so AKS’s Azure Policy add-on doesn’t break subsequent upgrades. AddIssuer adds a typed ClusterIssuer parented to that installation, and WithTls(issuer) on a gateway adds an HTTPS listener that cert-manager will populate with a real certificate.
var certManager = aks.AddCertManager("cert-manager");
var letsencrypt = certManager.AddIssuer("letsencrypt-prod") .WithLetsEncryptProduction("ops@contoso.com") .WithHttp01Solver();
aks.AddGateway("storefront") .WithLoadBalancer(publicLb) .WithRoute("/", api.GetEndpoint("http")) .WithTls(letsencrypt);const certManager = await aks.addCertManager('cert-manager');
const letsencrypt = await certManager.addIssuer('letsencrypt-prod');await letsencrypt.withLetsEncryptProduction('ops@contoso.com');await letsencrypt.withHttp01Solver();
const storefront = await aks.addGateway('storefront');await storefront.withLoadBalancer(publicLb);await storefront.withRoute('/', api.getEndpoint('http'));await storefront.withTls(letsencrypt);WithTls(issuer) is the strongly-typed overload — it validates at AppHost-build time that the gateway and the issuer’s cert-manager installation belong to the same Kubernetes environment. There is also a no-argument WithTls() (auto-generated secret name, no issuer annotation) and a WithTls(secretName) overload for cases where you manage the certificate yourself.
Calling WithTls flips the gateway to an HTTPS-first posture:
- A
301 Moved Permanentlyredirect is added to the HTTP listener so requests on port 80 are upgraded tohttps://automatically. The cert-manager HTTP-01 solver registers an exact-match route for/.well-known/acme-challenge/<token>and wins over the redirect’s/prefix, so ACME continues to work. - A
Strict-Transport-Security: max-age=31536000response header is emitted on HTTPS responses so returning browsers refuse plain HTTP for a year.
To tune or disable these defaults, pass an options callback. The includeSubDomains and preload HSTS flags are off by default because they’re hard to reverse once browsers cache them — opt in explicitly when you’re ready to commit to the hostname:
aks.AddGateway("storefront") .WithLoadBalancer(publicLb) .WithRoute("/", api.GetEndpoint("http")) .WithTls(letsencrypt, options => { options.Hsts.IncludeSubDomains = true; options.Hsts.Preload = true; });await storefront.withTls(letsencrypt, { hsts: { includeSubDomains: true, preload: true },});How the certificate gets issued
Section titled “How the certificate gets issued”cert-manager validates domain ownership using the HTTP-01 challenge configured by WithHttp01Solver — it asks the gateway to serve a token at /.well-known/acme-challenge/<token> and verifies the response. There is no DNS API access required, so this works for the auto-assigned *.alb.azure.com FQDN out of the box.
To break the chicken-and-egg problem where AGC won’t program a Gateway whose HTTPS listener references a missing secret, Aspire pre-creates a self-signed bootstrap secret on the first deploy. Once AGC publishes the gateway’s FQDN, Aspire patches the listener with the real hostname and cert-manager swaps the bootstrap certificate for a Let’s Encrypt one. You may briefly see a self-signed certificate during the first deploy — this is expected.
Verify the certificate
Section titled “Verify the certificate”After the deploy completes, watch the Certificate resource transition to Ready=True:
kubectl get certificatekubectl describe certificate storefront-tlsConfirm port 80 is now redirecting to HTTPS:
curl -i http://<the-fqdn>/# HTTP/1.1 301 Moved Permanently# Location: https://<the-fqdn>/Then curl the gateway over HTTPS and confirm the certificate chain comes from Let’s Encrypt and the Strict-Transport-Security header is present:
curl -sIv https://<the-fqdn>/ 2>&1 | grep -iE "issuer:|strict-transport-security:"# * issuer: C=US; O=Let's Encrypt; CN=R10# strict-transport-security: max-age=31536000Use a custom domain
Section titled “Use a custom domain”The *.alb.azure.com FQDN is fine for testing but real applications need a hostname customers can remember. Adding a custom domain is a small change to the AppHost plus a DNS record at your registrar.
Tell the gateway about your hostname
Section titled “Tell the gateway about your hostname”WithHostname adds the hostname to the gateway’s HTTPRoutes and to the HTTPS listener so cert-manager issues the certificate for the right name. Use the parameter overload when the hostname differs across deployment environments.
var hostname = builder.AddParameter("hostname"); // e.g. "api.contoso.com"
aks.AddGateway("storefront") .WithLoadBalancer(publicLb) .WithHostname(hostname) .WithRoute("/", api.GetEndpoint("http")) .WithTls(letsencrypt);const hostname = builder.addParameter('hostname'); // e.g. "api.contoso.com"
const storefront = await aks.addGateway('storefront');await storefront.withLoadBalancer(publicLb);await storefront.withHostname(hostname);await storefront.withRoute('/', api.getEndpoint('http'));await storefront.withTls(letsencrypt);Provide the hostname value at deploy time:
aspire deploy --parameter hostname=api.contoso.comPoint your DNS at the AGC frontend
Section titled “Point your DNS at the AGC frontend”Read the AGC FQDN from the deployed gateway, then create a CNAME record at your DNS provider that points your custom hostname at it. The DNS provider doesn’t matter — Azure DNS, Cloudflare, Route 53, GoDaddy, anything that supports CNAME records will work.
kubectl get gateway storefront -o jsonpath='{.status.addresses[0].value}'# 8a3...cd.fz12.alb.azure.comapi.contoso.com. CNAME 8a3...cd.fz12.alb.azure.com.Re-deploy and verify
Section titled “Re-deploy and verify”Re-running aspire deploy is idempotent. Once your DNS record propagates, cert-manager’s HTTP-01 challenge will succeed against the new hostname and a fresh certificate is issued automatically:
curl -v https://api.contoso.com/ 2>&1 | grep -i "issuer:\|subject:"Publish AKS artifacts
Section titled “Publish AKS artifacts”To generate deployment artifacts without deploying, use aspire publish:
aspire publish -o aks-artifactsThis generates Helm charts and Bicep infrastructure templates that you can review, customize, and deploy using your own CI/CD pipeline or GitOps workflow.
Azure-specific considerations
Section titled “Azure-specific considerations”Authentication
Section titled “Authentication”By default, local aspire deploy uses Azure CLI credentials. If you want a different credential source, set Azure:CredentialSource to one of the supported values: AzureCli, AzureDeveloperCli, VisualStudio, VisualStudioCode, AzurePowerShell, InteractiveBrowser, or Default.
For detailed Azure authentication configuration, see Deploy to Azure.
Azure settings
Section titled “Azure settings”After authentication, aspire deploy needs a target subscription and location. These are configured via external parameters or environment variables:
Azure:SubscriptionId— the Azure subscription to deploy toAzure:Location— the Azure region for resource provisioning (for example,eastus2)
Customizing Azure resources
Section titled “Customizing Azure resources”Use PublishAsKubernetesService to customize the Kubernetes resources generated for individual services:
using Aspire.Hosting.Kubernetes.Resources;
builder.AddProject<Projects.MyApi>("api") .PublishAsKubernetesService(resource => { // Scale to 3 replicas if (resource.Workload is Deployment deployment) { deployment.Spec.Replicas = 3; } });const api = await builder .addNodeApp('api', './api', 'src/index.ts') .withHttpEndpoint({ env: 'PORT' });await api.publishAsKubernetesService(async (resource) => { // Scale to 3 replicas await resource.workload.spec.replicas.set(3);});Install external Helm charts
Section titled “Install external Helm charts”Use AddHelmChart on the AKS environment to install pre-existing Helm charts — such as cert-manager or NGINX ingress — as post-deploy pipeline steps alongside your application.
See Install external Helm charts for a full walkthrough of adding external charts, configuring Helm values, overriding namespaces and release names, and choosing destroy-time uninstall behavior.
Troubleshooting
Section titled “Troubleshooting”AKS cluster not reachable after deploy
Section titled “AKS cluster not reachable after deploy”After aspire deploy provisions the AKS cluster, your local kubectl context is configured automatically. If you can’t reach the cluster, ensure your Azure CLI session is active and fetch credentials:
az aks get-credentials --resource-group <resource-group> --name <cluster-name>Image pull failures
Section titled “Image pull failures”If pods fail with ImagePullBackOff, verify that the AKS cluster has the correct role assignment to pull from the provisioned ACR:
az aks check-acr --resource-group <resource-group> --name <cluster-name> --acr <acr-name>.azurecr.ioCertificate stays Ready=False
Section titled “Certificate stays Ready=False”If kubectl get certificate reports the certificate as not ready for more than a few minutes, describe it and the cert-manager Order/Challenge resources to see what failed:
kubectl describe certificate storefront-tlskubectl describe challenge -ACommon causes:
- DNS hasn’t propagated yet when using a custom domain — Let’s Encrypt fetches
http://<your-host>/.well-known/acme-challenge/...and that has to resolve to the AGC frontend. - HTTP traffic is blocked between Let’s Encrypt and the gateway — HTTP-01 needs port 80 open. AGC keeps the HTTP listener that
AddGatewaygenerates open by default; don’t remove it. - Production rate limit hit while iterating — switch the issuer to
WithLetsEncryptStaginguntil everything else works, then flip back to production. - The contact email was rejected by ACME — see Let’s Encrypt rejects the contact email.
- The bootstrap TLS secret is missing — see Gateway HTTPS listener reports
InvalidCertificateRef.
Let’s Encrypt rejects the contact email
Section titled “Let’s Encrypt rejects the contact email”The first time you aspire deploy with WithLetsEncryptProduction(email) or WithLetsEncryptStaging(email), cert-manager creates an ACME account using that email. Let’s Encrypt validates the address and refuses to register accounts whose email uses a reserved or forbidden domain. The ClusterIssuer will report Ready=False and kubectl describe clusterissuer <name> will show one of:
Domain name does not end with a valid public suffix— the TLD isn’t a registered public suffix. Reserved TLDs from RFC 2606 (.test,.example,.invalid,.localhost) are common offenders.contact email has forbidden domain example.com— Let’s Encrypt explicitly blocks the IANA example domains (example.com,example.net,example.org).
Use a real address on a registered public domain (your corporate domain, or a personal mailbox like you@gmail.com). The address must be syntactically valid, but Let’s Encrypt does not send mail to it during provisioning — it’s the contact for expiry warnings.
Gateway HTTPS listener reports InvalidCertificateRef
Section titled “Gateway HTTPS listener reports InvalidCertificateRef”If kubectl describe gateway storefront shows Secret default/storefront-tls does not exist on the HTTPS listener, AGC will refuse to program the HTTPS frontend and any in-flight cert-manager Challenge will stall because its solver HTTPRoute can’t be attached. This typically happens if you delete the storefront-tls secret manually to “force a reissue” — the bootstrap self-signed secret Aspire pre-creates is also wiped, breaking the chicken-and-egg pattern described in How the certificate gets issued.
Recover by recreating a placeholder secret so AGC can program the listener again — cert-manager will overwrite it with a real certificate within a minute:
TMPDIR=$(mktemp -d)openssl req -x509 -newkey rsa:2048 -nodes -days 1 \ -keyout "$TMPDIR/key.pem" -out "$TMPDIR/cert.pem" \ -subj "/CN=bootstrap.invalid"kubectl create secret tls storefront-tls \ --cert="$TMPDIR/cert.pem" --key="$TMPDIR/key.pem" -n defaultrm -rf "$TMPDIR"To force a legitimate reissue without removing the secret, annotate the Certificate instead:
kubectl cert-manager renew storefront-tlsGateway never gets an FQDN
Section titled “Gateway never gets an FQDN”If kubectl get gateway shows no address after aspire deploy succeeds, check that the AGC ingress profile actually came up on the cluster:
kubectl get gatewayclass azure-alb-externalkubectl get pods -n kube-system -l app=alb-controllerIf the azure-alb-external GatewayClass doesn’t exist, the most common cause is that the ManagedGatewayAPIPreview and ApplicationLoadBalancerPreview features aren’t registered on the subscription — re-run the az feature register commands from Prerequisites for AGC, then aspire deploy again.
helm upgrade fails on cert-manager SSA conflicts
Section titled “helm upgrade fails on cert-manager SSA conflicts”AddCertManager already passes --force-conflicts to helm upgrade to handle the field-manager conflict that AKS’s Azure Policy add-on creates against cert-manager’s ValidatingWebhookConfiguration. If you install cert-manager yourself with raw AddHelmChart instead, add .WithForceConflicts() to the chart resource, otherwise the second deploy will fail with a server-side-apply conflict on .webhooks[*].namespaceSelector.
See also
Section titled “See also”- AKS integration
- Kubernetes integration
- Install external Helm charts
- Deploy to Kubernetes
- Expose services with Ingress and Gateway API
- Gateway API on AKS (bring-your-own cluster)
- Ingress on AKS (bring-your-own cluster)
- Deploy to Azure
- Pipelines and app topology
- Azure Kubernetes Service documentation
- Application Gateway for Containers documentation
- cert-manager documentation