The Azure cloud controller manager (cloud-provider-azure) is the AKS component that turns Kubernetes Service and Node events into Azure API calls. When you create a Service of type LoadBalancer, the CCM provisions an Azure load balancer frontend, a public IP, and network security group rules, then writes the assigned address back as the Service EXTERNAL-IP.
The CCM is a control-plane, provisioning-time component: it reconciles the Azure resources when a Service is created, updated, or deleted. It is not in the runtime data path (traffic flows through the load balancer and kube-proxy to the pods, not through the CCM).
These scripts exercise that reconcile against the LocalStack for Azure emulator: public and internal load balancers, source-range NSG rules, the nodeIP backend-pool variant, and an NGINX ingress controller.
Because the emulated load balancer has no real dataplane, the EXTERNAL-IP is a synthetic, non-routable placeholder. To actually reach a workload, use kubectl port-forward, which tunnels straight to the pod and bypasses the EXTERNAL-IP.
Running on LocalStack? Install the lstk CLI and run
lstk az start-interceptionto route Azure CLI calls to the emulator. See Run against LocalStack for the full setup.
The cloud controller manager turns Kubernetes Service objects into Azure load-balancer resources in the node resource group, and writes the assigned address back onto the Service:
%%{init: {'themeVariables': {'clusterBkg': 'transparent', 'clusterBorder': '#8c8c8c'}}}%%
flowchart LR
subgraph aks["AKS cluster"]
subgraph kubesystem["kube-system"]
ccm["cloud-controller-manager"]
cnm["cloud-node-manager"]
end
subgraph appns["test namespace"]
svcpub["Service<br/>type LoadBalancer"]
svcint["Service<br/>internal annotation"]
backend["backend Deployment"]
end
end
subgraph noderg["node resource group MC_*"]
lb["kubernetes<br/>load balancer"]
lbint["kubernetes-internal<br/>load balancer"]
pip["public IP address"]
nsg["network security group"]
end
svcpub -.->|"watched by"| ccm
svcint -.->|"watched by"| ccm
ccm -->|"frontend, rule, backend pool"| lb
ccm -->|"private frontend"| lbint
ccm -->|"allocates"| pip
ccm -->|"Allow rule per loadBalancerSourceRanges"| nsg
ccm -->|"writes EXTERNAL-IP back"| svcpub
cnm -->|"labels and addresses nodes"| aks
lb -.->|"backend pool targets"| backend
- An AKS cluster reachable through
kubectl, created by scripts/01-user-assigned-managed-identity.sh. The scripts do not create the cluster; they source./00-variables.sh, whose values must match it (local-aks-testin resource grouplocal-rg, locationItalyNorth). - Azure CLI (
az) and kubectl. - Helm for
05-test-nginx-ingress-controller.sh.
Each script sources ./00-variables.sh, merges the cluster credentials, and guards on the node resource group (exiting with instructions if the cluster does not exist). Run them from this folder, in order or individually.
cd ccm/scripts
./01-test-public-loadbalancer.sh
./02-test-internal-loadbalancer.sh
./03-test-loadbalancer-source-ranges.sh
./05-test-nginx-ingress-controller.sh00-variables.sh: Shared variables sourced by every test: cluster name, resource group, and location (which must match the cluster-creation script); the derived node resource group; the load-balancer, namespace, Service, ingress, and backend names; and theEXTERNAL-IPpoll timeout.01-test-public-loadbalancer.sh: Creates a publicServiceof typeLoadBalancerand asserts it receives anEXTERNAL-IP, that a matching public IP exists in the node resource group, and that thekubernetesload balancer has a frontend and a rule.02-test-internal-loadbalancer.sh: Creates an internal LoadBalancer Service (via theservice.beta.kubernetes.io/azure-load-balancer-internalannotation) and asserts it receives a privateEXTERNAL-IPon thekubernetes-internalload balancer and that no public IP is created for it.03-test-loadbalancer-source-ranges.sh: Creates a public Service withloadBalancerSourceRangesand asserts the CCM reconciles an inbound Allow rule scoped to that CIDR on the node resource group network security group.04-test-nodeip-backend-pool.sh: Exercises thenodeIPbackend-pool variant. It self-guards on the cluster'sbackendPoolType: unless the cluster was created with--load-balancer-backend-pool-type nodeIPit prints how to re-create the cluster and exits, becausebackendPoolTypeis a create-time property. When it isnodeIP, it asserts thekubernetesbackend pool holds node IP addresses rather than NIC IP-configuration references.05-test-nginx-ingress-controller.sh: Installs the NGINX ingress controller (its own front Service is typeLoadBalancer, so the CCM assigns it anEXTERNAL-IPand a node resource group public IP), then deploys a backend Deployment, a ClusterIP Service, and an Ingress, and verifies that traffic reaches the backend through the controller with akubectl port-forwardpass-through check.
- Cloud Controller Manager (Kubernetes documentation)
- Cluster Architecture (Kubernetes documentation)
- Developing Cloud Controller Manager (Kubernetes)
- Cloud Controller Manager Administration (Kubernetes)
- KEP-2392: Cloud Controller Manager
- Cloud provider for Azure documentation site
- Core concepts for Azure Kubernetes Service (AKS)
- Use a public standard load balancer in AKS
- Use an internal load balancer in AKS
- Use a static public IP address and DNS label with the AKS load balancer
- Configure the public standard load balancer in AKS
- Managed NGINX ingress with the application routing add-on