Escalado vertical de tus pods en Kubernetes con VerticalPodAutoscaler

Ya te he contado en varios artículos cómo puedes hacer para que tus aplicaciones crezcan de un modo u otro, utilizando HorizontalPodAutoscaler, KEDA o desbordando con Virtual Kubelet. Una de las opciones que me quedaba por explorar era cómo autoescalar los pods verticalmente gracias al recurso VerticalPodAutoscaler.

En el artículo «Administrar los recursos para tus contenedores en Kubernetes» te contaba que es buena práctica especificar por cada contenedor cuántos son los recursos que este va a necesitar. El problema es que normalmente no es un dato fácil de estimar, ya que normalmente solo es posible conseguirlo a base de monitorizar tu aplicación a lo largo del tiempo. Aquí es donde entra en juego VPA, quien hace este trabajo por ti.

Instalación de VPA en tu clúster

A día de hoy este recurso no está por defecto instalado en los clústeres de Kubernetes. Para este ejemplo voy a utilizar Azure Kubernetes Service, aunque se pueden seguir estos mismos pasos para cualquier otro.

#Login on Azure
az login

#Variables
RESOURCE_GROUP="VPA-AKS"
AKS_NAME="vpa-aks"

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

#Clone autoscaler repository
git clone https://github.com/kubernetes/autoscaler.git
cd autoscaler/vertical-pod-autoscaler
#Install VerticalPodAutoscaler
./hack/vpa-up.sh

En el código anterior, específicamente para Azure, inicio sesión en la plataforma y recupero las credenciales del clúster donde quiero instalar VPA. Clono el proyecto kubernetes/autoscaler, donde está incluido el script vpa-up.sh, que da de alta todos los recursos necesarios para dejar mi clúster listo.

¿Cómo pruebo el escalado vertical?

Ahora que ya tienes todo preparado, lo siguiente es entender cómo funciona este recurso. Para ello, voy a crear un despliegue tan sencillo como el que sigue:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: web
        image: nginx

El objetivo es que los contenedores de web-deployment ajusten de manera automática el apartado requests que, como ves, en este ejemplo no están ni especificados. Para ello, debes crear un VPA asociado a este despliegue:

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: web-vpa
spec:
  targetRef:
    apiVersion: "apps/v1"
    kind:       Deployment
    name:       web-deployment
  updatePolicy:
    updateMode: "Auto"

En este ejemplo he puesto el modo automático, el cual no solo nos mostrará las recomendaciones para nuestros contenedores sino que además aplicará las mismas:

➜  VPA kubectl get vpa web-vpa -o yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"autoscaling.k8s.io/v1","kind":"VerticalPodAutoscaler","metadata":{"annotations":{},"name":"web-vpa","namespace":"default"},"spec":{"targetRef":{"apiVersion":"apps/v1","kind":"Deployment","name":"web-deployment"},"updatePolicy":{"updateMode":"Auto"}}}
  creationTimestamp: "2020-07-25T06:35:07Z"
  generation: 24
  name: web-vpa
  namespace: default
  resourceVersion: "175400"
  selfLink: /apis/autoscaling.k8s.io/v1/namespaces/default/verticalpodautoscalers/web-vpa
  uid: 3403142a-4d8c-4317-a60a-c958e97f4660
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-deployment
  updatePolicy:
    updateMode: Auto
status:
  conditions:
  - lastTransitionTime: "2020-07-25T06:36:10Z"
    status: "True"
    type: RecommendationProvided
  recommendation:
    containerRecommendations:
    - containerName: web
      lowerBound:
        cpu: 25m
        memory: 262144k
      target:
        cpu: 25m
        memory: 262144k
      uncappedTarget:
        cpu: 25m
        memory: 262144k
      upperBound:
        cpu: 799m
        memory: "836064315"

Hay que tener cuidado con esto ya que cada vez que VPA intente hacer un ajuste recreará los contenedores del pod. También es importante que sepas que, sino pones ningún máximo en tu VPA, este modo puede hacer que algunos pods se queden en estado pendiente por no tener suficiente espacio.

Para que esto no ocurra, puedes modificar su definición, añadiendo el apartado containerPolicies > maxAllowed:

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: web-vpa
spec:
  targetRef:
    apiVersion: "apps/v1"
    kind:       Deployment
    name:       web-deployment
  updatePolicy:
    updateMode: "Auto"
  resourcePolicy:
    containerPolicies:
    - containerName: '*'
      maxAllowed:
        cpu: 50m
        memory: 10m

En este caso he puesto un asterisco para que estos máximos apliquen a todos los contenedores, pero podría especificarlos de forma individual si fuera necesario.

También es posible recibir únicamente las recomendaciones, sin que estas se apliquen, poniendo a updateMode el valor Off y aplicar estas de manera manual.

Otra de las cosas que te pueden interesar es excluir del autoajuste a alguno de los contenedores de tu pod. Podría añadir un segundo contenedor a mi despliegue:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: web
        image: nginx
      - name: cache
        image: redis

Y en el VPA indicar que el contenedor cache queda deshabilitado para el ajuste:

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: web-vpa
spec:
  targetRef:
    apiVersion: "apps/v1"
    kind:       Deployment
    name:       web-deployment
  updatePolicy:
    updateMode: "Auto"
  resourcePolicy:
    containerPolicies:
    - containerName: '*'
      maxAllowed:
        cpu: 50m
        memory: 10m
    - containerName: cache
      mode: "Off"

¡Saludos!