web-dev-qa-db-fra.com

Docker / Kubernetes + Gunicorn / Celery - Multiple Workers vs Replicas?

Je me demandais quelle était la bonne approche pour déployer une application conteneurisée Django utilisant gunicorn & celery).

Plus précisément, chacun de ces processus possède une méthode intégrée de mise à l'échelle verticale, en utilisant workers pour gunicorn et concurrency pour céleri. Et puis il y a l'approche Kubernetes pour la mise à l'échelle en utilisant replicas

Il y a aussi cette notion de définir des travailleurs égaux à une fonction des CPU. Gunicorn recommande

2-4 travailleurs par noyau

Cependant, je ne comprends pas ce que cela signifie sur les K8 où le processeur est une ressource partagée divisible - sauf si j'utilise resoureceQuotas.

Je veux comprendre quelle est la meilleure pratique. Il y a trois options auxquelles je peux penser:

  • Avez-vous des ouvriers célibataires pour la licorne et une concurrence de 1 pour le céleri, et faites-les évoluer à l'aide des répliques? (mise à l'échelle horizontale)
  • Faites fonctionner gunicorn & celery dans une seule réplique de déploiement avec mise à l'échelle interne (mise à l'échelle verticale). Cela signifierait fixer des valeurs assez élevées de travailleurs et de simultanéité respectivement.
  • Une approche mixte entre 1 et 2, où nous exécutons gunicorn et céleri avec une petite valeur pour les travailleurs et la concurrence, (disons 2), puis utilisons des répliques de déploiement K8 pour évoluer horizontalement.

Il y a quelques questions sur SO autour de cela, mais aucune n'offre une réponse approfondie/réfléchie. J'apprécierais que quelqu'un puisse partager son expérience.

Remarque: Nous utilisons la classe ouvrière par défaut sync pour Gunicorn

27
rtindru

Ces technologies ne sont pas aussi similaires qu'elles le paraissent au départ. Ils concernent différentes parties de la pile d'applications et sont en fait complémentaires.

Gunicorn est destiné à la mise à l'échelle de la simultanéité des demandes Web, tandis que le céleri doit être considéré comme une file d'attente de travail. Nous arriverons bientôt à kubernetes.


Gunicorn

La simultanéité des demandes Web est principalement limitée par les E/S réseau ou "liées aux E/S". Ces types de tâches peuvent être mis à l'échelle à l'aide de la planification coopérative fournie par les threads. Si vous trouvez que la simultanéité des demandes limite votre application, l'augmentation des threads de travail de gunicorn pourrait bien être le point de départ.


Céleri

Tâches de levage lourdes, par ex. compresser une image, exécuter un algo ML, sont des tâches "liées au CPU". Ils ne peuvent pas bénéficier du threading autant que plus de CPU. Ces tâches doivent être déchargées et parallélisées par les travailleurs du céleri.


Kubernetes

Lorsque Kubernetes est utile, c'est en offrant une évolutivité horizontale et une tolérance aux pannes prêtes à l'emploi.

Sur le plan architectural, j'utiliserais deux déploiements k8 distincts pour représenter les différentes préoccupations d'évolutivité de votre application. Un déploiement pour l'application Django et un autre pour les travailleurs du céleri. Cela vous permet de faire évoluer indépendamment le débit des demandes par rapport à la puissance de traitement.

J'exécute des travailleurs de céleri épinglés sur un seul cœur par conteneur (-c 1) cela simplifie considérablement le débogage et adhère au mantra de Docker "un processus par conteneur". Il vous offre également l'avantage supplémentaire de la prévisibilité, car vous pouvez faire évoluer la puissance de traitement sur une base par cœur en incrémentant le nombre de répliques.

La mise à l'échelle de l'application Django est l'endroit où vous devrez DYOR pour trouver les meilleurs paramètres pour votre application particulière. Encore une fois, utilisez --workers 1 il n'y a donc qu'un seul processus par conteneur, mais vous devriez expérimenter avec --threads pour trouver la meilleure solution. Laissez à nouveau l'échelle horizontale à Kubernetes en changeant simplement le nombre de répliques.

HTH C'est définitivement quelque chose que je devais envelopper ma tête lorsque je travaillais sur des projets similaires.

15
stacksonstacks

Nous exécutons un kluster Kubernetes avec Django et Celery, et avons implémenté la première approche. En tant que tel, certaines de mes réflexions sur ce compromis et pourquoi nous choisissons pour cette approche.

À mon avis, Kubernetes consiste à faire évoluer horizontalement votre réplique (appelée déploiements). À cet égard, il est plus logique de garder vos déploiements aussi uniques que possible et d'augmenter les déploiements (et les pods si vous en manquez) à mesure que la demande augmente. Le LoadBalancer gère ainsi le trafic vers les déploiements Gunicorn, et la file d'attente Redis gère les tâches vers les travailleurs Celery. Cela garantit que les conteneurs Docker sous-jacents sont simples et petits, et nous pouvons les mettre à l'échelle individuellement (et automatiquement) comme bon nous semble.

Quant à votre réflexion sur le nombre de workers/concurrency dont vous avez besoin par déploiement, cela dépend vraiment du matériel sous-jacent sur lequel votre Kubernetes fonctionne et nécessite une expérimentation pour bien fonctionner.

Par exemple, nous exécutons notre cluster sur Amazon EC2 et expérimentons différents types d'instances EC2 et workers pour équilibrer les performances et les coûts. Plus vous avez de CPU par instance, moins vous avez besoin d'instances et plus workers vous pouvez déployer par instance. Mais nous avons découvert que le déploiement d'instances plus petites est dans notre cas moins cher. Nous déployons maintenant plusieurs instances m4.large avec 3 employés par déploiement.

note intéressante: nous avons eu de très mauvaises performances de gunicorn en combinaison avec les équilibreurs de charge Amazon, en tant que tel, nous sommes passés à uwsgi avec de grandes augmentations de performances. Mais les principes sont les mêmes.

11
Boris