Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
API Server VNet Integration lets you place the control‑plane IP inside your own VNet. The pattern described here extends that capability to more VNets by chaining Private Link. It's useful for hub‑and‑spoke topologies, dedicated build networks, or jump‑host VNets that must administer production clusters without opening the API server to the public internet.
This article applies only to clusters that are created with API Server VNet Integration and shows you how to:
- Deploy a private AKS cluster with API Server VNet Integration.
- Expose the API server through a Private Link Service (PLS) inside the cluster virtual network.
- Create a Private Endpoint (PE) in a different virtual network.
- Configure private DNS so Kubernetes tools resolve the cluster’s private FQDN inside the remote network.
For private clusters that do not use API Server VNet Integration, see Create a private AKS cluster.
Region availability
API Server VNet Integration is currently available in a subset of Azure regions and is subject to regional capacity limits. Before you begin, confirm that your target region is supported. For more information, see API Server VNet Integration.
Prerequisites
Requirement | Minimum |
---|---|
Azure CLI | 2.73.0 |
Permissions | Contributor + Network Contributor on both subscriptions |
If you use custom DNS servers, add Azure’s virtual IP 168.63.129.16 as an upstream forwarder.
Set environment variables
Set the following environment variables for use throughout this article. Feel free to replace the placeholder values with your own.
LOCATION="westus3"
# Resource groups
AKS_RG="aks-demo-rg"
REMOTE_RG="client-demo-rg"
# AKS cluster
AKS_CLUSTER="aks-private"
AKS_SUBNET="aks-subnet"
# Private Link Service
PLS_NAME="apiserver-pls"
PLS_SUBNET="pls-subnet"
PLS_PREFIX="10.225.0.0/24"
# Remote VNet
REMOTE_VNET="client-vnet"
REMOTE_SUBNET="client-subnet"
REMOTE_VNET_PREFIX="192.168.0.0/16"
REMOTE_SUBNET_PREFIX="192.168.1.0/24"
PE_NAME="aks-pe"
PE_CONN_NAME="aks-pe-conn"
# DNS
DNS_ZONE="private.${LOCATION}.azmk8s.io"
DNS_LINK="dns-link"
Create resource groups
# Create resource groups for the AKS cluster
az group create --name $AKS_RG --___location $LOCATION
# Create a resource group for the remote VNet
az group create --name $REMOTE_RG --___location $LOCATION
Deploy a private cluster with API Server VNet Integration
Important
API Server VNet Integration requires its own subnet. If you don't supply one, AKS automatically creates it in the node resource group.
az aks create \
--name $AKS_CLUSTER \
--resource-group $AKS_RG \
--___location $LOCATION \
--enable-private-cluster \
--enable-apiserver-vnet-integration
After the cluster finishes provisioning, capture the autogenerated node resource group, cluster VNet name, and private FQDN label:
AKS_NODE_RG=$(az aks show -g $AKS_RG -n $AKS_CLUSTER --query nodeResourceGroup -o tsv)
AKS_VNET=$(az network vnet list --resource-group $AKS_NODE_RG --query '[0].name' -o tsv)
DNS_RECORD=$(az aks show -g $AKS_RG -n $AKS_CLUSTER --query privateFqdn -o tsv | cut -d'.' -f1,2)
FRONTEND_IP_CONFIG_ID=$(az network lb show \
--name kube-apiserver \
--resource-group $AKS_NODE_RG \
--query "frontendIPConfigurations[0].id" \
-o tsv)
Create a Private Link Service (PLS) in the AKS cluster VNet
Add a dedicated subnet for the PLS and disable network policies, which aren't supported on Private Link subnets.
Create the PLS and point it to the kube‑apiserver internal load balancer that AKS created for the control plane.
# Subnet for the PLS
az network vnet subnet create \
--name $PLS_SUBNET \
--vnet-name $AKS_VNET \
--resource-group $AKS_NODE_RG \
--address-prefixes $PLS_PREFIX \
--disable-private-link-service-network-policies
# PLS pointing to the API‑server ILB
az network private-link-service create \
--name $PLS_NAME \
--resource-group $AKS_NODE_RG \
--vnet-name $AKS_VNET \
--subnet $PLS_SUBNET \
--lb-frontend-ip-configs $FRONTEND_IP_CONFIG_ID \
--___location $LOCATION
Create a PrivateEndpoint (PE) in the remote VNet
# Remote VNet and subnet
az network vnet create \
--name $REMOTE_VNET \
--resource-group $REMOTE_RG \
--___location $LOCATION \
--address-prefixes $REMOTE_VNET_PREFIX
az network vnet subnet create \
--name $REMOTE_SUBNET \
--vnet-name $REMOTE_VNET \
--resource-group $REMOTE_RG \
--address-prefixes $REMOTE_SUBNET_PREFIX
# Private Endpoint
PLS_ID=$(az network private-link-service show \
--name $PLS_NAME \
--resource-group $AKS_NODE_RG \
--query id -o tsv)
az network private-endpoint create \
--name $PE_NAME \
--resource-group $REMOTE_RG \
--vnet-name $REMOTE_VNET \
--subnet $REMOTE_SUBNET \
--private-connection-resource-id $PLS_ID \
--connection-name $PE_CONN_NAME \
--___location $LOCATION
When the Private Endpoint finishes provisioning, note its network interface (NIC) ID so you can retrieve the allocated private IP address.
PE_NIC_ID=$(az network private-endpoint show \
--name $PE_NAME \
--resource-group $REMOTE_RG \
--query 'networkInterfaces[0].id' \
--output tsv)
# Capture the IP from the NIC
PE_IP=$(az network nic show \
--ids $PE_NIC_ID \
--query 'ipConfigurations[0].privateIPAddress' \
--output tsv)
Configure private DNS
# Create or reuse the regional DNS zone
az network private-dns zone create \
--name $DNS_ZONE \
--resource-group $REMOTE_RG
az network private-dns record-set a create \
--name $DNS_RECORD \
--zone-name $DNS_ZONE \
--resource-group $REMOTE_RG
az network private-dns record-set a add-record \
--record-set-name $DNS_RECORD \
--zone-name $DNS_ZONE \
--resource-group $REMOTE_RG \
--ipv4-address $PE_IP
# Link zone to the remote VNet
REMOTE_VNET_ID=$(az network vnet show \
--name $REMOTE_VNET \
--resource-group $REMOTE_RG \
--query id -o tsv)
az network private-dns link vnet create \
--name $DNS_LINK \
--zone-name $DNS_ZONE \
--resource-group $REMOTE_RG \
--virtual-network $REMOTE_VNET_ID \
--registration-enabled false
Test the connection
If you try to test the connection locally, you might get an error because the DNS zone isn't linked to your local network.
az aks get-credentials --resource-group $AKS_RG --name $AKS_CLUSTER
kubectl get nodes
Deploy a test VM in the remote VNet
To confirm the Private Endpoint path, deploy a test VM in the remote VNet and use it to connect to the AKS cluster.
# Create Network Security Group that allows inbound SSH (TCP 22)
az network nsg create \
--name "${REMOTE_VNET}-nsg" \
--resource-group $REMOTE_RG \
--___location $LOCATION
az network nsg rule create \
--nsg-name "${REMOTE_VNET}-nsg" \
--resource-group $REMOTE_RG \
--name allow-ssh \
--priority 100 \
--access Allow \
--protocol Tcp \
--direction Inbound \
--source-address-prefixes '*' \
--destination-port-ranges 22
# Associate the NSG with the remote subnet
az network vnet subnet update \
--name $REMOTE_SUBNET \
--vnet-name $REMOTE_VNET \
--resource-group $REMOTE_RG \
--network-security-group "${REMOTE_VNET}-nsg"
# Create a small Ubuntu VM in that subnet (with a public IP for quick SSH)
az vm create \
--resource-group $REMOTE_RG \
--name test-vm \
--image Ubuntu2204 \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--vnet-name $REMOTE_VNET \
--subnet $REMOTE_SUBNET \
--public-ip-sku Standard
# Capture the public IP address
VM_PUBLIC_IP=$(az vm show -d -g $REMOTE_RG -n test-vm --query publicIps -o tsv)
Connect to the VM and test the connection
ssh -i ~/.ssh/id_rsa azureuser@$VM_PUBLIC_IP
# Inside the VM
# Install Azure CLI
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
# Install kubectl
sudo az aks install-cli
# re-export the aks variables
export AKS_RG="aks-demo-rg"
export AKS_CLUSTER="aks-private"
# login to Azure. Follow the instructions to authenticate
az login
# Get the AKS credentials
az aks get-credentials --resource-group $AKS_RG --name $AKS_CLUSTER
# Test the connection
kubectl get nodes
# You should see the AKS nodes
# Exit the VM
exit
Clean up resources
To avoid ongoing Azure charges, delete the resource groups using the az group delete
command.
az group delete --name $AKS_RG --yes --no-wait
az group delete --name $REMOTE_RG --yes --no-wait
Next steps
Azure Kubernetes Service