CloudBrum
CloudBrum · Cloud decision-support platform
Azure Containers

Azure Kubernetes ServiceLast reviewedReading time~12 minLevelBeginner to advanced

Microsoft Azure · Containers · Managed Kubernetes

Kubernetes with a free control plane and two ways to run it. Microsoft hosts the Kubernetes control plane for AKS. A Standard cluster leaves node pools, networking and upgrades to you. An Automatic cluster picks sensible defaults, adds nodes on demand and applies guard rails to what you deploy. Decide early on the pricing tier, because the free one has no uptime SLA, and on the pod network, because Azure CNI Overlay and the older options behave very differently once the cluster grows.

On this page (17 sections)

    Mind map: Azure Kubernetes Service at a glance

    Azure Kubernetes Service mind map Azure Kubernetes Service at a glance: Cluster modes: Automatic, Standard, Free, Standard, Premium; Node pools: System and user pools, Spot, Windows, GPU, Node auto-provisioning; Networking: Azure CNI Overlay, Cilium dataplane, Private cluster; Identity: Entra ID sign-in, Azure RBAC, Workload ID; Registry and storage: Container Registry, Managed disks, Files, Blob CSI; Upgrades: Auto-upgrade channels, Maintenance windows, Long-Term Support; Security: Deployment safeguards, Defender for Containers, Key Vault CSI; Pricing: Tier per cluster, Node VMs, Traffic and logs. Azure Kubernetes Service at a glance Managed Kubernetes on Azure: the main building blocks and decisions CloudBrum Cloud decision-support platform CloudBrum Automatic Standard Free, Standard, Premium Cluster modes System and user pools Spot, Windows, GPU Node auto-provisioning Node pools Azure CNI Overlay Cilium dataplane Private cluster Networking Entra ID sign-in Azure RBAC Workload ID Identity Container Registry Managed disks Files, Blob CSI Registry and storage Auto-upgrade channels Maintenance windows Long-Term Support Upgrades Deployment safeguards Defender for Containers Key Vault CSI Security Tier per cluster Node VMs Traffic and logs Pricing AKS17-point analysis CloudBrum· 17-point cloud service analysis cloudbrum.com
    CloudBrum AKS at a glance · 17-point analysis Cluster modes Automatic Standard Free, Standard, Premium Node pools System and user pools Spot, Windows, GPU Node auto-provisioning Networking Azure CNI Overlay Cilium dataplane Private cluster Identity Entra ID sign-in Azure RBAC Workload ID Registry and storage Container Registry Managed disks Files, Blob CSI Upgrades Auto-upgrade channels Maintenance windows Long-Term Support Security Deployment safeguards Defender for Containers Key Vault CSI Pricing Tier per cluster Node VMs Traffic and logs CloudBrum · cloudbrum.com

    Free to share. The downloaded image includes the CloudBrum link.

    Key features (summary)

    • A hosted control planeMicrosoft runs the API server, etcd, scheduler and controllers for you, spread over availability zones when you ask for them. The Free tier charges nothing for it, which is unusual among the big clouds.
    • Automatic or StandardAKS Automatic comes with node provisioning, scaling, monitoring, Azure RBAC and deployment safeguards switched on. Standard gives you full control over node pools, networking and add-ons, and leaves the work to you.
    • Node poolsWorkers live in node pools backed by virtual machine scale sets. Keep system components in their own pool, put applications in user pools, and add Spot, GPU or Windows pools where needed.
    • Entra ID and Workload IDPeople sign in with Microsoft Entra ID and get Kubernetes rights through Azure RBAC. Pods reach Azure resources such as Blob Storage and Key Vault with Workload ID: a federated credential trusts one Kubernetes service account, so no secret is stored.
    • Overlay pod networkingAzure CNI Overlay gives pods addresses from a private range that doesn’t use up your VNet subnets, which removes the most common scaling limit of older AKS setups. Cilium can run the dataplane and enforce network policies.
    • Auto-upgrade channelsPick a channel for the Kubernetes version (patch, stable or rapid) and another for the node operating system. Maintenance windows say when AKS may do the work, and surge settings say how many nodes can be replaced at once.
    • Guard rails and scanningDeployment safeguards use Azure Policy to refuse risky manifests. Microsoft Defender for Containers scans images and watches runtime behaviour. The Key Vault provider mounts secrets as volumes.
    • Long-Term SupportThe Premium tier keeps a Kubernetes version patched for longer than the community does, for teams that can’t upgrade as often as the release cadence demands.

    Basic concepts

    Cluster
    The hosted control plane plus its node pools. An AKS cluster sits in a resource group, and its nodes land in a second, automatically created node resource group.
    Pricing tier
    Free, Standard or Premium. The tier sets the uptime SLA (financially backed on paid tiers) and whether Long-Term Support is available.
    AKS Automatic
    A cluster mode that turns on node auto-provisioning, autoscaling, managed monitoring, Azure RBAC, Workload ID and deployment safeguards. It always runs on the Standard tier.
    Node pool
    A group of identical virtual machines that run your pods. A cluster has at least one system pool, and any number of user pools.
    System pool
    The pool that runs core components such as CoreDNS and metrics-server. It needs a minimum size and a tolerable VM size, and it shouldn’t run your applications.
    Node auto-provisioning (NAP)
    Azure’s managed Karpenter. It watches for pods that don’t fit, picks a VM size and creates the node, then removes nodes that sit mostly empty.
    Azure CNI Overlay
    Pod networking in which pods get addresses from a private pod CIDR and traffic leaving the cluster is translated to the node’s VNet address.
    Microsoft Entra Workload ID
    Lets a Kubernetes service account exchange its projected token for a Microsoft Entra token, through a federated credential on a managed identity.
    OIDC issuer
    The URL that publishes the keys for tokens issued to your cluster’s service accounts. Workload ID needs it switched on.
    Azure RBAC for Kubernetes
    Lets you grant Kubernetes rights with Azure role assignments, such as Azure Kubernetes Service RBAC Reader, at cluster or namespace scope.
    App Routing add-on
    A managed NGINX-based ingress controller with Azure DNS and Key Vault integration for certificates.
    Auto-upgrade channel
    A cluster setting that picks how eagerly AKS moves the Kubernetes version or the node image forward.
    Deployment safeguards
    Azure Policy rules, in warning or enforcement mode, that check for missing resource requests, probes, unsafe settings and similar mistakes.
    Node resource group
    The extra resource group AKS creates for scale sets, disks and load balancers. Treat it as read-only, since edits there can break the cluster.

    How to create & configure it

    Create an AKS cluster with the Azure CLI

    1. Choose the region, the tier and the modePick a region near your users (Central India or South India). Use the Standard tier for anything that carries real traffic, and choose Automatic if you’d rather not tune node pools and policies yourself.
    2. Plan the networkCreate a VNet with a subnet for the nodes, and pick a pod CIDR for Azure CNI Overlay that doesn’t clash with your VNet, peered networks or on-premises ranges. Changing it later means rebuilding the cluster.
    3. Create a registryMake an Azure Container Registry for your images and attach it to the cluster, so nodes can pull without stored credentials.
    4. Create the clusterRun az aks create with zones, the autoscaler, Azure CNI Overlay with Cilium, Entra ID with Azure RBAC, local accounts off, and the OIDC issuer and Workload ID on. Add --enable-private-cluster if nobody should reach the API from the internet.
    5. Set the upgrade channelsChoose patch for the cluster and NodeImage for the node operating system, and add a maintenance window outside your busy hours.
    6. Create the pod identityMake a managed identity for the application, give it the narrowest role on the storage account, and add a federated credential that trusts one namespace and service account of this cluster.
    7. Give people accessAssign an Azure Kubernetes Service RBAC role to the right Entra groups at the right scope, then run az aks get-credentials and kubelogin to sign in.
    8. DeployApply your namespace, service account, deployment, service, ingress, autoscaler, disruption budget and network policy. The deployment must carry the label azure.workload.identity/use: "true" to get a token.
    9. Plan the first upgradeCheck the support calendar for your version, see what az aks get-upgrades offers, and upgrade a staging cluster first.

    Tip: the portal’s Automatic option is the fastest way to a sensible cluster. Once it makes sense, write the settings as Bicep or Terraform (see the code references), because cluster-wide choices like the pod CIDR and the network plugin can’t be edited casually afterwards.

    Common issues after configuration

    SymptomLikely causeWhat to check / fix
    Forbidden: User cannot list resource after signing in with EntraThe account has no Kubernetes role, or kubectl is using an old token cacheAssign a role such as Azure Kubernetes Service RBAC Reader at the right scope, then run kubelogin convert-kubeconfig -l azurecli and try again. Roles can take a few minutes to appear.
    AADSTS70021: No matching federated identity record foundThe issuer URL, namespace or service account in the federated credential doesn’t match the podCompare az aks show --query oidcIssuerProfile.issuerUrl with the credential’s issuer, and check the subject reads system:serviceaccount::.
    Pod starts but no Azure token arrivesThe pod lacks the azure.workload.identity/use: "true" label, or the service account has no client-id annotationAdd the label to the pod template (not just the deployment), annotate the service account with the identity’s client ID, and restart the pods.
    kubectl can’t reach a private clusterThe API server only has a private address in a VNet your machine isn’t part ofWork from a VM, VPN or pipeline agent inside the network, or use az aks command invoke for a quick command. Check the private DNS zone is linked to your VNet.
    QuotaExceeded or SkuNotAvailable while creating nodesThe subscription’s vCPU quota for that VM family or region is used up, or the size isn’t offered in that zoneRequest more quota for the VM family, pick another size, or add zones that carry the SKU.
    ImagePullBackOff from Azure Container RegistryThe kubelet identity has no AcrPull on the registryRun az aks update --attach-acr, or grant AcrPull to the kubelet identity yourself, and check the registry’s network rules.
    Pods can’t be scheduled and the VNet subnet is fullA node-subnet network setup gives every pod a VNet addressMove new clusters to Azure CNI Overlay. On an existing cluster, add a larger subnet or plan a migration.
    A service of type LoadBalancer stays The cluster identity can’t change the subnet, a public IP quota is reached, or the subnet has no free addressesRun kubectl describe svc and read the event. Give the cluster identity network permissions on the subnet and check the public IP quota.
    Cluster autoscaler doesn’t add nodesThe pool reached its maximum, pods have no resource requests, or the pending pod needs something no pool offersRaise the maximum, set requests on every container, and read the cluster-autoscaler-status ConfigMap in kube-system.
    Upgrade fails with a drain errorA Pod Disruption Budget leaves no replica that may be evicted, or there is no quota for surge nodesCheck kubectl get pdb -A, lower maxSurge if quota is tight, and make sure each budget leaves room for one eviction.
    Everything is stopped and kubectl failsSomeone ran az aks stop to save moneyRun az aks start. Remember that a stopped cluster still keeps its disks and IP addresses, which still bill.

    Use cases: when and where to use it

    Good fit

    • Teams on Azure that want Kubernetes with Entra ID sign-in and Azure Policy already in place
    • Microservice platforms that need rolling updates, autoscaling and shared tooling
    • Development and test clusters, because the free tier makes them cheap to keep around
    • Windows and Linux containers side by side in node pools of their own
    • Batch, machine learning and GPU jobs on Spot or GPU node pools
    • Organisations that want guard rails by default and pick AKS Automatic

    Consider something else

    • One container or a small web app → Azure Container Apps or App Service, which hide Kubernetes completely
    • Event-driven functions → Azure Functions, with no cluster to look after
    • A single server with an application on it → Azure Virtual Machines
    • No one on the team to own upgrades and cluster policy → a higher-level platform such as Container Apps
    • The same job on other clouds → compare Amazon EKS and Google Kubernetes Engine

    Pros: why consider it?

    Why teams choose it

    • A free control plane tier, and a paid one with a real uptime SLA
    • Entra ID, Azure RBAC and Workload ID fit nicely into Azure estates
    • Azure CNI Overlay removes the old subnet-size ceiling
    • AKS Automatic gives a defensible baseline without a platform team
    • Several upgrade channels and maintenance windows
    • Tight links to Azure Container Registry, Key Vault, Monitor and Defender

    Trade-offs to weigh

    • The Free tier has no uptime SLA, so production needs the Standard tier
    • Standard clusters still leave node pools, upgrades and policies to you
    • Some networking choices are fixed at creation, and the older ones are being retired
    • Costs gather in load balancers, public IPs, NAT, disks and Log Analytics
    • Add-ons and preview features change names and defaults often
    • Kubernetes remains hard, and AKS doesn’t hide that

    Common threats, vulnerabilities & risks

    RiskSeverityWhy it mattersMitigation
    A public API server open to everyoneHighThe management endpoint is reachable by anyone who can guess or steal a credential.Use a private cluster, or limit the API server to authorised IP ranges, and sign in only with Entra ID.
    Local accounts left enabledHighA static admin credential works without Entra, MFA or conditional access.Disable local accounts, and keep a break-glass group with its own review.
    Production on the Free tierHighThere is no financially backed uptime SLA, and the control plane gets fewer guarantees.Use the Standard or Premium tier for anything customers depend on.
    Broad cluster-admin rightsMediumOne mistaken command or leaked token can remove every workload.Grant namespace-scoped roles to Entra groups, review assignments and use just-in-time elevation.
    Running an unsupported Kubernetes versionMediumNo more fixes, upgrades may be blocked, and you can’t skip minor versions to catch up.Pick an auto-upgrade channel, watch the support calendar and upgrade at least twice a year.
    Pod CIDR that overlaps another networkMediumRouting breaks in odd ways once you peer networks or connect on-premises.Plan address space up front and choose an overlay range that nothing else uses.
    Workloads without requests, probes or budgetsMediumThe scheduler and autoscaler guess, and upgrades cause outages.Turn on deployment safeguards in enforcement mode and review warnings before enforcing.
    Secrets in manifests or environment variablesLow–MedAnyone who can read the namespace or the repository can read them.Use Key Vault with the CSI driver and Workload ID, and keep secrets out of Git.
    Cost drift from idle pools and public IPsLow–MedOversized node pools, test clusters and forgotten load balancers bill every hour.Use the autoscaler, stop dev clusters, tag by team and review Cost Management monthly.

    Performance optimisation

    • Set resource requests from real usage. The scheduler and the autoscalers work from requests, so wrong numbers waste money or cause evictions.
    • Keep application pods off the system pool, and give it enough capacity that CoreDNS and metrics-server aren’t squeezed.
    • Use the Horizontal Pod Autoscaler (or KEDA for queue-based scaling) for replicas, with the cluster autoscaler or node auto-provisioning for nodes.
    • Choose Azure CNI Overlay for new clusters, and the Cilium dataplane for faster policy enforcement and better observability.
    • Spread replicas across zones with topology spread constraints, and keep chatty services in the same zone when latency matters.
    • Pick ephemeral OS disks where the VM size has the cache for it. Node start-up is faster and the disk is free.
    • Scale CoreDNS and consider node-local DNS caching for services that make many lookups.
    • Choose VM sizes from the workload: memory-heavy services want a different family than CPU-bound ones, and ARM-based sizes often cost less if your images support them.

    Reduce monthly cost

    FinOps is the habit of managing cloud spend together with engineering. These are the FinOps checks that matter most for Azure Kubernetes Service.

    • Use the Free tier for development and learning, and pay for the Standard tier on production clusters where the SLA matters.
    • Enable the cluster autoscaler or node auto-provisioning so idle capacity disappears overnight.
    • Run stateless and batch workloads on Spot node pools, with a regular pool behind them.
    • Buy Reservations or a Savings Plan for the steady base of VMs, and keep spikes on pay-as-you-go.
    • Stop non-production clusters outside working hours with az aks stop. Disks and IP addresses still bill while it’s stopped.
    • Right-size requests and VM sizes from metrics. Over-requested pods force larger nodes.
    • Share one ingress or gateway instead of a load balancer and public IP per service.
    • Trim Log Analytics ingestion. Container Insights can cost more than small clusters if it collects every log line.
    • Use a NAT gateway or firewall deliberately, since both charge per hour and per GB of traffic.
    • Tag the cluster and node resource group, and set a budget alert in Cost Management.

    The tier prices here are the Azure list prices as we read them in October 2026 from secondary sources, and Azure changes them. Region, VM size, reservations and offers all move your bill, so check the live pricing page (see Pricing).

    Security optimisation

    • Make the cluster private, or limit the API server with authorised IP ranges, and reach it through a VPN or ExpressRoute.
    • Sign in with Microsoft Entra ID, use Azure RBAC for Kubernetes, and disable local accounts.
    • Use Workload ID for every application that calls Azure, and avoid service principal secrets in pods.
    • Turn on deployment safeguards and Azure Policy to refuse privileged containers, host paths and missing limits.
    • Apply Pod Security Standards per namespace, and run containers as non-root with a read-only file system where you can.
    • Add network policies from the start, with default deny, using the Cilium engine on overlay clusters.
    • Keep secrets in Key Vault and mount them with the CSI driver, and encrypt the cluster’s data disks with your own key if your rules ask for it.
    • Enable Microsoft Defender for Containers for image scanning, runtime alerts and recommendations.
    • Use the NodeImage upgrade channel or a patching schedule, so nodes get security fixes without manual work.
    • Send diagnostic logs (kube-audit, kube-apiserver) to a Log Analytics workspace, and alert on role changes and exec into pods.

    Governance & compliance

    Guardrails

    • Tagging policies for the cluster, node resource group and namespaces (owner, environment, cost centre)
    • An Azure Policy initiative that denies clusters with local accounts enabled, public API servers or no Entra integration in production subscriptions
    • A shared Bicep or Terraform module for clusters, with tier, private access, Entra, Workload ID and upgrade channels already set
    • An approved list of Kubernetes versions with a retirement date, and a rule about how far behind production may fall

    Continuous checks (Azure Policy and Defender for Cloud)

    • “Azure Kubernetes Service clusters should have local authentication methods disabled”
    • “Azure Kubernetes Service Private Clusters should be enabled”
    • “Azure Kubernetes Service clusters should have Defender profile enabled”
    • “Kubernetes cluster pods should only use approved host network and port range”
    • “Kubernetes cluster containers should only use allowed images”

    Azure Activity Log shows who created, changed or deleted clusters, Azure Policy applies and reports on the rules above, Defender for Cloud gives posture and runtime findings, and Microsoft Purview and the Service Trust Portal hold compliance material. The shared duties follow the managed Kubernetes pattern: Microsoft operates the control plane, while you answer for node pools you configure, workload permissions, network policy, images and upgrading on time.

    Reduce carbon footprint

    • Right-size VM sizes and requests, because idle nodes still draw power.
    • Let the autoscaler or node auto-provisioning remove nodes you aren’t using.
    • Stop development clusters overnight and at weekends.
    • Try ARM-based VM sizes where your images support them, since they do more work per watt.
    • Share clusters between teams with namespaces, instead of one small cluster per application.
    • Cut log volume and keep chatty traffic inside a zone, so less data is stored and moved.
    • The Azure Emissions Impact Dashboard estimates the footprint of a subscription. Check what it includes for AKS before you cite a figure.

    Pricing

    Purchase options

    Pricing tier Per cluster-hour

    Free costs nothing and has no uptime SLA. Standard (about $0.10 per hour) adds the SLA, and Premium (about $0.60 per hour) adds Long-Term Support.

    Node virtual machines Per VM-second

    Each node is a VM billed at its normal rate, with Spot, reservation and savings plan discounts available. This is nearly always the biggest line.

    Disks and networking Varies

    Managed disks for nodes and volumes, a Standard Load Balancer, public IP addresses, NAT gateways and data transfer out of Azure.

    Add-ons Varies

    Log Analytics ingestion for Container Insights, Defender for Containers per vCore, managed Prometheus and Grafana, and anything you pull from the Marketplace.

    What you actually pay for

    • The pricing tier of each cluster, if it isn’t Free, per hour
    • Virtual machines in every node pool, with discounts if you reserve or use Spot
    • Managed disks, load balancers, public IPs and NAT gateways
    • Outbound data transfer and cross-zone traffic where it applies
    • Monitoring, Defender and other add-ons you turn on

    Prices differ by region and over time, and the tier figures above come from secondary sources, so confirm them. See the official AKS pricing page and the Azure pricing calculator, and Microsoft's page on the Free, Standard and Premium pricing tiers for what each one includes.

    Important code references

    The same orders-api service, shaped for Azure: a private Standard-tier cluster over three zones, Azure CNI Overlay with Cilium, Entra sign-in with Azure RBAC, local accounts off and Workload ID on. The Azure CLI tab builds it, links the application’s managed identity to one service account and applies the manifest with kubectl. Terraform builds the same cluster. The Python tab runs inside the pod and writes a receipt to Blob Storage with the federated token, no secret needed. The C# tab runs from a pipeline and reports nodes and pods that need attention. Replace the names, group IDs and subscription with your own. A cluster bills from the moment it exists, so delete the test one when you’re done.

    # Placeholders: replace names, the admin group ID and the storage account with your own.
    RG=rg-orders
    LOC=centralindia
    CLUSTER=aks-orders-prod
    ACR=acrordersexample
    STORAGE=storordersexample
    ADMIN_GROUP=00000000-0000-0000-0000-000000000000
    
    az group create --name $RG --location $LOC
    az network vnet create --resource-group $RG --name vnet-orders --address-prefixes 10.20.0.0/16 \
      --subnet-name snet-nodes --subnet-prefixes 10.20.0.0/22
    SUBNET_ID=$(az network vnet subnet show --resource-group $RG --vnet-name vnet-orders --name snet-nodes --query id -o tsv)
    az acr create --resource-group $RG --name $ACR --sku Standard
    
    # The cluster: Standard tier, three zones, overlay networking with Cilium, Entra sign-in, no local accounts, private API server.
    az aks create --resource-group $RG --name $CLUSTER --location $LOC --tier standard \
      --zones 1 2 3 --node-count 3 --node-vm-size Standard_D4ds_v5 \
      --enable-cluster-autoscaler --min-count 3 --max-count 9 \
      --vnet-subnet-id $SUBNET_ID --network-plugin azure --network-plugin-mode overlay --pod-cidr 192.168.0.0/16 \
      --network-dataplane cilium --network-policy cilium \
      --enable-managed-identity --enable-aad --enable-azure-rbac --aad-admin-group-object-ids $ADMIN_GROUP --disable-local-accounts \
      --enable-oidc-issuer --enable-workload-identity --enable-private-cluster \
      --auto-upgrade-channel patch --node-os-upgrade-channel NodeImage \
      --attach-acr $ACR --generate-ssh-keys
    
    # Updates only between 02:00 and 06:00 on Sundays (UTC).
    az aks maintenanceconfiguration add --resource-group $RG --cluster-name $CLUSTER --name aksManagedAutoUpgradeSchedule \
      --schedule-type Weekly --day-of-week Sunday --interval-weeks 1 --start-time 02:00 --duration 4 --utc-offset +00:00
    
    # The application's identity: a managed identity, a narrow role on one storage account, and a federated credential for one service account.
    az identity create --resource-group $RG --name id-orders-api
    CLIENT_ID=$(az identity show --resource-group $RG --name id-orders-api --query clientId -o tsv)
    PRINCIPAL_ID=$(az identity show --resource-group $RG --name id-orders-api --query principalId -o tsv)
    ISSUER=$(az aks show --resource-group $RG --name $CLUSTER --query oidcIssuerProfile.issuerUrl -o tsv)
    az identity federated-credential create --resource-group $RG --identity-name id-orders-api --name orders-api \
      --issuer "$ISSUER" --subject system:serviceaccount:orders:orders-api --audiences api://AzureADTokenExchange
    az role assignment create --assignee-object-id $PRINCIPAL_ID --assignee-principal-type ServicePrincipal \
      --role "Storage Blob Data Contributor" \
      --scope $(az storage account show --resource-group $RG --name $STORAGE --query id -o tsv)
    
    # Let the support group read one namespace.
    az role assignment create --assignee $ADMIN_GROUP --role "Azure Kubernetes Service RBAC Reader" \
      --scope "$(az aks show --resource-group $RG --name $CLUSTER --query id -o tsv)/namespaces/orders"
    
    # Deploy. The API server is private, so run kubectl from the VNet, or wrap commands in "az aks command invoke".
    az aks get-credentials --resource-group $RG --name $CLUSTER
    kubelogin convert-kubeconfig -l azurecli
    kubectl apply -f - <<EOF
    apiVersion: v1
    kind: Namespace
    metadata:
      name: orders
      labels:
        pod-security.kubernetes.io/enforce: restricted
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: orders-api
      namespace: orders
      annotations:
        azure.workload.identity/client-id: "$CLIENT_ID"
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: orders-api
      namespace: orders
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: orders-api
      template:
        metadata:
          labels:
            app: orders-api
            azure.workload.identity/use: "true"
        spec:
          serviceAccountName: orders-api
          securityContext:
            runAsNonRoot: true
            seccompProfile:
              type: RuntimeDefault
          topologySpreadConstraints:
            - maxSkew: 1
              topologyKey: topology.kubernetes.io/zone
              whenUnsatisfiable: ScheduleAnyway
              labelSelector:
                matchLabels:
                  app: orders-api
          containers:
            - name: api
              image: $ACR.azurecr.io/orders-api:1.4.2
              ports:
                - containerPort: 8080
              env:
                - name: RECEIPT_ACCOUNT_URL
                  value: https://$STORAGE.blob.core.windows.net
              resources:
                requests:
                  cpu: 250m
                  memory: 256Mi
                limits:
                  memory: 512Mi
              readinessProbe:
                httpGet:
                  path: /healthz
                  port: 8080
              securityContext:
                allowPrivilegeEscalation: false
                readOnlyRootFilesystem: true
                capabilities:
                  drop: ["ALL"]
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: orders-api
      namespace: orders
    spec:
      selector:
        app: orders-api
      ports:
        - port: 80
          targetPort: 8080
    ---
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: orders-api
      namespace: orders
    spec:
      ingressClassName: webapprouting.kubernetes.azure.com
      rules:
        - host: orders.example.com
          http:
            paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: orders-api
                    port:
                      number: 80
    ---
    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    metadata:
      name: orders-api
      namespace: orders
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: orders-api
      minReplicas: 3
      maxReplicas: 12
      metrics:
        - type: Resource
          resource:
            name: cpu
            target:
              type: Utilization
              averageUtilization: 70
    ---
    apiVersion: policy/v1
    kind: PodDisruptionBudget
    metadata:
      name: orders-api
      namespace: orders
    spec:
      minAvailable: 2
      selector:
        matchLabels:
          app: orders-api
    ---
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: orders-api-from-ingress
      namespace: orders
    spec:
      podSelector:
        matchLabels:
          app: orders-api
      policyTypes: ["Ingress"]
      ingress:
        - from:
            - namespaceSelector:
                matchLabels:
                  kubernetes.io/metadata.name: app-routing-system
          ports:
            - port: 8080
    EOF
    kubectl -n orders rollout status deployment/orders-api
    
    # Later: which versions can this cluster move to?
    az aks get-upgrades --resource-group $RG --name $CLUSTER --output table
    # Placeholders: pass in your own group ID and storage account.
    variable "location" {
      type    = string
      default = "centralindia"
    }
    
    variable "admin_group_id" {
      type = string
    }
    
    variable "storage_account_id" {
      type = string
    }
    
    resource "azurerm_resource_group" "orders" {
      name     = "rg-orders"
      location = var.location
    }
    
    resource "azurerm_virtual_network" "orders" {
      name                = "vnet-orders"
      location            = azurerm_resource_group.orders.location
      resource_group_name = azurerm_resource_group.orders.name
      address_space       = ["10.20.0.0/16"]
    }
    
    resource "azurerm_subnet" "nodes" {
      name                 = "snet-nodes"
      resource_group_name  = azurerm_resource_group.orders.name
      virtual_network_name = azurerm_virtual_network.orders.name
      address_prefixes     = ["10.20.0.0/22"]
    }
    
    resource "azurerm_kubernetes_cluster" "orders" {
      name                = "aks-orders-prod"
      location            = azurerm_resource_group.orders.location
      resource_group_name = azurerm_resource_group.orders.name
      dns_prefix          = "orders-prod"
      sku_tier            = "Standard"
    
      private_cluster_enabled   = true
      local_account_disabled    = true
      oidc_issuer_enabled       = true
      workload_identity_enabled = true
    
      automatic_upgrade_channel = "patch"
      node_os_upgrade_channel   = "NodeImage"
    
      default_node_pool {
        name                 = "system"
        vm_size              = "Standard_D4ds_v5"
        vnet_subnet_id       = azurerm_subnet.nodes.id
        zones                = ["1", "2", "3"]
        auto_scaling_enabled = true
        min_count            = 3
        max_count            = 9
    
        upgrade_settings {
          max_surge = "33%"
        }
      }
    
      identity {
        type = "SystemAssigned"
      }
    
      network_profile {
        network_plugin      = "azure"
        network_plugin_mode = "overlay"
        network_data_plane  = "cilium"
        network_policy      = "cilium"
        pod_cidr            = "192.168.0.0/16"
      }
    
      azure_active_directory_role_based_access_control {
        azure_rbac_enabled     = true
        admin_group_object_ids = [var.admin_group_id]
      }
    
      maintenance_window_auto_upgrade {
        frequency   = "Weekly"
        interval    = 1
        duration    = 4
        day_of_week = "Sunday"
        start_time  = "02:00"
        utc_offset  = "+00:00"
      }
    }
    
    resource "azurerm_user_assigned_identity" "orders_api" {
      name                = "id-orders-api"
      location            = azurerm_resource_group.orders.location
      resource_group_name = azurerm_resource_group.orders.name
    }
    
    resource "azurerm_federated_identity_credential" "orders_api" {
      name                = "orders-api"
      resource_group_name = azurerm_resource_group.orders.name
      parent_id           = azurerm_user_assigned_identity.orders_api.id
      issuer              = azurerm_kubernetes_cluster.orders.oidc_issuer_url
      subject             = "system:serviceaccount:orders:orders-api"
      audience            = ["api://AzureADTokenExchange"]
    }
    
    resource "azurerm_role_assignment" "orders_blob" {
      scope                = var.storage_account_id
      role_definition_name = "Storage Blob Data Contributor"
      principal_id         = azurerm_user_assigned_identity.orders_api.principal_id
    }
    
    output "orders_api_client_id" {
      value = azurerm_user_assigned_identity.orders_api.client_id
    }
    # Runs inside the orders-api pod. The Workload ID webhook projects a token file into the pod and sets AZURE_CLIENT_ID,
    # AZURE_TENANT_ID and AZURE_FEDERATED_TOKEN_FILE; DefaultAzureCredential notices them and swaps the token for an Entra token.
    import json
    import os
    from datetime import datetime, timezone
    
    from azure.core.exceptions import ResourceExistsError
    from azure.identity import DefaultAzureCredential
    from azure.storage.blob import BlobServiceClient
    
    
    class ReceiptStore:
        def __init__(self, account_url=None, container="receipts"):
            self.container = container
            credential = DefaultAzureCredential(exclude_interactive_browser_credential=True)
            self.service = BlobServiceClient(account_url or os.environ["RECEIPT_ACCOUNT_URL"], credential=credential)
    
        def put(self, order_id, total):
            now = datetime.now(timezone.utc)
            name = "%s/%s.json" % (now.strftime("%Y/%m/%d"), order_id)
            blob = self.service.get_blob_client(self.container, name)
            payload = json.dumps({"order": order_id, "total": total, "at": now.isoformat()})
            try:
                blob.upload_blob(payload.encode(), overwrite=False)    # an order id is written once
            except ResourceExistsError:
                return name + " (already stored)"
            return name
    
    
    if __name__ == "__main__":
        store = ReceiptStore()
        print("saved", store.put("A-1042", 1249.50))
    // dotnet add package KubernetesClient
    // A quick health report for a pipeline: nodes that are not Ready, and pods stuck in Pending with the scheduler's reason.
    // The kubeconfig written by "az aks get-credentials" plus kubelogin handles the Entra sign-in.
    using k8s;
    using k8s.Models;
    
    var client = new Kubernetes(KubernetesClientConfiguration.BuildConfigFromConfigFile());
    int problems = 0;
    
    V1NodeList nodes = await client.CoreV1.ListNodeAsync();
    foreach (V1Node node in nodes.Items)
    {
        var ready = node.Status.Conditions.FirstOrDefault(c => c.Type == "Ready");
        if (ready?.Status != "True")
        {
            problems++;
            Console.WriteLine($"NODE  {node.Metadata.Name}: not ready ({ready?.Reason ?? "no Ready condition"})");
        }
    }
    
    V1PodList pending = await client.CoreV1.ListNamespacedPodAsync("orders", fieldSelector: "status.phase=Pending");
    foreach (V1Pod pod in pending.Items)
    {
        problems++;
        var why = pod.Status.Conditions?.FirstOrDefault(c => c.Type == "PodScheduled" && c.Status == "False");
        Console.WriteLine($"POD   {pod.Metadata.Name}: pending - {why?.Message ?? "no scheduling message yet"}");
    }
    
    Console.WriteLine(problems == 0 ? $"{nodes.Items.Count} nodes ready, no pending pods in 'orders'." : $"{problems} problem(s) found.");
    return problems == 0 ? 0 : 1;

    Best videos to explore

    Short walkthrough videos for this service are being prepared. They will appear here and load only when you press play. Until then, follow CloudBrum on YouTube for new videos.

    Introduction to Azure Kubernetes ServiceVideo coming soon · Beginner
    AKS Automatic versus Standard clustersVideo coming soon · Intermediate
    Workload ID and Entra sign-in on AKSVideo coming soon · Intermediate
    Upgrading AKS: channels, windows and surgeVideo coming soon · Advanced

    Interview questions

    BasicWhat is Azure Kubernetes Service?

    It’s Microsoft’s managed Kubernetes service. Azure hosts the control plane (the API server, etcd and controllers), and your containers run on virtual machines in node pools that you size, or that AKS Automatic sizes for you. You use the normal Kubernetes tools to deploy.

    BasicIs the AKS control plane really free?

    On the Free tier, yes, you pay only for the nodes and other resources, but there is no financially backed uptime SLA. The Standard tier (about $0.10 per cluster-hour) adds the SLA and Premium (about $0.60) adds Long-Term Support. Check the pricing page for current figures.

    BasicWhat is the difference between AKS Automatic and AKS Standard?

    Automatic makes the common choices for you: node auto-provisioning, autoscaling, managed monitoring, Azure RBAC, Workload ID and deployment safeguards are on by default, and the cluster runs on the Standard tier. Standard leaves every choice to you, which suits custom networking, special VM sizes or existing automation.

    IntermediateHow does a pod on AKS get permission to read from Blob Storage without a secret?

    With Workload ID. You turn on the OIDC issuer and Workload ID, create a managed identity with a role on the storage account, and add a federated credential that trusts one service account in one namespace. The pod carries the azure.workload.identity/use label, the service account carries the identity’s client ID, and the Azure SDK exchanges the projected token for an Entra token.

    IntermediateWhy is Azure CNI Overlay the usual choice for new clusters?

    Pods get addresses from a private pod CIDR, so they don’t consume VNet subnet space. The old model gave every pod a VNet address, and large clusters ran out of room. You must still choose a pod CIDR that doesn’t overlap networks you’ll connect to, because you can’t change it casually.

    IntermediateWhat would you check if the cluster autoscaler never adds a node?

    Look at whether the pool has reached its maximum, whether the pending pods have resource requests, and whether any pool can actually fit them (taints, zones, VM size, GPU). The cluster-autoscaler-status ConfigMap in kube-system and the pod events usually say why.

    AdvancedHow would you keep a production AKS cluster upgraded without surprises?

    Use the patch auto-upgrade channel and the NodeImage node OS channel, set a planned maintenance window, and keep maxSurge within quota. Move minor versions on purpose, one at a time, after testing in staging. Pod Disruption Budgets and topology spread constraints make drains safe, and the Premium tier gives you Long-Term Support when a version can’t move yet.

    AdvancedHow do you lock down the network of a private AKS cluster?

    Make the API server private (private cluster or API server VNet integration), link the private DNS zone to every network that needs it, route egress through a firewall or NAT gateway, and add network policies with default deny. Use Cilium for policy enforcement, keep the node subnet small, and reach the API through a VPN, ExpressRoute or a pipeline agent in the VNet.

    AdvancedA federated credential exists but the pod still gets AADSTS70021. What do you check?

    The subject, issuer and audience must match exactly. The subject must read system:serviceaccount:<namespace>:<name>, the issuer must be the cluster’s current OIDC issuer URL, and the audience api://AzureADTokenExchange. Also confirm the pod template has the Workload ID label and the service account annotation, and that the pods restarted after the change.

    AdvancedHow would you design a cluster that several teams share?

    Give each team its own namespaces with quotas and default-deny network policies, scope Azure RBAC role assignments to those namespaces, enforce Pod Security Standards and deployment safeguards, and use separate node pools with taints for noisy or sensitive workloads. Namespaces are a soft boundary, so tenants that need hard isolation should get their own cluster or subscription.

    Similar or alternative services on other clouds

    CapabilityAWSAzureGoogle Cloud
    ServiceAmazon EKSAzure Kubernetes Service (AKS)Google Kubernetes Engine (GKE)
    Control plane charge$0.10 per hour, $0.60 once a version is in extended supportFree tier; Standard about $0.10 per hour; Premium about $0.60$0.10 per hour, offset by a monthly free credit for one cluster
    Low-effort modeEKS Auto ModeAKS AutomaticGKE Autopilot
    Passwordless access for podsPod Identity (or IRSA)Entra Workload IDWorkload Identity Federation
    Node scalingKarpenter, Cluster Autoscaler, Auto ModeNode auto-provisioning, Cluster AutoscalerAutopilot, node auto-provisioning, Cluster Autoscaler
    Sign-in for peopleIAM and access entriesEntra ID with Azure RBACGoogle IAM with Kubernetes RBAC
    Pod addressingVPC addresses through the VPC CNIAzure CNI Overlay, or VNet addressesVPC-native clusters with secondary ranges
    Longer supportExtended support, 12 months at the higher feeLong-Term Support on the Premium tierExtended release channel on Standard clusters

    If you only need to run a few containers, look at Azure Container Apps or App Service first, since they hide Kubernetes entirely. For event-driven code with no cluster use Azure Functions. The data behind a cluster typically lives outside it, for example in Azure SQL Database or Azure Blob Storage.

    Did you find this article helpful?

    Your answer helps us improve this page for the next reader.

    Sources and further reading

    CloudBrum wrote this page in its own words, using the provider’s public documentation listed below as the reference. Features, limits, names and prices change, so confirm anything that matters on the provider’s own pages before you decide.