Acceder a un clúster privado de AKS a través de VPN P2S

Hoy he estado jugando con el siguiente escenario: Necesitaba montarme un clúster de AKS privado y, como no soy una empresa 🙂 y no quería instalarme una máquina de salto en la misma red que mi clúster, quería acceder a este a través de una conexión VPN P2S, conectada a la red donde está mi AKS. Te cuento cómo lo he hecho.

Crear la red virtual

Lo primero que voy a hacer es crear la red, y aprovecho para crear el grupo de recursos, donde estará mi AKS.

#Variables
RESOURCE_GROUP="private-aks"
LOCATION="North Europe"
VNET_NAME="aks-vnet"
AKS_VNET_CIDR=10.10.0.0/16
AKS_SUBNET_NAME="aks-subnet"
AKS_SUBNET_CIDR=10.10.1.0/24
#Create resource group
az group create -n $RESOURCE_GROUP -l $LOCATION
#Create the virtual network
az network vnet create \
--name $VNET_NAME \
--resource-group $RESOURCE_GROUP \
--location $LOCATION \
--address-prefixes $AKS_VNET_CIDR \
--subnet-name $AKS_SUBNET_NAME \
--subnet-prefixes $AKS_SUBNET_CIDR

No he dejado que se genere durante la creación del clúster porque me va a ser más sencillo manipular esta misma después. También necesito crear un service principal que tenga permisos de Network contributor sobre este para que mi AKS pueda luego manipular la red.

#Create a service principal and assign permissions
az ad sp create-for-rbac --skip-assignment > auth.json
VNET_ID=$(az network vnet show --resource-group $RESOURCE_GROUP --name $VNET_NAME --query id -o tsv)
SUBNET_ID=$(az network vnet subnet show --resource-group $RESOURCE_GROUP --vnet-name $VNET_NAME --name $AKS_SUBNET_NAME --query id -o tsv)
APP_ID=$(jq -r .appId auth.json)
SECRET=$(jq -r .password auth.json)
# The service principal needs to have permissions to manage the virtual network
az role assignment create --assignee $APP_ID --scope $VNET_ID --role "Network Contributor"

Ahora que ya tengo la red,la subnet que va a usar mi AKS y el Service Principal el siguiente paso es crear el clúster.

Crear un clúster de AKS privado en una red propia

Para este paso puedo utilizar este comando, uniéndolo con todo lo anterior:

AKS_NAME="private-aks"
#Create a private AKS cluster
az aks create \
--resource-group $RESOURCE_GROUP \
--name $AKS_NAME \
--node-count 1 \
--vnet-subnet-id $SUBNET_ID \
--service-principal $APP_ID \
--client-secret $SECRET \
--generate-ssh-keys \
--load-balancer-sku standard \
--enable-private-cluster

Ahora bien, una vez que el proceso termine, si recupero las credenciales:

#Get credentials
az aks get-credentials -n $AKS_NAME -g $RESOURCE_GROUP

e intento cualquier operación sobre el AKS, el resultado será que no es posible, ya que no estoy en la misma red que el clúster y este es privado.

No puedo acceder desde local al clúster de AKS porque no tiene IP pública

Es por ello que necesitamos un componente que nos permita conectarnos a dicha red desde mi local, un VPN Gateway.

Crear un VPN Gateway

En realidad, te mostré un caso parecido con un App Service usando private links hace unos meses. En este caso le toca el turno a AKS, por lo que me creo este componente dentro de mi grupo de recursos, asociado a la misma red, de la siguiente manera:

#Let's create a VPN gateway to connect to the cluster
VPN_GATEWAY_NAME="gateway"
VPN_GATEWAY_CIDR=10.10.2.0/24
#Create a subnet in the vnet for the gateway
az network vnet subnet create \
--name GatewaySubnet \
--vnet-name $VNET_NAME \
--resource-group $RESOURCE_GROUP \
--address-prefixes $VPN_GATEWAY_CIDR
#Create a public IP for the VPN Gateway
az network public-ip create \
  --name "${VPN_GATEWAY_NAME}-ip" \
  --resource-group $RESOURCE_GROUP \
  --allocation-method Dynamic
  
# Define CIDR block for the VPN clients
ADDRESS_POOL_FOR_VPN_CLIENTS=10.30.0.0/16
# Azure Active Directory info
#https://login.microsoftonline.com/<YOUR_TENANT_ID>
TENANT_ID=$(jq -r .tenant auth.json)
AZURE_VPN_CLIENT_ID="41b23e61-6c1e-4545-b367-cd054e0ed4b4"
#You have to consent Azure VPN application in your tenant first:
https://login.microsoftonline.com/common/oauth2/authorize?client_id=41b23e61-6c1e-4545-b367-cd054e0ed4b4&response_type=code&redirect_uri=https://portal.azure.com&nonce=1234&prompt=admin_consent
# Create a VPN Gateway
az network vnet-gateway create \
  --name $VPN_GATEWAY_NAME \
  --location $LOCATION \
  --public-ip-address "${VPN_GATEWAY_NAME}-ip" \
  --resource-group $RESOURCE_GROUP \
  --vnet $VNET_NAME \
  --gateway-type Vpn \
  --sku VpnGw2 \
  --vpn-type RouteBased \
  --address-prefixes $ADDRESS_POOL_FOR_VPN_CLIENTS \
  --client-protocol OpenVPN \
  --vpn-auth-type AAD \
  --aad-tenant "https://login.microsoftonline.com/${TENANT_ID}" \
  --aad-audience $AZURE_VPN_CLIENT_ID \
  --aad-issuer "https://sts.windows.net/${TENANT_ID}/"
# Get VPN client configuration
az network vnet-gateway vpn-client generate \
--resource-group $RESOURCE_GROUP \
--name $VPN_GATEWAY_NAME

Estos comandos crearán una nueva subnet en la red donde está alojado mi clúster de AKS, generará una IP pública que asociaremos a un VPN gateway, que además ya he dejado configurado para que pueda autenticarme usando Azure Active Directory. El último comando nos proporciona un zip con la configuración para usarla con Azure VPN, como mostré en el articulo de App Service con Private Link en este mismo escenario.

Sin embargo, si vuelvo a ejecutar el comando anterior, kubectl get nodes, seguiré obteniendo el mismo error. ¿Por qué ocurre esto? Porque si bien tenemos acceso a los recursos de esa red no estamos siendo capaces de resolver el nombre del private link asociado la IP del API Server de nuestro Kubernetes.

Modificar /etc/hosts

Para este artículo voy a dejarlo en la opción más sencilla, que es añadir el nombre a resolver en el archivo /etc/hosts. Con estos comandos puedes recuperar el nombre a resolver, el del private link, y la IP que hay detrás de este:

#Get AKS private link
PRIVATE_FQDN=$(az aks show --resource-group $RESOURCE_GROUP --name $AKS_NAME --query "privateFqdn" -o tsv)
#Get private IP
NODE_RESOURCE_GROUP=$(az aks show --resource-group $RESOURCE_GROUP --name $AKS_NAME --query "nodeResourceGroup" -o tsv)
KUBE_API_SERVER_IP=$(az network nic list --resource-group $NODE_RESOURCE_GROUP --query "[?contains(name, 'kube-apiserver')].ipConfigurations[0].privateIpAddress" -o tsv)
echo "$KUBE_API_SERVER_IP $PRIVATE_FQDN" >> /etc/hosts

En mi caso quedaría una entrada como la siguiente:

10.10.1.4 private-ak-private-aks-ca0cd4-1ca3b861.c1a215f7-292d-47ea-825e-2f8af8ffefa1.privatelink.northeurope.azmk8s.io

Si ahora vuelves a intentar hablar con tu clúster verás que ya es posible.

Para un entorno de pruebas/desarrollo esta sería una opción válida. Sin embargo en entornos productivos se recomienda el uso de un DNS Forwarder.

El código con el script lo tienes en mi GitHub.

¡Saludos!

2 Comments

  1. SurielR junio 12, 2025 at 3:38 pm

    Hola buenas tardes, ya hice los pasos anteriores (la diferencia es que mi aks esta en una red diferente a la del VPN Gateway pero ya estan emparejadas (peering), al hacer una conexion Point to Site desde la VPN (que por naturaleza me conecta en otro segmento diferente a la VPN, pero si logro alcanzar los servicios de la VPN pero no asi el servicio aks) estoy obteniendo el siguiente mensaje: dial tcp 15.15.1.4:443: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.

    Reply
  2. SurielR septiembre 8, 2025 at 3:34 pm

    Respondiendo a mi pregunta, recién revise de nuevo esta situación y el hecho de que el aks estaba en diferente red que mi vpn fue el problema, solución: " Del lado de la VPN configurar el P2S agregando como rutas adicionales (Additional routes to advertise) la red en la que se encuentra el aks ej(15.15.1.0), esto permitira fluir el trafico de la vpn hacia el servicio aks, en segundo paso dentro de la configuracion peering, permitir el flujo de trafico entre ambas redes y lo mas importante en el peering sobre la red del aks hacia la VPN es necesario indicar que se usarara el gateway de la red de la VPN esto permitira recibir trafico de manera inversa para permitir la comunicación bilateral

    Reply

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.