Optimización del escalado automático de servicio de Amazon ECS
Un servicio de Amazon ECS es un conjunto administrado de tareas. Cada servicio tiene una definición de tareas asociada, un recuento de tareas deseado y una estrategia de ubicación opcional. El escalado automático del servicio Amazon ECS se implementa mediante el servicio Application Auto Scaling. Application Auto Scaling utiliza las métricas de CloudWatch como origen para las métricas de escalado. También utiliza las alarmas de CloudWatch para establecer umbrales sobre cuándo ampliar o reducir el servicio. Los umbrales para el escalado se establecen mediante la fijación de un objetivo métrico (escalado de seguimiento de objetivos) o la especificación de umbrales (escalado por pasos). Después de configurar Application Auto Scaling, calcula continuamente el recuento de tareas deseado adecuado para el servicio. También notifica a Amazon ECS cuando debe cambiar el número de tareas deseado, ya sea escalándolo o reduciéndolo horizontalmente.
Para utilizar el escalado automático del servicio de manera eficaz, debe elegir una métrica de escalado adecuada.
Se debe ampliar una aplicación si se prevé que la demanda supere la capacidad actual. Por el contrario, una aplicación se puede ampliar para ahorrar costos cuando los recursos superan la demanda.
Identificación de métricas
Para escalar de forma eficaz, es fundamental identificar una métrica que indique la utilización o la saturación. Esta métrica debe presentar las siguientes propiedades para que sea útil a la hora de escalar.
-
La métrica debe estar correlacionada con la demanda. Cuando los recursos se mantienen estables, pero la demanda cambia, el valor de la métrica también debe cambiar. La métrica debe aumentar o disminuir cuando la demanda aumenta o disminuye.
-
El valor de la métrica debe escalarse en proporción a la capacidad. Cuando la demanda se mantiene constante, la adición de más recursos debe provocar un cambio proporcional en el valor de la métrica. Por lo tanto, si se duplica el número de tareas, la métrica debería disminuir un 50 %.
La mejor forma de identificar una métrica de uso es mediante pruebas de carga en un entorno de preproducción, como un entorno de ensayo. Las soluciones de pruebas de carga comerciales y de código abierto están ampliamente disponibles. Por lo general, estas soluciones pueden generar una carga sintética o simular el tráfico de usuarios reales.
Para iniciar el proceso de pruebas de carga, cree paneles de control para las métricas de uso de su aplicación. Estas métricas incluyen el uso de la CPU, el uso de la memoria, las operaciones de E/S, la profundidad de las colas de E/S y el rendimiento de la red. Puede recopilar estas métricas con un servicio como Información de contenedores. Para obtener más información, consulte Supervisión de contenedores de Amazon ECS mediante Información de contenedores con capacidad de observabilidad mejorada. Durante este proceso, asegúrese de recopilar y trazar métricas para los tiempos de respuesta de su aplicación o las tasas de finalización del trabajo.
Comience con una pequeña tasa de solicitudes o de inserción de tareas. Mantenga esta velocidad constante durante varios minutos para permitir que la aplicación se vaya iniciando. Luego, aumente lentamente la velocidad y manténgala estable durante unos minutos. Repita este proceso y vaya aumentando la velocidad hasta que los tiempos de respuesta o finalización de la aplicación sean demasiado lentos para cumplir sus objetivos de nivel de servicio (SLO).
Durante las pruebas de carga, examine cada una de las métricas de utilización. Las métricas que aumentan junto con la carga son las mejores para ser sus mejores métricas de uso.
A continuación, identifique el recurso que alcanza la saturación. Al mismo tiempo, examine también las métricas de uso para ver cuál se aplana primero en un nivel alto o si alcanza un pico y, después, bloquea primero la aplicación. Por ejemplo, si el uso de la CPU aumenta del 0 % al 70-80 % a medida que se agrega carga y, después, se mantiene en ese nivel después de agregar aún más carga, se puede decir con seguridad que la CPU está saturada. Según la arquitectura de la CPU, es posible que nunca alcance el 100 %. Por ejemplo, supongamos que el uso de la memoria aumenta a medida que se agrega carga y, después, la aplicación se bloquea repentinamente cuando alcanza el límite de memoria de la tarea o de la instancia de Amazon EC2. En esta situación, es probable que la memoria se haya consumido por completo. Es posible que la aplicación consuma varios recursos. Por lo tanto, elija primero la métrica que represente el recurso que se agota.
Por último, vuelva a intentar llevar a cabo las pruebas de carga después de duplicar el número de tareas o instancias de Amazon EC2. Suponga que la métrica clave aumenta o disminuye a la mitad del ritmo anterior. Si este es el caso, la métrica es proporcional a la capacidad. Esta es una buena métrica de uso para el escalado automático.
Consideremos ahora este escenario hipotético. Suponga que lleva a caob una prueba de carga de una aplicación y descubre que el uso de la CPU finalmente alcanza el 80 % con 100 solicitudes por segundo. Cuando se agrega más carga, el uso de la CPU ya no aumenta. Sin embargo, hace que la aplicación responda más lentamente. A continuación, vuelva a ejecutar la prueba de carga y duplique el número de tareas, pero mantenga la velocidad en su valor máximo anterior. Si observa que el uso medio de la CPU se reduce a aproximadamente un 40 %, el uso medio de la CPU es una buena opción para una métrica de escalado. Por otro lado, si el uso de la CPU se mantiene en un 80 % después de aumentar el número de tareas, el uso medio de la CPU no es una buena métrica de escalado. En ese caso, se necesita más investigación para encontrar una métrica adecuada.
Modelos de aplicación comunes y propiedades de escalado
En AWS se ejecuta todo tipo de software. Muchas cargas de trabajo son propias, mientras que otras se basan en el popular software de código abierto. Independientemente de dónde se originen, hemos observado algunos patrones de diseño comunes para los servicios. La forma de escalar de manera efectiva depende en gran medida del patrón.
El servidor eficiente conectado a la CPU
El servidor eficiente conectado a la CPU casi no utiliza recursos distintos del rendimiento de la CPU y la red. La aplicación puede gestionar cada solicitud por sí misma. Las solicitudes no dependen de otros servicios, como las bases de datos. La aplicación puede gestionar cientos de miles de solicitudes simultáneas y, para ello, puede utilizar varias CPU de forma eficiente. Cada solicitud se atiende mediante un subproceso dedicado con poca sobrecarga de memoria, o bien hay un bucle de eventos asíncrono que se ejecuta en cada CPU que atiende las solicitudes. Cada réplica de la aplicación tiene la misma capacidad de gestionar una solicitud. El único recurso que puede agotarse antes que la CPU es el ancho de banda de la red. En los servicios dependientes de la CPU, el uso de la memoria, incluso con un rendimiento máximo, es una fracción de los recursos disponibles.
Este tipo de aplicación es adecuada para el escalado automático basado en CPU. La aplicación disfruta de la máxima flexibilidad en términos de escalado. Se puede escalar verticalmente si se le proporcionan instancias de Amazon EC2 de mayor tamaño o vCPU de Fargate. Además, también se puede escalar horizontalmente mediante la adición de más réplicas. Agregar más réplicas o duplicar el tamaño de la instancia reduce a la mitad el uso medio de la CPU en relación con la capacidad.
Si utiliza la capacidad de Amazon EC2 para esta aplicación, considere colocarla en instancias optimizadas para la computación, como la familia c5
o c6g
.
El servidor eficiente dependiente de la memoria
El servidor eficiente dependiente de la memoria asigna una cantidad significativa de memoria por solicitud. Con la máxima simultaneidad, pero no necesariamente con el rendimiento, la memoria se agota antes de que se agoten los recursos de la CPU. La memoria asociada a una solicitud se libera cuando la solicitud finaliza. Se pueden aceptar solicitudes adicionales siempre que haya memoria disponible.
Este tipo de aplicación es adecuada para el escalado automático basado en memoria. La aplicación disfruta de la máxima flexibilidad en términos de escalado. Se puede escalar verticalmente si se le proporcionan mayores recursos de memoria de Amazon EC2 o Fargate. Además, también se puede escalar horizontalmente mediante la adición de más réplicas. Agregar más réplicas o duplicar el tamaño de la instancia puede reducir a la mitad el uso medio de la memoria en relación con la capacidad.
Si utiliza la capacidad de Amazon EC2 para esta aplicación, considere colocarla en instancias optimizadas para memoria, como la familia r5
o r6g
.
Algunas aplicaciones vinculadas a la memoria no liberan la memoria asociada a una solicitud cuando finaliza, de modo que una reducción de la simultaneidad no se traduce en una reducción de la memoria utilizada. Para ello, no es recomendable que utilice el escalado basado en memoria.
El servidor basado en trabajadores
El servidor basado en trabajadores procesa una solicitud para cada subproceso de trabajo individual, una tras otra. Los subprocesos de trabajo pueden ser subprocesos ligeros, como los subprocesos POSIX. También pueden ser hilos más pesados, como los procesos UNIX. No importa de qué subprocesos se trate, siempre hay una simultaneidad máxima que la aplicación puede admitir. Por lo general, el límite de simultaneidad se establece proporcionalmente a los recursos de memoria disponibles. Si se alcanza el límite de simultaneidad, las solicitudes adicionales se ubican en una cola acumulada. Si la cola de trabajo pendiente se desborda, las solicitudes entrantes adicionales se rechazan inmediatamente. Entre las aplicaciones habituales que se ajustan a este patrón se incluyen el servidor web Apache y Gunicorn.
La simultaneidad de solicitudes suele ser la mejor métrica para escalar esta aplicación. Como hay un límite de simultaneidad para cada réplica, es importante escalar horizontalmente antes de alcanzar el límite medio.
La mejor forma de obtener métricas de simultaneidad de solicitudes es hacer que la aplicación las informe a CloudWatch. Cada réplica de su aplicación puede publicar el número de solicitudes simultáneas como una métrica personalizada con una frecuencia alta. Recomendamos que la frecuencia se establezca como mínimo una vez cada minuto. Tras recopilar varios informes, puede utilizar la simultaneidad media como métrica de escalado. Para calcular esta métrica, se toman la simultaneidad total y se dividen entre el número de réplicas. Por ejemplo, si la simultaneidad total es 1000 y el número de réplicas es 10, la simultaneidad media es 100.
Si su aplicación está detrás de un equilibrador de carga de aplicaciones, también puede usar la métrica ActiveConnectionCount
del equilibrador de carga como un factor en la métrica de escalado. La métrica ActiveConnectionCount
se debe dividir por el número de réplicas para obtener un valor promedio. El valor promedio debe usarse para escalar, en lugar del valor de recuento sin procesar.
Para que este diseño funcione mejor, la desviación estándar de la latencia de respuesta debe ser pequeña a tasas de solicitud bajas. Recomendamos que, durante los periodos de baja demanda, la mayoría de las solicitudes se respondan en poco tiempo, y no hay muchas solicitudes que tarden mucho más tiempo que la media en responder. El tiempo de respuesta promedio debe estar cerca del percentil 95. De lo contrario, podrían producirse desbordamientos de colas como consecuencia de ello. Esto provoca errores. Le recomendamos que proporcione réplicas adicionales cuando sea necesario para mitigar el riesgo de desbordamiento.
El servidor de espera
El servidor de espera procesa en parte cada solicitud, pero depende en gran medida de uno o más servicios intermedios para funcionar. Las aplicaciones contenedoras suelen hacer un uso intensivo de los servicios secundarios, como las bases de datos y otros servicios de API. Estos servicios pueden tardar algún tiempo en responder, especialmente en escenarios de alta capacidad o alta simultaneidad. Esto se debe a que estas aplicaciones suelen utilizar pocos recursos de la CPU y utilizan su máxima simultaneidad en términos de memoria disponible.
El servicio de espera es adecuado tanto en el modelo de servidor limitado en memoria como en el modelo de servidor basado en el trabajador, según el diseño de la aplicación. Si la simultaneidad de la aplicación está limitada únicamente por la memoria, se debe utilizar el uso medio de la memoria como métrica de escalado. Si la simultaneidad de la aplicación se basa en un límite de trabajadores, se debe utilizar la simultaneidad media como métrica de escalado.
El servidor basado en Java
Si su servidor basado en Java está vinculado a la CPU y se escala proporcionalmente a los recursos de la CPU, podría ser adecuado para el patrón eficiente de servidor dependiente de la CPU. Si ese es el caso, el uso medio de la CPU podría ser adecuado como métrica de escalado. Sin embargo, muchas aplicaciones Java no dependen de la CPU, lo que dificulta su escalabilidad.
Para obtener el mejor rendimiento, le recomendamos que asigne la mayor cantidad de memoria posible al montón de máquinas virtuales de Java (JVM). Las versiones recientes de la JVM, incluida la actualización 191 o posterior de Java 8, establecen automáticamente el tamaño del montón lo más grande posible para que quepa en el contenedor. Esto significa que, en Java, el uso de la memoria rara vez es proporcional al uso de las aplicaciones. A medida que aumentan la tasa de solicitudes y la simultaneidad, el uso de la memoria permanece constante. Por este motivo, no recomendamos escalar los servidores basados en Java en función del uso de la memoria. En su lugar, solemos recomendar escalar el uso de la CPU.
En algunos casos, los servidores basados en Java sufren el agotamiento del montón antes de agotar la CPU. Si su aplicación es propensa al agotamiento del montón en alta simultaneidad, el promedio de conexiones es la mejor métrica de escalado. Si su aplicación es propensa al agotamiento del montón en alto rendimiento, la tasa media de solicitudes es la mejor métrica de escalado.
Servidores que utilizan otros tiempos de ejecución de recopilación de elementos no utilizados
Muchas aplicaciones de servidor se basan en tiempos de ejecución de recopilación de elementos no utilizados, como .NET y Ruby. Es posible que estas aplicaciones de servidor se ajusten a uno de los patrones descritos anteriormente. Sin embargo, al igual que con Java, no recomendamos escalar estas aplicaciones en función de la memoria, ya que su uso medio de memoria observado no suele estar correlacionado con el rendimiento o la simultaneidad.
En el caso de estas aplicaciones, se recomienda escalar el uso de la CPU si la aplicación está vinculada a esta. De lo contrario, le recomendamos que escale según el rendimiento medio o la simultaneidad media, en función de los resultados de las pruebas de carga.
Procesadores de tareas
Muchas cargas de trabajo implican el procesamiento asíncrono de las tareas. Incluyen aplicaciones que no reciben solicitudes en tiempo real, sino que se suscriben a una cola de tareas para recibir trabajos. Para este tipo de aplicaciones, la métrica de escalado adecuada casi siempre es la profundidad de la cola. El crecimiento de las colas indica que el trabajo pendiente supera la capacidad de procesamiento, mientras que una cola vacía indica que hay más capacidad que trabajo por hacer.
Los servicios de mensajería de AWS, como Amazon SQS y Amazon Kinesis Data Streams, proporcionan métricas de CloudWatch que se pueden utilizar para escalar. Para Amazon SQS, ApproximateNumberOfMessagesVisible
es la mejor métrica. En Kinesis Data Streams, considere utilizar la métrica MillisBehindLatest
, publicada por Kinesis Client Library (KCL). Esta métrica debe promediarse entre todos los consumidores antes de utilizarla para escalar.