Be careful with Kubernetes requests and limits

Puede que en pleno año 2022 debatir entorno a los requests y limits de Kubernetes, no sea precisamente noticia de primera plana, pero, desgraciadamente, la experiencia personal de un servidor dice que se emplean de forma incorrecta más veces de lo que uno podría desear.

Así, en el presente artículo se pretender detallar que son y cómo hacer uso correcto de estos atributos, para evitar tener que debuguear un cluster en producción, un sábado cualquiera.

Requests and Limits

Tal y como señala la documentación oficial de Kubernetes, el atributo request permite definir la cantidad de CPU o memoria RAM, entre otros, que un contenedor necesita, para que el kube-scheduler pueda determinar el nodo en el que ubicarlo y el kubelet reserve, al menos, la cantidad de recursos solicitada en el cluster.

Esta frase que a priori resulta fácil de comprender, esconde en realidad un matiz brutalmente importantes para su correcta definición, que por lo general, se tiende a pasa por alto. El texto señala que “el request se trata de un valor que especifica la cantidad de recursos que un contenedor necesita“… ¿Para qué exactamente?

La respuesta es para poder entregar el rendimiento que se espera de él, ni más ni menos. Esta afirmación, que bien puede parecer una interpretación un tanto subjetiva de la escueta descripción proporcionada, se fundamenta en como funciona el mecanismo de escalado horizontal de los pods, aunque el hecho de que esta información se emplee para buscar un nodo que pueda satisfacer la demanda y reservar los recursos pertinentes, deberían ser razones más que suficientes para respaldarla.

De nuevo, tal y como señala la documentación oficial de Kubernetes, el HorizontalPodAutoscaler toma como referencia el valor del request para determinar cuándo se ha incrementar o reducir el número de replicas de un Pod. Por ejemplo, si se establece como condición para el escalado que la media de uso de la CPU supere el 80% y se han asignado como request 1000 millicores (1 Core), Kubernetes ampliará el número de instancias cuando se haga un uso medio de 800 millicores, durante un determinado periodo de tiempo (15 segundos por defecto).

apiVersion: apps/v1
kind: Deployment
metadata:
  name: php-apache
spec:
  selector:
    matchLabels:
      run: php-apache
  replicas: 1
  template:
    metadata:
      labels:
        run: php-apache
    spec:
      containers:
      - name: php-apache
        image: k8s.gcr.io/hpa-example
        ports:
        - containerPort: 80
        resources:
          limits:
            cpu: 1100m
          requests:
            cpu: 1000m
---
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: php-apache
spec:
  maxReplicas: 10
  minReplicas: 3
  scaleTargetRef:
    apiVersion: extensions/v1beta1
    kind: Deployment
    name: php-apache
  targetCPUUtilizationPercentage: 80

En lo que al limits se refiere, la documentación oficial de Kubernetes lo define como el atributo permite definir la cantidad de CPU o memoria RAM, entre otros, que un contenedor puede llegar a emplear, teniendo este valor que ser igual o superior al request, de lo contrario, Kubernetes lanzará un error y no se iniciará.

Es decir, si con el request se hace una reserva de los recursos que el contenedor require para entregar el rendimiento esperado, con el limit se establece cuántos más puede tomar, si el nodo el que que se ubica los dispusiera, ya que, por defecto, tratará de emplear todos lo que este a su disposición.

Por ejemplo, si se establece un request de 1 core de CPU y es desplegado en un nodo completamente vacío con 8 cores de CPU, tratará de emplear el resto de cores libres, siempre y cuando no se establezca el atributo limit, claro está.

En definitiva, se podría decir que es una forma de aprovechar los recursos temporalmente en desuso, pero jamás debe ser entendido como el valor requerido para que la aplicación funcione en optimas condiciones.

Bad practice example

Llega la sección que todos estabais esperando, esa en la que se muestran las malas practicas que se emplean día sí y día también a la hora de desplegar aplicativos sobre el orquestador de contenedores ideado por Google.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: php-apache
spec:
  selector:
    matchLabels:
      run: php-apache
  replicas: 1
  template:
    metadata:
      labels:
        run: php-apache
    spec:
      containers:
      - name: php-apache
        image: k8s.gcr.io/hpa-example
        ports:
        - containerPort: 80
        resources:
          limits:
            cpu: 1000m
            memory: 512Mi
          requests:
            cpu: 100m
            memory: 64Mi
---
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: php-apache
spec:
  maxReplicas: 10
  minReplicas: 1
  scaleTargetRef:
    apiVersion: extensions/v1beta1
    kind: Deployment
    name: php-apache
  targetCPUUtilizationPercentage: 80

¿Os habéis encontrado alguna vez con algún Deployment similar a este? ¡Seguro que sí! Lo más seguro es que si preguntas al ideólogo que está detrás de este YAML, os conteste que está perfectamente configurado, que lo ha probado en varios entornos, normalmente no-productivos, y que no ha tenido problema alguno.

Bien, lo primero es hacerle entender que el request no indica el número mínimo de recursos para arrancar o funcionar, sino la cantidad minima necesaria para entregar un rendimiento optimo.

Lo segundo es explicarle que los recursos del limit no están garantizados y que todos los contenedores que residan en un mismo nodo pelearan por ellos. Habrá veces en los que disponga de ellos y otras en las que no, por lo que no es valor fiable con el que él aplicativo pueda responser según lo esperado.

Lo tercero es que comprenda que el autoescalado se calcula en base al request y no al limit, y que con la configuración proporcionada, Kubernetes no tardara mucho en tratar de aumentar el número de instancias del contenedor, a pesar de que los niveles de carga de trabajo sean bajos o nulos. Probablemente esto no lo haya podido comprobar de primero mano, ya que, generalmente, el escalado automático no suele estar activado en entornos no productivos.

Por ultimo, sugerirle una configuración en la que el request disponga de los valores actuales del limit, y que este ultimo sea igual o ligeramente superior. ¿El problema? Si recordáis lo comentado en la sección anterior, el kubelet reserva, al menos, la cantidad de recursos del cluster especificada en el request. Es decir, dado un nodo de 8 cores de CPU, donde antes entraban 80 aplicaciones ahora entran 8, independiente del nivel de carga de trabajo.

Para un entorno productivo o pre-productivo, no hay discusión alguna, se trata de un error de configuración que ha de ser subsanado con urgencia por el bien de la organización, ya que así es como funciona Kubernetes. Existen otros medios para reducir los costes, como el uso de Spot Instances en la nube, aunque existe la posibilidad de que Kubernetes no sea la herramienta mas idónea para dicho caso de uso y se requiera volver a la fase de diseño.

Para entornos no-productivos, el debate esta servido, ya que evidentemente ayuda a ahorrar un buen puñado de dólares, sin muchos quebraderos de cabeza, pero a su vez puede arrastrar problemas hasta el infierno de producción.

Conclusiones

En conclusión, se ha comentado como el request permite definir la cantidad de CPU o memoria RAM, entre otros, que un contenedor necesita para poder entregar el rendimiento que se espera de él, mientras que el limit establece el máximo número de recursos que un contenedor puede llegar a emplear.

Adicionalmente, se ha expuesto un ejemplo con malas practicas, detallando los puntos claves, y que correcciones se deberían aplicar.

Referencias

Se recomienda encarecidamente leer los siguientes artículos que han servido de base para el escrito:

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s