Проект Django должен работать с 3 основными рабочими задачами (процессы планирования, задания ansible и сервер API). Но Nginx продолжает убивать моих рабочих

Я использую Django backend, который имеет 2 основные рабочие нагрузки:

  1. API server for the Angular UI Frontend and Django Admin Screens
  2. Scheduler, which kick off about 8 difference scheduled services
    • Metric collection: API calls against a fleet of servers for regular status updates, then stores the responses in the PostgreSQL database.
    • Runner: Runs Ansible jobs against our fleet of servers

Все привязано к одной и той же базе данных PostgreSQL и часто перекрестно ссылается. Поэтому не думайте, что это логически разные приложения, это не так.

Проблема связана с веб-сервером Gunicorn и таймаутами. Gunicorn имеет 6 рабочих. С настройками по умолчанию Gunicorn любит убивать процессы, которые не использовались в течение последних ~30 секунд по умолчанию. Это отлично работает, если ваша рабочая нагрузка - это только HTTP вызовы, а не фоновые процессы. Но поскольку у меня явно есть и то, и другое, у меня есть проблемы с тем, что Gunicorn убивает или не убивает мои процессы

В течение многих лет у меня был очень высокий тайм-аут в gunicorn - 21600 секунд (6 часов). При таких настройках он позволял моим заданиям Ansible завершаться и заданиям планировщика завершаться. Но в базе данных было большое количество неактивных (2000~4000) и активных (1~20) сессий. База данных будет вялой, а соединения будут медленно отмирать

Недавно я снизил значение до 60 секунд, и все мои задания планировщика и Ansible не выполнялись. Затем я перешел к 800 секундам. Большинство заданий планировщика завершаются, но задания Ansible просто умирают, когда проходит 800 секунд, если мои HTTP-вызовы поступают медленно.

Теперь я четко вижу, что у меня есть две рабочие нагрузки. 1) Вызовы API и 2) Обработка бэкенда

Текущая служба Gunicorn

[Unit]
Description = RocketDBaaS_runner
Requires=runner.socket cntlm.service
After=network.target cntlm.service

[Service]
PIDFile=/run/runner.pid
User=root
Group=dbaas
RuntimeDirectory=runner
Environment=http_proxy=http://localhost:3128
Environment=https_proxy=http://localhost:3128
LimitNOFILE=131072
LimitNPROC=64000
LimitMEMLOCK=infinity
ExecStart=/opt/dbaas/RocketDBaaS_api/venv/bin/gunicorn \
         --pid /run/runner.pid \
         --access-logfile None \
         --workers 6 \
         --bind unix:/run/runner.socket \
         --pythonpath /opt/dbaas/RocketDBaaS_api \
         --timeout 300 \
         --graceful-timeout 30 \
         RocketDBaaS_api.wsgi
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
PrivateTmp=true
KillMode=mixed
KillSignal=SIGKILL

Так что же делать???

Моя главная идея - создать три сервиса, вызывающих одну и ту же кодовую базу. Но передать в переменную service_function='Scheduler|API|Runner'

  1. API: Basically use what I have above but pass in service_function='API' and lower my timeout to 60 seconds
  2. Scheduler: I'm torn
    A) Create service that calls Django directly and pass service_function='Scheduler'. But a I've heard it's not wise to run Django this way because it insecure. But I would not be taking in HTTP request. FYI: The scheduler starts and handles it's own threads.
    B) Just set up a new Gunicorn server with no workers or threads and timeout=0
  3. Runner: Create service like #2 and pass service_function='Runner'

Я мог бы объединить #2 & #3, но думаю, что было бы гораздо проще искать journalctl с 3 отдельными службами. Но мои файлы журналов уже разделены, так что это не является проблемой.

В файле wsgi.py я планирую считать этот входной параметр и использовать его в качестве глобальной переменной. Эта переменная будет указывать Django, когда он должен действовать как API, Scheduler или Runner сервер.

В настоящее время мой планировщик использует механизм блокировки сервера в порядке поступления до тех пор, пока процесс не заглохнет. Что является своего рода недостатком текущего дизайна, так как все должны пройти через всю логику, чтобы увидеть, что процесс запуска расписания только для того, чтобы найти файл блокировки существует и процесс в файле блокировки активен. Данное решение решит эту проблему с помощью простого оператора If.

Пожалуйста, я команда из 1 человека. Мне нужны другие точки зрения, прежде чем идти по этому пути. Я много гуглил, но так и не нашел подобного вопроса.

Стек:

UI: Angular
API: Django
Automation: Ansible
Database: PostgreSQL ~1TB
Interacting with: ~600 VMs

Заранее спасибо.

Я решил выбрать вариант 2B, "новый сервер Gunicorn без рабочих и потоков и с таймаутом=0".
Это казалось наиболее простым. И, возможно, когда-нибудь я захочу использовать тестовый URL для запуска чего-либо, и тогда Gunicorn будет более безопасным.

Итак, файл службы API gunicorn теперь выглядит следующим образом:

[Unit]
Description = RocketDBaaS_api
Requires=api.socket cntlm.service
After=network.target cntlm.service

[Service]
PIDFile=/run/api.pid
User=root
Group=dbaas
RuntimeDirectory=api
Environment=http_proxy=http://localhost:3128
Environment=https_proxy=http://localhost:3128
LimitNOFILE=131072
LimitNPROC=64000
LimitMEMLOCK=infinity
ExecStart=/opt/dbaas/RocketDBaaS_api/venv/bin/gunicorn \
         --env WorkloadType=RocketApi \
         --name api \
         --pid /run/api.pid \
         --access-logfile None \
         --workers 6 \
         --bind unix:/run/api.socket \
         --pythonpath /opt/dbaas/RocketDBaaS_api \
         --timeout 300 \
         --graceful-timeout 30 \
         RocketDBaaS_api.wsgi
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
PrivateTmp=true
KillMode=mixed
KillSignal=SIGKILL

[Install]
WantedBy=multi-user.target

А файл службы Scheduler gunicorn выглядит следующим образом, обратите внимание на "WorkloadType=RocketScheduler", "--timeout 0" и отсутствие "--workers".

[Unit]
Description = RocketDBaaS_Scheduler
Requires=rocket.socket cntlm.service
After=network.target cntlm.service

[Service]
PIDFile=/run/rocket.pid
User=root
Group=dbaas
RuntimeDirectory=rocket
Environment=http_proxy=http://localhost:3128
Environment=https_proxy=http://localhost:3128
LimitNOFILE=131072
LimitNPROC=64000
LimitMEMLOCK=infinity
ExecStart=/opt/dbaas/RocketDBaaS_api/venv/bin/gunicorn \
         --env WorkloadType=RocketScheduler \
         --name rocket \
         --pid /run/rocket.pid \
         --access-logfile None \
         --bind unix:/run/rocket.socket \
         --pythonpath /opt/dbaas/RocketDBaaS_api \
         --timeout 0 \
         --graceful-timeout 30 \
         RocketDBaaS_api.wsgi
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
PrivateTmp=true
KillMode=mixed
KillSignal=SIGKILL

[Install]
WantedBy=multi-user.target

Как вы можете видеть, в обоих файлах я включил переменную окружения под названием "WorkloadType". Я нашел это наиболее простым способом передачи переменной в Python.

В моем файле python wsgi.py есть код, который выглядит следующим образом.
"workload_type = os.getenv('WorkloadType')" - это то, как я читаю переменную окружения. Затем я запускаю планировщик в зависимости от значения.
Я также могу включить эту переменную в другие файлы python с помощью простого импорта и затем некоторой логики в зависимости от того, что должно произойти. Может быть, отключить все маршруты???

import os
import logging

from django.core.wsgi import get_wsgi_application
log = logging.getLogger(__name__)


def start_app(_workload_type: str):
    if _workload_type == 'RocketScheduler':
        from dbaas.services.scheduler.start_the_scheduler import main_scheduler
        log.info('[RocketScheduler]: Starting main_scheduler()')
        main_scheduler()
    elif _workload_type == 'RocketApi':
        log.info('[RocketApi]: Starting')
    else:
        log.error(f'Started the RocketDBaaS without a valid workload_type({_workload_type})')


os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'RocketDBaaS_api.settings')

application = get_wsgi_application()

from dbaas.rocket_config import *
set_globals()

pid = os.getpid()

workload_type = os.getenv('WorkloadType')
start_app(workload_type)

Я запускал это уже несколько дней и не имел никаких исключений по тайм-ауту. :)

Вернуться на верх