Desescalar nodos de AKS apagando las máquinas en lugar de eliminarlas

A veces tenemos escenarios donde el escalado de AKS requiere que las máquinas estén disponibles lo antes posible, y me refiero no solo su creación sino también las imágenes que necesitamos ya precargadas. Hasta ahora, cada vez que escalábamos nuestro clúster, y luego volvía a desescalar, las máquinas restantes se eliminaban, haciendo con ello que no tuvieramos ningún tipo de coste por estas, pero también que todo lo descargado desapareciera. Es por ello que ahora tenemos un modo llamado Deallocate para nuestros node pools, que nos permite apagarlas en lugar de eliminarlas. En este artículo te comparto mis pruebas.

Crear un clúster de prueba

Si todavía no tienes un entorno de AKS, puedes generar uno de manera sencilla con estos pasos:

#Variables
RESOURCE_GROUP="scale-down-mode-deallocate"
LOCATION="northeurope"
AKS_NAME="deallocate-mode-aks"
# Create Resource Group
az group create --name $RESOURCE_GROUP --location $LOCATION
# Create AKS
az aks create \
--resource-group $RESOURCE_GROUP \
--name $AKS_NAME \
--node-vm-size Standard_B4ms \
--node-count 1 \
--generate-ssh-keys
# Get credentials
az aks get-credentials \
--resource-group $RESOURCE_GROUP \
--name $AKS_NAME

Crear nodepools de prueba

Hasta ahora, cuando creábamos un nuevo node pool lo hacíamos de la siguiente forma:

SCALE_DOWN_DELETE_NODE_POOL="delete"
# Create a nodepool with delete mode
az aks nodepool add \
--node-count 10 \
--node-osdisk-type Managed \
--max-pods 10 \
--name $SCALE_DOWN_DELETE_NODE_POOL \
--cluster-name $AKS_NAME \
--resource-group $RESOURCE_GROUP

Cuando finaliza la creación tenemos un Virtual Machine Scale Set con diez máquinas ejecutándose:

nodepool delete con 10 instancias ejecutándose
nodepool delete con 10 instancias ejecutándose

Si desescalamos el mismo:

# Scale down to 2
az aks nodepool scale \
--node-count 2 \
--name $SCALE_DOWN_DELETE_NODE_POOL \
--cluster-name $AKS_NAME \
--resource-group $RESOURCE_GROUP

Este eliminará las máquinas sobrantes en el proceso, desapareciendo estas del apartado Nodes o Instances, en el VMSS:

nodepool desescalado a 2 nodos desaparecen las instancias
nodepool desescalado a 2 nodos desaparecen las instancias

Ahora vamos a crear otro pool con el modo Deallocate:

SCALE_DOWN_DEALLOCATE_NODE_POOL="deallocate"
# Create a nodepool with deallocate mode
az aks nodepool add \
--node-count 10 \
--scale-down-mode Deallocate \
--node-osdisk-type Managed \
--name $SCALE_DOWN_DEALLOCATE_NODE_POOL \
--cluster-name $AKS_NAME \
--resource-group $RESOURCE_GROUP

Como ves, en este ejemplo lo que hemos hecho ha sido añadir un nuevo parámetro llamado –scale-down-mode Deallocate (necesitas tener la última versión de Azure CLI), que nos va a permitir justamente eso. El comportamiento durante el escalado la primera vez será exactamente igual, es decir que creará las máquinas, pero lo que cambia es el desescalado:

# Scale down to 2
az aks nodepool scale \
--node-count 2 \
--name $SCALE_DOWN_DEALLOCATE_NODE_POOL \
--cluster-name $AKS_NAME \
--resource-group $RESOURCE_GROUP

En este caso te darás cuenta de que efectivamente las máquinas no desaparecen sino que se apagan, haciendo que todo lo que hubiera en ellas, como las imágenes descargadas previamente, permanezcan ahí.

nodepool deallocate desescalado a dos máquinas el resto se apagan, pero no desaparecen
nodepool deallocate desescalado a dos máquinas el resto se apagan, pero no desaparecen

Y la próxima vez que escales en lugar de crear máquinas nuevas utilizará las previamente apagadas.

AKS - deallocate pool escalando utilizando los nodos apagados
deallocate pool escalando utilizando los nodos apagados

Para un escenario como el que comentaba con OpenKruise y la precarga de imágenes pesadas esta aproximación nos puede ayudar a tener máquinas siempre preparadas antes de ser necesarias, agilizando en este sentido el tiempo de escalado. Una configuración para el ImagePullJob podría ser esta:

apiVersion: apps.kruise.io/v1alpha1
kind: ImagePullJob
metadata:
  name: always-mmlspark
spec:
  image: mcr.microsoft.com/mmlspark/release   # [required] image to pull
  parallelism: 10      # [optional] the maximal number of Nodes that pull this image at the same time, defaults to 1
  selector:           # [optional] the names or label selector to assign Nodes (only one of them can be set)
    matchLabels:
      agentpool: deallocate
  completionPolicy:
    type: Always                  # [optional] defaults to Always
    activeDeadlineSeconds: 1200   # [optional] no default, only work for Alway type
    ttlSecondsAfterFinished: 300  # [optional] no default, only work for Alway type
  pullPolicy:                     # [optional] defaults to backoffLimit=3, timeoutSeconds=600
    backoffLimit: 3
    timeoutSeconds: 300

De esta forma, nos aseguramos que el nodepool llamado deallocate tendrá la imagen mmlspark cuando creamos los 10 nodos y cuando apagamos 8 de ellos los mismos seguirán manteniendo esta para cuando necesitemos escalar, sin necesidad de descargarla de nuevo como ocurría hasta ahora. Incluso si la imagen se actualiza, si los Dockerfile están correctamente diseñados muchas de las capas se podrán reaprovechar y solamente se descargará aquellas que hayan cambiado.

Puedes probar con qué rapidez se ejecuta un pod en una máquina del nodepool deallocate vs. el nodepool delete ejecutando estos dos comandos en paralelo:

# Test in a node from deallocate nodepool
kubectl debug node/aks-deallocate-<NODE_NAME> -it --image=mcr.microsoft.com/mmlspark/release
# Test in a node from delete nodepool
kubectl debug node/aks-delete-<NODE_NAME> -it --image=mcr.microsoft.com/mmlspark/release

Tiempos en el escalado

Otra prueba que he hecho ha sido el tiempo de escalado entre un node pool en modo deallocate y otro en modo delete y estos han sido los resultados de la petición:

Tiempos de respuesta en el escalado de un pool en modo deallocate y otro en modo delete

Como ves el tiempo de escalado de las máquinas en sí solo es de medio minuto de diferencia aproximadamente, pero si le sumamos la carga de las imágenes para los pods que requieren de estas el modo deallocate será mucho más eficiente en este sentido.

Importante: En este modo debes tener en cuenta que tendrás un coste adicional por los discos de las máquinas virtuales desalocadas.

¡Saludos!