Me preguntaba cuál era el enfoque correcto para implementar una aplicación Django en contenedor usando gunicorn & celery.
Específicamente, cada uno de estos procesos tiene una forma integrada de escalar verticalmente, usando workers para gunicorn y concurrency para celery. Y luego está el enfoque de Kubernetes para escalar usando replicas .
También existe esta noción de equiparar a los trabajadores con alguna función de las CPU. Gunicorn recomienda
2-4 trabajadores por núcleo
Sin embargo, estoy confundido en lo que esto se traduce en K8 donde la CPU es un recurso compartido divisible, a menos que use resoureceQuotas.
Quiero entender cuál es la mejor práctica. Hay tres opciones que se me ocurren:
Hay algunas preguntas sobre SO en torno a esto, pero ninguna ofrece una respuesta profunda/reflexiva. Agradecería si alguien puede compartir su experiencia.
Nota: Usamos la sync de clase de trabajador predeterminada para Gunicorn
Ejecutamos un kluster de Kubernetes con Django y Celery, e implementamos el primer enfoque. Como tal, algunos de mis pensamientos sobre esta compensación y por qué elegimos este enfoque.
En mi opinión, Kubernetes se trata de escalar horizontalmente sus réplicas (llamadas implementaciones). En ese sentido, tiene más sentido mantener sus implementaciones para un solo uso como sea posible y aumentar las implementaciones (y los pods si se agotan) a medida que aumenta la demanda. El LoadBalancer administra el tráfico a las implementaciones de Gunicorn y la cola de Redis administra las tareas a los trabajadores de Celery. Esto garantiza que los contenedores acoplables subyacentes sean simples y pequeños, y que podamos escalarlos individualmente (y automáticamente) como mejor nos parezca.
En cuanto a su idea de cuántos workers / concurrency necesita por implementación, eso realmente depende del hardware subyacente en el que se ejecuta su Kubernetes y requiere experimentación para hacerlo bien.
Por ejemplo, ejecutamos nuestro clúster en Amazon EC2 y experimentamos con diferentes tipos de instancias EC2 y workers para equilibrar el rendimiento y los costos. Cuanta más CPU tenga por instancia, menos instancias necesitará y más workers podrá implementar por instancia. Pero descubrimos que implementar instancias más pequeñas es, en nuestro caso, más económico. Ahora implementamos múltiples instancias m4.large con 3 trabajadores por implementación.
nota al margen interesante: hemos tenido un rendimiento realmente malo de gunicorn en combinación con los balanceadores de carga de Amazon, como tal, cambiamos a uwsgi con grandes aumentos de rendimiento. Pero los principios son los mismos.
Estas tecnologías no son tan similares como parecen inicialmente. Abordan diferentes partes de la pila de aplicaciones y, en realidad, son complementarias.
Gunicorn es para escalar la concurrencia de solicitudes web, mientras que celery debe considerarse como una cola de trabajadores. Pronto llegaremos a kubernetes.
La simultaneidad de solicitudes web está limitada principalmente por la E/S de la red o "límite de E/S". Estos tipos de tareas se pueden escalar mediante la programación cooperativa proporcionada por subprocesos. Si encuentra que la concurrencia de solicitudes está limitando su aplicación, el aumento de los subprocesos de trabajo de gunicorn bien puede ser el lugar para comenzar.
Las tareas de trabajo pesado, por ejemplo, comprimir una imagen, ejecutar algún algoritmo ML, son tareas "vinculadas a la CPU". No pueden beneficiarse de subprocesos tanto como más CPU. Estas tareas deben ser descargadas y paralelizadas por los trabajadores del apio.
Donde Kubernetes es útil es proporcionando escalabilidad horizontal y tolerancia a fallas listas para usar.
Arquitectónicamente, usaría dos implementaciones de k8s separadas para representar las diferentes preocupaciones de escalabilidad de su aplicación. Una implementación para la aplicación Django y otra para los trabajadores del apio. Esto le permite escalar de forma independiente el rendimiento de las solicitudes frente a la potencia de procesamiento.
Ejecuto trabajadores de apio anclados a un solo núcleo por contenedor ( -c 1 ), esto simplifica enormemente la depuración y se adhiere al mantra de "un proceso por contenedor" de Docker. También le brinda el beneficio adicional de la previsibilidad, ya que puede escalar la potencia de procesamiento por núcleo incrementando el número de réplicas.
Escalar la implementación de la aplicación Django es donde necesitará DYOR para encontrar la mejor configuración para su aplicación en particular. De nuevo, apéguese al uso --workers 1 para que haya un solo proceso por contenedor, pero debe experimentar con --threads para encontrar la mejor solución. Nuevamente, deje la escala horizontal a Kubernetes simplemente cambiando el recuento de réplicas.
HTH Definitivamente es algo en lo que tenía que pensar cuando trabajaba en proyectos similares.