Перейти к содержанию

Прокси для клиентского приложения

Для корректной работы пользовательского приложения при установке приложения в docker контейнере и без него нужен прокси, на котором будут выполняться роли:

  1. Хостинг приложения по пути /
  2. Проксирование запросов к бэкенду по путям
    1. /api
    2. /socket.io
    3. /metrics
  3. Подготовка и отправка статики по пути /static
  4. Опционально: предоставление доступа к ендпоинту /metrics для мониторинга приложения с использованием Prometheus
  5. Опционально: предоставление ендпоинта для проксирования запросов и организации кэширования ответов тайлового сервера Martin.

Таким образом прокси будет выполнять роль диспетчера всех запросов приложения и является критически важным элементом при развертывании приложения. В качестве прокси используется Nginx 1.25.4.

Базовая конфигурация

nginx.conf
# Определяем пользователя
user  nginx;

# Определяем количество рабочих процессов автоматически
# Параметр auto поддерживается только начиная с версий 1.3.8 и 1.2.5.
worker_processes auto;

# Данная директива указывает сколько файловых дескрипторов будет использовать Nginx. На каждое соединение надо
# выделять по два дескриптора, даже для статических файлов (картинки/JS/CSS): один для соединения с клиентом,
# а второй — для открытия статического файла. Таким образом, значение worker_rlimit_nofile должно быть не меньше
# удвоенного значению Max Clients.
# В системе это значение можно установить из командной строки ulimit -n 200000 или используя
# /etc/security/limits.conf.
# Проверить установленное значение в системе
# hard limit `ulimit -Hn` и soft limit `ulimit -Sn`
worker_rlimit_nofile 20000;

# !! для установки без контейнера надо вывод ошибок перенаправить в файлы, а не STDOUT !!
# pid        /var/log/nginx/nginx.pid;
# задаем общие параметры логирования
# error_log  /var/log/nginx/error.log warn;
error_log  /dev/stdout info;
# логируем только критические ошибки
# error_log /var/log/nginx/error.log crit
# Если нужно полностью отключить ошибки (использовать с пониманием зачем это нужно)
# error_log /dev/null crit;

events {
   # увеличение по сравнению со стандартным 1024, для продуктивного контура, можно увеличивать больше в зависимости от нагрузки и ресурсов
   # worker_processes * worker_connections = max_clients (кол-во одновременных соединений)
   worker_connections  2048;

   # предпочтительнее для Linux
   use epoll;

   # Для того, чтобы Nginx пытался принять максимальное количество подключений, необходимо включить
   # директиву multi_accept. Однако при слишком маленьком значении worker_connections, их лимит может быть
   # быстро исчерпан.
   multi_accept on;
}

http {

    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    # Куда писать лог доступа и уровень логирования
    # добавляем  buffer=16k для оптимизации логирования
    # access_log  /var/log/nginx/access.log  main buffer=16k;
    access_log /dev/stdout main;
    # либо отключаем лог - так будет работать намного быстрее
    # access_log off;

    # Для нормального ответа 304 Not Modified;
    if_modified_since before;

    # Включаем поддержку WebP
    map $http_accept $webp_ext {
        default "";
        "~*webp" ".webp";
    }

    ##
    # Basic Settings
    ##

    # Используется, если количество имен серверов большое
    #server_names_hash_max_size 1200;
    #server_names_hash_bucket_size 64;


    ### Обработка запросов ###

    # Метод отправки данных sendfile более эффективен, чем стандартный метод read+write
    sendfile on;
    # Будет отправлять заголовки и и начало файла в одном пакете
    tcp_nopush on;
    tcp_nodelay on;

    ### Адрес резолвера DNS ###
    # resolver 10.0.0.1 valid=30s;

    ### Информация о файлах ###

    # Максимальное количество файлов, информация о которых будет содержаться в кеше
    open_file_cache max=200000 inactive=20s;
    # Через какое время информация будет удалена из кеша
    open_file_cache_valid 30s;
    # Кеширование информации о тех файлах, которые были использованы хотя бы 2 раза
    open_file_cache_min_uses 2;
    # Кеширование информации об отсутствующих файлах
    open_file_cache_errors on;


    # Удаляем информацию об nginx в headers
    server_tokens off;
    # Будет ждать 30 секунд перед закрытием keepalive соединения
    keepalive_timeout 30s;
    ## Максимальное количество keepalive запросов от одного клиента
    keepalive_requests 100;

    # Разрешает или запрещает сброс соединений по таймауту
    reset_timedout_connection on;
    # Будет ждать 30 секунд тело запроса от клиента, после чего сбросит соединение
    client_body_timeout 30s;
    # В этом случае сервер не будет принимать запросы размером более 200Мб
    client_max_body_size 200m;
    # Если клиент прекратит чтение ответа, Nginx подождет 30 секунд и сбросит соединение
    send_timeout 30s;

    # Proxy #
    # Задаёт таймаут для установления соединения с проксированным сервером.
    # Необходимо иметь в виду, что этот таймаут обычно не может превышать 75 секунд.
    proxy_connect_timeout 30s;
    # Задаёт таймаут при передаче запроса проксированному серверу.
    # Таймаут устанавливается не на всю передачу запроса, а только между двумя операциями записи.
    # Если по истечении этого времени проксируемый сервер не примет новых данных, соединение закрывается.
    proxy_send_timeout 30s;
    # Задаёт таймаут при чтении ответа проксированного сервера.
    # Таймаут устанавливается не на всю передачу ответа, а только между двумя операциями чтения.
    # Если по истечении этого времени проксируемый сервер ничего не передаст, соединение закрывается.
    proxy_read_timeout 30s;

    # Размер буфера используемого для проксированых запросов
    # proxy_buffers 32 4k;

    ##
    # Кэш для геосервера
    ##
    # proxy_cache_path    /var/cache/nginx/
    #                     levels=1:2
    #                     max_size=10g
    #                     inactive=60m
    #                     use_temp_path=off
    #                     keys_zone=tiles_cache:10m;

    ##
    # Gzip Settings
    ##

    # Включаем сжатие gzip
    gzip on;
    # Для IE6 отключить
    gzip_disable "msie6";
    # Добавляет Vary: Accept-Encoding в Headers
    gzip_vary on;
    # Cжатие для всех проксированных запросов (для работы NGINX+Apache)
    gzip_proxied any;
    # Устанавливает степень сжатия ответа методом gzip. Допустимые значения находятся в диапазоне от 1 до 9
    gzip_comp_level 6;
    # Задаёт число и размер буферов, в которые будет сжиматься ответ
    gzip_buffers 16 8k;
    # Устанавливает минимальную HTTP-версию запроса, необходимую для сжатия ответа. Значение по умолчанию
    gzip_http_version 1.1;
    # MIME-типы файлов в дополнение к text/html, которые нужно сжимать
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/svg+xml;
    # Минимальная длина файла, которую нужно сжимать
    gzip_min_length 10;

  # Подключаем конфиги конкретных сайтов
  include /etc/nginx/conf.d/*.conf;

}    

Для самого приложения в контейнере или в самом nginx настраивается конфигурация default.conf, которая располагается по пути /etc/nginx/conf.d/ Если установка администратором проведена в другое место (при установке не в контейнере) - необходимо не забыть заменить в файле nginx.conf путь до конфигураций сайтов.

default.conf
upstream app_server {
    # указываем правильный адрес
    server vismind-backend:35115 max_fails=5 fail_timeout=5s;
    # ребуется для оптимизации keep alive соединений
    keepalive 50;
}


server { # подключение к бэкенду 80 и 443 порты

    listen       80;
    # listen       443 http2 ssl;
    # ssl_certificate     /etc/ssl/certs/ssl.crt;
    # ssl_certificate_key /etc/ssl/private/ssl.key;

    # устанавливать если меняется на доменное имя
    # server_name  my_server_name;

    # Основное приложений
    location / {
        root   /usr/share/nginx/html;
        try_files $uri$args $uri$args/ $uri/ /index.html;
    }

    # Папка статики
    location /static/ {
        root /usr/share/nginx/media;
        add_header Access-Control-Allow-Origin *;
        # expires 1d; # VISMIND-6615
    }

    # Обратный прокси для бэкенда
    location /api/ {
        proxy_http_version 1.1;
        proxy_set_header   "Connection" "";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_connect_timeout   600;
        proxy_send_timeout      600;
        proxy_read_timeout      600;
        proxy_buffers           32 4k;

        proxy_redirect $scheme://$host/api/v1/ $scheme://$http_host/api/;

        # с бэкендом работа идет по http
        proxy_pass http://app_server/api/v1/;
    }

    # Проброс сокетов
    location /socket.io {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://app_server;
        tcp_nodelay on;
        keepalive_timeout 600;
    }

    # Ендпоинт для получения метрик приложения
    location /metrics {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://app_server/metrics;
    }

    # Геосервер с кэшированием запросов
    location ~ /tilecache/(?<fwd_path>.*) {
        proxy_pass              http://dev.int.bittechno.ru:3000/static/$fwd_path$is_args$args;
    }

    # Геосервер без кэширования, по сути прокси
    location ~ /tileserver/(?<fwd_path>.*) {
        proxy_pass              http://dev.int.bittechno.ru:3000/dynamic/$fwd_path$is_args$args;
    }

    # 404 ошбка
    #error_page  404              /404.html;

    # перенаправление при ошибках 50х на /50x.html2
    #error_page   500 502 503 504  /50x.html;
    #location = /50x.html {
    #    root   /usr/share/nginx/html;
    #}
}

Прокси для относительных адресов геосервера

Дополнительная роль, которую может выполнять nginx - перенаправление запросов на геосервер в случае использования относительных адресов. Это необходимо для того, чтобы не переписывать адреса при переносе задач между разными окружениями.

Для этого в конфигурации задается location для кэшируемых и динамических тайлов. Тайловый сервер должен иметь свой прокси с настроенным кэшированием по путям /static и /dynamic.

Пример location для reverse proxy
# Геосервер с кэшированием запросов
    location ~ /tilecache/(?<fwd_path>.*) {
        proxy_pass              http://martin_address/static/$fwd_path$is_args$args;
    }

    # Геосервер без кэширования, по сути прокси
    location ~ /tileserver/(?<fwd_path>.*) {
        proxy_pass              http://martin_address:3000/dynamic/$fwd_path$is_args$args;
    }

При использовании проброса запросов на геосервер они уходят на прокси геосервера, которая отвечает за кэширование и их перенаправления на сам геосервер.

Важно

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

Подробнее про прокси геосервера см. раздел Геосервер Martin

Схема работы прокси и геосервера