Fastcgi_buffers vs. proxy_buffers при обслуживании API Django с помощью Daphne

Я запускаю два контейнера в сети docker (либо через docker-compose, либо через AWS ECS):

  1. Daphne для ASGI к Django
  2. Nginx для перенаправления запросов к Daphne и обслуживания статических файлов

Все работает нормально. Я могу запрашивать конечные точки API, использовать админку сайта Django, загружать статические файлы и т.д. Проблема в том, что некоторые ответы API имеют огромный размер (>10MiB), и хотя конечные точки, возвращающие эти массивные ответы, очень оптимизированы и быстры, я получаю таймауты из-за буферизации ответа. Я могу судить об этом по таким логам, как:

[warn] 28#28: *252 an upstream response is buffered to a temporary file /var/cache/nginx/proxy_temp/1/00/0000000001 while reading upstream, client: 1.2.3.4, server: _, request: "GET /my-app/123/very-big-response/ HTTP/1.1", upstream: "http://172.17.0.2:9000/my-app/123/very-big-response/", host: "app.acme.com", referrer: "https://app.acme.com/"

Я потратил последние несколько часов, читая о различных настройках буфера nginx, и хотя в документации полностью объясняется, что означают те или иные опции, я не могу найти четкую и достоверную информацию о:

  • Стратегии определения приблизительных значений этих параметров
  • Какие именно директивы nginx следует использовать

Повторюсь, у меня есть два контейнера: #1 (daphne/django), и #2 (nginx).

Контейнер #1 (daphne/django) использует supervisord для запуска daphne. (Примечание, прежде чем я продолжу: Я полностью осведомлен о некоторых других отклонениях от лучших практик, таких как использование supervisord в первую очередь, и комментарии по этому поводу приветствуются, но здесь я спрашиваю только о правильных конфигурациях буфера nginx.)

[supervisord]
user = root
nodaemon = true

[fcgi-program:startup-operations]
process_name=asgi%(process_num)d
# Socket used by nginx upstream
socket=tcp://my-app:9000
numprocs=1
command=/bin/bash -c "./.docker/services/my-app/files/startup-operations.sh"
autostart=true
autorestart=false
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
redirect_stderr=true
directory=/home/docker/my-app
startsecs=0
exitcodes=0
killasgroup=true
environment=PROCESS_NUM=%(process_num)d

Моя конфигурация nginx (с удалением всех неактуальных директив, включая gzip, ssl, таймауты и т. д.):

upstream fastcgi-socket {
    server my-app:9000;
}

server {
    listen 80;
    listen [::]:80;
    server_name _;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl default_server;
    server_name _;

    # NOTE FOR STACK OVERFLOW:
    #   I am setting a "small" max body size here, and a larger one in specific locations.
    #   Does that make sense? There's only one location where large body sizes are expected.
    client_max_body_size 1M;
    underscores_in_headers on;

    # NOTE FOR STACK OVERFLOW:
    #   Are these relevant here?
    fastcgi_connect_timeout 300;
    fastcgi_send_timeout 300;
    fastcgi_read_timeout 300;
    fastcgi_buffer_size 16k;


    location @honeypot {
        include honeypot.conf;
    }

    location /static {
        alias /home/docker/my-app/static;
    }

    location / {
        try_files $uri @proxy_to_app;
    }

    location @proxy_to_app {
        client_max_body_size 20M;

        # NOTE FOR STACK OVERFLOW:
        #   This is OPTION A - `proxy_buffers`
        proxy_buffers 8 1280k;

        # NOTE FOR STACK OVERFLOW:
        #   This is OPTION B - `fastcgi_buffers`
        fastcgi_buffers 8 1280k;

        # Probably not relevant to the question, but included just in case
        proxy_read_timeout 30;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Host $server_name;
        proxy_redirect off;
        proxy_pass http://fastcgi-socket;
    }

Мое основное замешательство связано с разницей между proxy_buffers и fastcgi_buffers. Я понимаю, что есть похожий вопрос , который включает это в вопрос:

Насколько я понимаю, директивы proxy_buffers применяются, когда когда вы проксируете http-службу, такую как apache, tomcat или сторонний сайт. и не применимы к php-fpm с fastcgi.

И наоборот, fastcgi_buffers используется, когда вы проксируете движок динамического языка (или как они там называются), например php-fpm, unicorn, или passenger.

Ответ в целом согласен с этим, но также подразумевает, что существует множество исключений, которые не указаны.

Кроме этого, все, что я могу найти, это просто различные формулировки минимальной информации в документации, а также некоторые примеры конфигураций.

Главный вопрос: Какую директиву buffers использовать? Что именно я здесь проксирую? Мое понимание сокетов, прокси и протоколов, связанных с ними, в целом плохое. Но как я понимаю:

  • Мы говорим, что для любого запроса, соответствующего location @proxy_to_app, мы будем записывать эти запросы из контейнера #2 в сокет FastCGI, который существует в контейнере #1, но:
  • Однако, поскольку эта программа FastCGI запущена в другом контейнере (Container #1), я фактически подключаюсь к ней как server через HTTP к имени ее хоста (который в этой докерной сети оказывается my-app). Nginx не воспринимает это как fastcgi сокет.
  • Вместо этого ответ проксируется на http://my-app:9000, а supervisord обрабатывает запись этих данных на FastCGI-сокет (где-то в контейнере №1) благодаря строке socket=tcp://my-app:9000 в конфигурации моего супервизора, указывающей ему на это.
  • Следовательно, я должен не использовать fastcgi_buffers в конфигурации nginx, а вместо этого proxy_buffers?

Вторичный вопрос #1: Понимание потока данных Если предположить, что я правильно ответил на свой главный вопрос (здесь я должен использовать proxy_buffers, а не fastcgi_buffers), то в моем понимании все еще остаются дыры.

Сценарий, запущенный командой fastcgi-program (см. конфигурацию супервизора), startup-operations.sh, заканчивается так:

daphne -u "/run/daphne/daphne$PROCESS_NUM.sock" --fd 0 --proxy-headers main.asgi:application

Здесь так много всего, что было сшито из различных руководств и учебников, часто с крайне противоречивыми советами/информацией и разными подходами, что я должен признать, что у меня нет полного понимания того, как все складывается вместе.

Я не понимаю, как утверждение socket=tcp://my-app:9000 позволяет запросам, переданным туда, быть перехваченными этими /run/daphne/daphne$PROCESS_NUM.sock сокетами.

Как именно запросы из upstream в контейнере №1 (neinx) записываются в tcp://my-app:9000 в контейнере №2 (daphne/django), а затем каким-то образом обрабатываются daphne?

Выбор порта 9000 был произвольным. Есть ли какая-то «магия» за программой fcgi-program супервизора, которая позволяет этой связи tcp://my-app:9000->daphne.sock происходить?

Вторичный вопрос 2: Правильная настройка буферов. Предполагая, что теперь я понимаю поток данных (и, следовательно, какие директивы использовать в конфигурации nginx), и я знаю, что правильные директивы делают (создают x буферов размера y для буферизации ответов), я все еще не знаю значения количества буферов против размера каждого буфера.

Какова разница между 1 буфером на 10 MiB и 2 буферами по 5 MiB? Зачем их разделять? Если есть польза от их разделения, почему бы не иметь 1000x1k буферов вместо 100x10k буферов? Как они используются, или, скорее, как мне вообще определить, сколько буферов у меня должно быть и какого размера должен быть каждый буфер?

Я понимаю это:

  • Если мой самый большой ожидаемый ответ составляет 10 MiB, но это случается лишь изредка, то, вероятно, мне не нужен буфер 1x10MiB для каждого запроса, чтобы полностью исключить возможность записи на диск.
  • Я также не хочу 2х5MiB буферов, потому что это все равно 10 MiB на запрос. Но в чем разница?
  • Я также не хочу что-то вроде 8x4KiB буферов, потому что это слишком мало для этого самого большого запроса, что, по сути, гарантирует, что он задержится и сделает мой сервис недоступным.

Извините за длину и количество подвопросов, я чувствую себя полностью потерянным и не могу найти объяснения, которые мне нужны, чтобы понять этот материал.

(Также я хотел бы удалить супервизор из образа для контейнера №1, но поскольку я недостаточно хорошо разбираюсь в этих вещах, в настоящее время я полагаюсь на него только в аспекте межконтейнерного взаимодействия. Может быть, я могу попросить daphne написать unix-сокет на общий том, чтобы обойти это?)

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