Azure Kubernetes Service (AKS)
Introduction
Section titled “Introduction”Azure Kubernetes Service (AKS) is Azure’s managed Kubernetes offering. Azure operates the control plane while you manage node pools of worker machines that run your workloads. For more information, see What is Azure Kubernetes Service?.
LocalStack for Azure creates real, working Kubernetes clusters on your machine. az aks create
produces a cluster backed by k3d that you can reach with kubectl, so manifests,
Helm charts, and operators behave as they would against a cluster in the cloud. The supported APIs
are available on our API Coverage section, which provides information on the extent
of AKS’s integration with LocalStack.
Getting started
Section titled “Getting started”This guide is designed for users new to AKS and assumes basic knowledge of the Azure CLI, kubectl,
and our lstk az proxy.
Launch LocalStack using your preferred method. For more information, see Introduction to LocalStack for Azure. Once the container is running, enable Azure CLI interception by running:
lstk az start-interceptionThis command points the az CLI away from the public Azure management REST API and toward the
LocalStack for Azure emulator API. To revert this configuration, run:
lstk az stop-interceptionThis reconfigures the az CLI to send commands to the official Azure management REST API.
Create a resource group
Section titled “Create a resource group”Create a resource group to hold the cluster:
az group create \ --name rg-aks-demo \ --location westeurope{ "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-aks-demo", "location": "westeurope", "managedBy": null, "name": "rg-aks-demo", "properties": { "provisioningState": "Succeeded" }, "tags": null, "type": "Microsoft.Resources/resourceGroups"}Create a cluster
Section titled “Create a cluster”Create a cluster with a single node in its system node pool:
az aks create \ --resource-group rg-aks-demo \ --name aks-demo \ --node-count 1 \ --generate-ssh-keysThe command returns when the cluster is ready to use. Locally that takes a couple of minutes: the emulator provisions a k3d cluster, so what you get back is a live API server, not a mock.
{ "currentKubernetesVersion": "1.34.4", "fqdn": "aks-demo-rg-aks-demo-000000-oq7mpqgx.hcp.westeurope.azmk8s.io", "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/rg-aks-demo/providers/Microsoft.ContainerService/managedClusters/aks-demo", "kubernetesVersion": "1.34", "location": "westeurope", "name": "aks-demo", "nodeResourceGroup": "MC_rg-aks-demo_aks-demo_westeurope", "powerState": { "code": "Running" }, "provisioningState": "Succeeded", ...}Show and list clusters
Section titled “Show and list clusters”Retrieve the details of a single cluster:
az aks show \ --resource-group rg-aks-demo \ --name aks-demo{ "currentKubernetesVersion": "1.34.4", "dnsPrefix": "aks-demo-rg-aks-demo-000000", "kubernetesVersion": "1.34", "location": "westeurope", "name": "aks-demo", "nodeResourceGroup": "MC_rg-aks-demo_aks-demo_westeurope", "provisioningState": "Succeeded", ...}List the clusters in a resource group:
az aks list \ --resource-group rg-aks-demo \ --output tableName Location ResourceGroup KubernetesVersion CurrentKubernetesVersion ProvisioningState Fqdn-------- ---------- --------------- ------------------- -------------------------- ------------------- -------------------------------------------------------------aks-demo westeurope rg-aks-demo 1.34 1.34.4 Succeeded aks-demo-rg-aks-demo-000000-oq7mpqgx.hcp.westeurope.azmk8s.ioUpdate a cluster
Section titled “Update a cluster”az aks update changes the properties of an existing cluster. The following example sets resource
tags:
az aks update \ --resource-group rg-aks-demo \ --name aks-demo \ --tags environment=local team=platform{ "name": "aks-demo", "provisioningState": "Succeeded", "tags": { "environment": "local", "team": "platform" }, ...}Connect with kubectl
Section titled “Connect with kubectl”Merge the cluster credentials into your local kubeconfig:
az aks get-credentials \ --resource-group rg-aks-demo \ --name aks-demo \ --overwrite-existingMerged "aks-demo" as current context in /home/user/.kube/configQuery the nodes:
kubectl get nodesNAME STATUS ROLES AGE VERSIONaks-nodepool1-5829393-vmss000000 Ready <none> 99s v1.36.2+k3s1k3d-aks-demo-7da4c24d-server-0 Ready control-plane 2m1s v1.36.2+k3s1Manage node pools
Section titled “Manage node pools”Add a user node pool with two nodes:
az aks nodepool add \ --resource-group rg-aks-demo \ --cluster-name aks-demo \ --name workers \ --mode User \ --node-count 2{ "count": 2, "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/rg-aks-demo/providers/Microsoft.ContainerService/managedClusters/aks-demo/agentPools/workers", "mode": "User", "name": "workers", "orchestratorVersion": "1.34", "osType": "Linux", "provisioningState": "Succeeded", ...}List the node pools of the cluster:
az aks nodepool list \ --resource-group rg-aks-demo \ --cluster-name aks-demo \ --output tableName OsType VmSize Count MaxPods ProvisioningState Mode--------- -------- -------- ------- --------- ------------------- ------nodepool1 Linux 1 250 Succeeded Systemworkers Linux 2 250 Succeeded UserInspect a single node pool:
az aks nodepool show \ --resource-group rg-aks-demo \ --cluster-name aks-demo \ --name workers{ "count": 2, "mode": "User", "name": "workers", "orchestratorVersion": "1.34", "osType": "Linux", "powerState": { "code": "Running" }, "provisioningState": "Succeeded", ...}Delete the node pool when you no longer need it:
az aks nodepool delete \ --resource-group rg-aks-demo \ --cluster-name aks-demo \ --name workersStop, start, and delete
Section titled “Stop, start, and delete”Stop the cluster to free local resources while preserving its state:
az aks stop \ --resource-group rg-aks-demo \ --name aks-demoVerify that the cluster has stopped by confirming that powerState reports Stopped:
az aks show \ --resource-group rg-aks-demo \ --name aks-demo \ --query powerState.code \ --output tsvStart the cluster again. It restarts with the previous control plane state and number of agent nodes:
az aks start \ --resource-group rg-aks-demo \ --name aks-demoDelete the cluster once you are done. A deleted cluster cannot be recovered:
az aks delete \ --resource-group rg-aks-demo \ --name aks-demo \ --yesTeardown
Section titled “Teardown”Remove the resource group and any resources it still contains:
az group delete \ --name rg-aks-demo \ --yesDisable Azure CLI interception to point the az CLI back to the official Azure management REST API:
lstk az stop-interceptionAutoscale workloads with KEDA
Section titled “Autoscale workloads with KEDA”Kubernetes Event-driven Autoscaling (KEDA)
scales workloads from the amount of pending work instead of CPU or memory usage. Its ScaledObject
resources can scale consumers against emulated Service Bus queues, Storage queues, and Event Hubs
backlogs.
Enable the managed KEDA add-on, OIDC issuer, and Workload Identity on the cluster:
az aks update \ --resource-group rg-aks-demo \ --name aks-demo \ --enable-keda \ --enable-oidc-issuer \ --enable-workload-identityVerify that the add-on is enabled and its deployments are ready:
az aks show \ --resource-group rg-aks-demo \ --name aks-demo \ --query workloadAutoScalerProfile.keda.enabled \ --output tsv
kubectl get deployments \ --namespace kube-system \ --selector app.kubernetes.io/part-of=keda-operatortrue
NAME READY UP-TO-DATE AVAILABLE AGEkeda-admission 1/1 1 1 1mkeda-metrics-apiserver 1/1 1 1 1mkeda-operator 1/1 1 1 1mConfigure Workload Identity
Section titled “Configure Workload Identity”KEDA 2.15 and later use Microsoft Entra Workload ID instead of Azure AD Pod Identity. Create a user-assigned managed identity and federate it to the KEDA operator service account:
KEDA_IDENTITY=aks-keda-identity
az identity create \ --name "$KEDA_IDENTITY" \ --resource-group rg-aks-demo \ --location westeurope
KEDA_CLIENT_ID=$(az identity show \ --name "$KEDA_IDENTITY" \ --resource-group rg-aks-demo \ --query clientId \ --output tsv)
OIDC_ISSUER=$(az aks show \ --resource-group rg-aks-demo \ --name aks-demo \ --query oidcIssuerProfile.issuerUrl \ --output tsv)
az identity federated-credential create \ --name keda-operator \ --identity-name "$KEDA_IDENTITY" \ --resource-group rg-aks-demo \ --issuer "$OIDC_ISSUER" \ --subject system:serviceaccount:kube-system:keda-operator \ --audience api://AzureADTokenExchangeAnnotate the operator service account with the identity’s client ID, then restart the operator to apply the identity:
kubectl annotate serviceaccount keda-operator \ --namespace kube-system \ "azure.workload.identity/client-id=${KEDA_CLIENT_ID}" \ --overwrite
kubectl rollout restart deployment keda-operator \ --namespace kube-system
kubectl rollout status deployment keda-operator \ --namespace kube-system \ --timeout=300sGrant the identity the data-plane role required by the scaler. This Service Bus example creates a queue and grants Azure Service Bus Data Owner on the namespace:
SERVICE_BUS_NAMESPACE=sbkedademoSERVICE_BUS_QUEUE=work-items
az servicebus namespace create \ --name "$SERVICE_BUS_NAMESPACE" \ --resource-group rg-aks-demo \ --location westeurope \ --sku Standard
az servicebus queue create \ --name "$SERVICE_BUS_QUEUE" \ --namespace-name "$SERVICE_BUS_NAMESPACE" \ --resource-group rg-aks-demo
KEDA_PRINCIPAL_ID=$(az identity show \ --name "$KEDA_IDENTITY" \ --resource-group rg-aks-demo \ --query principalId \ --output tsv)
SERVICE_BUS_ID=$(az servicebus namespace show \ --name "$SERVICE_BUS_NAMESPACE" \ --resource-group rg-aks-demo \ --query id \ --output tsv)
az role assignment create \ --role "Azure Service Bus Data Owner" \ --assignee-object-id "$KEDA_PRINCIPAL_ID" \ --assignee-principal-type ServicePrincipal \ --scope "$SERVICE_BUS_ID"Configure private scaler endpoints
Section titled “Configure private scaler endpoints”Service Bus and Storage Queue scalers use cloud: Private when targeting the emulator. Derive the
endpointSuffix from the endpoints returned by Azure Resource Manager instead of hardcoding it:
SERVICE_BUS_ENDPOINT=$(az servicebus namespace show \ --name "$SERVICE_BUS_NAMESPACE" \ --resource-group rg-aks-demo \ --query serviceBusEndpoint \ --output tsv)
ARM_ENDPOINT=$(az cloud show \ --query endpoints.resourceManager \ --output tsv)
SERVICE_BUS_HOST="${SERVICE_BUS_ENDPOINT#*://}"SERVICE_BUS_HOST="${SERVICE_BUS_HOST%%/*}"SERVICE_BUS_HOST="${SERVICE_BUS_HOST%%:*}"ARM_ENDPOINT="${ARM_ENDPOINT%/}"ARM_PORT="${ARM_ENDPOINT##*:}"if [[ "$ARM_PORT" == "$ARM_ENDPOINT" || "$ARM_PORT" == *"/"* ]]; then ARM_PORT=443fi
SERVICE_BUS_SUFFIX="${SERVICE_BUS_HOST#${SERVICE_BUS_NAMESPACE}.}:${ARM_PORT}"printf '%s\n' "$SERVICE_BUS_SUFFIX"servicebus.azure.localhost.localstack.cloud:4566Use the derived suffix in the ScaledObject. The target Deployment can start with zero replicas;
KEDA creates and manages its Horizontal Pod Autoscaler:
apiVersion: keda.sh/v1alpha1kind: TriggerAuthenticationmetadata: name: service-bus-authspec: podIdentity: provider: azure-workload identityId: "<managed-identity-client-id>"---apiVersion: keda.sh/v1alpha1kind: ScaledObjectmetadata: name: service-bus-scalerspec: scaleTargetRef: name: service-bus-consumer pollingInterval: 5 cooldownPeriod: 30 minReplicaCount: 0 maxReplicaCount: 4 triggers: - type: azure-servicebus metadata: queueName: work-items namespace: sbkedademo messageCount: "5" cloud: Private endpointSuffix: servicebus.azure.localhost.localstack.cloud:4566 authenticationRef: name: service-bus-authFor an Azure Storage Queue scaler, derive the suffix from the storage account’s
primaryEndpoints.queue property and use the same private-cloud pattern:
triggers: - type: azure-queue metadata: queueName: jobs accountName: stkedademo queueLength: "5" cloud: Private endpointSuffix: queue.core.azure.localhost.localstack.cloud:4566Event Hubs scalers use development-emulator connection strings exposed on the target deployment:
triggers: - type: azure-eventhub metadata: consumerGroup: keda-consumer unprocessedEventThreshold: "5" blobContainer: eh-checkpoints checkpointStrategy: blobMetadata connectionFromEnv: EVENTHUB_CONNECTION storageConnectionFromEnv: STORAGE_CONNECTIONLocalStack returns Service Bus and Event Hubs connection strings with an sb:// endpoint and
UseDevelopmentEmulator=true.
The AKS KEDA tutorials contain complete producer and consumer applications for Service Bus, Storage Queue, and Event Hubs. Each tutorial verifies that a backlog scales its consumer from zero to multiple replicas, drains without dead-lettering messages, and scales back to zero.
Observed scale-out: 0 -> 2 -> 3 -> 4PASS: the consumer drained the work-items queue with no dead-lettered messagesPASS: the sb-consumer deployment scaled back to zero replicasSUCCESS: KEDA scaled the sb-consumer deployment from zero to 4 replicas and back to zeroDisable KEDA
Section titled “Disable KEDA”Delete your KEDA custom resources before disabling the add-on:
kubectl delete scaledobject service-bus-scalerkubectl wait \ --for=delete scaledobject/service-bus-scaler \ --timeout=60sThen disable the managed add-on with az aks update:
az aks update \ --resource-group rg-aks-demo \ --name aks-demo \ --disable-keda \ --query workloadAutoScalerProfile.keda.enabled \ --output tsvfalseFeatures
Section titled “Features”The local control plane implements the following capabilities:
- Networking: Azure CNI overlay with the Cilium data plane, Cilium and Calico network policies, Hubble observability, and the managed Gateway API add-on with NGINX Gateway Fabric as its implementation.
- Storage: The Secrets Store CSI driver for Azure Key Vault and the Azure Files CSI driver. The Azure Disk CSI driver is in progress.
- Scaling: The cluster autoscaler, node auto-provisioning based on the AKS Karpenter provider, the Kubernetes Event-driven Autoscaling (KEDA) add-on, and the Vertical Pod Autoscaler.
- Identity: Microsoft Entra Workload ID with a working OIDC issuer, so pods can exchange service account tokens for Azure credentials without secrets.
- Operations: Multiple node pools with tags, labels, and taints; the Azure cloud controller
manager reconciling
LoadBalancerservices; and cluster stop and start. - Tooling: The same clusters can be provisioned with the Azure CLI, Terraform, or Bicep.
Cluster-creation scripts
Section titled “Cluster-creation scripts”The aks-samples repository provides two interchangeable scripts that provision a production-shaped cluster, complete with a virtual network, a container registry, a Log Analytics workspace, and system and user node pools. Both scripts run unchanged against real Azure and the emulator; they differ only in the cluster identity:
| Script | Cluster identity | When to use |
|---|---|---|
| 01-system-assigned-managed-identity.sh | System-assigned managed identity | Simplest option: Azure creates and manages the identity lifecycle together with the cluster. |
| 01-user-assigned-managed-identity.sh | User-assigned managed identity | Use when you need a stable, pre-created identity that can be reused across resources and granted role assignments ahead of time. |
Both scripts are idempotent and safe to re-run. The same folder also contains optional add-on installers for Prometheus, the NGINX ingress controller, the Gateway API CRDs, NGINX Gateway Fabric, and cert-manager.
Samples
Section titled “Samples”Every sample deploys the same Vacation Planner web application, a small Python Flask single-page app. Only the data service, its provisioning, and the way the app authenticates to it change from one sample to the next.
| Sample | Description |
|---|---|
| web-app-sql-database | Stores activities in an Azure SQL Database, connecting with a SQL login over TDS. |
| web-app-mysql-flexible-server | Stores activities in an Azure Database for MySQL flexible server. |
| web-app-postgresql-flexible-server | Stores activities in an Azure Database for PostgreSQL flexible server. |
| web-app-in-cluster-postgresql | Stores activities in an in-cluster PostgreSQL database deployed as a Kubernetes StatefulSet, with a primary and two streaming replicas. |
| web-app-cosmosdb-mongodb-api | Stores activities in a collection of an Azure Cosmos DB for MongoDB account. |
| web-app-cosmosdb-nosql-api | Stores activities in a container of an Azure Cosmos DB for NoSQL account. |
| web-app-blob-storage | Stores activities in an Azure Blob Storage container, using a connection string. |
| web-app-file-storage | Stores activities as text files on an Azure Files share mounted by the Azure Files CSI driver, over either SMB or NFS. |
| web-app-managed-identity | Stores activities in Azure Blob Storage, authenticating with Microsoft Entra Workload ID instead of a secret, and optionally exposes the app through the Gateway API with a managed TLS certificate. |
Tutorials
Section titled “Tutorials”The same repository includes standalone tutorials that exercise individual AKS capabilities. Unlike the samples, they do not deploy the web application:
| Tutorial | Description |
|---|---|
| policies | Kubernetes network policies that enforce zero-trust traffic control with Calico and Cilium: cluster-wide default-deny, DNS-aware egress, and L3/L4/L7 ingress. |
| ccm | Exercises the Azure cloud controller manager: public and internal LoadBalancer services, source ranges, the nodeIP backend-pool variant, and an NGINX ingress controller. |
| gateway-api | Enables the managed Gateway API CRDs, installs NGINX Gateway Fabric, and routes traffic to a backend through a Gateway and an HTTPRoute. |
| keda | Event-driven autoscaling with the KEDA add-on: a producer creates a backlog on an Azure event source, and a ScaledObject scales a consumer from zero to four replicas and back. |
| keda/service-bus | Scales a consumer on an Azure Service Bus queue with the azure-servicebus scaler, authenticating with Microsoft Entra Workload ID. Start here if you are new to KEDA. |
| keda/queue-storage | Scales a consumer on an Azure Storage queue with the azure-queue scaler. Workload identity is used end to end, so no data-plane secret exists anywhere. |
| keda/event-hubs | Scales a consumer on an Azure Event Hubs hub with the azure-eventhub scaler, whose backlog is the distance between the last enqueued event and the consumer group’s blob checkpoints. |
| key-vault-csi-driver | Mounts secrets from Azure Key Vault into a pod with the Secrets Store CSI driver, in both the workload identity and the user-assigned managed identity access modes. |
| terraform/tags-labels-taints | Deploys a modular, feature-rich AKS stack with Terraform, steering workloads across agent pools with Azure resource tags, node labels, and taints, then validates them through both the ARM and Kubernetes APIs. |
| bicep/tags-labels-taints | The same modular AKS stack built with Bicep, with parameters and outputs that mirror the Terraform tutorial one-to-one. |
API Coverage
Section titled “API Coverage”| Operation ▲ | Implemented ▼ |
|---|