Integración de Services de Kubernetes con Azure Private Links

En el mes de mayo se anunció, en public preview, la posibilidad de integrar los objetos de tipo Services con la creación de Private Links de Azure. Gracias a ello, ahora será más sencillo la creación de estos y no tendrás que buscar la IP del balanceador interno al que asociarlos, quedando además reflejado como parte de la configuración del manifiesto del servicio. En este artículo te cuento cómo probarlo.

Crear un clúster de prueba

Si todavía no lo tienes, puedes crear un clúster de prueba ejecutando los siguientes comandos:

# Variables
RESOURCE_GROUP="aks-private-link-service-resource"
AKS_NAME="aks-pls-demo"
LOCATION="northeurope"
AKS_VNET="aks-vnet"
AKS_SUBNET="aks-subnet"
PLS_SUBNET="pls-subnet"
# Create a resource group
az group create --name $RESOURCE_GROUP --location $LOCATION
# Create a vnet for the cluster
az network vnet create \
--resource-group $RESOURCE_GROUP \
--name $AKS_VNET \
--address-prefixes 10.0.0.0/8 \
--subnet-name $AKS_SUBNET \
--subnet-prefixes 10.240.0.0/24
# Create a subnet for the Private Links
az network vnet subnet create \
--resource-group $RESOURCE_GROUP \
--vnet-name $AKS_VNET \
--name $PLS_SUBNET \
--address-prefixes 10.241.0.0/24
# Assign permissions to the vnet
# Create a service principal and read in the application ID
SP=$(az ad sp create-for-rbac --output json)
SP_ID=$(echo $SP | jq -r .appId)
SP_PASSWORD=$(echo $SP | jq -r .password)
# Wait 15 seconds to make sure that service principal has propagated
echo "Waiting for service principal to propagate..."
sleep 15
# Get the virtual network resource ID
VNET_ID=$(az network vnet show --resource-group $RESOURCE_GROUP --name $AKS_VNET --query id -o tsv)
# Assign the service principal Contributor permissions to the virtual network resource
az role assignment create --assignee $SP_ID --scope $VNET_ID --role Contributor
# Get the virtual network subnet resource ID
SUBNET_ID=$(az network vnet subnet show --resource-group $RESOURCE_GROUP --vnet-name $AKS_VNET --name $AKS_SUBNET --query id -o tsv)
# Create an AKS cluster
az aks create \
--resource-group $RESOURCE_GROUP \
--name $AKS_NAME \
--node-count 1 \
--generate-ssh-keys \
--node-vm-size Standard_B4ms \
--vnet-subnet-id $SUBNET_ID \
--service-principal $SP_ID \
--client-secret $SP_PASSWORD \
--docker-bridge-address 172.17.0.1/16 \
--dns-service-ip 10.2.0.10 \
--service-cidr 10.2.0.0/24 \
--network-plugin azure
# Get the cluster credentials
az aks get-credentials \
--resource-group $RESOURCE_GROUP \
--name $AKS_NAME

Como ves, estoy creando la red virtual a parte y he añadido además una subnet específica para los private links que vaya generando, asociados a mis servicios.

Servicios integrados con Private Link

Una vez que tengas el clúster, para probar esta integración es tan sencillo como esto:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: web
        image: nginx
        resources:
          limits:
            memory: "128Mi"
            cpu: "500m"
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: my-private-link-service
  annotations:
    service.beta.kubernetes.io/azure-load-balancer-internal: "true" # Right now PLS must be used with internal LB
    service.beta.kubernetes.io/azure-pls-create: "true"
    service.beta.kubernetes.io/azure-pls-name: my-private-link-service
    service.beta.kubernetes.io/azure-pls-ip-configuration-subnet: pls-subnet
    service.beta.kubernetes.io/azure-pls-ip-configuration-ip-address-count: "1"
    service.beta.kubernetes.io/azure-pls-ip-configuration-ip-address: 10.241.0.9 # Must be available in pls-subnet
    # service.beta.kubernetes.io/azure-pls-fqdns: "fqdn1 fqdn2"
    # service.beta.kubernetes.io/azure-pls-proxy-protocol: "false"
    service.beta.kubernetes.io/azure-pls-visibility: "*"
    # service.beta.kubernetes.io/azure-pls-auto-approval: "subId1"
spec:
  type: LoadBalancer
  selector:
    app: web
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80

Como ves, he creado un Deployment con la imagen de nginx, para tener algo a lo que consultar, y después he generado un Service del tipo LoadBalancer con un montón de anotaciones:

  • En primer lugar, es necesario que el balanceador que se utilice sea de tipo internal.
  • En este caso, queremos que con la creación de este servicio se cree el private link.
  • Le decimos en qué subnet queremos que alojemos el private link (pls-subnet)
  • Cuántas IP le asociamos
  • Y la IP que queremos asociarla, la cual debe estar disponible dentro de la subnet, en este caso la 10.241.0.9.
  • Hay otras anotaciones que puedes consultar aquí, que para este ejemplo no hacen falta.

El resto de especificaciones son las habituales en un Service.

Probar la comunicación con los servicios

Para comprobar que todo funciona correctamente, he creado una máquina virtual dentro de la misma red donde se encuentra el clúster:

# Create a VM in the same vnet as the cluster
az vm create \
--resource-group $RESOURCE_GROUP \
--name jumpbox-vm \
--image UbuntuLTS \
--admin-username azureuser \
--admin-password AzurePassword1234 \
--size Standard_B4ms \
--vnet-name $AKS_VNET \
--subnet $VM_SUBNET 
# Get the VM public IP address
VM_IP=$(az vm list-ip-addresses --resource-group $RESOURCE_GROUP --name jumpbox-vm --query '[0].virtualMachine.network.publicIpAddresses[0].ipAddress' -o tsv)
# Connect to the vm via ssh
ssh [email protected]$VM_IP

Recupero la IP del balanceador generada para el Servicio:

k get svc my-private-link-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}'

Y hago una llamada a esta IP, para acceder a través del puerto 80 al Servicio que a su vez está asociado al Private Link:

curl http://10.240.0.35 # This is the Azure Internal Load Balancer IP

Por cada nuevo servicio que crees el balanceador interno cogerá una nueva IP y la asociará al mismo:

Balanceador interno alocando IPs por cada Service que creamos

De la misma forma que creará el private link correspondiente:

Private Link Services creados con la integración con los objetos de tipo Service

y estos se asociarán de manera automática al balanceador interno de nuestro clúster de Kubernetes.

¡Saludos!