Докер/Кубернетес + Гуйкорн/Сельдерей - Несколько рабочих против реплик?

Мне было интересно, какой правильный подход к развертыванию контейнерного приложения Django с использованием gunicorn & celery был.

В частности, каждый из этих процессов имеет встроенный способ масштабирования по вертикали, используя workers для пушки и concurrency для сельдерея. И тогда есть подход Кубернеса к масштабированию с использованием replicas

Существует также понятие о том, что работники равны некоторой функции процессоров. Гуникорн рекомендует

2-4 рабочих на ядро

Однако я смущен тем, что это означает для K8s, где ЦП - это делимый общий ресурс - если только я не использую resoureceQuotas.

Я хочу понять, что такое "Лучшая практика". Есть три варианта, о которых я могу думать:

  • Имеют ли одинокие рабочие для пушки и параллелизма 1 для сельдерея и масштабируют их с помощью реплик? (горизонтальное масштабирование)
  • Попробуйте использовать пушки и сельдерей в одном реплике с внутренним масштабированием (вертикальное масштабирование). Это означало бы установление довольно высоких значений рабочих и параллелизма соответственно.
  • Смешанный подход между 1 и 2, где мы запускаем пушки и сельдерей с небольшой стоимостью для рабочих и параллелизма (скажем, 2), а затем используем реплики развертывания K8s для горизонтального масштабирования.

Есть несколько вопросов по поводу этого, но никто не предлагает углубленного/продуманного ответа. Был бы признателен, если кто-то может поделиться своим опытом.

Примечание. Мы используем стандартную sync employee_class для Gunicorn

Ответ 1

Эти технологии не так похожи, как они изначально кажутся. Они обращаются к различным частям стека приложений и фактически дополняют друг друга.

Gunicorn предназначен для масштабирования параллелизма веб-запросов, в то время как сельдерей следует рассматривать как рабочую очередь. Скоро мы доберемся до кубернетов.


Gunicorn

Параллелизм веб-запросов в основном ограничивается сетевым вводом-выводом или "привязкой ввода-вывода". Эти типы задач можно масштабировать, используя совместное планирование, предоставляемое потоками. Если вы обнаружите, что параллелизм запросов ограничивает ваше приложение, увеличение рабочих потоков для артиллеристов вполне может стать началом.


Сельдерей

Задачи с тяжелым подъемом, например, сжимать изображение, запускать некоторый ML-алгоритм, являются задачами, связанными с "процессором". Они не могут использовать потоки столько же, сколько больше процессоров. Эти задачи должны быть разгружены и распараллелены работниками сельдерея.


Kubernetes

Там, где Kubernetes пригодится, можно установить горизонтальную масштабируемость и отказоустойчивость.

Архитектурно, я бы использовал два отдельных развертывания k8s для представления различных задач масштабируемости вашего приложения. Одно развертывание для приложения Django и другое для работников сельдерея. Это позволяет вам независимо масштабировать пропускную способность запроса и вычислительную мощность.

Я управляю работниками сельдерея, прикрепленными к одному ядру на контейнер (-c 1), это значительно упрощает отладку и придерживается докеры "один процесс за контейнер" мантры. Это также дает вам дополнительное преимущество предсказуемости, так как вы можете масштабировать вычислительную мощность на основе -c руды, увеличивая количество реплик.

Масштабирование развертывания приложения Django - это то, где вам нужно будет DYOR, чтобы найти лучшие настройки для вашего конкретного приложения. Снова придерживайтесь использования --workers 1 поэтому для каждого контейнера есть один процесс, но вы должны поэкспериментировать с --threads чтобы найти лучшее решение. Снова оставьте горизонтальное масштабирование до Kubernetes, просто изменив счетчик реплик.

HTH Это определенно то, что я должен был обернуть вокруг себя, работая над подобными проектами.

Ответ 2

Мы запускаем Kubernetes kluster с Django и Celery, и реализовали первый подход. Как таковые некоторые из моих мыслей об этом компромиссе и почему мы выбираем для этого подхода.

На мой взгляд, Kubernetes - это горизонтальное масштабирование вашей реплики (называемое развертыванием). В этом отношении наиболее целесообразно поддерживать развертывание как можно более единым, а также увеличивать развертывания (и контейнеры, если вы заканчиваете работу) по мере увеличения спроса. Таким образом, LoadBalancer управляет трафиком для развертываний Gunicorn, а очередь Redis управляет задачами работников Celery. Это гарантирует, что базовые контейнеры докеров являются простыми и малыми, и мы можем индивидуально (и автоматически) масштабировать их по своему усмотрению.

Что касается вашей мысли о том, сколько много workers/concurrency вам необходимо для развертывания, это действительно зависит от базового оборудования, на котором работает ваш Kubernetes, и требует экспериментов, чтобы получить право.

Например, мы запускаем наш кластер на Amazon EC2 и экспериментируем с различными типами экземпляров EC2 и workers чтобы сбалансировать производительность и затраты. Чем больше у вас процессора на один экземпляр, тем меньше требуется вам экземпляров и больше workers вы можете развернуть на один экземпляр. Но мы выяснили, что развертывание более мелких экземпляров в нашем случае дешевле. Теперь мы развертываем несколько экземпляров m4.large с тремя рабочими за развертывание.

Интересная сторона примечания: у нас была действительно плохая производительность gunicorn в сочетании с балансирами нагрузки амазонки, поэтому мы переключились на uwsgi с большим увеличением производительности. Но принципы одинаковы.