Background
Sections
IntroductionFoundations1. Resource Hierarchy2. Resource Manager3. Identity and RBAC4. Regions and Availability5. Naming and TaggingVirtual Machines1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetVirtual Network1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetBlob Storage1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure SQL Database1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Kubernetes Service1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Container Registry1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetMicrosoft Entra ID1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure RBAC1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Functions1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAPI Management1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure App Configuration1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Machine Learning1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Monitor1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure AI Foundry1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and Cheatsheet

6. Integrations

10 min read

No Azure service is an island, and AKS is the least island-like of all of them — a cluster is mostly a machine for wiring other Azure services together. These are the pairings you will meet on essentially every real cluster.

The two glue mechanisms, and the AKS-specific third

Two mechanisms answer most "how do these talk to each other" questions across Azure, and AKS adds one of its own:

  • Managed identity + role assignment — the keyless way one Azure resource authenticates to another. On AKS this splits three ways (cluster identity, kubelet identity, workload identity) and choosing the wrong one is a real security bug, not a style preference.
  • Private endpoint + Private DNS zone — the way one Azure resource reaches another without the public internet. From a cluster, the pod resolves the target's public FQDN, Private DNS returns a private IP in your VNet, and traffic never leaves it. Name the sub-resource the endpoint targets: blob, vault, registry, management (for a private cluster's API server).
  • CSI drivers — the AKS-specific glue: a Kubernetes volume request becomes an Azure resource. Disks, Files, Blob, and Key Vault secrets all reach pods this way.

The pairings

Pairs with Why The glue
Azure Container Registry Somewhere to store the images the cluster runs AcrPull on the kubelet identityaz aks update --attach-acr does exactly this. Private endpoint on the registry for private clusters
Key Vault Hold the secrets a Kubernetes Secret only base64-encodes Secrets Store CSI driver add-on: the secret mounts as a file in the pod, authenticated by workload identity, with rotation on
Microsoft Entra ID Who may use the cluster, and who a pod is Entra-integrated Kubernetes RBAC (or Azure RBAC for Kubernetes Authorization) for humans; Workload ID federated to the cluster's OIDC issuer for pods
Azure Monitor Logs, metrics, and traces from a system that emits enormous volumes of all three Container Insights for logs, managed Prometheus for metrics, Managed Grafana for dashboards. All add-ons; all off by default
Application Gateway / App Routing Get HTTPS traffic into the cluster with a WAF in front Application Gateway for Containers (via the ALB controller) or the App Routing add-on (managed NGINX + optional Entra-integrated certificate management)
Virtual Network + NAT Gateway Where the nodes and pods live, and how they get out Node subnet passed at create time; outbound_type = managedNATGateway for SNAT that doesn't exhaust
Azure Storage (Disk / Files / Blob) Persistent state for stateful workloads The corresponding CSI driver and a StorageClass; a PVC becomes a real Azure resource
Service Bus / Event Hubs / Storage Queues The event sources that should drive scaling KEDA add-on scalers, authenticated with workload identity — scale on queue depth, not CPU
Azure Front Door Global entry, WAF, and the traffic switch for a blue/green cluster migration Origin pointing at the cluster's ingress IP, or Private Link origin to an internal load balancer
Azure Policy Enforce rules the cluster itself should refuse to break Azure Policy add-on (Gatekeeper) — admission-time Deny on privileged containers, untrusted registries, missing limits
Azure DevOps / GitHub Actions Build images and deploy manifests OIDC federation for the infrastructure pipeline; Flux or Argo CD for in-cluster delivery

[Image Prompt: 2D minimalistic hub-and-spoke diagram with an AKS cluster at the centre connected to Azure Container Registry, Key Vault, Microsoft Entra ID, Azure Monitor, Application Gateway, and Azure Storage, with labelled edges naming the glue mechanism for each, flat design, clean vector art style, white background]

Azure Container Registry — the first thing that breaks

Every cluster needs an image source, and ImagePullBackOff is the most common first failure on a new AKS cluster. The cause is almost always the same: the kubelet identity has no AcrPull role assignment on the registry.

# The one-liner that creates the role assignment for you
az aks update -g rg-payments-aks-prod -n aks-payments-prod --attach-acr acrpayments

Do it in Terraform instead, so it survives a rebuild and appears in a plan:

resource "azurerm_role_assignment" "acr_pull" {
  scope                = azurerm_container_registry.this.id
  role_definition_name = "AcrPull"
  principal_id         = azurerm_kubernetes_cluster.this.kubelet_identity[0].object_id
}

Three follow-ons worth knowing:

  • Role assignments propagate asynchronously. A pull that fails immediately after an apply may succeed a couple of minutes later. Don't debug a race as if it were a misconfiguration.
  • On a private cluster, the registry needs a private endpoint (registry sub-resource) and its Private DNS zone linked to the node VNet, or pulls fail on DNS rather than on authorization.
  • imagePullSecrets are the wrong answer on AKS. They're a stored credential with a rotation problem, and the identity-based path exists specifically to replace them.

Key Vault — where secrets should actually live

A Kubernetes Secret is base64, not encryption. Anyone with API read access on the namespace can read it, and it sits in etcd. The Azure answer is the Key Vault provider for the Secrets Store CSI driver, enabled as an add-on:

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: payments-kv
  namespace: payments
spec:
  provider: azure
  parameters:
    usePodIdentity: "false"
    useVMManagedIdentity: "false"
    clientID: "<workload-identity-client-id>"   # the pod's identity, not the node's
    keyvaultName: "kv-payments-prod"
    tenantId: "<tenant-id>"
    objects: |
      array:
        - |
          objectName: db-connection-string
          objectType: secret

The pod mounts it as a volume; the secret arrives as a file. Two things to get right:

  • Authenticate with workload identity, not the kubelet identity. Granting the kubelet identity Key Vault Secrets User gives every pod on every node access to those secrets — a lateral movement path handed over for convenience.
  • Turn on secret rotation (secret_rotation_enabled = true) and make sure the application re-reads the file. Rotation updates the mounted file; it does not restart your process.

Microsoft Entra Workload ID — how a pod becomes an identity

This is the mechanism that removes credentials from AKS entirely, and it's worth understanding end-to-end because it's the answer to at least three interview questions.

  1. The cluster has an OIDC issuer enabled, publishing a public discovery document.
  2. A Kubernetes ServiceAccount is annotated with a user-assigned managed identity's client ID.
  3. A federated identity credential on that managed identity trusts tokens from the cluster's issuer, for that specific namespace and service account name.
  4. A pod using that service account gets a projected token; the Azure SDK exchanges it with Entra ID for a real access token; the token is used against Storage, Key Vault, Cosmos DB, anything.
az identity federated-credential create \
  --name "payments-api" \
  --identity-name "id-payments-api" \
  --resource-group "rg-payments-aks-prod" \
  --issuer "$(az aks show -g rg-payments-aks-prod -n aks-payments-prod --query oidcIssuerProfile.issuerURL -o tsv)" \
  --subject "system:serviceaccount:payments:payments-api" \
  --audience "api://AzureADTokenExchange"
apiVersion: v1
kind: ServiceAccount
metadata:
  name: payments-api
  namespace: payments
  annotations:
    azure.workload.identity/client-id: "<user-assigned-identity-client-id>"
---
apiVersion: apps/v1
kind: Deployment
# ...
spec:
  template:
    metadata:
      labels:
        azure.workload.identity/use: "true"    # without this label, nothing happens
    spec:
      serviceAccountName: payments-api

Note the subject string: system:serviceaccount:<namespace>:<name>. The trust is scoped to one service account in one namespace, which is exactly the granularity you want — and exactly the granularity the retired AAD Pod Identity struggled to provide. If a tutorial tells you to install AzureIdentity and AzureIdentityBinding CRDs, it predates this and should not be followed.

If you're coming from AWS: this is IRSA, nearly feature-for-feature.

Observability — Azure Monitor, and the volume problem

Nothing is collected by default. Three add-ons cover the ground:

  • Container Insights — container stdout/stderr and inventory into a Log Analytics workspace. Powerful and the single largest source of surprise AKS bills. Use data collection rules to exclude kube-system noise and namespaces you don't need, and set the workspace's daily cap.
  • Azure Monitor managed Prometheus — metrics, scraped in the Prometheus ecosystem's own idiom, stored in an Azure Monitor workspace. This is what you alert on.
  • Azure Managed Grafana — dashboards over both, with AKS dashboards preinstalled.

The KQL query you'll write most often — what is restarting, and how much:

KubePodInventory
| where TimeGenerated > ago(1h)
| where ClusterName == "aks-payments-prod"
| summarize Restarts = max(PodRestartCount) by Namespace, Name = Name, ContainerName = ContainerName
| where Restarts > 0
| order by Restarts desc

And the one that finds the reason:

KubeEvents
| where TimeGenerated > ago(1h)
| where KubeEventType == "Warning"
| summarize Count = count(), Latest = max(TimeGenerated) by Reason, Name, Namespace
| order by Count desc

Route the control plane's own logs too, via a diagnostic setting on the cluster resource: kube-apiserver, kube-audit (very high volume — use kube-audit-admin unless you need read events), kube-controller-manager, cluster-autoscaler, and guard (the Entra authorization component). cluster-autoscaler logs are the only way to answer "why didn't it scale up".

Ingress — three options and how to choose

Option What it is Choose when
App Routing add-on Managed NGINX ingress controller, with optional Entra-integrated DNS and certificate management from Key Vault You want a supported ingress controller without owning its lifecycle. The sensible default
Application Gateway for Containers Azure's L7 load balancer, driven by the ALB controller via Gateway API You want WAF, mutual TLS, and Azure-native L7 outside the cluster's data plane. The successor to AGIC — check migration guidance if you're on AGIC today
Your own controller (NGINX, Traefik, Envoy Gateway, Istio gateway) Whatever you install You need behaviour the managed options don't have, and accept owning upgrades and CVEs

Whichever you choose, the ingress terminates on an Azure Load Balancer IP — public, or internal (service.beta.kubernetes.io/azure-load-balancer-internal: "true") with Front Door or Application Gateway in front. Internal-only ingress is the right default for anything that shouldn't be on the public internet.

Storage — a PVC is an Azure resource

Driver Backing resource Access mode Use for
Azure Disk CSI Managed disk ReadWriteOnce — one node at a time Databases, anything wanting block storage and low latency. The default StorageClass
Azure File CSI Azure Files share (SMB or NFS) ReadWriteMany Shared state across pods, legacy apps expecting a file share
Blob CSI Blob container (via blobfuse or NFS) ReadWriteMany Large sequential data, ML datasets, model artifacts

Two traps:

  • Zone affinity. A managed disk lives in one availability zone; a pod using it can only be scheduled on a node in that zone. A zone outage takes that pod down and no amount of replicas elsewhere helps. WaitForFirstConsumer volume binding avoids provisioning the disk in the wrong zone in the first place.
  • Reclaim policy. The default Delete removes the Azure disk when the PVC goes — convenient, and catastrophic if the PVC was deleted by accident. Retain keeps your data and quietly keeps billing you for orphaned disks. Pick per storage class, deliberately, and audit for unattached disks monthly.

Event-driven scaling with KEDA

The pairing that makes AKS genuinely good at asynchronous work: KEDA (an add-on) turns an external metric into an HPA, and can scale a Deployment to zero.

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: payments-worker
  namespace: payments
spec:
  scaleTargetRef:
    name: payments-worker
  minReplicaCount: 0
  maxReplicaCount: 50
  triggers:
    - type: azure-servicebus
      metadata:
        queueName: payments-inbound
        namespace: sb-payments-prod
        messageCount: "20"          # target messages per replica
      authenticationRef:
        name: keda-workload-identity  # no connection string anywhere

Note what this doesn't do: scaling pods to zero doesn't scale nodes to zero. The node savings come from the cluster autoscaler or node autoprovisioning noticing the empty nodes afterwards — with minutes of lag in both directions.


Next: Production →

← Back to the Azure Kubernetes Service overview · ← Previous: Deployment