Autoescalar tus aplicaciones en Kubernetes

Ahora que ya te he contado cómo desplegar tus pods a través de Jobs, CronJobs, Deployments, ReplicaSets (obvié los ReplicationControllers porque deberían caer en el deuso) y los StatefulSets, para terminar la semana quería mostrarte cómo de sencillo es autoescalar tus aplicaciones dentro de Kubernetes.

HorizontalPodAutoscaler

Para que una aplicación pueda aumentar o disminuir de manera automática sus réplicas necesitas crear un objeto llamado HorizontalPodAutoscaler. Este se encargará de subir y/o bajar el número de réplicas que necesita tu aplicación en cada momento en base a unos umbrales que establezcas.

Para este ejemplo voy a utilizar un despliegue de una página web en ASP.NET Core:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: aspnet
  template:
    metadata:
      labels:
        app: aspnet
    spec:
      containers:
      - name: web
        image: mcr.microsoft.com/dotnet/core/samples:aspnetapp
        resources:
          requests:
            cpu: "100m"            
        ports:
        - containerPort: 80

---

apiVersion: v1
kind: Service
metadata:
  name: aspnet-svc
spec:
  type: LoadBalancer
  selector:
    app: aspnet
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

Esta tiene configurada durante su creación 3 réplicas. Ahora, para crear este objeto la forma más sencilla es a través del comando autoscale:

kubectl autoscale deployment web --cpu-percent=30 --min=1 --max=5

Este recuperará todas las métricas de los pods que componen tu despliegue y hará una operación aritmética para determinar si el número de pods actual es el adecuado, en base al porcentaje establecido para el umbral, en este caso del 30%. Para este ejemplo sólo me estoy basando en el estado de la CPU, pero también es posible combinarlo con la memoria, o solamente basarnos en esta.
El proceso de muestreo no es inmediato sino que se revisan las métricas cada cierto tiempo (por defecto cada 15 segundos). Es por ello que inicialmente el valor de la operación que te contaba aparecerá como unknown y después de unos segundos comenzará a mostrar el valor actual del cálculo realizado:

Giselas-MacBook-Pro:HPA gis$ kubectl get hpa --watch
NAME   REFERENCE        TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
web    Deployment/web   <unknown>/30%   1         5         0          7s
web    Deployment/web   <unknown>/30%   1         5         3          20s
web    Deployment/web   2%/30%          1         5         3          95s

De hecho, si esperas unos minutos comprobarás que de las 3 réplicas que habías establecido se quedará únicamente una al no tener actividad en la web:

Giselas-MacBook-Pro:HPA gis$ kubectl get hpa --watch
NAME   REFERENCE        TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
web    Deployment/web   <unknown>/30%   1         5         0          7s
web    Deployment/web   <unknown>/30%   1         5         3          20s
web    Deployment/web   2%/30%          1         5         3          95s
web    Deployment/web   3%/30%          1         5         3          2m36s
web    Deployment/web   1%/30%          1         5         3          3m37s
web    Deployment/web   1%/30%          1         5         3          5m24s
web    Deployment/web   1%/30%          1         5         1          5m39s

Date cuenta de que para que un despliegue pueda autoescalar es necesario indicar los requests de los containers del pod que estableces para el HPA, ya que de lo contrario no funcionará. Te darás cuenta porque en los Eventos del Horizontal Pods Autoscaler aparecerá lo siguiente:

Events:
  Type     Reason                        Age               From                       Message
  ----     ------                        ----              ----                       -------
  Warning  FailedGetResourceMetric       7s (x2 over 22s)  horizontal-pod-autoscaler  missing request for cpu
  Warning  FailedComputeMetricsReplicas  7s (x2 over 22s)  horizontal-pod-autoscaler  invalid metrics (1 invalid out of 1), first error is: failed to get cpu utilization: missing request for cpu

Ahora que ya tienes una web y un objeto HPA, vamos a intentar que el despliegue llegue hasta las 5 réplicas que tiene establecidas como máximo, haciendo que la CPU de los pods se dispare. Antes de ello, vigila en dos terminales separados lo siguiente:

#Terminal 1
kubectl get hpa --watch

#Terminal 2
kubectl get deployment --watch

Para estresar mis pods voy a utilizar Apache Benchmark con el siguiente comando:

ab -n 50000 -c 200 http://IP_DEL_ASPNET_SVC/

Como ves, le he pedido 50.000 peticiones simulando 200 usuarios concurrentes. Igual me he pasado un poco 😛 pero tendrás tiempo más que de sobra para ver cómo se comporta el autoescalado:

Giselas-MacBook-Pro:HPA gis$ kubectl get hpa --watch
NAME   REFERENCE        TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
web    Deployment/web   <unknown>/30%   1         5         0          7s
web    Deployment/web   <unknown>/30%   1         5         3          20s
web    Deployment/web   2%/30%          1         5         3          95s
web    Deployment/web   3%/30%          1         5         3          2m36s
web    Deployment/web   1%/30%          1         5         3          3m37s
web    Deployment/web   1%/30%          1         5         3          5m24s
web    Deployment/web   1%/30%          1         5         1          5m39s
web    Deployment/web   2%/30%          1         5         1          6m40s
web    Deployment/web   1%/30%          1         5         1          7m41s
web    Deployment/web   2%/30%          1         5         1          8m41s
web    Deployment/web   366%/30%        1         5         1          9m42s
web    Deployment/web   366%/30%        1         5         4          9m58s
web    Deployment/web   366%/30%        1         5         5          10m
web    Deployment/web   50%/30%         1         5         5          10m
web    Deployment/web   65%/30%         1         5         5          11m
web    Deployment/web   62%/30%         1         5         5          12m
web    Deployment/web   1%/30%          1         5         5          13m
web    Deployment/web   1%/30%          1         5         5          18m
web    Deployment/web   1%/30%          1         5         1          18m

También verás cómo el deployment ha ido ajustando el número de réplicas según la CPU iba aumentando o disminuyendo para tus pods:

Giselas-MacBook-Pro:HPA gis$ kubectl get deployment --watch
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
web    0/3     3            0           17s
web    1/3     3            1           24s
web    2/3     3            2           25s
web    3/3     3            3           26s
web    3/1     3            3           5m28s
web    3/1     3            3           5m28s
web    1/1     1            1           5m28s
web    1/4     1            1           9m46s
web    1/4     1            1           9m46s
web    1/4     1            1           9m46s
web    1/4     4            1           9m47s
web    2/4     4            2           9m49s
web    3/4     4            3           9m49s
web    4/4     4            4           9m49s
web    4/5     4            4           10m
web    4/5     4            4           10m
web    4/5     4            4           10m
web    4/5     5            4           10m
web    5/5     5            5           10m
web    5/1     5            5           18m
web    5/1     5            5           18m
web    1/1     1            1           18m

Por último si revisas los eventos del HorizontalPodAutoscaler:

kubectl describe hpa web

Podrás comprobar todas las subidas y bajas que ha tenido que realizar en base a las métricas recibidas de tus pods:

Events:
  Type     Reason                        Age                 From                       Message
  ----     ------                        ----                ----                       -------
  Warning  FailedGetResourceMetric       19m (x5 over 20m)   horizontal-pod-autoscaler  unable to get metrics for resource cpu: no metrics returned from resource metrics API
  Warning  FailedComputeMetricsReplicas  19m (x5 over 20m)   horizontal-pod-autoscaler  invalid metrics (1 invalid out of 1), first error is: failed to get cpu utilization: unable to get metrics for resource cpu: no metrics returned from resource metrics API
  Normal   SuccessfulRescale             11m                 horizontal-pod-autoscaler  New size: 4; reason: cpu resource utilization (percentage of request) above target
  Normal   SuccessfulRescale             10m                 horizontal-pod-autoscaler  New size: 5; reason: cpu resource utilization (percentage of request) above target
  Normal   SuccessfulRescale             2m7s (x2 over 15m)  horizontal-pod-autoscaler  New size: 1; reason: All metrics below target

Si bien todo esto nos permite gestionar picos de manera automatizada, la versión estable, a día de hoy, de los HorizontalPodAutoscaler solo se puede basar en las métricas CPU y memoria. En muchos escenarios son más que suficientes pero en otros se requiere vigilar algo más. Por ejemplo, si tu aplicación es más propensa a tener cuellos de botella en la red, más que en la CPU o en la memoria, seguramente te interese valorar otras métricas. Por otro lado, también puede ocurrir que necesites tener en cuenta servicios externos, como pueden ser colas de mensajerías.

A partir de Kubernetes 1.6 se añade soporte para hacer uso de métricas personalizadas y externas a través de la versión autoscaling/v2beta2. En un próximo artículo te mostraré cómo hacerlo.

¡Saludos!