Logotipo de Zephyrnet

Utilice Karpenter para acelerar Amazon EMR en el escalado automático de EKS

Fecha:

Amazon EMR en Amazon EKS es una opción de implementación para EMR de Amazon que permite a las organizaciones ejecutar Apache Spark en Servicio Amazon Elastic Kubernetes (Amazon EKS). Con EMR en EKS, los trabajos de Spark se ejecutan en el tiempo de ejecución de Amazon EMR para Apache Spark. Esto aumenta la rendimiento de sus trabajos de Spark para que se ejecuten más rápido y cuesten menos que Apache Spark de código abierto. Además, puede ejecutar aplicaciones Apache Spark basadas en Amazon EMR con otros tipos de aplicaciones en el mismo clúster de EKS para mejorar la utilización de los recursos y simplificar la administración de la infraestructura.

carpintero se presentó en AWS re:Invent 2021 para proporcionar una solución de escalado automático de clúster de código abierto, dinámica y de alto rendimiento para Kubernetes. Aprovisiona automáticamente nuevos nodos en respuesta a pods no programables. Observa las solicitudes de recursos agregados de los pods no programados y toma decisiones para lanzar nuevos nodos y detenerlos para reducir las latencias de programación y los costos de infraestructura.

Para configurar Karpenter, crea aprovisionadores que definen cómo Karpenter gestiona los pods que están pendientes y caducan los nodos. Aunque la mayoría de los casos de uso se abordan con un solo aprovisionador, varios aprovisionadores son útiles en casos de uso de múltiples inquilinos, como aislar nodos para facturación, usar diferentes restricciones de nodo (como sin GPU para un equipo) o usar diferentes configuraciones de desaprovisionamiento. Karpenter lanza nodos con recursos informáticos mínimos para adaptarse a pods no programables para un binpacking eficiente. Funciona en conjunto con el programador de Kubernetes para vincular pods no programables a los nuevos nodos que se aprovisionan. El siguiente diagrama ilustra cómo funciona.

Esta publicación muestra cómo integrar Karpenter en su EMR en la arquitectura EKS para lograr capacidades de escalado automático más rápidas y conscientes de la capacidad para acelerar sus cargas de trabajo de big data y aprendizaje automático (ML) mientras reduce los costos. Ejecutamos la misma carga de trabajo con Cluster Autoscaler y Karpenter, para ver algunas de las mejoras que analizamos en la siguiente sección.

Mejoras en comparación con Cluster Autoscaler

como Karpenter, Escalador automático de clústeres de Kubernetes (CAS) está diseñado para agregar nodos cuando llegan solicitudes para ejecutar pods que no se pueden cumplir con la capacidad actual. Cluster Autoscaler es parte del proyecto Kubernetes, con implementaciones de los principales proveedores de la nube de Kubernetes. Al revisar el aprovisionamiento, Karpenter ofrece las siguientes mejoras:

  • Sin sobrecarga de administración de grupos de nodos – Debido a que tiene diferentes requisitos de recursos para diferentes cargas de trabajo de Spark junto con otras cargas de trabajo en su clúster de EKS, debe crear grupos de nodos separados que puedan cumplir con sus requisitos, como tamaños de instancia, zonas de disponibilidad y opciones de compra. Esto puede crecer rápidamente a decenas y cientos de grupos de nodos, lo que agrega una sobrecarga de administración adicional. Karpenter administra cada instancia directamente, sin el uso de mecanismos de orquestación adicionales como grupos de nodos, adoptando un enfoque sin grupos llamando al API de flota de EC2 directamente a los nodos de aprovisionamiento. Esto le permite a Karpenter usar diversos tipos de instancias, zonas de disponibilidad y opciones de compra con solo crear un único aprovisionador, como se muestra en la siguiente figura.
  • Reintentos rápidos - Si el Nube informática elástica de Amazon (Amazon EC2) la capacidad no está disponible, Karpenter puede volver a intentarlo en milisegundos en lugar de minutos. Esto puede ser realmente útil si está utilizando EC2 Spot Instances y no puede obtener capacidad para tipos de instancias específicos.
  • Diseñado para manejar la flexibilidad total de la nube – Karpenter tiene la capacidad de abordar de manera eficiente la gama completa de tipos de instancias disponibles a través de AWS. Cluster Autoscaler no se creó originalmente con la flexibilidad para manejar cientos de tipos de instancias, zonas de disponibilidad y opciones de compra. Recomendamos ser lo más flexible posible para permitir que Karpenter obtenga la capacidad justo a tiempo que necesita.
  • Mejora la utilización general del nodo mediante el empaquetamiento en contenedores – Karpenter procesa por lotes los pods pendientes y luego los empaqueta según la CPU, la memoria y las GPU requeridas, teniendo en cuenta la sobrecarga del nodo (por ejemplo, los recursos del conjunto de demonios requeridos). Después de que los pods se empaquetan en el tipo de instancia más eficiente, Karpenter toma otros tipos de instancia que son similares o más grandes que el empaque más eficiente y pasa las opciones de tipo de instancia a una API llamada EC2 Fleet, siguiendo algunas de las mejores prácticas de diversificación de instancias. para mejorar las posibilidades de obtener la capacidad de solicitud.

Mejores prácticas usando Karpenter con EMR en EKS

Para conocer las mejores prácticas generales con Karpenter, consulte Mejores prácticas de Karpenter. Las siguientes son cosas adicionales a considerar con EMR en EKS:

  • Evitando costo de transferencia de datos entre AZ ya sea configurando el aprovisionador de Karpenter para que se inicie en una sola zona de disponibilidad o use seleccionador de nodos or afinidad y antiafinidad para programar el controlador y los ejecutores del mismo trabajo en una sola zona de disponibilidad. Ver el siguiente código:
    nodeSelector: topology.kubernetes.io/zone: us-east-1a

  • Optimice los costes de las cargas de trabajo de Spark mediante EC2 Spot Instances para ejecutores e On-Demand Instances para el controlador mediante el selector de nodos con la etiqueta karpenter.sh/capacity-type en el plantillas de cápsulas. Recomendamos usar plantillas de pod para especificar pods de controladores para ejecutar en instancias bajo demanda y pods de ejecución para ejecutar en instancias de spot. Esto le permite consolidar las especificaciones del aprovisionador porque no necesita dos especificaciones por tipo de trabajo. También sigue la mejor práctica de usar la personalización definida en los tipos de carga de trabajo y mantener las especificaciones del aprovisionador para admitir una cantidad más amplia de casos de uso.
  • Cuando utilice EC2 Spot Instances, maximice la diversificación de instancias en la configuración del aprovisionador para cumplir con los y las mejores prácticas. Para seleccionar tipos de instancia adecuados, puede utilizar el Selector de instancias EC2, una herramienta CLI y una biblioteca Go que recomienda tipos de instancias en función de criterios de recursos como vCPU y memoria.

Resumen de la solución

Esta publicación proporciona un ejemplo de cómo configurar Cluster Autoscaler y Karpenter en un clúster de EKS y comparar las mejoras de escalado automático mediante la ejecución de un EMR de muestra en la carga de trabajo de EKS.

El siguiente diagrama ilustra la arquitectura de esta solución.

Usamos Transaction Processing Performance Council-Decision Support (TPC-DS), un punto de referencia de soporte de decisiones para ejecutar secuencialmente tres consultas Spark SQL (q70-v2.4, q82-v2.4, q64-v2.4) con un número fijo de 50 ejecutores, contra 17.7 mil millones de registros, aproximadamente 924 GB de datos comprimidos en formato de archivo Parquet. Para obtener más detalles sobre TPC-DS, consulte el repositorio de GitHub de eks-spark-benchmark.

Enviamos el mismo trabajo con diferentes especificaciones de controlador y ejecutor de Spark para imitar diferentes trabajos únicamente para observar el comportamiento de escalado automático y el empaquetado en contenedores. Le recomendamos que dimensione correctamente sus ejecutores de Spark en función de las características de la carga de trabajo para las cargas de trabajo de producción.

El siguiente código es un ejemplo de configuración de Spark que da como resultado solicitudes de especificaciones de pod de 4 vCPU y 15 GB:

--conf spark.executor.instances=50 --conf spark.driver.cores=4 --conf spark.driver.memory=10g --conf spark.driver.memoryOverhead=5g --conf spark.executor.cores=4 --conf spark.executor.memory=10g --conf spark.executor.memoryOverhead=5g

Utilizamos plantillas de cápsulas para programar controladores Spark en instancias bajo demanda y ejecutores en instancias de spot EC2 (que pueden ahorrar hasta un 90 % sobre los precios de las instancias bajo demanda). La resiliencia inherente de Spark hace que el controlador lance nuevos ejecutores para reemplazar los que fallan debido a las interrupciones de Spot. Ver el siguiente código:

apiVersion: v1
kind: Pod
spec: nodeSelector: karpenter.sh/capacity-type: spot containers: - name: spark-kubernetes-executor apiVersion: v1
kind: Pod
spec: nodeSelector: karpenter.sh/capacity-type: on-demand containers: - name: spark-kubernetes-driver

Requisitos previos

Usamos un Nube de AWS9 IDE para ejecutar todas las instrucciones a lo largo de esta publicación.

Para crear su IDE, ejecute los siguientes comandos en AWS CloudShell. La región predeterminada es us-east-1, pero puede cambiarlo si es necesario.

# clone the repo
git clone https://github.com/black-mirror-1/karpenter-for-emr-on-eks.git
cd karpenter-for-emr-on-eks
./setup/create-cloud9-ide.sh

Navegue al IDE de AWS Cloud9 utilizando la URL de la salida del script.

Instalar herramientas en el IDE de AWS Cloud9

Instale las siguientes herramientas requeridas en el entorno de AWS Cloud9 mediante la ejecución de un script:

Ejecute las siguientes instrucciones en su entorno AWS Cloud9 y no en CloudShell.

  1. Clona el repositorio de GitHub:
    cd ~/environment
    git clone https://github.com/black-mirror-1/karpenter-for-emr-on-eks.git
    cd ~/environment/karpenter-for-emr-on-eks

  2. Configure las variables de entorno requeridas. Siéntase libre de ajustar el siguiente código de acuerdo a sus necesidades:
    # Install envsubst (from GNU gettext utilities) and bash-completion
    sudo yum -y install jq gettext bash-completion moreutils # Setup env variables required
    export EKSCLUSTER_NAME=aws-blog
    export EKS_VERSION="1.23"
    # get the link to the same version as EKS from here https://docs.aws.amazon.com/eks/latest/userguide/install-kubectl.html
    export KUBECTL_URL="https://s3.us-west-2.amazonaws.com/amazon-eks/1.23.7/2022-06-29/bin/linux/amd64/kubectl"
    export HELM_VERSION="v3.9.4"
    export KARPENTER_VERSION="v0.18.1"
    # get the most recent matching version of the Cluster Autoscaler from here https://github.com/kubernetes/autoscaler/releases
    export CAS_VERSION="v1.23.1"

  3. Instale las herramientas de la CLI de AWS Cloud9:
    cd ~/environment/karpenter-for-emr-on-eks
    ./setup/c9-install-tools.sh

Aprovisionar la infraestructura

Configuramos los siguientes recursos mediante el script de infraestructura de provisión:

  1. Cree el EMR en la infraestructura de EKS y Karpenter:
    cd ~/environment/karpenter-for-emr-on-eks
    ./setup/create-eks-emr-infra.sh

  2. Valide la configuración:
    # Should have results that are running
    kubectl get nodes
    kubectl get pods -n karpenter
    kubectl get po -n kube-system -l app.kubernetes.io/instance=cluster-autoscaler
    kubectl get po -n prometheus

Comprender las configuraciones de Karpenter

Debido a que la carga de trabajo de muestra tiene especificaciones de controlador y ejecutor de diferentes tamaños, hemos identificado las instancias de las familias c5, c5a, c5d, c5ad, c6a, m4, m5, m5a, m5d, m5ad y m6a de tamaños 2xlarge, 4xlarge, 8xlarge y 9xlarge para nuestra carga de trabajo usando el selector-de-instancia-amazon-ec2 CLI. Con CAS, necesitamos crear un total de 12 grupos de nodos, como se muestra en eksctl-config.yaml, pero puede definir las mismas restricciones en Karpenter con un solo aprovisionador, como se muestra en el siguiente código:

apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata: name: default
spec: provider: launchTemplate: {EKSCLUSTER_NAME}-karpenter-launchtemplate subnetSelector: karpenter.sh/discovery: {EKSCLUSTER_NAME} labels: app: kspark requirements: - key: "karpenter.sh/capacity-type" operator: In values: ["on-demand","spot"] - key: "kubernetes.io/arch" operator: In values: ["amd64"] - key: karpenter.k8s.aws/instance-family operator: In values: [c5, c5a, c5d, c5ad, m5, c6a] - key: karpenter.k8s.aws/instance-size operator: In values: [2xlarge, 4xlarge, 8xlarge, 9xlarge] - key: "topology.kubernetes.io/zone" operator: In values: ["{AWS_REGION}a"] limits: resources: cpu: "2000" ttlSecondsAfterEmpty: 30

Hemos configurado ambos escaladores automáticos para reducir los nodos que están vacíos durante 30 segundos usando ttlSecondsAfterEmpty en Karpenter y --scale-down-unneeded-time en CAS.

Karpenter por diseño intentará lograr el empaquetamiento más eficiente de los pods en un nodo en función de la CPU, la memoria y las GPU requeridas.

Ejecutar una carga de trabajo de muestra

Para ejecutar una carga de trabajo de muestra, complete los siguientes pasos:

  1. Vamos a revisar el Interfaz de línea de comandos de AWS (AWS CLI) comando para enviar un trabajo de muestra:
    aws emr-containers start-job-run --virtual-cluster-id $VIRTUAL_CLUSTER_ID --name karpenter-benchmark-${CORES}vcpu-${MEMORY}gb --execution-role-arn $EMR_ROLE_ARN --release-label emr-6.5.0-latest --job-driver '{ "sparkSubmitJobDriver": { "entryPoint": "local:///usr/lib/spark/examples/jars/eks-spark-benchmark-assembly-1.0.jar", "entryPointArguments":["s3://blogpost-sparkoneks-us-east-1/blog/BLOG_TPCDS-TEST-3T-partitioned","s3://'$S3BUCKET'/EMRONEKS_TPCDS-TEST-3T-RESULT-KA","/opt/tpcds-kit/tools","parquet","3000","1","false","q70-v2.4,q82-v2.4,q64-v2.4","true"], "sparkSubmitParameters": "--class com.amazonaws.eks.tpcds.BenchmarkSQL --conf spark.executor.instances=50 --conf spark.driver.cores='$CORES' --conf spark.driver.memory='$EXEC_MEMORY'g --conf spark.executor.cores='$CORES' --conf spark.executor.memory='$EXEC_MEMORY'g"}}' --configuration-overrides '{ "applicationConfiguration": [ { "classification": "spark-defaults", "properties": { "spark.kubernetes.node.selector.app": "kspark", "spark.kubernetes.node.selector.topology.kubernetes.io/zone": "'${AWS_REGION}'a", "spark.kubernetes.container.image": "'$ECR_URL'/eks-spark-benchmark:emr6.5", "spark.kubernetes.driver.podTemplateFile": "s3://'$S3BUCKET'/pod-template/karpenter-driver-pod-template.yaml", "spark.kubernetes.executor.podTemplateFile": "s3://'$S3BUCKET'/pod-template/karpenter-executor-pod-template.yaml", "spark.network.timeout": "2000s", "spark.executor.heartbeatInterval": "300s", "spark.kubernetes.executor.limit.cores": "'$CORES'", "spark.executor.memoryOverhead": "'$MEMORY_OVERHEAD'G", "spark.driver.memoryOverhead": "'$MEMORY_OVERHEAD'G", "spark.kubernetes.executor.podNamePrefix": "karpenter-'$CORES'vcpu-'$MEMORY'gb", "spark.executor.defaultJavaOptions": "-verbose:gc -XX:+UseG1GC", "spark.driver.defaultJavaOptions": "-verbose:gc -XX:+UseG1GC", "spark.ui.prometheus.enabled":"true", "spark.executor.processTreeMetrics.enabled":"true", "spark.kubernetes.driver.annotation.prometheus.io/scrape":"true", "spark.kubernetes.driver.annotation.prometheus.io/path":"/metrics/executors/prometheus/", "spark.kubernetes.driver.annotation.prometheus.io/port":"4040", "spark.kubernetes.driver.service.annotation.prometheus.io/scrape":"true", "spark.kubernetes.driver.service.annotation.prometheus.io/path":"/metrics/driver/prometheus/", "spark.kubernetes.driver.service.annotation.prometheus.io/port":"4040", "spark.metrics.conf.*.sink.prometheusServlet.class":"org.apache.spark.metrics.sink.PrometheusServlet", "spark.metrics.conf.*.sink.prometheusServlet.path":"/metrics/driver/prometheus/", "spark.metrics.conf.master.sink.prometheusServlet.path":"/metrics/master/prometheus/", "spark.metrics.conf.applications.sink.prometheusServlet.path":"/metrics/applications/prometheus/" }} ]}'

  2. Envíe cuatro trabajos con diferentes vCPU de controlador y ejecutor y tamaños de memoria en Karpenter:
    # the arguments are vcpus and memory
    export EMRCLUSTER_NAME=${EKSCLUSTER_NAME}-emr
    ./sample-workloads/emr6.5-tpcds-karpenter.sh 4 7
    ./sample-workloads/emr6.5-tpcds-karpenter.sh 8 15
    ./sample-workloads/emr6.5-tpcds-karpenter.sh 4 15
    ./sample-workloads/emr6.5-tpcds-karpenter.sh 8 31 

  3. Para monitorear el estado de escalado automático de los pods en tiempo real, abra una nueva terminal en Cloud9 IDE y ejecute el siguiente comando (no se devuelve nada al principio):
    watch -n1 "kubectl get pod -n emr-karpenter"

  4. Observe el estado de escalado automático de la instancia EC2 y del nodo en una segunda pestaña de terminal ejecutando el siguiente comando (por diseño, Karpenter programa en la zona de disponibilidad a):
    watch -n1 "kubectl get node --label-columns=node.kubernetes.io/instance-type,karpenter.sh/capacity-type,topology.kubernetes.io/zone,app -l app=kspark"

Comparar con Cluster Autoscaler (Opcional)

Hemos configurado Cluster Autoscaler durante el paso de configuración de la infraestructura con la siguiente configuración:

  • Lanzar nodos de EC2 en la zona de disponibilidad b
  • Contiene 12 grupos de nodos (6 de cada uno para On-Demand y Spot)
  • Reduzca los nodos innecesarios después de 30 segundos con --scale-down-unneeded-time
  • Usa el mínimo desperdicio expansor en CAS, que puede seleccionar el grupo de nodos que tendrá la CPU menos inactiva para la eficiencia de binpacking
  1. Envíe cuatro trabajos con diferentes vCPU de controlador y ejecutor y tamaños de memoria en CAS:
    # the arguments are vcpus and memory
    ./sample-workloads/emr6.5-tpcds-ca.sh 4 7
    ./sample-workloads/emr6.5-tpcds-ca.sh 8 15
    ./sample-workloads/emr6.5-tpcds-ca.sh 4 15
    ./sample-workloads/emr6.5-tpcds-ca.sh 8 31

  2. Para monitorear el estado de escalado automático de los pods en tiempo real, abra una nueva terminal en Cloud9 IDE y ejecute el siguiente comando (no se devuelve nada al principio):
    watch -n1 "kubectl get pod -n emr-ca"

  3. Observe el estado de escalado automático de la instancia EC2 y del nodo en una segunda pestaña de terminal ejecutando el siguiente comando (por diseño, programaciones de CAS en la zona de disponibilidad b):
    watch -n1 "kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone,app -l app=caspark"

Observaciones

En promedio, el tiempo desde la creación del pod hasta la programación es menor con Karpenter que con CAS, como se muestra en la siguiente figura; puede ver una diferencia notable cuando ejecuta cargas de trabajo a gran escala.

Como se muestra en las siguientes figuras, a medida que se completaban los trabajos, Karpenter pudo reducir los nodos que no se necesitaban en cuestión de segundos. Por el contrario, CAS tarda unos minutos, porque envía una señal a los grupos de nodos, lo que agrega latencia adicional. Esto, a su vez, ayuda a reducir los costos generales al reducir la cantidad de segundos que se ejecutan las instancias EC2 innecesarias.

Limpiar

Para limpiar su entorno, elimine todos los recursos creados en orden inverso ejecutando el script de limpieza:

export EKSCLUSTER_NAME=aws-blog
cd ~/environment/karpenter-for-emr-on-eks
./setup/cleanup.sh

Conclusión

En esta publicación, le mostramos cómo usar Karpenter para simplificar el aprovisionamiento de nodos de EKS y acelerar el escalado automático de EMR en cargas de trabajo de EKS. Lo alentamos a que pruebe Karpenter y proporcione sus comentarios creando un GitHub .

Seguí leyendo


Acerca de los autores

Changbin Gong es Arquitecto Principal de Soluciones en Amazon Web Services. Se relaciona con los clientes para crear soluciones innovadoras que aborden los problemas comerciales de los clientes y aceleren la adopción de los servicios de AWS. En su tiempo libre, a Changbin le gusta leer, correr y viajar.

Sandeep Palavalasa es Sr. Specialist Containers SA en Amazon Web Services. Es un líder en tecnología de software con más de 12 años de experiencia en la creación de sistemas de software distribuidos a gran escala. Su carrera profesional comenzó con un enfoque en el monitoreo y la observabilidad y tiene una sólida experiencia en arquitectura de nube. Le gusta trabajar en sistemas distribuidos y le entusiasma hablar sobre el diseño de la arquitectura de microservicios. Sus intereses actuales están en las áreas de servicios de contenedores y tecnologías sin servidor.

punto_img

Información más reciente

punto_img