Blog de Amazon Web Services (AWS)

Escalamiento de nodos de Amazon EKS con Karpenter

Por Douglas Ramiro, Arquitecto de Soluciones especializado en Spot e Graviton de AWS y
Por Lucas Duarte, Arquitecto Especialista en Containers de AWS 

 

Cuando se trata de Kubernetes, uno de los principales desafíos es el escalamiento de las aplicaciones. Una buena definición de la configuración de escalamiento beneficia el rendimiento, la resiliencia y también el ahorro de costos.

Se puede configurar un clúster de Kubernetes para escalar dinámicamente una aplicación tanto horizontalmente, a través del escalador automático de pod horizontal (HPA) o  verticalmente mediante el escalador automático de pod vertical (VPA) .

Durante el proceso de escalamiento, es posible que un pod nuevo quede en estado pendiente (pending) si el clúster no tiene recursos disponibles, tales como CPU o memoria. En este punto, nuevos nodos deben aprovisionarse y estar disponibles en el clúster. Este proceso se puede automatizar mediante el uso del Escalador Automático de Clústeres (Cluster Autoscaler).

El Escalador Automático de Clústeres en EKS se integra con AWS Auto Scaling habilitando la creación de nuevos nodos en un grupo de nodos mediante la modificación del estado deseado. Así mismo, un clúster de EKS puede requerir varios grupos de nodos con estrategias diferentes. Por ejemplo, puede que tenga un grupo de nodos que use instancias reservadas, otro que use instancias SPOT y otro más con instancias bajo demanda.

Debido a esto, se pueden encontrar algunos desafíos en este tipo de entornos. Por ejemplo, ¿qué tipos de instancias EC2 deben formar parte de cada grupo de nodos teniendo en cuenta sus aplicaciones? Dependiendo de la cantidad de pods nuevos que se crearán durante un periodo pico de acceso a la aplicación, puede ser necesario crear varios nodos al mismo tiempo, impactando asi en el tiempo necesario para la creación de nuevos nodos tomando en cuenta el tiempo de recuperación del Cluster Autoscaler.

Pensando en algunas limitaciones existentes en el escalamiento de los grupos de nodos cuando se aumentan la cantidad de nodos, se desarrolló Karpenter. Karpenter es una solución de administración de nodos incubada en AWS Labs y que fue diseñada para funcionar en cualquier clúster de Kubernetes, como una solución de código abierto.

Karpenter observa los pods que están en estado pendiente por falta de recursos y aprovisiona automáticamente un nuevo nodo con la capacidad requerida, seleccionando la mejor instancia para ese momento. Para realizar esta función, Karpenter analiza la definición de solicitud de recursos del Pod y también sus restricciones como, por ejemplo, el uso de NodeSelector para definir una etiqueta (label) específica. Karpenter tomará estas definiciones en consideración para el aprovisionamiento las nuevas instancias.

A modo de ejemplo, en un escenario en el que hay 8 pods que requieren 1 vCPU y 1 GiB de memoria, Karpenter puede aprovisionar una sola máquina que sirva para estos pods, tal como lo es una instancia c1.xlarge.

Karpenter optimiza los tiempos programación y la eficiencia de utilización basándose en dos bucles de control complementarios. El primero es el asignador, un controlador sensible a la latencia y de acción rápida, responsable de garantizar que los pods pendientes se asignen lo más rápido posible. El segundo es el reasignador, un controlador sensible a los costos y de acción lenta que reemplaza los nodos a medida que las solicitudes de pod y los precios de capacidad cambian con el tiempo. Juntos, maximizan la disponibilidad y la eficiencia de su clúster.

Además, Karpenter provee la capacidad de terminar los nodos que se consideran expirados, y la capacidad mover los pods entre nodos para realizar una mejor asignación de recursos. Dichos ajustes se configuran en una Definición de Recursos Personalizada (CRD) llamada Provisioner.

Karpenter es compatible con las versiones de Kubernetes v.1.19 y posteriores. Sin embargo, Karpenter depende de las nuevas funciones de Kubernetes, por lo que este alcance puede cambiar.

 

Instalación y configuración de un clúster de EKS para usar Karpenter

  1. Antes de instalar un clúster EKS y Karpenter, se deben instalar y configurar las siguientes utilidades:
    • CLI de AWS – interfaz de línea de comandos de AWS
    • kubectl – interfaz de línea de comandos de Kubernetes
    • eksctl – interfaz de línea de comandos de AWS EKS
    • helm – administrador de paquetes de Kubernetes
  1. Después de instalar y configurar las utilidades, se deben definir las siguientes variables de entorno:
export CLUSTER_NAME=karpenter-demo 
export AWS_DEFAULT_REGION=us-east-1 
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
  1. Utilizando eksctl, se crea el nuevo clúster de EKS. En este ejemplo, será un clúster simple que definirá un proveedor de OIDC de IAM. Esta configuración es necesaria para habilitar las funciones de IAM en los pods.
cat <<EOF > cluster.yaml
---
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: ${CLUSTER_NAME}
  region: ${AWS_DEFAULT_REGION}
  version: "1.20"
managedNodeGroups:
  - instanceType: m5.large
    amiFamily: AmazonLinux2
    name: ${CLUSTER_NAME}-nodegroup
    desiredCapacity: 1
    minSize: 1
    maxSize: 10
iam:
  withOIDC: true
EOF

eksctl create cluster -f cluster.yaml
NOTA
Karpenter se puede ejecutar en clústeres con grupos auto-administrados, grupos de nodos administrados o AWS Fargate. En esta demostración, se utiliza un grupo auto-administrados.
  1. Karpenter identifica qué zonas de disponibilidad podrá utilizar para aprovisionar los nuevos nodos del clúster a través de subredes que tengan la etiqueta io/cluster/$CLUSTER_NAME. Para agregar la etiqueta a las subredes privadas del clúster, se ejecuta el siguiente comando:
SUBNET_IDS=$(aws cloudformation describe-stacks \
 --stack-name eksctl-${CLUSTER_NAME}-cluster \
 --query 'Stacks[].Outputs[?OutputKey==`SubnetsPrivate`].OutputValue' \
 --output text)

aws ec2 create-tags \
 --resources $(echo $SUBNET_IDS | tr ',' '\n') \
 --tags Key="kubernetes.io/cluster/${CLUSTER_NAME}",Value=
  1. Las instancias creadas en el servicio EC2 por Karpenter deben ejecutarse con un perfil de instancia (InstanceProfile) que pueda ejecutar contenedores y configurar la red. Karpenter buscará un perfil que siga la siguiente nomenclatura:
KarpenterNodeRole-${ClusterName}

Use los siguientes comandos para crear los recursos de IAM requeridos:

TEMPOUT=$(mktemp)
curl -fsSL https://karpenter.sh/docs/getting-started/cloudformation.yaml > $TEMPOUT \
&& aws cloudformation deploy \  
--stack-name Karpenter-${CLUSTER_NAME} \  
--template-file ${TEMPOUT} \  
--capabilities CAPABILITY_NAMED_IAM \  
--parameter-overrides ClusterName=${CLUSTER_NAME}
  1. Las nuevas instancias necesitan permisos para conectarse al clúster. Ejecute el siguiente comando para agregar el rol Karpenter creada en el paso anterior en el aws-auth configmap, para que las nuevas instancias EC2 se puedan conectar al clúster.
eksctl create iamidentitymapping \  
--username system:node:{{EC2PrivateDNSName}} \  
--cluster  ${CLUSTER_NAME} \  
--arn arn:aws:iam::${AWS_ACCOUNT_ID}:role/KarpenterNodeRole-${CLUSTER_NAME} \  
--group system:bootstrappers \  
--group system:nodes
  1. Karpenter necesita permisos para crear nuevas instancias, por ejemplo. En el paso 5 se creó una política (policy) con los permisos necesarios. Cree una nueva cuenta de servicio para usar esa política:
eksctl create iamserviceaccount \
  --cluster $CLUSTER_NAME --name karpenter --namespace karpenter \
  --attach-policy-arn arn:aws:iam::$AWS_ACCOUNT_ID:policy/KarpenterControllerPolicy-$CLUSTER_NAME \
  --approve
  1. Si nunca ha utilizado instancias Spot en su cuenta de AWS, cree el rol de servicio asociado a EC2 Spot (EC2 Spot Service Linked Role):
aws iam create-service-linked-role —aws-service-name spot.amazonaws.com

Si esta función ya se creó anteriormente, se mostrará el siguiente error y puede ser ignorado:

An error occurred (InvalidInput) when calling the CreateServiceLinkedRole operation: 
Service role name AWSServiceRoleForEC2Spot has been taken in this account, please try a different suffix.
  1. La instalación de Karpenter se hace a través del despliegue de un chart utilizando helm:
helm repo add karpenter https://charts.karpenter.sh
helm repo update
helm upgrade --install --skip-crds karpenter karpenter/karpenter --namespace karpenter \  
--create-namespace --set serviceAccount.create=false --version 0.4.1 \  
--set controller.clusterName=${CLUSTER_NAME} \  
--set controller.clusterEndpoint=$(aws eks describe-cluster --name ${CLUSTER_NAME} --query "cluster.endpoint" --output json) \  
--wait
  1. Karpenter utiliza una definición de recursos personalizada (CRD) denominada CRD de aprovisionador (provisioner CRD) para aprovisionar nodos en el clúster.  Esta CRD puede definir varias configuraciones, como las manchas (taints) que tendrá el nodo, las etiquetas, qué instancias se pueden usar, en qué zonas de disponibilidad se puede crear el nuevo nodo, qué arquitectura de procesador se soporta, entre otras. Cree un nuevo aprovisionador usando el siguiente comando:
cat <<EOF | kubectl apply -f -
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
  name: default
spec:
  requirements:
    - key: karpenter.sh/capacity-type
      operator: In
      values: ["spot","on-demand"]
  provider:
    instanceProfile: KarpenterNodeInstanceProfile-${CLUSTER_NAME}
  ttlSecondsAfterEmpty: 30
EOF

Soportando diferentes arquitecturas de procesadores

Cuando un aprovisionador no tiene especificada la arquitectura de procesador ni los tipos de instancias con los que se crearán los nuevos nodos, de manera predeterminada se utilizarán todas las instancias y todas las arquitecturas soportadas (amd64 y arm64). Esto significa que se puede crear una instancia de Graviton y, como resultado, es posible que la aplicación que se esté ejecutando no admita la arquitectura de la instancia, generando errores en el momento del lanzamiento.

Por razones de costo y rendimiento, es interesante ejecutar contenedores que admitan múltiples arquitecturas de procesador. En el siguiente blogpost podrá ver cómo crear imágenes de contenedores con soporte multi-arquitectura.

En algunos casos no es posible utilizar la arquitectura ARM porque no tiene acceso a la construcción de imágenes o, incluso, porque la aplicación no admite la arquitectura. Para estas aplicaciones se pueden usar dos técnicas para la especificación de arquitectura.

La primera es crear un aprovisionador alternativo en el que solo se defina la arquitectura AMD. De esta manera, al crear un nuevo Deployment, es posible especificar ese nuevo abastecedor a través de un selector de nodos (node selector).  Para utilizar esta técnica, defina un selector de nodos en el que la llave sea karpenter.sh/provisioner-name y el valor sea el nombre del abastecedor creado:

spec:
  template:
    spec:
      nodeSelector:
        karpenter.sh/provisioner-name: novo-provisioner

La segunda forma es usar la well-known label kubernetes.io/arch en el manifiesto de la aplicación (deployment / POD) usando también un selector de nodos. Con esta forma, Karpenter aprovisionará un nodo que admita la arquitectura especificada.

spec:
  template:
    spec:
      nodeSelector:
        kubernetes.io/arch: amd64

Esta técnica se puede utilizar con otras etiquetas. Por ejemplo, puede utilizarse para especificar en qué zona de disponibilidad se debe crear el nuevo nodo.

Cómo Karpenter administra las interrupciones Spot en comparación con el grupo de nodos administrados de AWS

Para simular la interrupción de una instancia Spot, podemos usar AWS Fault Injection Simulator (FIS).  FIS es un servicio totalmente administrado para ejecutar experimentos de inyección de fallas en AWS, lo que facilita la mejora del rendimiento, la observabilidad y la resiliencia de una aplicación. Los experimentos de inyección de fallas se utilizan en la ingeniería del caos, que es la práctica de estresar una aplicación al probar o producir entornos que crean eventos disruptivos, como un aumento repentino en el consumo de CPU o memoria, observar cómo responde el sistema e implementar mejoras. El experimento de inyección de fallas ayuda a los equipos a crear las condiciones reales necesarias para descubrir errores ocultos, monitoreando puntos ciegos y los cuellos de botella de rendimiento que son difíciles de encontrar en los sistemas distribuidos.

Aprovechando FIS, podemos simular una interrupción de una instancia SPOT y comprobar su comportamiento cuando se detiene, independientemente si Karpenter o el grupo de nodos administrados la han creado.

Cuando una instancia es creada desde un grupo de nodos administrados, al simular una interrupción, tan pronto como se propaga el rebalanceo en respuesta al evento, el drenaje del nodo se realiza automáticamente. Aquí, el nodo se marca como no programable (unschedulable), lo que imposibilita la implementación de un nuevo pod en ese nodo. Si otros nodos tienen la capacidad de recibir los pods que se han finalizado y la cantidad de nodos es mayor o igual al mínimo establecido en el grupo de nodos administrados, no se crearán otros nodos. En caso contrario, el grupo de nodos administrados aprovisionará una nueva instancia EC2 de acuerdo con el tipo de instancia que fue definido para el grupo, la agregará al clúster y los pods se implementarán en el nuevo nodo.

Este flujo es diferente cuando se crea una instancia desde Karpenter. Karpenter no tiene integración nativa con los eventos Spot. En este caso, tan pronto como ocurre una interrupción, el nodo se convierte en NotReady inmediatamente y los pods que estaban en él se terminan. Luego, EKS aprovisiona nuevos pods para reemplazar los antiguos. Si tienes otros nodos capaces de recibir los pods terminados, no se crearán otros nodos. Si no hay capacidad, Karpenter analizará la mejor instancia para usar estos pods y aprovisionará una nueva instancia EC2 para que los pods se puedan implementar.

Es posible tener un comportamiento similar al del grupo de nodos administrados cuando se usa Karpenter, es decir, drenar el nodo cuando se inicia el evento de rebalanceo. Esto brinda la ventaja de la posibilidad de cerrar la aplicación con gracia.  Para ello, puede utilizar AWS Node Termination Handler. Con AWS Node Termination Handler, tan pronto como se envía el evento de rebalanceo, se realiza el drenaje y el nodo se marca como no programable.  Luego, EKS aprovisiona nuevos pods para reemplazar los antiguos. Si es necesario, Karpenter aprovisiona un nuevo nodo para recibir los pods. La diferencia entre esta solución para el grupo de nodos administrados es que el controlador de terminación de nodos de AWS requiere implementar un pod en cada nodo para realizar un monitoreo de la instancia, verificando si ha recibido un evento, mientras que el grupo de nodos administrados lo hace de forma nativa.

 

¿Cómo gestiona Karpenter las versiones de actualización del Control Plane?

Amazon EKS proporciona una forma prescriptiva de actualizar el Control Plane. El proceso de actualización consiste en que Amazon EKS lanza nuevos nodos de API Server con la versión actualizada de Kubernetes para reemplazar los nodos existentes. Como se trata de un servicio administrado, Amazon EKS realiza comprobaciones de estado en estos nuevos nodos para validar su funcionamiento. Es importante tener en cuenta que al actualizar la versión, la versión de los grupos de nodos debe ser la misma que la versión del Control Plane. Puede encontrar más información sobre la actualización prescriptiva en este enlace.

Para los nodos del Data Plane, Karpenter comprueba automáticamente la versión del Control Plane. En este punto, tan pronto como Karpenter lanza un nuevo nodo, la versión de kubelet ya está actualizada para reflejar la misma versión del Control Plane de Amazon EKS, lo que facilita el proceso de upgrade. Esto se debe a que Karpenter usa automáticamente una nueva AMI con la versión actualizada de kubeletEs importante recordar que sólo se actualiza kubelet, los complementos instalados por separado, así como los administrados por EKS, deben actualizarse si es necesario.

Conclusión

Karpenter, como se ha demostrado en este blog, es una solución para el ciclo de vida de los nodos en Kubernetes, instalada como un complemento (add-on) de nuestro clúster con la Definición de Recursos Personalizadas (CRD). Para facilitar su configuración, se puede utilizar junto con otros complementos, como AWS Node Termination Handler, y se integra a la perfección con Amazon EKS y otros servicios de AWS. Podemos usar Karpenter como reemplazo del Cluster AutoScaler, ya que sus características son similares.

Un beneficio importante de Karpenter es que tenemos la opción de seleccionar la instancia más rentable para satisfacer nuestra demanda, y podemos usar instancias de Graviton, que cuestan menos comparadas con x86, así como Spot de manera automática, simplemente definiendo esa especificación en el aprovisionador.

Existen varias formas de resolver el problema del ciclo de vida del nodo y Karpenter es una de ellas. Es importante tener en cuenta que cada caso es un caso de uso específico y que las pruebas con la solución elegida son muy importantes para garantizar que su clúster de Kubernetes funcione sin problemas, ya sea con Karpenter, el Cluster AutoScaler o cualquier otra herramienta disponible.

 

Este artículo fue traducido del Blog de AWS en Portugues.

 


Sobre los autores

Douglas Ramiro es un arquitecto de soluciones especializado en Spot y Graviton en AWS. Los clientes de AWS ahorran dinero todos los días al adoptar estas tecnologías. Douglas tiene la misión de difundir estas tecnologías en LATAM para que los clientes de AWS puedan reducir sus costos.

 

 

 

 

 

Lucas Duarte es un arquitecto experto en contenedores en AWS con un enfoque en los clientes de LATAM. Entusiasta de la automatización, la nube y la cultura de DevOps. Con experiencia previa en proyectos centrados en este segmento en empresas como iFood, Guiabolso y Mandic. Ha trabajado en diferentes proyectos relacionados principalmente con la orquestación de contenedores y microservicios.